diff options
Diffstat (limited to '.ai/workflows/helper-mode.org')
| -rw-r--r-- | .ai/workflows/helper-mode.org | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/.ai/workflows/helper-mode.org b/.ai/workflows/helper-mode.org new file mode 100644 index 0000000..b32d574 --- /dev/null +++ b/.ai/workflows/helper-mode.org @@ -0,0 +1,120 @@ +#+TITLE: Helper Mode Workflow +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-15 + +* Overview + +The role contract for a *helper instance*: a second Claude session running in the same project as a live primary session, spawned to look something up or make a scoped task update while the primary keeps working. This file is the single canonical home of the helper rules. [[file:../protocols.org][protocols.org]] carries a one-paragraph pointer here; the spawn paths and the wrap-up reference this file rather than restating it. + +A helper is not a subagent. When the work fits a dispatched subagent (the Agent tool) — a bounded lookup or analysis the primary folds back into its own context — use that instead; no second session exists and none of this applies. A helper is for interactive, long-lived parallel work Craig drives himself in a second terminal. + +The governing fact behind every rule below: the session-context split isolates each agent's /session state/ (=.ai/session-context.d/<id>.org=), but everything else in the project — =todo.org=, =.ai/notes.org=, =inbox/=, docs, the git index — is shared mutable state. Two Edit-tool writers on one org file lose updates silently: both read, both write, last write wins. The helper's job is to stay useful without ever being the second writer that clobbers the primary. + +* When to Use This Workflow + +No operator trigger phrase. A helper reaches this contract one of two ways: + +- The =ai --helper= launcher routes here after the roster confirms a live agent (the deterministic path). +- An explicit "you are a helper, follow helper-mode.org" instruction (the manual fallback). + +There is deliberately no third way, and the gap matters: *startup does not check the roster*. A bare =claude= launched into a project that already has a live session runs full primary startup — pulls, rsync, inbox processing — without ever reaching this file. That safety net is designed (see Status below) but unbuilt, so nothing catches a raw launch. Use =ai --helper=. + +If neither route applies, this is a primary session. Run normal [[file:startup.org][startup.org]], not this. + +* Identity + +A helper is =helper-<rand4>= (four random hex/alphanumeric characters, e.g. =helper-a83f=). + +- If the launcher exported =AI_AGENT_ID=, use it. Otherwise self-assign =helper-<rand4>= now. +- Record the chosen id as the *first line* of the session-context file, so it survives across tool calls (shell state does not). The id lives in the file, never in ambient shell state. +- The active session-context path is =.ai/session-context.d/<id>.org=. Resolve it by prefixing the id explicitly wherever a script consumes =AI_AGENT_ID=: + + #+begin_src bash + AI_AGENT_ID=<id> .ai/scripts/session-context-path + #+end_src + + The id must be unique per run; =helper-<rand4>= and the launcher's epoch-tailed id both satisfy that (see protocols.org "Agent-scoped path"). + +* Read/Write Contract + +Four tiers, by how much coordination the write needs. + +** Always safe + +Any read, anywhere in the project. Writes to the helper's own =.ai/session-context.d/<id>.org=. + +** Safe by discipline — scoped task updates (the case Craig named) + +Scoped writes to shared org files (=todo.org=, =.ai/notes.org=) are allowed under four rules, together: + +1. Re-read the file region immediately before each edit. +2. Anchor the edit on a unique heading. +3. Scope each edit to that single heading's subtree. +4. Never reflow, restructure, or sweep the file. + +Appending a new =**= task at a section's end and editing one existing task's body or state both qualify. The race window collapses to seconds, and a collision corrupts one heading rather than the whole file. Log the intended edit first (see Write-ahead journal below). + +** Primary-only — never as a helper + +- File-wide passes: =todo-cleanup.el=, =lint-org.el=, =wrap-org-table.el=, archive sweeps. +- Inbox processing and template sync. +- ALL git mutation: commit, push, pull, stash. Two committers in one worktree contend on the index lock and interleave staging. +- Startup's Phase A.0 pulls and the =.ai/= rsync. The primary already did them, and a concurrent pull-under-edit is exactly the race the startup guards exist to prevent. +- Memory writes. =MEMORY.md= is a shared read-modify-write index with no heading anchors, so it has the lost-update shape of =todo.org= with none of the scoped-edit protection. + +The git ban is concurrency-scoped. /Helper wrap-up/ below lifts it for exactly one case: an orphaned helper that finds itself alone. + +** Escalation + +Anything the contract blocks gets reported to Craig, or — for a cross-project handoff — routed through =inbox-send= to the owning project's =inbox/=. The helper leaves its tree changes for the primary's next commit, or describes them in a note to Craig. + +* Data-Integrity Rules + +The scoped-edit discipline covers helper-vs-primary edits on /different/ headings. Four loss windows remain. They matter doubly in a consolidated project where one =todo.org= carries every task and corruption has maximal blast radius. + +1. *Primary file-wide passes vs a live helper.* A whole-file rewrite run while a helper is mid-edit clobbers the helper's just-written change. Enforced primary-side (the live-helper gate before any hygiene pass), but the helper's part is to keep its own session-context file current so the primary's gate can see it is live. +2. *A new primary starting while a helper runs.* The helper's uncommitted edits make the tree dirty; Phase A.0 already skips pulls on a dirty tree, and startup surfaces live =session-context.d/= files so the new primary knows /why/ the tree is dirty rather than treating it as mess to resolve. +3. *Write-ahead edit journal.* Before applying any shared-file edit, log it — file, heading, one-line intent — to the helper's own session-context file. This tightens the Session Log discipline to log-before-write for shared files specifically, so after a crash the journal shows which edits landed. +4. *The memory dir.* Helpers don't write memory at all. Candidate memories go into the helper's session log; the primary (or its wrap-up promotion check) writes them. + +One collision nit: =inbox-send= filenames carry minute-resolution timestamps, so a helper and primary sending to the same target in the same minute with the same slug would collide. Helper-originated sends include the agent id in the slug. + +* Light Startup + +A helper does not run normal startup. It runs a light version: + +1. Self-assign or adopt the identity (above) and create =.ai/session-context.d/<id>.org= with the id on the first line. +2. Read [[file:../protocols.org][protocols.org]] and this file. Read =.ai/notes.org= for project context if useful. +3. Do NOT run Phase A.0 pulls, =make install=, or the =.ai/= rsync — the primary owns those. +4. Do NOT process the inbox — primary-only. +5. Begin the work Craig spawned the helper for. + +* Helper Wrap-Up + +When the helper's work is done: + +1. Re-run the roster to learn whether a primary is still live. Pass the project root explicitly — =agent-roster= defaults to =$PWD= and keeps only agents at or inside that root, so calling it from a subdirectory hides a primary sitting at the root and reports "alone": + + #+begin_src bash + root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + if [ -x "$root/.ai/scripts/agent-roster" ]; then + "$root/.ai/scripts/agent-roster" "$root"; rc=$? + else + rc=2 + fi + echo "roster rc=$rc" + #+end_src + + Read rc as =wrap-it-up.org= Step 0 does: 1 means a primary is still live, 0 means this helper is orphaned, and 2 (or an absent script) means unavailable — which takes the same archive-only path as 1, because leaving work uncommitted is recoverable and committing under a live primary is not. +2. *Primary still live (the normal case):* finalize the Summary in the helper's own =.ai/session-context.d/<id>.org=, archive it to =.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<description>.org=, and stop. Do NOT commit, push, or run hygiene — the primary's next commit picks up the archived file and any scoped edits the helper left in the tree. +3. *Orphaned helper (roster shows the helper is now alone):* the primary already exited, so the helper assumes full closing duties — the git ban lifts because the concurrency that justified it is gone. Commit and push the tree (including the helper's own edits, which would otherwise strand as a dirty tree), per the normal wrap-up flow in [[file:wrap-it-up.org][wrap-it-up.org]]. + +* Status + +Phase 1.5 of the generic-agent-runtime spec. This contract is the canonical home; the spawn paths and the [[file:wrap-it-up.org][wrap-it-up.org]] helper branch route here. + +Live now: =ai --helper <project>= (roster check, id assignment, helper opener, its own tmux window), the explicit "you are a helper" instruction, and the wrap-it-up.org Step 0 helper branch. + +Not built yet, and worth knowing because it is the gap you can fall into: *startup has no roster check*. A second session launched as a bare =claude= in a project that already has one runs full primary startup — pulls, rsync, inbox processing — with no idea another agent is live. Until that safety net exists, =ai --helper= is not merely the preferred path, it is the only one that makes a helper without being told. + +Also unbuilt: the live-helper gate that pauses a primary's file-wide hygiene passes (=todo-cleanup.el=, =lint-org.el=, =wrap-org-table.el=) while a helper is mid-edit. Data-integrity rule 1 above describes the intended behavior; nothing enforces it yet, so a primary running hygiene can still clobber a helper's just-written scoped edit. |
