aboutsummaryrefslogtreecommitdiff
path: root/claude-templates/.ai/workflows/inbox.org
diff options
context:
space:
mode:
Diffstat (limited to 'claude-templates/.ai/workflows/inbox.org')
-rw-r--r--claude-templates/.ai/workflows/inbox.org47
1 files changed, 37 insertions, 10 deletions
diff --git a/claude-templates/.ai/workflows/inbox.org b/claude-templates/.ai/workflows/inbox.org
index 3bd9335..914433d 100644
--- a/claude-templates/.ai/workflows/inbox.org
+++ b/claude-templates/.ai/workflows/inbox.org
@@ -103,7 +103,7 @@ The full question-battery answers live in the session log and the =working/= dir
* Core §3 — The disposition ladder
-Every item that clears the value gate gets one disposition. The first six are the per-item outcomes; *park* is the no-approvals shared-asset path from core §2.
+Every item that clears the value gate gets exactly one disposition: *implement now*, *fold into an existing TODO*, *file as a TODO*, or *reject*. *Park* is the no-approvals shared-asset path from core §2. Whichever it gets, nothing stays in =inbox/=.
** Implement now
Small, scoped, clear, no design call required. The work is the disposition. Do the work, commit per the project's commit flow, delete the inbox file. The commit message references the inbox item by filename so the provenance lands in =git log=.
@@ -124,8 +124,17 @@ On a =<destination>\tstrong= or =<destination>\tweak= result, stamp the new TODO
*Blocking-dependency handoff.* A special shape: another project sends a note that *this* project's work is blocking one of theirs ("your task X is blocked on us — we need Y"). File or link the owning task, tag it =:blocker:=, and name the requesting project in the body (see the cross-project dependency convention in =todo-format.md=). The =:blocker:= tag makes =open-tasks.org= surface that task *first*, since clearing it unblocks the other project. Dedup against an existing task rather than filing a duplicate. When the work later lands, drop =:blocker:= and notify the waiting project (=inbox-send <their-project> --text "Delivered: <what> — you're unblocked."=) so it can lift its own =:blocked:=.
-** Defer
-Rename in place to =inbox/PROCESSED-<original-filename>= and add a brief comment line at the top: =# Deferred YYYY-MM-DD: <condition>=. Don't accumulate deferred items indefinitely — sweep them on a future process pass when the condition is met or the deferral has aged out.
+** Deferral is not a disposition: file it or delete it
+There is no rung that leaves an item in =inbox/= under another name. An item whose work waits is a *task*, so file it (core §3 "File as TODO") with the waiting condition in its body. An item worth keeping for reference is *content*, so move it to =docs/design/= and link it from whatever task cites it. An item that is neither gets deleted. Every path ends with the inbox file gone.
+
+The retired rung renamed deferred items in place to =inbox/PROCESSED-<original>=, and it failed twice in ways worth naming, because the shape recurs:
+
+1. *It accumulated.* The rung told you not to let deferrals pile up and gave that instruction no enforcement, so nothing surfaced them and nothing owned them. Rulesets reached 40 before a manual sweep on 2026-07-27, and the rest of the fleet was still carrying 114 when this rung was removed on 2026-08-20.
+2. *It lost the content it was meant to preserve.* A =PROCESSED-= file is not a durable home, so a task citing one as its source breaks the moment somebody sweeps. Rulesets' =todo.org= carried exactly that dangling reference.
+
+Both follow from one defect: =PROCESSED-*= was a third state that is neither a tracked task nor gone. It sat in =inbox/=, hidden from =inbox-status=, absent from =todo.org=, and carried by no review cycle. Filing and deleting are the two states that already have homes and cadences, which is why they are the only two on offer.
+
+*Legacy files.* A =PROCESSED-*= file predating this change is not grandfathered. It is an undispositioned item. Give it the same choice as anything else: file it, move its content to =docs/design/=, or delete it.
** Reject — by source
- *From Craig* — push back honestly in chat. State why you won't implement; offer the conditions under which you would, if any. The inbox file stays until Craig confirms — override re-enters as accept, acknowledgment deletes the file. Don't theatre the pushback: if you don't genuinely think Craig is wrong, just do the work.
@@ -163,6 +172,20 @@ An org capture is usually only a few seconds of mid-finalize state, so =--wait=
- *Auto inbox zero (=/loop=) cycle* → don't surface or wait further; defer the roam reconcile to the next cycle, which is itself the retry at loop cadence. The items were already filed in Phase C, so the next cycle's Phase C status-check drops the duplicates and its Phase D removes them. Note one line: "roam reconcile deferred — a capture is still open; next cycle catches it."
- *Wrap-up sub-step* → don't block the wrap. Skip the roam reconcile for this run and surface one line: "Skipped roam-inbox reconcile — a live org-capture is open against it; claimed items stay and get caught next run." The items were already filed into =todo.org= in roam mode Phase C, so the next roam run's Phase C status-check drops the duplicates and its Phase D removes them — the skip self-heals.
+*The roam-write lock (around the Phase D edit).* Capture-guard protects against a live *human* capture; the roam-write lock protects against a concurrent *agent* writer (a sentry inbox pass, a KB promotion) editing =~/org/roam= at the same time. Acquire it after the capture-guard clears and release it after the edit-and-trigger, so the two guards nest — capture-guard underneath, the agent lock around the write:
+
+#+begin_src bash
+if [ -x .ai/scripts/agent-lock ]; then
+ .ai/scripts/agent-lock acquire roam-write --wait || { echo "roam-write busy; deferring roam reconcile" >&2; exit 1; }
+fi
+# capture-guard (above), then the Phase D read-modify-write of ~/org/roam/inbox.org,
+# then trigger the sync — roam-sync stays the only committer:
+systemctl --user start roam-sync.service
+[ -x .ai/scripts/agent-lock ] && .ai/scripts/agent-lock release roam-write
+#+end_src
+
+Degrade gracefully when =agent-lock= isn't installed (an older checkout mid-sync): the write proceeds unlocked, today's behavior. A *present* helper reporting the lock busy after its bounded wait defers the roam reconcile (the auto-loop and wrap-up paths already defer-and-retry per the fallback list above); an *absent* helper never blocks it.
+
* Core §6 — Priority-scheme check
This gates filing whenever there are accept-and-file items. Check whether =todo.org= has a top-of-file priority scheme (an explicit legend defining =[#A]= through =[#D]= semantics and mandatory/optional tag conventions — a =* <Project> Priority Scheme= section or similar).
@@ -174,13 +197,13 @@ The point is to avoid adding ungraded =TODO= entries to a project that's never a
* Mode: process
-Reads the project-local =inbox/= dir. Entry: a trigger phrase, or startup Phase C on a non-empty inbox. Exit: inbox empty (excluding =.gitkeep= and intentional =PROCESSED-*=), session log updated, =:LAST_INBOX_PROCESS:= stamped.
+Reads the project-local =inbox/= dir. Entry: a trigger phrase, or startup Phase C on a non-empty inbox. Exit: inbox empty (excluding =.gitkeep= and the =lint-followups.org= pipeline artifact), session log updated, =:LAST_INBOX_PROCESS:= stamped.
** Phase A — Inventory (one parallel batch)
Issue these reads in one parallel batch:
-1. List =inbox/= excluding =.gitkeep= and =PROCESSED-*= prefixes (use =\ls -la inbox/= per the protocols.org exa-alias note).
+1. List =inbox/= excluding =.gitkeep= (use =\ls -la inbox/= per the protocols.org exa-alias note). A =PROCESSED-*= file from before the deferral rung was retired is a legacy item, not an artifact: list it and disposition it like any other.
2. Read =notes.org= *Project-Specific Context* if mission isn't already loaded in the session.
3. Read =todo.org='s top-of-file priority scheme if present.
@@ -231,7 +254,7 @@ Disposition recommendation: <implement / fold into <TODO> / file [#X] :tags: / r
1. <recommendation as item 1>
2. <alternative>
-3. Defer — leave in inbox under PROCESSED-<topic>.<ext> until <condition>
+3. Delete — nothing worth filing and no reference worth keeping
4. Something else
#+end_example
@@ -245,7 +268,7 @@ Apply each disposition per the ladder (core §3). The flow is autonomous past Cr
** Phase E — Close out
-Verify =inbox/= is empty (excluding =.gitkeep= and any intentional =PROCESSED-*= files). Run =\ls -la inbox/= and confirm.
+Verify =inbox/= is empty (excluding =.gitkeep= and =lint-followups.org=). Run =\ls -la inbox/= and confirm. Nothing is left behind under a renamed prefix, because deferral-in-place is not a disposition.
Update the session log per =protocols.org= with one short paragraph: count processed, count accepted (implement/fold/file split), count rejected (Craig/handoff/script split), and the commit SHA if a commit landed.
@@ -332,7 +355,7 @@ When Craig has put the session in no-approvals mode, an accepted item may be imp
2. *Quick* — the whole implementation, including verification, is under ~15 minutes.
3. *Solo* — you can carry it end to end without a decision from Craig. Manual verification you perform yourself is fine; needing Craig to choose an option, approve a design, or resolve an ambiguity is not.
-All three → implement it, verify, then commit and push at the end of that item (the Step 0 reconcile and pre-push check from =commits.md= still run). Miss any one and it doesn't self-apply: a shared-asset or convention change needs Craig's decision, so it fails *solo* and routes to the defer-and-stage park (core §2 / core §3); an oversized item fails *quick* and gets filed.
+All three → implement it, verify, then commit and push at the end of that item (the Step 0 reconcile and pre-push check from the =publish= skill still run). Miss any one and it doesn't self-apply: a shared-asset or convention change needs Craig's decision, so it fails *solo* and routes to the defer-and-stage park (core §2 / core §3); an oversized item fails *quick* and gets filed.
** Replying to handoffs
@@ -342,6 +365,8 @@ Close the loop per the reply-to-sender discipline (core §4): confirm what lande
=.ai/scripts/inbox-status= lists unprocessed handoffs and exits nonzero when any are pending. Exclusions match the wrap-up sanity check (=.gitkeep=, =lint-followups.org=, =PROCESSED-*=). Exit 0 = clean, 1 = pending, 2 = no inbox/ or bad usage. Use =-q= for the count-only form the cadence check calls.
+*The =PROCESSED-*= exclusion is transitional, not a supported state.* Nothing creates such a file any more, so the exclusion can only ever match a legacy item from before the deferral rung was retired. It stays for one reason: 114 of them were sitting across five projects on 2026-08-20, and dropping the exclusion would have made every one pending at once, blocking the next session of each project behind a cleanup it didn't ask for. The sweep is tracked as its own task instead. When it finishes, the exclusion comes out of this script, the wrap-up check, and =scripts/status.sh= together, and the ladder's "not grandfathered" rule stops needing a caveat.
+
** Close out — before finishing
End the way it started: clean worktree, green suite. Before stopping the loop or reporting the pass done:
@@ -355,6 +380,8 @@ If either can't be satisfied — a half-done item, a failure introduced during t
Reads the *global roam inbox* (=~/org/roam/inbox.org=), Craig's cross-project GTD capture: one shared file every project can see. This mode routes each roam item to the project that owns it. The current session claims only the items belonging to THIS project, files them into the project's =todo.org=, and removes them from the shared inbox. Everything it doesn't own stays.
+*Allowed from any project, work included.* Tidying the shared roam inbox is housekeeping on a shared resource, not a cross-project boundary crossing and not a durable KB-node write, so the =knowledge-base.md= work-denylist doesn't gate it (a sentry inbox-zero pass mis-parked the whole inbox as a boundary crossing from the work project on 2026-07-19 — the error this note closes). Reading roam and tidying its inbox are fine from work; only promoting a durable =agents/= node stays work-denylisted.
+
The aspiration is inbox zero: after this mode runs, the current project's local handoff inbox has been processed (Phase A delegates to process mode) and the shared roam inbox no longer contains items explicitly owned by this project.
This is distinct from the wrap-up inbox/transcript routing feature (which moves session-filed keepers between projects). This routes the shared roam capture file by ownership prefix.
@@ -466,14 +493,14 @@ A recurring, *interactive* roam check. Trigger phrase: "auto inbox zero" (match
** Per cycle
1. Run roam mode's scan (Phase A local check + Phase B roam scan), read-only — no =git pull=. The capture-guard still gates any write: use =capture-guard --wait= (core §5) so a transient capture clears itself; if it's still open after the wait, *defer this cycle's roam reconcile to the next cycle* rather than surfacing — the loop cadence is the retry, and the filed items get swept next time. The rare write hands its git to =roam-sync= (roam Phase D).
-2. *Nothing found* → no inbox summary. One acknowledgement line: =ran at HH:MM, nothing found=. Nothing else. The acknowledge-only-on-empty rule keeps a quiet inbox quiet.
+2. *Nothing found* → no inbox summary. One heartbeat line: =inbox zero at HH:MM: nothing= (HH:MM local, from =date=) — the silent-until-signal policy, see =docs/specs/2026-07-20-silent-until-signal-monitors-spec.org=. Nothing else. Keeping a quiet inbox quiet is the whole point.
3. *Items found* → summarize the found items, file them as tasks (roam Phase C), and *append them to a displayed queue* — the harness task list, via =TaskCreate= — so the queue accumulates across cycles. Then ask: "run this batch next?"
- *Yes* → chain into =work-the-backlog.org= as an explicit second step after routing completes: pass it the eligibility query over the queued items (status =TODO= + =:solo:= per the scheme header, priority-ordered), =file-only= mode, paging off, cap 1. The highest-priority eligible candidate runs; the rest wait for the next tick or a later yes.
- *No* → they stay queued for a later go.
This mode never implements anything itself — routing ends here, and the execution loop lives in =work-the-backlog.org=, its one home.
4. *Cross-cycle dedup.* Subsequent cycles add only *newly-found* items to the same displayed queue, never re-surfacing what's already there. Dedup against the queue (the =TaskCreate= list), not against what's already been implemented — a find that was queued-but-not-yet-run must not reappear, and one already filed into =todo.org= is dropped by roam Phase C's status check.
-A find is always surfaced and filed; execution happens only through the =work-the-backlog.org= chain and waits for Craig's yes. A quiet inbox produces only the timestamped acknowledgement. =auto inbox zero= is inherently in-session because its chain step waits for that yes.
+A find is always surfaced and filed; execution happens only through the =work-the-backlog.org= chain and waits for Craig's yes. A quiet inbox produces only the =inbox zero at HH:MM: nothing= heartbeat. =auto inbox zero= is inherently in-session because its chain step waits for that yes.
** Fully-unattended pass (=/schedule=) — vNext, not v1