aboutsummaryrefslogtreecommitdiff
path: root/docs/design/2026-07-14-sentry-workflow-proposal.org
blob: d29ceb876a33e1899595a5afd068549a225bedcb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#+TITLE: Sentry Workflow — Proposal for rulesets
#+AUTHOR: Craig Jennings
#+DATE: 2026-07-13

* Status
Proposal from the work project to the rulesets session, for implementation as a
general (shared-layer) workflow: =.ai/workflows/sentry.org= plus a small
supporting script or two under =.ai/scripts/=.

* Problem

An agent session left running overnight can keep a project clean and healthy on
a cadence, but the current pieces don't coordinate. Two gaps:

1. *No agent-vs-agent collision guard on shared public files.* =capture-guard=
   only detects Emacs org-capture buffers (the human case) on one target file,
   and it's advisory, not a lock. There is no flock anywhere in =.ai/scripts/=.
   Multiple sessions (several projects, sometimes both daily drivers) can
   read-modify-write =~/org/roam/inbox.org= and the =agents/= KB nodes at once.
   The only coordination is the git pull/commit/push discipline in
   knowledge-base.md, which is optimistic — a pull/push race forks a
   =*sync-conflict*= file, and the KB search already globs those out, so we know
   the forks happen and get dropped.

2. *No single serialized runner.* Firing inbox-zero and triage-intake as
   separate hourly crons means they can overlap each other and other agents.

Sentry is one supervisor loop that runs a fixed sequence of hygiene passes
serially, holds locks so it never collides with itself or other agents, and
checkpoints aggressively for crash recovery.

* What sentry is

A single workflow, driven by one interactive =/loop= (one cron), that on each
fire walks an ordered list of passes inside a single turn. It replaces the
per-task crons. Design invariants:

- *Serial.* Passes run one at a time, in order. They never overlap each other.
- *Single-runner lock.* Sentry takes a project-local flock
  (=.ai/.sentry.lock=) at entry. If a prior fire is still running, this fire
  skips instead of doubling up.
- *Shared-file lock.* Any pass that writes a public roam file first acquires a
  roam-write lock (flock on =~/org/roam/.roam-write.lock=) held across the whole
  read-modify-write-commit-push cycle, so it serializes against other agents on
  the same host. =capture-guard= still runs underneath for the human case. (The
  roam-write lock is new; it's the general guard the dropped KB-guard note would
  have proposed, folded in here.)
- *Non-destructive by default.* Read-only and idempotent-filing passes run
  fully. Anything destructive or judgment-heavy (triage trash/mark-read, task
  regrades, spec flips) queues its proposal into the digest for morning
  approval, never fires unattended.
- *Idempotent + isolated.* Every pass is safe to repeat. A pass that errors
  doesn't abort the rest; its failure is a loud banner in the digest.

* Crash-recovery spine (applies to every pass)

Crash recovery has been a live consideration. Two enforced steps wrap each pass:

1. *Session-context between passes.* Before the next pass starts, the current
   pass appends a dated entry to =.ai/session-context.org= recording what it
   did. A crash mid-sequence leaves a readable "got this far" trail, so the next
   session resumes without re-deriving state.
2. *Local commit, no push, on any disk change.* Every pass that writes to disk
   gets its own =chore(sentry): <pass> — <what changed>= commit on the current
   branch, unpushed. A crash loses at most one pass. Craig reviews the stack in
   the morning and pushes deliberately. Conventional messages, no AI
   attribution. (Open decision: commit on the current branch vs a dedicated
   =sentry/<date>= branch — see Open decisions.)

The unit per pass is therefore: do the work → log to session-context → commit.
That triplet is the recovery boundary.

* Passes (v1 — all ten, plus the first-run KB promotion)

Ordered so read-only/pull passes precede writes, and the heaviest external
sweep sits mid-run:

1. *Roam pull* — =git -C ~/org/roam pull --ff-only= so later reads are fresh.
   Read-only.
2. *Inbox zero* — inbox.org roam mode. Route roam-inbox items this project
   owns. Acquires the roam-write lock for the inbox edit.
3. *Triage intake* — triage-intake.org. External-accounts sweep, classify,
   file Action tasks. Destructive actions (trash/mark-read) queue for morning;
   =mbsync -a= and the sentinel advance run.
4. *Todo cleanup* — clean-todo.org + todo-cleanup.el + lint-org.el. Convert
   level-3 done subtasks to dated logs, archive done subtrees, flag lint.
