diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-23 23:06:46 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-23 23:06:46 -0400 |
| commit | 24ca58d764dbcc2bad57a914a10e9e9b89a3f66e (patch) | |
| tree | dc3ab8db840b1562db36cc048e44a391e31fa73f /claude-templates | |
| parent | 27da1f1d3290314d32455eac4e049118b00a7a13 (diff) | |
| download | rulesets-24ca58d764dbcc2bad57a914a10e9e9b89a3f66e.tar.gz rulesets-24ca58d764dbcc2bad57a914a10e9e9b89a3f66e.zip | |
feat(inbox): consolidate three inbox workflows into one engine
I merged process-inbox, monitor-inbox, and inbox-zero into one inbox.org engine. A shared core (value gate, skeptical review, disposition ladder, reply discipline, capture-guard, priority-scheme check) holds the logic that used to be duplicated and cross-referenced across the three files. Each mode (process, monitor, roam) references the core by name instead of restating it.
Every trigger phrase still works, now routing to a mode, so there's nothing to relearn. I added the interactive auto inbox zero mode: ask for an interval, run roam mode on /loop, acknowledge-only on an empty cycle, surface a find to a queue gated on a yes. The fully-unattended /schedule pass stays vNext, tracked separately.
I repointed every live caller (INDEX, protocols, startup Phase C, wrap-up Step 3, triage-intake, broadcast) at inbox.org and its modes, then deleted the three old files. triage-intake and no-approvals stay separate by design. The value gate, dispositions, capture-guard, and reply discipline all behave as before.
Built from the Ready spec. Workflow-integrity and sync-check pass on both the canonical and mirror trees, the stale-reference grep is clean, and the full suite is green.
Claude-Session: https://claude.ai/code/session_017PtX1nt1rtYVATuzmzBS4f
Diffstat (limited to 'claude-templates')
| -rw-r--r-- | claude-templates/.ai/protocols.org | 4 | ||||
| -rwxr-xr-x | claude-templates/.ai/scripts/capture-guard | 4 | ||||
| -rw-r--r-- | claude-templates/.ai/scripts/tests/capture-guard.bats | 2 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/INDEX.org | 11 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/broadcast.org | 4 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/create-workflow.org | 8 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/inbox-zero.org | 117 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/inbox.org | 481 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/monitor-inbox.org | 122 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/process-inbox.org | 220 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/startup.org | 6 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/triage-intake.org | 2 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/wrap-it-up.org | 12 |
13 files changed, 507 insertions, 486 deletions
diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org index da6928f..3048df2 100644 --- a/claude-templates/.ai/protocols.org +++ b/claude-templates/.ai/protocols.org @@ -203,7 +203,7 @@ Check =inbox/= at every task boundary (after finishing a unit of work, before re .ai/scripts/inbox-status -q #+end_src -Exit 1 means handoffs are pending — process them per =process-inbox.org=. For each accepted handoff, the act-vs-file rule: *act now* when it's clear, bounded, low-risk, in-scope, and cheaper than deferring — just do it, no asking; *file* otherwise — ask first, with filing as option 1 and "do it now" as option 2; *ask* if unsure. Exception: a proposal to change a shared asset (template workflow, rule, skill, synced script) or a substantive convention never silently acts now — it goes through process-inbox's Skeptical Review and its approval (or park) step. Always reply to a handoff's sender (confirm on accept, the why on reject). Full process, the reply discipline, and the opt-in background-monitor =/loop= recipe live in =monitor-inbox.org=. +Exit 1 means handoffs are pending — process them per =inbox.org= process mode. For each accepted handoff, the act-vs-file rule: *act now* when it's clear, bounded, low-risk, in-scope, and cheaper than deferring — just do it, no asking; *file* otherwise — ask first, with filing as option 1 and "do it now" as option 2; *ask* if unsure. Exception: a proposal to change a shared asset (template workflow, rule, skill, synced script) or a substantive convention never silently acts now — it goes through the inbox engine's skeptical review and its approval (or park) step. Always reply to a handoff's sender (confirm on accept, the why on reject). Full process, the reply discipline, and the opt-in background-monitor =/loop= recipe live in =inbox.org= monitor mode. ** Recursive Reads — Honor =.aiignore= @@ -460,7 +460,7 @@ When Craig says this phrase: - If exact match found: Read and guide through process 3. **Fuzzy match across both directories:** Ask for clarification - - Example: User says "empty inbox" but we have "inbox-zero.org" + - Example: User says "empty inbox" but we have "inbox.org" (roam mode) - Ask: "Did you mean the 'inbox zero' workflow, or create new 'empty inbox'?" 4. **No match at all:** Offer to create it diff --git a/claude-templates/.ai/scripts/capture-guard b/claude-templates/.ai/scripts/capture-guard index 1958309..52f4e06 100755 --- a/claude-templates/.ai/scripts/capture-guard +++ b/claude-templates/.ai/scripts/capture-guard @@ -5,8 +5,8 @@ # Editing a file on disk while Emacs has an indirect org-capture buffer # cloned from it reverts the base buffer underneath the capture, wedging it: # the capture can no longer finalize cleanly with C-c C-c, and a freshly-typed -# item can be lost or written back against post-edit content. inbox-zero -# Phase D edits ~/org/roam/inbox.org, the file Craig captures into constantly, +# item can be lost or written back against post-edit content. inbox.org +# roam mode Phase D edits ~/org/roam/inbox.org, the file Craig captures into constantly, # so it calls this guard first. See claude-rules/emacs.md. # # Usage: capture-guard [TARGET_FILE] (default ~/org/roam/inbox.org) diff --git a/claude-templates/.ai/scripts/tests/capture-guard.bats b/claude-templates/.ai/scripts/tests/capture-guard.bats index 12ecb83..2bfee61 100644 --- a/claude-templates/.ai/scripts/tests/capture-guard.bats +++ b/claude-templates/.ai/scripts/tests/capture-guard.bats @@ -2,7 +2,7 @@ # # Tests for claude-templates/.ai/scripts/capture-guard — detects live # org-capture buffers visiting a target file before a workflow edits that -# file on disk (the roam inbox, in inbox-zero Phase D). Editing the file +# file on disk (the roam inbox, in inbox.org roam mode Phase D). Editing the file # underneath an indirect org-capture buffer wedges the capture (see emacs.md). # # Contract under test: diff --git a/claude-templates/.ai/workflows/INDEX.org b/claude-templates/.ai/workflows/INDEX.org index 5ae8480..c6543ca 100644 --- a/claude-templates/.ai/workflows/INDEX.org +++ b/claude-templates/.ai/workflows/INDEX.org @@ -44,12 +44,11 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e - Triggers: "let's do a journal entry", "create a journal entry" - =clean-todo.org= — tidy =todo.org=: hygiene pass + =--archive-done=, then summarize. Wrap-up does this automatically; this is the manual entry point. - Triggers: "clean up todo.org", "clean-todo", "tidy the todo file", "archive the done items in todo.org", "run the todo cleanup" -- =process-inbox.org= — evaluate each inbox item against a three-question value gate (advances an existing TODO / improves the project / serves the mission), then implement, fold, file, defer, or reject per source (Craig / project handoff / script). Auto-invoked by startup when inbox is non-empty. Source-aware rejection flow: handoff rejections write a response back via =inbox-send= naming the failed gate question and any reconsideration condition. - - Triggers: "process inbox", "process the inbox", "handle the inbox", "what's in inbox", "what's in the inbox", "let's clear the inbox", "let's process the inbox items" -- =monitor-inbox.org= — the cadence + act-vs-file + reply layer over process-inbox: "monitor the inbox" runs a pass now then loops process-inbox every 15 min; gates on a clean tree + green suite at both ends; in no-approvals mode auto-executes only agreed + quick + solo items (else files or parks); also the ambient =inbox-status= task-boundary check and the reply-to-sender discipline. - - Triggers: "monitor the inbox", "watch the inbox", "respond to the handoffs", "handle the handoffs" -- =inbox-zero.org= — route the *global roam inbox* (=~/org/roam/inbox.org=) to owning projects by =<project>:= heading prefix. Distinct from =process-inbox.org= (the project's own =inbox/= dir). The current session claims only its own prefixed items, files them into =todo.org=, removes them from the shared inbox, and leaves foreign/unowned items. Every scan reports the total item count plus how many appear related to this project. v1 is single-destination (prefix-claim only); domain-aware whole-inbox routing is deferred. Called read-only from startup (count + offer) and as a wrap-up Step 3 sub-step. - - Triggers: "inbox zero", "empty the inbox", "process the roam inbox", "triage my roam inbox" +- =inbox.org= — one engine for the project's inbox surfaces, with the shared value gate / skeptical review / disposition ladder / reply discipline / capture-guard / priority-scheme check in one place, plus thin per-surface modes. *Process mode* evaluates each project-local =inbox/= item against the three-question value gate, then implements / folds / files / defers / rejects per source (auto-invoked by startup when inbox is non-empty). *Monitor mode* runs process mode now then loops it every 15 min, gating on a clean tree + green suite and adding the act-vs-file + no-approvals-execute + reply discipline. *Roam mode* routes the global roam inbox (=~/org/roam/inbox.org=) to owning projects by =<project>:= prefix (read-only nudge at startup, sweep at wrap-up). *Auto inbox zero* runs roam mode on an interactive =/loop= at a Craig-chosen interval. Distinct from =triage-intake.org= (external accounts), which stays separate. + - Process-mode triggers: "process inbox", "process the inbox", "handle the inbox", "what's in inbox", "what's in the inbox", "let's clear the inbox", "let's process the inbox items" + - Monitor-mode triggers: "monitor the inbox", "watch the inbox", "respond to the handoffs", "handle the handoffs" + - Roam-mode triggers: "inbox zero", "empty the inbox", "process the roam inbox", "triage my roam inbox" + - Auto-mode trigger: "auto inbox zero" (match before "inbox zero") ** Calendar diff --git a/claude-templates/.ai/workflows/broadcast.org b/claude-templates/.ai/workflows/broadcast.org index 1be07d2..60e9ed1 100644 --- a/claude-templates/.ai/workflows/broadcast.org +++ b/claude-templates/.ai/workflows/broadcast.org @@ -159,11 +159,11 @@ broadcast, not a task and not tailored to this project. - Ask Craig any follow-up questions then — this message is deliberately general. #+end_example -The "For the receiving agent" block is fixed text — it travels with every situational broadcast so the message is self-describing. A receiving project's =process-inbox= reads it and acts on those instructions without needing any special-casing; the value gate accepts it as situational awareness that improves how the project works. +The "For the receiving agent" block is fixed text — it travels with every situational broadcast so the message is self-describing. A receiving project's =inbox.org= process mode reads it and acts on those instructions without needing any special-casing; the value gate accepts it as situational awareness that improves how the project works. ** Receiving behavior (what a project does with an incoming situational broadcast) -When =process-inbox= encounters a =Broadcast:= item, the disposition is *record-and-hold*, not file-as-task: +When =inbox.org= process mode encounters a =Broadcast:= item, the disposition is *record-and-hold*, not file-as-task: 1. Add a dated entry to =notes.org= Active Reminders capturing the situation and its end date (if any). 2. If the event bears on an open task, note the connection in that task's body. diff --git a/claude-templates/.ai/workflows/create-workflow.org b/claude-templates/.ai/workflows/create-workflow.org index 6060df1..393fce5 100644 --- a/claude-templates/.ai/workflows/create-workflow.org +++ b/claude-templates/.ai/workflows/create-workflow.org @@ -195,7 +195,7 @@ After the Q&A, ask together: Decide on a name for this workflow. *Naming convention:* Action-oriented (verb form) -- Examples: "refactor", "inbox-zero", "create-workflow", "review-code" +- Examples: "refactor", "clean-todo", "create-workflow", "review-code" - Why: Shorter, natural when saying "let's do a [name] workflow" - Filename: =.ai/workflows/[name].org= @@ -240,10 +240,10 @@ Update =notes.org=: Example entry: #+begin_src org -,** inbox-zero -File: =.ai/workflows/inbox-zero.org= +,** journal-entry +File: =.ai/workflows/journal-entry.org= -Workflow for processing inbox to zero: +Workflow for capturing a daily journal entry: 1. [Brief workflow summary] 2. [Key steps] diff --git a/claude-templates/.ai/workflows/inbox-zero.org b/claude-templates/.ai/workflows/inbox-zero.org deleted file mode 100644 index 231f721..0000000 --- a/claude-templates/.ai/workflows/inbox-zero.org +++ /dev/null @@ -1,117 +0,0 @@ -#+TITLE: Inbox Zero Workflow -#+AUTHOR: Craig Jennings & Claude -#+DATE: 2026-06-13 - -* Overview - -Inbox zero means both inboxes that can feed the current project are checked: - -1. The project-local =inbox/= directory, which receives handoffs from other projects, scripts, and Craig. This workflow delegates those items to =process-inbox.org=; it does not duplicate that workflow's value gate or disposition mechanics. -2. The roam global inbox (=~/org/roam/inbox.org=), Craig's cross-project GTD capture: one shared file every project can see. This workflow routes each roam inbox 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. - -The aspiration is inbox zero: after this workflow runs, the current project's local handoff inbox has been processed and the shared roam inbox no longer contains items explicitly owned by this project. - -This is also 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. - -** Scope: single-destination (v1) - -This version routes each item to its one owning project, identified by an explicit =<project>:= heading prefix. The multi-project domain-aware mode, which would guess the owner of every unprefixed item and empty the whole inbox in one run, is deferred (see "Deferred: domain-aware routing" at the end). v1 claims only what's prefixed for the current project, surfaces the rest, and never guesses. - -** Three callers - -Reused from three callers so the steps live in one place: -- *Startup* (read-only nudge) — count the items, identify which appear related to this project, surface both numbers, offer processing as one of the startup options. Never auto-files. -- *Wrap-up* (Step 3 sub-step) — sweep items that belong here before the cleanup scripts, so imported tasks lint and ride the wrap commit. -- *On demand* — "inbox zero", "empty the inbox", "process the roam inbox", "triage my roam inbox". - -Each project touches the roam inbox at least twice a session this way: once at startup, once at wrap-up. - -* The ownership rule (the coordination primitive) - -The inbox is shared, so the workflow must never let two projects fight over an item or let one project grab another's. Ownership is by explicit prefix: - -- =<project>: ...= heading → owned by that project. The current project claims only items prefixed with its own identifier. -- Prefixed for *another* project → leave untouched (cross-project boundary, =protocols.org=). -- *No prefix* → unowned. Never auto-claim. Surface as candidates a human can claim or prefix. - -The prefix partition is what makes concurrent triage across projects safe: each project only ever removes its own items, so two sessions editing the inbox touch disjoint lines. - -** Resolving this project's identifier (v1) - -Use the project root basename plus its common aliases (=.emacs.d= ↔ =emacs=, and the obvious ones: =rulesets=, =work=, =home=). A project may override the inferred set with an =:INBOX_PREFIX:= line in =notes.org='s *Workflow State* section when the basename is fragile (a dot in the name, an alias the inference misses). The explicit override is optional in v1; the durable multi-project resolution is part of the deferred domain-aware mode. - -* Phase A — Process the project-local inbox - -1. Check the project-local =inbox/= with =.ai/scripts/inbox-status -q=. -2. If pending handoffs exist, run [[file:process-inbox.org][process-inbox.org]] before touching the roam inbox. Project handoffs are already addressed to this project, so they are higher-confidence and cheaper to clear than shared roam captures. -3. If =inbox-status= reports no =inbox/= directory, note it and continue to the roam inbox. Some projects only participate in the shared roam capture flow. -4. If =process-inbox.org= cannot finish because it needs Craig's decision, stop after surfacing that decision. Do not remove roam items in the same run; the project still does not have a clean inbox. - -* Phase B — Identify, count, and match roam items - -1. Resolve the current project's identifier and aliases (above). -2. Read =~/org/roam/inbox.org=. If absent, silent no-op (the file lives only on machines with the roam clone). -3. Bucket every item under the inbox heading: - - *claimed* — prefixed for this project - - *foreign* — prefixed for another project → leave - - *unowned* — no project prefix - - *empty* — a heading with no title and no body: just stars, optionally a =TODO=/keyword, and whitespace (e.g. =** =, =** TODO =, =*** TODO =). These are aborted or accidental captures, owned by nobody, and safe to delete regardless of project. A heading with any title text or any body content is never empty. -4. *Summarize the scan* (Craig's requirement, every scan): report the total item count in the inbox, then the count that appears related to this project. "Appears related" is the union of claimed items (exact prefix) and any unowned item whose topic plainly concerns this project's domain (a content judgment, surfaced as a candidate, never auto-claimed). Foreign-prefixed items are not "related" — they belong to their owner. Note the empty count separately. -5. If claimed, related-unowned, *and* empty are all absent, report the total and stop (the common case for most wraps). Empty entries on their own are enough to enter Phase D — the cleanup runs even when this project owns nothing else in the inbox, since empties belong to nobody and removing them is what "check the inbox" should always do. - -* Phase C — File each claimed roam item into todo.org - -Apply =process-inbox.org='s discipline against the project's =todo.org=; don't reinvent it: - -1. *Status check first.* Already done, or already a task in =todo.org=? → drop it, or fold into the existing task (dated sub-entry per =todo-format.md=). Don't duplicate. -2. *Rewrite* to terse-heading + body per =todo-format.md=. -3. *Priority + tags from THIS project's scheme* — the legend at the top of its =todo.org=, tags from that scheme's allowed set only. The project expresses someday-maybe with =[#D]=; there's no special someday-maybe routing. -4. *File* under the project's Open Work section. - -* Phase D — Reconcile the shared roam inbox - -The roam inbox lives in a git repo (=~/org/roam=, auto-synced by the =roam-sync= timer). Edit it carefully: - -1. *Guard against a live org-capture session* — before any read-modify-write of the roam inbox, run =.ai/scripts/capture-guard "$HOME/org/roam/inbox.org"=. This runs first because the pull in step 2 *also* rewrites the file on disk, and a fast-forward landing underneath a live capture wedges it just as a hand edit would. - - *Exit 0* → no live capture (or no reachable Emacs). Proceed. - - *Exit 1* → an indirect org-capture buffer is cloned from the roam inbox (the script prints the offending buffer name). Editing or fast-forwarding the file underneath it would leave the capture pointing at stale state and unable to finalize with =C-c C-c= (see =emacs.md=). Behavior depends on the caller: - - *On-demand / interactive run* → stop and surface: "You have a live org-capture session open against the roam inbox (=<buffer>=) — finalize it (=C-c C-c=) or abort it (=C-c C-k=) and I'll continue." Re-run the guard and resume once it returns clean. - - *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 Phase C, so the next inbox-zero run's Phase C status-check drops the duplicates and its Phase D removes them from the roam inbox — the skip self-heals, it doesn't lose anything. -2. *Pull first* (=git -C ~/org/roam pull --ff-only=). If it can't fast-forward (dirty tree, divergence), surface and stop. Don't auto-stash, auto-merge, or force. Resolve before removing items. -3. *Remove the claimed items and the empty entries.* Never touch foreign or unowned (titled) items. Empty entries (Phase B's =empty= bucket — a heading with no title and no body) are removed on every triage regardless of who would own a titled version, since an aborted capture belongs to nobody. The claimed-item removal and the empty sweep happen in the same edit. -4. *Commit the roam repo as its own commit* (separate from any project wrap commit). Subject by what changed: =chore(inbox): route <project> tasks to <project>/todo.org= when items were claimed, =chore(inbox): drop empty entries= when the run only swept empties, or both clauses when it did both. Push, or leave for the =roam-sync= timer. Surface a blocked push; don't force. - -* Phase E — Surface - -Report: local project inbox disposition first (processed count and whether it is clear), then roam disposition: moved (with their new priorities and tags), folded, dropped-as-done, and empty entries swept (count). Then the residue: foreign items (left for their owners, count only) and unowned items (count plus the headings that appear related to this project, for manual claim or prefix). Same "summarize what we kept" shape. - -If triaging this batch surfaced a durable, cross-project fact (a reference pointer worth keeping, a pattern worth recording), consider writing it to the agent KB as one =:agent:= node (see =knowledge-base.md=; personal projects only). Skip silently when nothing durable came up — never pad an empty run with a KB line. - -* Skip conditions - -- No project-local =inbox/= and no =~/org/roam/inbox.org= → silent no-op. -- Project-local =inbox/= exists but has no pending handoffs → continue to roam scan. -- No =~/org/roam/inbox.org= after the local inbox check → report the local inbox disposition and stop. -- No claimed, no related-unowned, and no empty roam entries → report the total, stop. -- Roam pull blocked → surface, stop before editing. - -* Caller integration - -** Startup (read-only nudge) - -Startup already checks the project-local =inbox/= via =inbox-status= and processes it through =process-inbox.org= when needed. It also reads =~/org/roam/inbox.org= and produces the roam scan summary; Phase C surfaces one line: "Roam inbox: N items total, M appear related to this project (K empty entries to sweep) — say 'inbox zero' to file them." Offered as one of the priority options. The empty count rides along so a clean-up-only run still gets offered. Startup never auto-files or auto-sweeps roam items; it counts and offers (the read-only nudge never edits, so empties are reported, not removed, until a real triage runs). - -** Wrap-up (Step 3 sub-step) - -A sub-step at the start of wrap-up Step 3 (before the cleanup scripts, so imported tasks get linted and ride the wrap commit) delegates here for the claimed set. Skip-fast when nothing matches. - -* Deferred: domain-aware routing (future work, multi-project) - -v1 handles the single-destination case via the prefix rule. The multi-project parts are deferred until the need is real: - -1. *Domain-aware empty-it-all mode.* If rulesets held a description of each project's domain, one run could guess the owner of every item (prefixed or not) and empty the whole inbox at once, delivering each item to its owning project's =inbox/= via =inbox-send= (where that project's =process-inbox= gate still decides whether to file it). This turns "inbox zero" from a per-project aspiration into a single command. Open: where the domain map lives (central registry vs each project's =notes.org=), how confident a guess must be before auto-routing vs surfacing, and whether a low-confidence item stays put. -2. *Explicit per-project =:INBOX_PREFIX:= as the durable resolver*, replacing basename inference. -3. *Unowned-item lifecycle* once domain-aware routing exists (no item stays unrouted indefinitely). -4. *Concurrent push contention* on the shared roam repo: the pull-before-edit + ff-only + surface-on-conflict floor may want a retry-once-after-pull. - -Take these up when the single-destination version is in use and the multi-project pain is concrete. diff --git a/claude-templates/.ai/workflows/inbox.org b/claude-templates/.ai/workflows/inbox.org new file mode 100644 index 0000000..26bf008 --- /dev/null +++ b/claude-templates/.ai/workflows/inbox.org @@ -0,0 +1,481 @@ +#+TITLE: Inbox Workflow (Engine) +#+AUTHOR: Craig Jennings & Claude +#+DATE: 2026-06-23 + +* Overview + +One engine for the project's inbox surfaces. Inbox items are *ideas to evaluate*, not orders to execute — each is a proposal that earns a place in =todo.org= or git history only when it passes the value gate. The engine holds the shared disposition machinery once: the three-question value gate, the skeptical review, the disposition ladder, the reply-to-sender discipline, the capture-guard before a roam write, and the priority-scheme check. Each *mode* is a thin section that names which surface it reads, how it enters and exits, and which core steps it runs. + +Two surfaces feed a project, and there is a recurring check over the second: + +1. *Project-local =inbox/= dir* — handoffs from other projects (via =inbox-send=), from scripts, and from Craig (typed directives saved as files). Handled by *process mode*; watched on a cadence by *monitor mode*. +2. *Global roam inbox* (=~/org/roam/inbox.org=) — Craig's cross-project GTD capture, one shared file every project can see. Handled by *roam mode*, which claims only the items this project owns. *Auto inbox zero* runs roam mode on a recurring interactive loop. + +A *third* surface — external accounts (email / calendar / PRs) — is a different domain and stays in its own engine: =triage-intake.org= and its source plugins are *not* part of this engine. "Deal with my inbox dirs" is here; "what's new across my accounts" is there. + +*Two altitudes.* For the user, the trigger phrase picks the mode and the phrases are unchanged (see When to Use). For the implementer, this is one file: the core sections are written once, and each mode references them by name ("run the value gate (core §1) on each item") rather than restating them. + +* When to Use This Workflow + +The trigger phrase selects the mode. Every phrase below still works; it now routes to a mode of this engine. + +** Process mode — the local =inbox/= dir + +- "process inbox" / "process the inbox" +- "handle the inbox" +- "what's in inbox" / "what's in the inbox" +- "let's clear the inbox" / "let's process the inbox items" + +Auto-invocation: startup Phase C delegates here when the local inbox is non-empty — don't ask, just run it. + +** Monitor mode — process mode on a cadence + +- "monitor the inbox" / "watch the inbox" — *the defined meaning:* one process pass now, then loop every 15 minutes (see the Monitor mode Cadence section). The phrase *is* the loop, not an opt-in extra. +- "respond to the handoffs" / "handle the handoffs" — a single pass now, no loop. + +Ambient (always on, even with no loop running): the =inbox-status= task-boundary check (Monitor mode). + +** Roam mode — the global roam inbox + +- "inbox zero" / "empty the inbox" / "process the roam inbox" / "triage my roam inbox" + +Called read-only from startup (count + offer) and as a wrap-up Step 3 sub-step. + +** Auto inbox zero — recurring interactive roam check + +- "auto inbox zero" + +Match this before "inbox zero" — the auto phrase contains the roam phrase as a substring, so the longer match wins. Starts a recurring =/loop=-driven roam-mode pass; see the Auto inbox zero mode. + +** Boundary + +Do *not* invoke this engine for an inbox item that is clearly out-of-scope for the project — that is a cross-project routing problem, handled per the cross-project boundary rule in =protocols.org=. And do not invoke it for external-account triage ("what's new in email/cal/PRs") — that is =triage-intake.org=. + +* Core §1 — The value gate + +Every inbox item (local or roam) passes through three questions. One *yes* is enough to accept. + +1. *Does it advance an existing TODO?* Look up by topic in =todo.org='s open work. If the item extends a filed task, fold it in. If it implements a filed task, do the work. +2. *Does it improve how the project works?* Architecture cleanup, workflow refinement, tooling, rule hygiene, drift detection — anything that makes the project itself more effective. +3. *Does it serve the project's stated mission?* Read =notes.org= *Project-Specific Context* if the mission isn't obvious from the working directory and current task. The item should advance that mission, not orbit it. + +Three *no*s means reject. The rejection isn't lazy — an idea that doesn't help any current task, doesn't improve the system, and doesn't serve the mission is genuine noise, and accepting it inflates =todo.org= without payoff. + +* Core §2 — The skeptical review + +The value gate decides whether an item is worth taking. This review decides whether what it proposes is *right*, *complete*, and *as simple as it should be*. Run it on every task and file that arrives — not only shared-asset change proposals. Pure FYIs and replies that ask for nothing skip it. + +Approach the file with curiosity and skepticism. Work through, in writing — the core pass on every item: + +1. Is the request actually right — does it do what it claims, and is the claim correct for this project? +2. Is it complete, or does it leave a gap — an unhandled case, a missing step, an untested path? +3. Should it be simpler? +4. Can it be enhanced to be more effective than as proposed? +5. Does it conflict with any existing instruction — workflows, skills, rules, protocols, CLAUDE.md? + +When the item proposes a change to *shared assets* — template workflows, rules, skills, scripts, anything synced to consuming projects — or to a substantive convention, add the cross-project battery. It arrived from one project's context; you're evaluating it for all of them: + +6. Does this make sense for *all* consuming projects, or just the sender's situation? +7. How does it change a common activity Craig performs — better, worse, or differently than the sender assumed? +8. Plus at least three more questions specific to this change — what breaks for artifacts already using the old shape, what tooling interacts with it, what's underspecified, what the sender's worked example doesn't exercise. + +Output: a short summary of the thinking and a recommendation (do it / do it with named changes / file / reject). For shared-asset and convention changes the recommendation is surfaced to Craig for approval before applying; for ordinary tasks and files it feeds the act-vs-file and no-approvals-execute decision (Monitor mode). + +** In a no-approvals session: shared-asset changes defer and stage + +Shared-asset and convention changes still don't self-apply when Craig has put the session in no-approvals mode — they need his decision, so they fail the *solo* test in Monitor mode's executing-in-no-approvals criteria. Ordinary tasks and files that pass the review and are quick + solo execute under that criteria instead; this defer-and-stage path is for the shared-asset and convention changes that don't qualify. Run the review, prepare the edits in =working/<task-slug>/= (a patch file or the worked-out diff), file a =[#B]= VERIFY carrying the decision package, and reply to the sender that it's parked. The sender's local stopgap (per =cross-project.md='s propagation process) means the delay costs nothing — the canonical update is about durability, not speed. + +Wording-only fixes — no consuming project acts differently — may proceed even then, logged in the session log. + +The VERIFY shape (top-level, =[#B]= so startup's A/B surfacing catches it; no =SCHEDULED= unless the proposal names a real deadline): + +#+begin_example +** VERIFY [#B] Parked: <proposal topic> (from <sender>) +What arrived: <one line — what the handoff proposes>. +Recommendation: <accept as-is / accept with changes / reject> — <2-3 line +skeptical-review summary: what's right, what to change, what was checked>. +Prepared diff: [[file:working/<slug>/proposed.diff]] — apply is mechanical on +your go. +Say "approve the parked <topic>" (or adjust / reject) and it gets applied. +#+end_example + +The full question-battery answers live in the session log and the =working/= dir, not the task body — the body carries the conclusion, with the trail one link away. + +* 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. + +** 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=. + +** Fold into existing TODO +The item extends a task already filed. Update the parent TODO's body with a dated reconciliation sub-entry per =todo-format.md= (=*** YYYY-MM-DD Day @ HH:MM:SS -ZZZZ <what landed>=). Move substantive content to =docs/design/<date>-<topic>.<ext>= if it's worth keeping; reference from the TODO body. Delete the inbox file. + +** File as TODO +Substantive but waits, or needs design/triage before implementation. Add the TODO under =* <Project> Open Work= with priority + tags per the priority-scheme check (core §6). Body summarizes the proposal and links the inbox content if it's been moved to =docs/design/=. Delete the inbox file (or move it to =docs/design/= first if the content survives). + +** 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. + +** 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. +- *From another project (handoff)* — write a response file at =/tmp/inbox-response-<topic>.org=: a heading naming the original handoff and date, one paragraph on the rejection rationale (*which* value-gate question failed and why), one paragraph on the condition under which you'd reconsider (or "never, this misreads the project's mission" if that's the truth). Deliver via =inbox-send <sender> --file /tmp/inbox-response-<topic>.org=. Delete the local inbox file after the response lands. Silent rejection on a handoff trains the sender to escalate around the channel — always close the loop. +- *From a script or automated system* — just delete. No notification. + +** Park (skeptical review in a no-approvals session) +Move the proposal file into =working/<task-slug>/= alongside the prepared diff, file the =[#B]= VERIFY per core §2, reply to the sender that it's parked for Craig's review, and delete the inbox file. On Craig's approval the apply is mechanical: apply the prepared edits, run the normal verify-and-publish flow, close the parked =**= VERIFY per =todo-format.md= (a top-level VERIFY resolves to =DONE= + =CLOSED:=, not a dated header), and send the acceptance reply. On rejection, the reject-from-another-project flow above runs unchanged. + +* Core §4 — Reply-to-sender discipline + +A handoff came from another project's agent (or the user). Close the loop: + +- *Accepted and acted on* — send a confirmation to the sender via =inbox-send <sender> --text "..."=, naming what landed and the commit, so they're not left guessing (they can't see this project's git log). =inbox-send= excludes the current project as a target, so a self-sourced item is handled in-session, not sent. +- *Accepted and filed* — a short confirmation that it's filed and where, so the sender knows it wasn't dropped. +- *Rejected* — always state the why (which value-gate question failed), per the reject-by-source ladder (core §3). + +Cross-project boundary: never act on a file under another project's =.ai/= scope from here — route it back as a handoff (see =cross-project.md=). + +* Core §5 — Capture-guard before a roam write + +Before *any* read-modify-write of =~/org/roam/inbox.org=, run =.ai/scripts/capture-guard "$HOME/org/roam/inbox.org"=. This runs first because a =git pull= on the roam repo *also* rewrites the file on disk, and a fast-forward landing underneath a live capture wedges it just as a hand edit would. + +- *Exit 0* → no live capture (or no reachable Emacs). Proceed. +- *Exit 1* → an indirect org-capture buffer is cloned from the roam inbox (the script prints the offending buffer name). Editing or fast-forwarding the file underneath it would leave the capture pointing at stale state and unable to finalize with =C-c C-c= (see =emacs.md=). Behavior depends on the caller: + - *On-demand / interactive run* → stop and surface: "You have a live org-capture session open against the roam inbox (=<buffer>=) — finalize it (=C-c C-c=) or abort it (=C-c C-k=) and I'll continue." Re-run the guard and resume once it returns clean. + - *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. + +* 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). + +- *Scheme present* — file new TODOs per the scheme. Every TODO gets a priority cookie matching the legend's rules, the mandatory type tag, and any applicable effort/autonomy tags. +- *Scheme absent* — surface one sentence: "This project has no priority scheme. We should adopt one before filing the new TODOs from this inbox pass — want me to propose one based on the rulesets scheme?" If Craig says yes, do that first (the =/research-priority-scheme= research subagent pattern in rulesets is the reference). If Craig says no, file the TODOs without grading but flag in the commit message that they're un-prioritized pending a scheme. + +The point is to avoid adding ungraded =TODO= entries to a project that's never agreed on what =[#A]= means. + +* 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. + +** 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). +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. + +For each inbox file, parse the filename for sender. Two common patterns: + +- =YYYY-MM-DD-HHMM-from-<sender>-<topic>.<ext>= — from another project via =inbox-send=. +- =<topic>.org= — typically from Craig directly, or from a script. + +Note the file type. =.eml= files need the extract script (not raw =Read=): + +#+begin_src bash +# View mode +python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/<file>.eml + +# Pipeline mode (extract attachments to a directory) +python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/<file>.eml --output-dir assets/<target>/ +#+end_src + +Everything else, read directly. + +** Phase B — Evaluate each item + +For each inbox file: + +1. *Read it.* Full read for substantive proposals (org files with TODO entries, design notes, multi-section docs); skim short FYIs and one-liner asks. +2. *Identify the shape.* Instruction, question, proposal, FYI, or handoff — shapes guide disposition. +3. *Apply the value gate* (core §1). One yes → candidate accept. Three nos → candidate reject. +4. *Run the skeptical review* (core §2) on the item before classifying — the core pass on every accepted task and file, plus the cross-project battery when it proposes a shared-asset or convention change. Its summary + recommendation rides along to Phase C; in a no-approvals session it gates whether the item self-applies (quick + solo + agreed, per Monitor mode) or, for shared-asset and convention changes, defers and stages. +5. *Within accept, classify* by the disposition ladder (core §3): implement now / fold into existing TODO / file as TODO. +6. *Within reject, classify by source* (core §3): from Craig / from another project / from a script. + +** Phase B.1 — Priority-scheme check + +Run core §6. This gates Phase C filing when there are accept-and-file items. + +** Phase C — Surface dispositions + +Numbered options inline per =interaction.md= (no popup). Recommendation at item 1. + +Batch trivial items (one-line rejections of script noise, obvious file-as-TODO accepts where the scheme is already settled) into a single confirm-all prompt. Walk substantive items one at a time so the decision is visible. + +Per-item template: + +#+begin_example +<filename> from <sender>: <one-line summary> +Value-gate read: <yes/no on each of the three questions, one phrase each> +Disposition recommendation: <implement / fold into <TODO> / file [#X] :tags: / reject> + +1. <recommendation as item 1> +2. <alternative> +3. Defer — leave in inbox under PROCESSED-<topic>.<ext> until <condition> +4. Something else +#+end_example + +For items that went through the skeptical review, the surfaced disposition includes its summary + recommendation, and approval here is what authorizes the apply. In a no-approvals session those items are reported as parked (the =[#B]= VERIFY) rather than surfaced for live approval. + +For pure FYIs that need no action, surface as a single line and recommend delete-with-acknowledgment. + +** Phase D — Apply + +Apply each disposition per the ladder (core §3). The flow is autonomous past Craig's Phase C approval. + +** Phase E — Close out + +Verify =inbox/= is empty (excluding =.gitkeep= and any intentional =PROCESSED-*= files). Run =\ls -la inbox/= and confirm. + +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. + +Stamp =:LAST_INBOX_PROCESS:= in =notes.org='s *Workflow State* section if it exists, so future workflows that gate on freshness can read it. Same format as =:LAST_AUDIT:= (=YYYY-MM-DD=). + +* Mode: monitor + +Process mode on a cadence. This is the *when, how-often, and act-vs-file* layer; the per-item disposition mechanics are the core sections, run via process mode — not restated here. Monitor decides *that* an item gets handled and *how I respond*; the core decides *what disposition* each item gets. + +The gap it closes: handoffs that arrive mid-session used to sit unseen until the user asked or the next startup ran. A handoff the sender can't see being handled trains them to escalate around the inbox channel. + +** Preconditions — before starting + +Never begin monitoring on a dirty worktree or a failing test suite. A dirty tree means the auto-commit at the end of an executed item sweeps up unrelated changes; a red suite means you can't tell whether the monitor broke something. At the start: + +1. =git status --porcelain= is empty (clean worktree). +2. A full test run is all green (=make test= here, or the project's full-suite command). + +If *dirty*: offer to commit the pending changes in discrete, logical batches before starting. If *red*: offer to investigate the failures first. Surface the blocker with inline numbered options per =interaction.md= and wait — monitoring does not start until the tree is clean and the suite is green. + +** Cadence — how often to check + +*"Monitor the inbox" = run now, then loop every 15 minutes.* Do one process pass over any pending handoffs immediately, then start the loop: + +#+begin_src +/loop 15m check the inbox with inbox-status and run inbox.org process mode over any pending handoffs +#+end_src + +Each firing runs the cheap =inbox-status= check first and only does a full process pass when items are pending. The loop is the monitoring; it runs until Craig stops it or the session ends. Honor the Preconditions gate before the first pass and the Close-out gate when the loop stops. + +*Ambient task-boundary check (always on, even without a loop).* After finishing a unit of work, before reporting back or asking "what's next," run the cheap status check: + +#+begin_src bash +.ai/scripts/inbox-status -q +#+end_src + +Exit 1 means handoffs are pending — list them (drop =-q=) and run process mode. Exit 0 means clean; say nothing. This is one =find=; it costs nothing to run often, and it's the fix for handoffs piling up unseen during long sessions. + +*Startup and wrap-up already cover their ends.* Startup Phase C processes a non-empty inbox; the wrap-up sanity check refuses to wrap with unprocessed handoffs. The task-boundary cadence fills the middle. + +*Mid-task arrivals.* If a handoff lands while you're mid-task and it's urgent (blocks the current work, or is time-sensitive), surface it right away. Otherwise batch it to the next task boundary so the current work isn't thrashed. + +** The act-vs-file decision + +Every accepted handoff (one that clears the value gate) is then either acted on now or filed as a task. + +*Act immediately — and just do it, no asking — when all of these hold:* +- *Clear* — the action is unambiguous; no design decision or option-choice is needed. +- *Bounded* — small, finishable this session, ideally a tight file set. +- *Low-risk and verifiable now* — not a risky change to load-bearing infra (or trivially revertible), and testable/lintable this session. +- *In-scope and safe* — within this project, not destructive or outward-facing without confirmation, not across a project boundary. +- *Cheaper than deferring* — doing it now costs less than filing plus re-triaging later. + +When you decide to act, queue the work and do it. Don't ask first. + +*Exception:* a proposal to change a shared asset (template workflow, rule, skill, synced script) or a substantive convention never qualifies for silent act-now, however clear and bounded it looks — it routes through the skeptical review (core §2), which carries its own approval (or, in a no-approvals session, park) step. + +*File a task when any of these hold:* +- It needs a judgment call, a design decision, or an option the user would pick. +- It's large, multi-session, or sprawls across many files. +- It's blocked (a dependency, an external thing, the user is away). +- It's risky enough to want the user's eyes before it lands. +- It's off the session's active goal and acting now would derail it (file and keep going, unless it's urgent). + +When you decide to file, *ask first* — inline numbered options per =interaction.md=, with *filing as option 1 (the recommendation)* and *"do it now" as option 2*: + +#+begin_example +<handoff> wants <X>. My read: file it (needs <reason>). + +1. File as a TODO ([#?] :tags:) — Recommended +2. Do it now instead +3. Something else + +Pick a number. +#+end_example + +*Always ask if you're unsure* which side of the line an item falls on. Decisiveness on clear act-now items is the point of the rule; the ask is for genuine ambiguity and for filing. + +** Executing in no-approvals mode + +When Craig has put the session in no-approvals mode, an accepted item may be implemented automatically — but only when all three of these hold: + +1. *Agreed* — you've run the value gate and the full skeptical review and concluded the change should be done, not merely that it's harmless. +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. + +** Replying to handoffs + +Close the loop per the reply-to-sender discipline (core §4): confirm what landed (accepted-and-acted), confirm where it's filed (accepted-and-filed), or state the why (rejected). + +** The inbox-status script + +=.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. + +** Close out — before finishing + +End the way it started: clean worktree, green suite. Before stopping the loop or reporting the pass done: + +1. Commit or revert everything left in the worktree — nothing uncommitted remains. +2. Run the full test suite once more and confirm all green. + +If either can't be satisfied — a half-done item, a failure introduced during the pass — surface it rather than leaving it. The next monitor run assumes a clean, green starting state (the Preconditions gate). + +* Mode: roam + +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. + +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. + +** Scope: single-destination (v1) + +Routes each item to its one owning project, identified by an explicit =<project>:= heading prefix. The multi-project domain-aware mode (guess the owner of every unprefixed item and empty the whole inbox in one run) is deferred — see "Deferred: domain-aware routing" at the end. v1 claims only what's prefixed for the current project, surfaces the rest, and never guesses. + +** Callers + +The steps live here so three callers reuse them: +- *Startup* (read-only nudge) — count the items, identify which appear related to this project, surface both numbers, offer processing as one of the startup options. Never auto-files. +- *Wrap-up* (Step 3 sub-step) — sweep items that belong here before the cleanup scripts, so imported tasks lint and ride the wrap commit. +- *On demand* — the roam-mode trigger phrases. + +Each project touches the roam inbox at least twice a session this way: once at startup, once at wrap-up. + +** The ownership rule (the coordination primitive) + +The inbox is shared, so the mode must never let two projects fight over an item or let one grab another's. Ownership is by explicit prefix: + +- =<project>: ...= heading → owned by that project. The current project claims only items prefixed with its own identifier. +- Prefixed for *another* project → leave untouched (cross-project boundary, =protocols.org=). +- *No prefix* → unowned. Never auto-claim. Surface as candidates a human can claim or prefix. + +The prefix partition is what makes concurrent triage across projects safe: each project only ever removes its own items, so two sessions editing the inbox touch disjoint lines. + +*Resolving this project's identifier (v1).* Use the project root basename plus its common aliases (=.emacs.d= ↔ =emacs=, and the obvious ones: =rulesets=, =work=, =home=). A project may override the inferred set with an =:INBOX_PREFIX:= line in =notes.org='s *Workflow State* section when the basename is fragile. The explicit override is optional in v1; the durable multi-project resolution is part of the deferred domain-aware mode. + +** Phase A — Process the project-local inbox + +1. Check the project-local =inbox/= with =.ai/scripts/inbox-status -q=. +2. If pending handoffs exist, run *process mode* before touching the roam inbox. Project handoffs are already addressed to this project, so they are higher-confidence and cheaper to clear than shared roam captures. +3. If =inbox-status= reports no =inbox/= directory, note it and continue to the roam inbox. Some projects only participate in the shared roam capture flow. +4. If process mode cannot finish because it needs Craig's decision, stop after surfacing that decision. Do not remove roam items in the same run; the project still does not have a clean inbox. + +** Phase B — Identify, count, and match roam items + +1. Resolve the current project's identifier and aliases (above). +2. Read =~/org/roam/inbox.org=. If absent, silent no-op (the file lives only on machines with the roam clone). +3. Bucket every item under the inbox heading: + - *claimed* — prefixed for this project + - *foreign* — prefixed for another project → leave + - *unowned* — no project prefix + - *empty* — a heading with no title and no body: just stars, optionally a =TODO=/keyword, and whitespace (e.g. =** =, =** TODO =, =*** TODO =). These are aborted or accidental captures, owned by nobody, and safe to delete regardless of project. A heading with any title text or any body content is never empty. +4. *Summarize the scan* (Craig's requirement, every scan): report the total item count in the inbox, then the count that appears related to this project. "Appears related" is the union of claimed items (exact prefix) and any unowned item whose topic plainly concerns this project's domain (a content judgment, surfaced as a candidate, never auto-claimed). Foreign-prefixed items are not "related" — they belong to their owner. Note the empty count separately. +5. If claimed, related-unowned, *and* empty are all absent, report the total and stop (the common case for most wraps). Empty entries on their own are enough to enter Phase D — the cleanup runs even when this project owns nothing else, since empties belong to nobody and removing them is what "check the inbox" should always do. + +** Phase C — File each claimed roam item into todo.org + +Apply the core disposition discipline against the project's =todo.org=; don't reinvent it: + +1. *Status check first.* Already done, or already a task in =todo.org=? → drop it, or fold into the existing task (dated sub-entry per =todo-format.md=). Don't duplicate. +2. *Rewrite* to terse-heading + body per =todo-format.md=. +3. *Priority + tags from THIS project's scheme* (core §6) — the legend at the top of its =todo.org=, tags from that scheme's allowed set only. The project expresses someday-maybe with =[#D]=; there's no special someday-maybe routing. +4. *File* under the project's Open Work section. + +** Phase D — Reconcile the shared roam inbox + +The roam inbox lives in a git repo (=~/org/roam=, auto-synced by the =roam-sync= timer). Edit it carefully: + +1. *Guard against a live org-capture session* — run the capture-guard (core §5) before any read-modify-write. The guard runs first because the pull in step 2 also rewrites the file on disk. On exit 1 the caller-specific behavior (interactive stop-and-surface vs wrap-up skip-and-self-heal) is in core §5. +2. *Pull first* (=git -C ~/org/roam pull --ff-only=). If it can't fast-forward (dirty tree, divergence), surface and stop. Don't auto-stash, auto-merge, or force. Resolve before removing items. +3. *Remove the claimed items and the empty entries.* Never touch foreign or unowned (titled) items. Empty entries (Phase B's =empty= bucket) are removed on every triage regardless of who would own a titled version, since an aborted capture belongs to nobody. The claimed-item removal and the empty sweep happen in the same edit. +4. *Commit the roam repo as its own commit* (separate from any project wrap commit). Subject by what changed: =chore(inbox): route <project> tasks to <project>/todo.org= when items were claimed, =chore(inbox): drop empty entries= when the run only swept empties, or both clauses when it did both. Push, or leave for the =roam-sync= timer. Surface a blocked push; don't force. + +** Phase E — Surface + +Report: local project inbox disposition first (processed count and whether it is clear), then roam disposition: moved (with their new priorities and tags), folded, dropped-as-done, and empty entries swept (count). Then the residue: foreign items (left for their owners, count only) and unowned items (count plus the headings that appear related to this project, for manual claim or prefix). Same "summarize what we kept" shape. + +If triaging this batch surfaced a durable, cross-project fact (a reference pointer worth keeping, a pattern worth recording), consider writing it to the agent KB as one =:agent:= node (see =knowledge-base.md=; personal projects only). Skip silently when nothing durable came up — never pad an empty run with a KB line. + +** Skip conditions + +- No project-local =inbox/= and no =~/org/roam/inbox.org= → silent no-op. +- Project-local =inbox/= exists but has no pending handoffs → continue to roam scan. +- No =~/org/roam/inbox.org= after the local inbox check → report the local inbox disposition and stop. +- No claimed, no related-unowned, and no empty roam entries → report the total, stop. +- Roam pull blocked → surface, stop before editing. + +** Caller integration + +*Startup (read-only nudge).* Startup already checks the project-local =inbox/= via =inbox-status= and processes it through process mode when needed. It also reads =~/org/roam/inbox.org= and produces the roam scan summary; one line surfaces: "Roam inbox: N items total, M appear related to this project (K empty entries to sweep) — say 'inbox zero' to file them." Offered as one of the priority options. The empty count rides along so a clean-up-only run still gets offered. Startup never auto-files or auto-sweeps roam items; it counts and offers (the read-only nudge never edits, so empties are reported, not removed, until a real triage runs). + +*Wrap-up (Step 3 sub-step).* A sub-step at the start of wrap-up Step 3 (before the cleanup scripts, so imported tasks get linted and ride the wrap commit) delegates here for the claimed set. Skip-fast when nothing matches. + +** Deferred: domain-aware routing (future work, multi-project) + +v1 handles the single-destination case via the prefix rule. The multi-project parts are deferred until the need is real: + +1. *Domain-aware empty-it-all mode.* If rulesets held a description of each project's domain, one run could guess the owner of every item (prefixed or not) and empty the whole inbox at once, delivering each item to its owning project's =inbox/= via =inbox-send= (where that project's process-mode gate still decides whether to file it). Open: where the domain map lives, how confident a guess must be before auto-routing vs surfacing, and whether a low-confidence item stays put. +2. *Explicit per-project =:INBOX_PREFIX:= as the durable resolver*, replacing basename inference. +3. *Unowned-item lifecycle* once domain-aware routing exists (no item stays unrouted indefinitely). +4. *Concurrent push contention* on the shared roam repo: the pull-before-edit + ff-only + surface-on-conflict floor may want a retry-once-after-pull. + +Take these up when the single-destination version is in use and the multi-project pain is concrete. + +* Mode: auto inbox zero + +A recurring, *interactive* roam check. Trigger phrase: "auto inbox zero" (match before "inbox zero" — the longer phrase wins). On invocation, *ask Craig for the interval* (e.g. 30 min, 2 hours), then drive the loop with =/loop <interval>= running roam mode. It is in-session and interactive by design — each cycle reports, and a find waits for Craig's go before any work happens. + +** Per cycle + +1. Run roam mode's scan (Phase A local check + Phase B roam scan). The capture-guard and pull-before-edit discipline (core §5, roam Phase D) still gate any write. +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. +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* → launch into implementing the found items, each through the normal disposition ladder (core §3) + verify flow. + - *No* → they stay queued for a later go. +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 gated on Craig's yes; a quiet inbox produces only the timestamped acknowledgement. =auto inbox zero= is inherently in-session because its execute step waits for a yes. + +** Fully-unattended pass (=/schedule=) — vNext, not v1 + +A fully-unattended cron pass (firing while Craig is away) is a *different contract* and is deferred. It can't wait for a yes, so it has to decide up front whether it may mutate =todo.org= and the roam inbox or stays read-only, how a find reaches Craig asynchronously, how dedup state survives across runs that don't share a session, and what session/auth context a cron run carries. + +The =/schedule= recipe, once that contract is designed, would look like: + +#+begin_src +/schedule <cron-expression> run inbox.org roam mode read-only, and <surface-mechanism> any finds +#+end_src + +v1 ships only the interactive =/loop= shape above; the unattended contract is logged to =todo.org= for its own design pass. Don't invent the unattended behavior here — route a request for it to that task. + +* Common Mistakes + +1. *Treating items as orders.* Inbox content is a proposal. The value gate is the rule. Implementing every item without evaluation inflates =todo.org= and trains senders to keep sending noise. +2. *Filing without applying the value gate.* "File as TODO" is not a default — it's the disposition for proposals that pass the gate but wait. A reject is also a valid answer. +3. *Filing raw TODOs when the project has a priority scheme.* Core §6 is mandatory when the scheme exists. An un-graded TODO in a project with a legend is a defect. +4. *Silently deleting a project handoff.* Send a response naming which value-gate question failed. Silent rejection trains the sender to escalate to Craig instead of through the inbox channel. +5. *Pushing back on a Craig directive only to immediately implement it anyway.* If you genuinely think Craig is wrong, say so and wait. If you don't, just do the work — don't theatre the pushback. +6. *Skipping the implement-vs-fold-vs-file classification.* Defaulting every accept to "file as TODO" turns the inbox into a queue that flows into =todo.org= without filtering. +7. *Not propagating value-gate failure to the response.* When you reject a handoff, name *which* gate question failed so the sender can recalibrate, not just resend. +8. *Forgetting to delete the inbox file after acting.* The local inbox should be empty when process mode ends. Files left behind become noise on the next startup. +9. *Applying a shared-asset change proposal without the skeptical review.* The value gate alone asks whether to take the change, never whether the change is right, complete, or as simple as it should be. (Worked example: the 2026-06-12 spec-decisions handoff was applied as-is and the after-the-fact review surfaced a lost state, a vacuous gate pass, and an enhancement — all catchable up front.) +10. *Editing the roam inbox without the capture-guard.* A disk write under a live org-capture wedges the capture (core §5). Guard first, every roam write. +11. *Auto inbox zero re-surfacing queued items.* The loop must dedup against the displayed queue, not just against what's been implemented — or every cycle re-lists the same un-run finds. + +* Living Document + +Refine the value gate's three questions if the project's mission sharpens. Tune the per-source rejection-response template if =inbox-send= response loops surface a pattern. Tune the monitor cadence if task-boundary checking proves too frequent or too sparse. Capture the auto-loop interval that worked once the pattern recurs. + +If a mode wants real depth — enough that it bloats the core — it can become an =inbox.<mode>.org= plugin under this engine's namespace (the pattern =triage-intake= uses) rather than swelling this file. The principle that inbox items are *ideas to evaluate* is the part that doesn't change. diff --git a/claude-templates/.ai/workflows/monitor-inbox.org b/claude-templates/.ai/workflows/monitor-inbox.org deleted file mode 100644 index 4912a2b..0000000 --- a/claude-templates/.ai/workflows/monitor-inbox.org +++ /dev/null @@ -1,122 +0,0 @@ -#+TITLE: Monitor Inbox Workflow -#+AUTHOR: Craig Jennings & Claude -#+DATE: 2026-05-31 - -* Overview - -Keep the project's =inbox/= responsive: notice handoffs on a cadence, triage each one, decide whether to act now or file it, and reply to the sender. This workflow is the /when, how-often, and act-vs-file/ layer. The per-item disposition mechanics — the value gate, the implement/fold/file classification, the per-source rejection flow — live in [[file:process-inbox.org][process-inbox.org]] and are not duplicated here. Think of it as: monitor-inbox decides /that/ an item gets handled and /how I respond/; process-inbox decides /what disposition/ each item gets. - -The gap this closes: handoffs that arrive mid-session used to sit unseen until the user asked or the next startup ran. A handoff the sender can't see being handled trains them to escalate around the inbox channel. - -* Preconditions — before starting - -Never begin inbox monitoring on a dirty worktree or with a failing test suite. A dirty tree means the auto-commit at the end of an executed item sweeps up unrelated changes; a red suite means you can't tell whether the monitor broke something. At the start of the workflow, check both: - -1. =git status --porcelain= is empty (clean worktree). -2. A full test run is all green (=make test= here, or the project's full-suite command). - -If the worktree is *dirty*: offer to commit the pending changes in discrete, logical batches before starting. If the suite is *red*: offer to investigate the failures first. Surface the blocker with inline numbered options per =interaction.md= and wait — monitoring does not start until the tree is clean and the suite is green. - -* When to Use This Workflow - -Trigger phrases: - -- "monitor the inbox" / "watch the inbox" — *the defined meaning:* run one process-inbox pass now, then loop process-inbox every 15 minutes (see Cadence). Not an opt-in extra — the phrase *is* the loop. -- "respond to the handoffs" / "handle the handoffs" — a single pass now, no loop. - -Cadence auto-trigger (ambient, always on): check at every task boundary during a session, even when no loop is running — see Cadence below. - -* Cadence — how often to check - -*"Monitor the inbox" = run now, then loop every 15 minutes.* When Craig says monitor or watch the inbox, do one process-inbox pass over any pending handoffs immediately, then start the loop: - -#+begin_src -/loop 15m check the inbox with inbox-status and run process-inbox.org over any pending handoffs -#+end_src - -Each firing runs the cheap =inbox-status= check first and only does a full process-inbox pass when items are pending. The loop is the monitoring; it runs until Craig stops it or the session ends. Honor the Preconditions gate before the first pass and the Close-out gate when the loop stops. - -*Ambient task-boundary check (always on, even without a loop).* After finishing a unit of work, before reporting back or asking "what's next," run the cheap status check: - -#+begin_src bash -.ai/scripts/inbox-status -q -#+end_src - -Exit 1 means handoffs are pending — list them (drop =-q=) and process per process-inbox.org. Exit 0 means clean; say nothing. This is one =find=; it costs nothing to run often, and it's the fix for handoffs piling up unseen during long sessions. - -*Startup and wrap-up already cover their ends.* Startup Phase C processes a non-empty inbox; the wrap-up sanity check refuses to wrap with unprocessed handoffs. The task-boundary cadence fills the middle. - -*Mid-task arrivals.* If a handoff lands while you're mid-task and it's urgent (blocks the current work, or is time-sensitive), surface it right away. Otherwise batch it to the next task boundary so the current work isn't thrashed. - -* The act-vs-file decision - -Every accepted handoff (one that clears process-inbox's value gate) is then either acted on now or filed as a task. The rule, and how to surface it: - -*Act immediately — and just do it, no asking — when all of these hold:* -- *Clear* — the action is unambiguous; no design decision or option-choice is needed. -- *Bounded* — small, finishable this session, ideally a tight file set. -- *Low-risk and verifiable now* — not a risky change to load-bearing infra (or trivially revertible), and testable/lintable this session. -- *In-scope and safe* — within this project, not destructive or outward-facing without confirmation, not across a project boundary. -- *Cheaper than deferring* — doing it now costs less than filing plus re-triaging later. - -When you decide to act, queue the work and do it. Don't ask first. - -*Exception:* a proposal to change a shared asset (template workflow, rule, skill, synced script) or a substantive convention never qualifies for silent act-now, however clear and bounded it looks — it routes through process-inbox's *Skeptical Review*, which carries its own approval (or, in a no-approvals session, park) step. - -*File a task when any of these hold:* -- It needs a judgment call, a design decision, or an option the user would pick. -- It's large, multi-session, or sprawls across many files. -- It's blocked (a dependency, an external thing, the user is away). -- It's risky enough to want the user's eyes before it lands. -- It's off the session's active goal and acting now would derail it (file and keep going, unless it's urgent). - -When you decide to file, *ask first* — inline numbered options per =interaction.md=, with *filing as option 1 (the recommendation)* and *"do it now" as option 2*: - -#+begin_example -<handoff> wants <X>. My read: file it (needs <reason>). - -1. File as a TODO ([#?] :tags:) — Recommended -2. Do it now instead -3. Something else - -Pick a number. -#+end_example - -*Always ask if you're unsure* which side of the line an item falls on. Decisiveness on clear act-now items is the point of the rule; the ask is for genuine ambiguity and for filing. - -** Executing in no-approvals mode - -When Craig has put the session in no-approvals mode, an accepted item may be implemented automatically — but only when all three of these hold: - -1. *Agreed* — you've run the value gate and the full Skeptical Review and concluded the change should be done, not merely that it's harmless. -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 process-inbox's defer-and-stage park; an oversized item fails *quick* and gets filed. - -* Replying to handoffs - -A handoff came from another project's agent (or the user). Close the loop: - -- *Accepted and acted on* — send a confirmation to the sender via =inbox-send <sender> --text "..."=, naming what landed and the commit, so they're not left guessing (they can't see this project's git log). =inbox-send= excludes the current project as a target, so a self-sourced item is handled in-session, not sent. -- *Accepted and filed* — a short confirmation that it's filed and where, so the sender knows it wasn't dropped. -- *Rejected* — always state the why (which value-gate question failed), per process-inbox's per-source rejection flow. - -Cross-project boundary: never act on a file under another project's =.ai/= scope from here — route it back as a handoff (see =cross-project.md=). - -* The inbox-status script - -=.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. - -* Close out — before finishing - -End the workflow the way it started: clean worktree, green suite. Before stopping the loop or reporting the pass done: - -1. Commit or revert everything left in the worktree — nothing uncommitted remains. -2. Run the full test suite once more and confirm all green. - -If either can't be satisfied — a half-done item, a failure introduced during the pass — surface it rather than leaving it. The next monitor run assumes a clean, green starting state (the Preconditions gate), so handing it a dirty tree or a red suite breaks the next run before it begins. - -* Living Document - -Tune the cadence if task-boundary checking proves too frequent or too sparse in practice. Refine the act-vs-file criteria as edge cases recur. If the background-monitor loop becomes a common pattern, capture the interval that worked. The decision rule itself — act-now is silent, filing asks with file-as-option-1, ambiguity asks — is the stable core (set by Craig, 2026-05-30). diff --git a/claude-templates/.ai/workflows/process-inbox.org b/claude-templates/.ai/workflows/process-inbox.org deleted file mode 100644 index 687767e..0000000 --- a/claude-templates/.ai/workflows/process-inbox.org +++ /dev/null @@ -1,220 +0,0 @@ -#+TITLE: Process Inbox Workflow -#+AUTHOR: Craig Jennings & Claude -#+DATE: 2026-05-28 - -* Overview - -Inbox items are *ideas to evaluate*, not orders to execute. They arrive from Craig (typed directives saved as files), from other projects (handoffs via =inbox-send=), and from scripts/automated systems. Each is a proposal. An item earns a place in =todo.org= or git history only when it passes the value gate: it advances an existing task, improves how the project works, or serves the project's stated mission. - -The workflow is the disposition discipline. Read each item, evaluate honestly, apply the decision, then notify the sender if it was a project handoff and you're rejecting. Silent rejection on a handoff is worse than no reply. - -* When to Use This Workflow - -User triggers: - -- "process inbox" / "process the inbox" -- "handle the inbox" -- "what's in inbox" / "what's in the inbox" -- "let's clear the inbox" / "let's process the inbox items" - -Auto-invocation: - -- Startup =Phase C step 2= delegates here when the inbox is non-empty. Don't ask Craig — just run it. - -Do *not* invoke this for inbox items that are clearly out-of-scope for the project — those are cross-project routing problems, handled per the cross-project boundary rule in =protocols.org=. - -* The Value Gate - -Every inbox item passes through three questions. One *yes* is enough to accept. - -1. *Does it advance an existing TODO?* Look up by topic in =todo.org='s open work. If the item extends a filed task, fold it in. If it implements a filed task, do the work. -2. *Does it improve how the project works?* Architecture cleanup, workflow refinement, tooling, rule hygiene, drift detection — anything that makes the project itself more effective. -3. *Does it serve the project's stated mission?* Read =notes.org= *Project-Specific Context* if the mission isn't obvious from the working directory and current task. The item should advance that mission, not orbit it. - -Three *no*s means reject. The rejection isn't lazy — an idea that doesn't help any current task, doesn't improve the system, and doesn't serve the mission is genuine noise, and accepting it inflates =todo.org= without payoff. - -* The Skeptical Review (every arriving task and file) - -The value gate decides whether an item is worth taking. This review decides whether what it proposes is *right*, *complete*, and *as simple as it should be*. Run it on every task and file that arrives in the inbox — not only shared-asset change proposals. Pure FYIs and replies that ask for nothing skip it. - -Approach the file with curiosity and skepticism. Work through, in writing — the core pass on every item: - -1. Is the request actually right — does it do what it claims, and is the claim correct for this project? -2. Is it complete, or does it leave a gap — an unhandled case, a missing step, an untested path? -3. Should it be simpler? -4. Can it be enhanced to be more effective than as proposed? -5. Does it conflict with any existing instruction — workflows, skills, rules, protocols, CLAUDE.md? - -When the item proposes a change to shared assets — template workflows, rules, skills, scripts, anything synced to consuming projects — or to a substantive convention, add the cross-project battery. It arrived from one project's context; you're evaluating it for all of them: - -6. Does this make sense for *all* consuming projects, or just the sender's situation? -7. How does it change a common activity Craig performs — better, worse, or differently than the sender assumed? -8. Plus at least three more questions specific to this change — what breaks for artifacts already using the old shape, what tooling interacts with it, what's underspecified, what the sender's worked example doesn't exercise. - -Output: a short summary of the thinking and a recommendation (do it / do it with named changes / file / reject). For shared-asset and convention changes the recommendation is surfaced to Craig for approval before applying; for ordinary tasks and files it feeds the act-vs-file and no-approvals-execute decision (=monitor-inbox.org=). - -** In a no-approvals session: shared-asset changes defer and stage - -Shared-asset and convention changes still don't self-apply when Craig has put the session in no-approvals mode — they need his decision, so they fail the *solo* test in monitor-inbox's executing-in-no-approvals criteria. Ordinary tasks and files that pass the review and are quick + solo execute under that criteria instead; this defer-and-stage path is for the shared-asset and convention changes that don't qualify. Run the review, prepare the edits in =working/<task-slug>/= (a patch file or the worked-out diff), file a =[#B]= VERIFY carrying the decision package, and reply to the sender that it's parked. The sender's local stopgap (per =cross-project.md='s propagation process) means the delay costs nothing — the canonical update is about durability, not speed. - -Wording-only fixes — no consuming project acts differently — may proceed even then, logged in the session log. - -The VERIFY shape (top-level, =[#B]= so startup's A/B surfacing catches it; no =SCHEDULED= unless the proposal names a real deadline): - -#+begin_example -** VERIFY [#B] Parked: <proposal topic> (from <sender>) -What arrived: <one line — what the handoff proposes>. -Recommendation: <accept as-is / accept with changes / reject> — <2-3 line -skeptical-review summary: what's right, what to change, what was checked>. -Prepared diff: [[file:working/<slug>/proposed.diff]] — apply is mechanical on -your go. -Say "approve the parked <topic>" (or adjust / reject) and it gets applied. -#+end_example - -The full question-battery answers live in the session log and the =working/= dir, not the task body — the body carries the conclusion, with the trail one link away. - -* 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). -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 (look for a =* Priority and Tag Scheme= section or similar between the intro and the first =* <Project> Open Work= header). - -For each inbox file, parse the filename for sender. Two common patterns: - -- =YYYY-MM-DD-HHMM-from-<sender>-<topic>.<ext>= — from another project via =inbox-send=. -- =<topic>.org= — typically from Craig directly, or from a script. - -Note the file type. =.eml= files need the extract script (not raw =Read=): - -#+begin_src bash -# View mode -python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/<file>.eml - -# Pipeline mode (extract attachments to a directory) -python3 .ai/scripts/eml-view-and-extract-attachments.py inbox/<file>.eml --output-dir assets/<target>/ -#+end_src - -Everything else, read directly. - -* Phase B — Evaluate each item - -For each inbox file: - -1. *Read it.* For substantive proposals (org files with TODO entries, design notes, multi-section docs), the full read is the right move. For short FYIs and one-liner asks, skim. -2. *Identify the shape.* Is it an instruction, a question, a proposal, an FYI, or a handoff? Shapes guide disposition. -3. *Apply the value gate.* Three questions above. One yes → candidate accept. Three nos → candidate reject. -4. *Run the Skeptical Review* (section above) on the item before classifying — the core pass on every accepted task and file, plus the cross-project battery when it proposes a shared-asset or convention change. Its summary + recommendation rides along to Phase C; in a no-approvals session it gates whether the item self-applies (quick + solo + agreed, per =monitor-inbox.org=) or, for shared-asset and convention changes, defers and stages. -5. *Within accept, classify:* - - *Implement now* — small, scoped, clear, no design call required. The work is the disposition. - - *Fold into existing TODO* — the item extends a task already filed; update the TODO body and link the inbox content if substantive. - - *File as TODO* — substantive but waits, or needs design/triage before implementation. -6. *Within reject, classify by source:* - - *From Craig* — push back honestly in chat. State why you won't implement. Offer the conditions under which you would, if any. Wait for Craig to override or accept. - - *From another project* — write a response file naming the rejection rationale and (optionally) the condition under which you'd reconsider. Deliver via =inbox-send <sender> --file <response>= per the cross-project handoff convention. - - *From a script or automated system* — just delete; no notification needed. - -* Phase B.1 — Priority-scheme check - -This gates Phase C filing when 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). - -- *Scheme present* — file new TODOs per the scheme. Every TODO gets a priority cookie matching the legend's rules, the mandatory type tag, and any applicable effort/autonomy tags. -- *Scheme absent* — surface one sentence: "This project has no priority scheme. We should adopt one before filing the new TODOs from this inbox pass — want me to propose one based on the rulesets scheme?" If Craig says yes, do that first (the =/research-priority-scheme= research subagent pattern in rulesets is the reference). If Craig says no, file the TODOs without grading but flag in the commit message that they're un-prioritized pending a scheme. - -The point is to avoid adding ungraded =TODO= entries to a project that's never agreed on what =[#A]= means. - -* Phase C — Surface dispositions - -Numbered options inline per =interaction.md= (no popup). Recommendation at item 1. - -Batch trivial items (one-line rejections of script noise, obvious file-as-TODO accepts where the scheme is already settled) into a single confirm-all prompt. Walk substantive items one at a time so the decision is visible. - -Per-item template: - -#+begin_example -<filename> from <sender>: <one-line summary> -Value-gate read: <yes/no on each of the three questions, one phrase each> -Disposition recommendation: <implement / fold into <TODO> / file [#X] :tags: / reject> - -1. <recommendation as item 1> -2. <alternative> -3. Defer — leave in inbox under PROCESSED-<topic>.<ext> until <condition> -4. Something else -#+end_example - -For items that went through the Skeptical Review, the surfaced disposition includes its summary + recommendation, and approval here is what authorizes the apply. In a no-approvals session those items are reported as parked (the =[#B]= VERIFY) rather than surfaced for live approval. - -For pure FYIs that need no action, surface as a single line and recommend delete-with-acknowledgment. - -* Phase D — Apply - -Apply each disposition. The flow is autonomous past Craig's Phase C approval. - -** Implement-now - -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=. - -** Fold into existing TODO - -Update the parent TODO's body with a dated reconciliation sub-entry per =todo-format.md= (=*** YYYY-MM-DD Day @ HH:MM:SS -ZZZZ <what landed>=). Move substantive content to =docs/design/<date>-<topic>.<ext>= if it's worth keeping; reference from the TODO body. Delete the inbox file. - -** File as TODO - -Add the TODO under =* <Project> Open Work= with priority + tags per Phase B.1. Body summarizes the proposal and links the inbox content if it's been moved to =docs/design/=. Delete the inbox file (or move it to =docs/design/= first if the content survives). - -** Reject from Craig - -State the rejection in chat clearly: what you won't implement, why, and the conditions (if any) under which you would. Wait for Craig's override or acknowledgment. The inbox file stays until Craig confirms — if he overrides, re-enter Phase D as accept; if he acknowledges the rejection, delete the file. - -** Reject from another project (handoff) - -Write the response file at =/tmp/inbox-response-<topic>.org=. Contents: - -- Heading naming the original handoff and date -- One paragraph: the rejection rationale (which value-gate question failed and why) -- One paragraph: the condition under which you'd reconsider, if such a condition exists. If the answer is "never, this misreads the project's mission," say so directly. - -Deliver via =inbox-send <sender> --file /tmp/inbox-response-<topic>.org=. The =inbox-send= script (per =cross-project.md=) handles the from-prefix, date stamp, and target inbox path. - -Delete the local inbox file after the response lands in the sender's inbox. - -** Reject from script or automated system - -Just delete. No notification. - -** 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-inbox= run when the condition is met or the deferral has aged out. - -** Park (Skeptical Review in a no-approvals session) - -Move the proposal file into =working/<task-slug>/= alongside the prepared diff, file the =[#B]= VERIFY per the Skeptical Review section, reply to the sender that it's parked for Craig's review, and delete the inbox file. On Craig's approval the apply is mechanical: apply the prepared edits, run the normal verify-and-publish flow, close the parked =**= VERIFY per =todo-format.md= (a top-level VERIFY resolves to =DONE= + =CLOSED:=, not a dated header), and send the sender the acceptance reply. On rejection, the reject-from-another-project flow above runs unchanged. - -* Phase E — Close out - -Verify =inbox/= is empty (excluding =.gitkeep= and any intentional =PROCESSED-*= files). Run =\ls -la inbox/= and confirm. - -Update the session log per =protocols.org= with one short paragraph summarizing this pass: count processed, count accepted (implement/fold/file split), count rejected (Craig/handoff/script split), and the commit SHA if a commit landed. - -Stamp =:LAST_INBOX_PROCESS:= in =notes.org='s *Workflow State* section if it exists, so future workflows that gate on freshness can read it. Same format as =:LAST_AUDIT:= (=YYYY-MM-DD=). - -* Common Mistakes - -1. *Treating items as orders.* Inbox content is a proposal. The value gate is the rule. Implementing every item without evaluation inflates =todo.org= and trains senders to keep sending noise. -2. *Filing without applying the value gate.* "File as TODO" is not a default — it's the disposition for proposals that pass the gate but wait. A reject is also a valid file-as-TODO answer to nothing. -3. *Filing raw TODOs when the project has a priority scheme.* Phase B.1 is mandatory when the scheme exists. An un-graded TODO in a project with a legend is a defect. -4. *Silently deleting a project handoff.* Send a response. The sender's next session sees the response in their inbox and learns the rejection rationale. Silent rejection trains the sender to escalate to Craig instead of through the inbox channel. -5. *Pushing back on a Craig directive only to immediately implement it anyway.* If you genuinely think Craig is wrong, say so and wait for his call. If you don't, just do the work — don't theatre the pushback. -6. *Skipping the implement-vs-fold-vs-file classification.* Defaulting every accept to "file as TODO" turns the inbox into a queue that flows into =todo.org= without filtering. Small, scoped, clear items get implemented now; substantive proposals get filed; extensions to existing work get folded. -7. *Not propagating value-gate failure to the response.* When you reject a handoff, the response should name *which* gate question failed (advances no current task / doesn't improve the project / doesn't serve the mission) so the sender can recalibrate, not just resend. -8. *Forgetting to delete the inbox file after acting.* The inbox should be empty when this workflow ends. Files left behind become noise on the next startup. -9. *Applying a shared-asset change proposal without the Skeptical Review.* The value gate alone asks whether to take the change, never whether the change is right, complete, or as simple as it should be. A proposal that's clear and bounded can still carry a design gap — the review is where that surfaces, before the change syncs to every consuming project. (Worked example: the 2026-06-12 spec-decisions handoff was applied as-is and the after-the-fact review surfaced a lost state, a vacuous gate pass, and an enhancement — all catchable up front.) - -* Living Document - -Refine the value gate's three questions if the project's mission sharpens. Tune the per-source rejection-response template if =inbox-send= response loops surface a pattern. Add new auto-classification shortcuts if certain item shapes (e.g. routine FYIs from a script) become common. - -The workflow is shaped by use. The principle that inbox items are *ideas to evaluate* is the part that doesn't change. diff --git a/claude-templates/.ai/workflows/startup.org b/claude-templates/.ai/workflows/startup.org index 7540787..5e8f61e 100644 --- a/claude-templates/.ai/workflows/startup.org +++ b/claude-templates/.ai/workflows/startup.org @@ -150,7 +150,7 @@ These calls have no dependencies on each other. Issue them all together in one m 7. Read =.ai/project-workflows/startup-extras.org= if it exists. 8. =[ -f todo.org ] && .ai/scripts/task-review-staleness.sh todo.org 7 || true= — count top-level tasks overdue for review (the daily task-review habit's startup nudge). The =[ -f todo.org ]= guard skips projects without a root todo.org; =|| true= keeps Phase A from failing if the script isn't synced yet. Threshold 7 days is one review cycle of slack — softer than the wrap-up health check's 30-day alarm. 9. =bash ~/code/rulesets/scripts/sync-language-bundle.sh "$PWD" 2>/dev/null || true= — language-bundle freshness for the current project. Fingerprint-detects which bundle (if any) the project has, auto-fixes drifted rulesets-owned files (=.claude/rules/*.md=, =.claude/hooks/*=, =githooks/*=), and surfaces drift in =settings.json= without writing it (a project may have customized it). =CLAUDE.md= is deliberately left untracked — it's seed-only in =install-lang= and project-owned afterward, mirroring how =diff-lang= skips it. Quiet when there's no bundle or everything's clean. Hardcodes the rulesets path because =languages/= is the canonical source and lives only there — the same absolute-path dependency the rsyncs already carry. =|| true= keeps Phase A from failing on older checkouts where the script isn't present yet. The =.ai/= rsyncs and this call write to disjoint paths (=.ai/= vs =.claude/=/=githooks/=), so the batch stays parallel-safe. -10. =[ -f "$HOME/org/roam/inbox.org" ] && grep -cE '^\*\* ' "$HOME/org/roam/inbox.org" || true= — count items in the roam global inbox (=~/org/roam/inbox.org=), the inbox-zero startup nudge. Silent if the roam clone isn't on this machine. Phase C reads the file when the count is non-zero, splits total vs items related to this project, and surfaces the offer (see =inbox-zero.org=). Read-only; never files at startup. +10. =[ -f "$HOME/org/roam/inbox.org" ] && grep -cE '^\*\* ' "$HOME/org/roam/inbox.org" || true= — count items in the roam global inbox (=~/org/roam/inbox.org=), the roam-mode startup nudge. Silent if the roam clone isn't on this machine. Phase C reads the file when the count is non-zero, splits total vs items related to this project, and surfaces the offer (see =inbox.org= roam mode). Read-only; never files at startup. 11. KB surface prep (the read + contribute startup nudges; see =docs/design/2026-06-16-encourage-kb-contribution-spec.org=). Gated on the agent KB clone. Counts =:agent:= nodes, lists up to 5 whose content matches the current project basename (titles only; a few most-recent nodes as a fallback when nothing matches), and resolves the best-practices node path. Read-only; silent when the clone is absent. Phase C surfaces the relevant titles (consult) and the best-practices link (contribute). #+begin_src bash @@ -196,7 +196,7 @@ This phase touches the user and runs sequentially: - Mention Pending Decisions from notes.org. - Briefly note significant template updates noticed during sync (new workflows, protocol changes). - *Task-review nudge.* If the Phase A staleness count (step 11) is greater than zero, surface one line: "=<N>= top-level tasks unreviewed for >7 days — say 'let's do a task review' to run a cycle." If zero, say nothing. - - *Roam inbox nudge.* If the Phase A roam-inbox count is greater than zero, read =~/org/roam/inbox.org=, split total vs items related to this project (claimed by the =<project>:= prefix, plus any unprefixed item whose topic plainly concerns this project), and surface one line: "Roam inbox: =<N>= total, =<M>= appear related to this project — say 'inbox zero' to file them." Offer it as a priority option; never auto-file. If the count is zero or the file is absent, say nothing. See =inbox-zero.org=. + - *Roam inbox nudge.* If the Phase A roam-inbox count is greater than zero, read =~/org/roam/inbox.org=, split total vs items related to this project (claimed by the =<project>:= prefix, plus any unprefixed item whose topic plainly concerns this project), and surface one line: "Roam inbox: =<N>= total, =<M>= appear related to this project — say 'inbox zero' to file them." Offer it as a priority option; never auto-file. If the count is zero or the file is absent, say nothing. See =inbox.org= roam mode. - *KB consult nudge (read side).* If the Phase A KB-surface prep returned any =kb-relevant-titles=, surface one line listing them (capped 5): "KB lessons that may be relevant: =<title>=; =<title>=… — open the node before related work." The titles are declarative, so the list alone tells you whether to open one. Gated on the roam clone; silent when the clone is absent or nothing relevant surfaced. See the best-practices node and =knowledge-base.md=. - *KB contribute nudge (write side).* Once per session, surface one line pointing at the best-practices node (the =kb-bestpractices= path from Phase A): "Learned something durable? See =<path>= for how to write a KB node — contributing cross-project facts is welcome (personal projects only; work/unknown projects never write per =knowledge-base.md=)." Light encouragement, never a gate. Gated on the roam clone; silent when absent. - *Language-bundle sync.* If the Phase A step-12 call (=sync-language-bundle.sh=) printed anything, surface it. =fixed= lines are informational — the drift was already repaired (note that =.claude/= is now dirty if the project commits it). A =drift= line on =settings.json= is surface-only and needs the printed =make install-<lang> PROJECT=.= to reconcile; flag it so the user can decide. If the call was silent, say nothing. @@ -211,7 +211,7 @@ This phase touches the user and runs sequentially: #+end_src If it reports a count, surface one line: wrap-up's Step 4.0 will commit it as =chore: sync .ai tooling from templates=, or offer to commit it now. If silent, say nothing. This is the crashed-session counterpart to the wrap-up commit step (the primary fix). From the 2026-05-31 jr-estate + work handoffs. -2. *Process inbox if non-empty.* Mandatory — don't ask, just delegate to [[file:process-inbox.org][process-inbox.org]]. That workflow owns the value gate (advances an existing TODO / improves the project / serves the mission), the per-source rejection flow (Craig / project handoff / script), the priority-scheme check before filing, and the =.eml= extraction path. Single source of truth for the discipline. +2. *Process inbox if non-empty.* Mandatory — don't ask, just delegate to [[file:inbox.org][inbox.org]] process mode. That mode owns the value gate (advances an existing TODO / improves the project / serves the mission), the per-source rejection flow (Craig / project handoff / script), the priority-scheme check before filing, and the =.eml= extraction path. Single source of truth for the discipline. 3. *Execute project-specific startup extras* (the contents of =.ai/project-workflows/startup-extras.org= read in Phase A). If the file didn't exist, skip. 4. *Ask about priorities.* "What would you like to work on, or is there something urgent you need?" - If urgent: proceed immediately. diff --git a/claude-templates/.ai/workflows/triage-intake.org b/claude-templates/.ai/workflows/triage-intake.org index 7241017..a5a3bda 100644 --- a/claude-templates/.ai/workflows/triage-intake.org +++ b/claude-templates/.ai/workflows/triage-intake.org @@ -191,7 +191,7 @@ Running in the live session means MCP auth (Slack, Gmail, Linear) is inherited f ** Preconditions and Close-out -Auto mode borrows the inbox-monitor gates (=monitor-inbox.org=): do not start on a dirty worktree or a red test suite — a close's batch commit would otherwise sweep up unrelated changes — and leave the tree clean and green when the loop stops. Surface a blocker with inline numbered options per =interaction.md= and wait. +Auto mode borrows the inbox monitor-mode gates (=inbox.org= monitor mode): do not start on a dirty worktree or a red test suite — a close's batch commit would otherwise sweep up unrelated changes — and leave the tree clean and green when the loop stops. Surface a blocker with inline numbered options per =interaction.md= and wait. ** A sweep: accumulate, don't mutate diff --git a/claude-templates/.ai/workflows/wrap-it-up.org b/claude-templates/.ai/workflows/wrap-it-up.org index b1560eb..7c66c60 100644 --- a/claude-templates/.ai/workflows/wrap-it-up.org +++ b/claude-templates/.ai/workflows/wrap-it-up.org @@ -94,15 +94,15 @@ Replace =DESCRIPTION= with your picked slug. (=AI_AGENT_ID= should be filename-s If the project has a =todo.org= at its root, run the cleanup script before committing. Two passes, both fast and idempotent: a hygiene pass and an archive pass. -*** Roam inbox sweep (inbox-zero) +*** Roam inbox sweep (inbox roam mode) -Before the cleanup scripts, sweep the roam global inbox (=~/org/roam/inbox.org=) for items that belong to this project, so any imported tasks get linted and ride the wrap commit. Delegate to [[file:inbox-zero.org][inbox-zero.org]] for the claimed set. +Before the cleanup scripts, sweep the roam global inbox (=~/org/roam/inbox.org=) for items that belong to this project, so any imported tasks get linted and ride the wrap commit. Delegate to [[file:inbox.org][inbox.org]] roam mode for the claimed set. #+begin_src bash [ -f "$HOME/org/roam/inbox.org" ] && grep -cE '^\*\* ' "$HOME/org/roam/inbox.org" || true #+end_src -Skip-fast when nothing matches: if the roam clone isn't on this machine, or no item is prefixed for this project, this is a silent no-op. When claimed items exist, run inbox-zero's Phase B–C (file each into =todo.org=, then remove them from the shared inbox in a separate roam commit). Report the total count and how many appeared related to this project, per inbox-zero's scan-summary rule. +Skip-fast when nothing matches: if the roam clone isn't on this machine, or no item is prefixed for this project, this is a silent no-op. When claimed items exist, run roam mode's Phase B–C (file each into =todo.org=, then remove them from the shared inbox in a separate roam commit). Report the total count and how many appeared related to this project, per roam mode's scan-summary rule. *** Hygiene pass @@ -208,7 +208,7 @@ For an interactive walk of the judgments mid-day, run =/lint-org todo.org=. *** Inbox sanity check (surface unprocessed handoffs) -If the project has an =inbox/= directory, verify it holds nothing but =.gitkeep=, =lint-followups.org= (the lint-org pipeline file the next morning's daily-prep consumes), and any explicitly-deferred =PROCESSED-*= files before the wrap completes. An inbox that arrived at session start with handoffs from other projects, or that received handoffs mid-session, needs the =process-inbox.org= workflow to run and apply its value-gate dispositions. Wrapping with a dirty inbox silently defers the work to next session and accumulates handoff debt that the sender can't see. +If the project has an =inbox/= directory, verify it holds nothing but =.gitkeep=, =lint-followups.org= (the lint-org pipeline file the next morning's daily-prep consumes), and any explicitly-deferred =PROCESSED-*= files before the wrap completes. An inbox that arrived at session start with handoffs from other projects, or that received handoffs mid-session, needs =inbox.org= process mode to run and apply its value-gate dispositions. Wrapping with a dirty inbox silently defers the work to next session and accumulates handoff debt that the sender can't see. #+begin_src bash unprocessed=$(find inbox -maxdepth 1 -type f \ @@ -217,7 +217,7 @@ unprocessed=$(find inbox -maxdepth 1 -type f \ ! -name 'PROCESSED-*' \ 2>/dev/null | wc -l) if [ "$unprocessed" -gt 0 ]; then - echo "wrap-up: inbox/ has $unprocessed unprocessed item(s). Run process-inbox.org before wrapping, or explicitly defer each item with a one-line reason in the valediction." + echo "wrap-up: inbox/ has $unprocessed unprocessed item(s). Run inbox.org process mode before wrapping, or explicitly defer each item with a one-line reason in the valediction." find inbox -maxdepth 1 -type f \ ! -name '.gitkeep' \ ! -name 'lint-followups.org' \ @@ -230,7 +230,7 @@ If the count is zero or the project has no =inbox/= directory, the check is a si The check exempts =lint-followups.org= explicitly because lint-org runs earlier in the same wrap-up workflow and writes its judgment items to that file in =inbox/= by design. The file is a pipeline artifact for the next morning's =daily-prep=, not a handoff that needs the value gate. -This integrates with =process-inbox.org=, which stamps =:LAST_INBOX_PROCESS:= in =notes.org='s *Workflow State* section on completion. Wrap-up doesn't double-stamp. It only ensures the inbox carries nothing but the expected pipeline artifacts at session end. +This integrates with =inbox.org= process mode, which stamps =:LAST_INBOX_PROCESS:= in =notes.org='s *Workflow State* section on completion. Wrap-up doesn't double-stamp. It only ensures the inbox carries nothing but the expected pipeline artifacts at session end. *** Review-habit health check (surface a slipped daily task-review) |