5. *Task audit* — task-audit.org. Re-check =:solo:=/=:quick:= tags,
   priority-scheme conformance, bug severity×frequency grades, stale =DOING=
   specs with closed parents, blocked/blocker reciprocity. Regrades queue for
   morning.
6. *Working-files hygiene* — flag =working/<slug>/= dirs whose task is DONE but
   not filed to =assets/=. Report only; filing is a morning decision.
7. *Spec status board* — the docs-lifecycle grep. Flag =DOING= specs with
   closed parents, or specs stuck in =DRAFT=.
8. *Link integrity* — lint-org broken =file:= links across todo/notes/prep.
9. *Git health* — uncommitted drift, unpushed commits, stale local branches,
   whether main is behind origin.
10. *Prep + symlink freshness* — verify tomorrow's prep and both symlinks
    resolve.
11. *KB lesson promotion (first run of the session, and thereafter as new
    lessons accrue)* — promote recent durable lessons out of the fast capture
    layer. CLASSIFICATION-GATED:
    - Personal project → write to the roam KB as =agents/= nodes (pull, lock,
      write, commit, push), per knowledge-base.md.
    - Work / denylisted project → NEVER touch roam. Promote to the project's own
      durable store (e.g. =deepsat/knowledge.org=) instead, and emit the
      one-line refusal note from the knowledge-base.md refusal contract so
      nothing is lost silently.

* Digest output

One consolidated, timestamped summary per fire: run time first, then per-pass
one-liners (what it found / did), the morning-approval queue (proposed
trash/mark-read, regrades, files-to-move), and any failures as loud banners at
the top. Delta-only where a pass supports it (triage): a pass with nothing to
report is one line.

* Cadence

One hourly sentry to start (passes short-circuit fast when there's nothing to
do). A slower evening interval (every 2–3 hours) is a config knob if hourly
proves noisy.

* Open decisions for rulesets

1. *Commit target* — per-pass commits on the current branch (simplest, matches
   "commit but don't push") vs a dedicated =sentry/<date>= branch (keeps the
   working branch clean, costs a branch dance). Proposal leans current branch.
2. *Roam-write lock scope* — host-local flock only (solves same-host
   multi-agent, the common case) vs also hardening roam-sync to surface
   cross-host conflicts loudly. Proposal: ship the flock now, note the
   cross-host limit, treat roam-sync conflict-surfacing as a separate follow-up.
3. *Interval default* — hourly vs evening-only. Proposal: hourly, config knob.
4. *Which passes gate on a green tree* — e.g. skip todo-cleanup commits if the
   project suite is red, matching inbox monitor-mode's clean-tree gate.

* Cover note from sender (work, 2026-07-14 00:02)

Intro for the sentry-proposal.org file just sent. This is a NEW general workflow proposal (.ai/workflows/sentry.org + supporting scripts under .ai/scripts/), not an edit to an existing synced file.

What it is: one serialized supervisor loop that runs a project's evening hygiene passes on a cadence, with locks so it never collides with itself or with other agents. It replaces firing inbox-zero + triage-intake as separate crons.

Why now: an agent left running overnight can keep a project clean, but the current pieces don't coordinate. capture-guard only handles the Emacs-capture case on one file and holds no lock; there is no flock anywhere in .ai/scripts/; and separate crons let inbox-zero and triage overlap each other and other agents on shared roam files, which already fork *sync-conflict* files that the KB search globs out.

Three design points worth your judgment:
1. Crash-recovery spine on every pass (crash recovery has been a live concern): enforce a session-context.org update BETWEEN passes, and a local commit (no push) after any disk change, so a crash loses at most one pass.
2. A new roam-write flock (~/org/roam/.roam-write.lock) as the shared-file guard. This absorbs a separate 'guard all public KB files' note we considered and dropped. The lock is the better home for that concern than a bespoke per-file guard.
3. The KB lesson-promotion pass is classification-gated: personal projects promote to roam agents/ nodes; a denylisted work project promotes to its own local store (deepsat/knowledge.org) and never touches roam, per knowledge-base.md's refusal contract.

Companion files to reconcile: knowledge-base.md (the gated promotion + refusal contract), inbox.org (roam mode + the new lock), triage-intake.org (runs as a sentry pass; keep its own triggers), capture-guard (becomes one layer under the roam-write lock, not the whole guard), and clean-todo.org / task-audit.org / lint-org.el (invoked as passes). Open decisions for you are listed at the bottom of the proposal file: commit target, lock scope, interval default, and which passes gate on a green tree.