aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-15 08:25:37 -0500
committerCraig Jennings <c@cjennings.net>2026-06-15 08:25:37 -0500
commit0b681dca75299a81549c78681850f353e6a64a87 (patch)
tree1bbad672fb1dc1be370096eb2126db16240bca66
parentf8bdf302fd71ba73ae1424d86275e1e223f09cf3 (diff)
downloadrulesets-0b681dca75299a81549c78681850f353e6a64a87.tar.gz
rulesets-0b681dca75299a81549c78681850f353e6a64a87.zip
feat(ai): add helper-mode workflow contract
helper-mode.org is the canonical home of the helper-instance rules: a second Claude alongside a live primary in the same project. It defines the four read/write tiers (always-safe reads and own-context writes, safe-by-discipline scoped single-heading edits, primary-only file-wide passes and all git mutation, escalation), the four data-integrity windows, the light startup, and the helper wrap-up (archive own context file, skip commit, with the git ban lifting only for an orphaned helper that ends up alone). protocols.org gets a one-paragraph pointer, and INDEX.org gets a triggerless catalog entry like startup.org, so the no-trigger workflow clears the integrity check without a special case. The contract is the canonical home. The routing that sends a session here (ai --helper, startup's roster branch, the wrap-up helper branch) ships behind the feature's drill gate and isn't live yet. Until then a session adopts it by an explicit "you are a helper" instruction.
-rw-r--r--.ai/protocols.org2
-rw-r--r--.ai/workflows/INDEX.org1
-rw-r--r--.ai/workflows/helper-mode.org101
-rw-r--r--claude-templates/.ai/protocols.org2
-rw-r--r--claude-templates/.ai/workflows/INDEX.org1
-rw-r--r--claude-templates/.ai/workflows/helper-mode.org101
6 files changed, 208 insertions, 0 deletions
diff --git a/.ai/protocols.org b/.ai/protocols.org
index caa5303..05f889b 100644
--- a/.ai/protocols.org
+++ b/.ai/protocols.org
@@ -106,6 +106,8 @@ The epoch is baked into the id by the spawner, never minted inside =session-cont
Resolve the path with =.ai/scripts/session-context-path= rather than hardcoding =.ai/session-context.org=; it prints the right path for the current =AI_AGENT_ID=. Fall back to =.ai/session-context.org= if the script isn't present (older checkouts mid-sync). Everything below — the record/recovery purpose, the update triggers, the startup existence check, the wrap-up rename — operates on that resolved path. The prose says "session-context.org" as the default name; read it as "the resolved active path" when =AI_AGENT_ID= is set.
+A helper instance (a second agent running in this project while a primary session is live) follows a different contract: it skips the pulls and rsync, makes only scoped single-heading edits to shared files, leaves all git mutation to the primary, and wraps up by archiving its own context file without committing. The full rules — read/write tiers, data-integrity, light startup, helper wrap-up — live in [[file:workflows/helper-mode.org][workflows/helper-mode.org]]. A session is a helper only when something routes it there (the =ai --helper= launcher, startup's roster check, or an explicit "you are a helper" instruction); the routing itself ships behind the helper-instance feature gate and isn't live yet.
+
This file serves two purposes with one mechanism:
1. *Crash recovery* — if the session dies mid-work, the live file is all that's left. On 2026-01-22 a session crashed during a 20-minute design discussion and all context was lost because this file wasn't being updated.
2. *Session archive* — at wrap-up the file is renamed into =.ai/sessions/=, becoming the permanent record. No transcription to notes.org; the file IS the record.
diff --git a/.ai/workflows/INDEX.org b/.ai/workflows/INDEX.org
index 17963ef..95fe37d 100644
--- a/.ai/workflows/INDEX.org
+++ b/.ai/workflows/INDEX.org
@@ -15,6 +15,7 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e
** Session lifecycle
- =startup.org= — runs automatically at session start. No manual trigger.
+- =helper-mode.org= — role contract for a helper instance (a second Claude in the same project as a live primary). No manual trigger; the spawn paths route to it, "you are a helper" is the manual fallback.
- =first-session.org= — initialize =.ai/= for a brand-new project.
- Triggers: "this is a new project", "let's set this project up". Auto-runs if =.ai/sessions/= is empty.
- =wrap-it-up.org= — end-of-session: write summary, archive, commit, push.
diff --git a/.ai/workflows/helper-mode.org b/.ai/workflows/helper-mode.org
new file mode 100644
index 0000000..8ead37b
--- /dev/null
+++ b/.ai/workflows/helper-mode.org
@@ -0,0 +1,101 @@
+#+TITLE: Helper Mode Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+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 three ways:
+
+- The =ai --helper= launcher routes here after the roster confirms a live agent (the deterministic path).
+- Startup's roster check finds the session is not alone and routes here instead of running normal startup (the safety net for a raw =claude= launch).
+- An explicit "you are a helper, follow helper-mode.org" instruction (the manual fallback).
+
+If none of those applies — the roster shows the session is alone — 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 routes through the cross-agent message form (=machine.project.agent-id=), or just gets reported to Craig. The helper leaves its tree changes for the primary's next commit, or describes them in a targeted message.
+
+* 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 (=.ai/scripts/agent-roster=) to learn whether a primary is still live.
+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 (=ai --helper=, startup's roster branch) and the [[file:wrap-it-up.org][wrap-it-up.org]] helper branch route here. Those wiring pieces ship behind the spec's bats-then-drills-then-pilot gate and are not yet live; until then, the manual "you are a helper" instruction is how a session adopts this contract.
diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org
index caa5303..05f889b 100644
--- a/claude-templates/.ai/protocols.org
+++ b/claude-templates/.ai/protocols.org
@@ -106,6 +106,8 @@ The epoch is baked into the id by the spawner, never minted inside =session-cont
Resolve the path with =.ai/scripts/session-context-path= rather than hardcoding =.ai/session-context.org=; it prints the right path for the current =AI_AGENT_ID=. Fall back to =.ai/session-context.org= if the script isn't present (older checkouts mid-sync). Everything below — the record/recovery purpose, the update triggers, the startup existence check, the wrap-up rename — operates on that resolved path. The prose says "session-context.org" as the default name; read it as "the resolved active path" when =AI_AGENT_ID= is set.
+A helper instance (a second agent running in this project while a primary session is live) follows a different contract: it skips the pulls and rsync, makes only scoped single-heading edits to shared files, leaves all git mutation to the primary, and wraps up by archiving its own context file without committing. The full rules — read/write tiers, data-integrity, light startup, helper wrap-up — live in [[file:workflows/helper-mode.org][workflows/helper-mode.org]]. A session is a helper only when something routes it there (the =ai --helper= launcher, startup's roster check, or an explicit "you are a helper" instruction); the routing itself ships behind the helper-instance feature gate and isn't live yet.
+
This file serves two purposes with one mechanism:
1. *Crash recovery* — if the session dies mid-work, the live file is all that's left. On 2026-01-22 a session crashed during a 20-minute design discussion and all context was lost because this file wasn't being updated.
2. *Session archive* — at wrap-up the file is renamed into =.ai/sessions/=, becoming the permanent record. No transcription to notes.org; the file IS the record.
diff --git a/claude-templates/.ai/workflows/INDEX.org b/claude-templates/.ai/workflows/INDEX.org
index 17963ef..95fe37d 100644
--- a/claude-templates/.ai/workflows/INDEX.org
+++ b/claude-templates/.ai/workflows/INDEX.org
@@ -15,6 +15,7 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e
** Session lifecycle
- =startup.org= — runs automatically at session start. No manual trigger.
+- =helper-mode.org= — role contract for a helper instance (a second Claude in the same project as a live primary). No manual trigger; the spawn paths route to it, "you are a helper" is the manual fallback.
- =first-session.org= — initialize =.ai/= for a brand-new project.
- Triggers: "this is a new project", "let's set this project up". Auto-runs if =.ai/sessions/= is empty.
- =wrap-it-up.org= — end-of-session: write summary, archive, commit, push.
diff --git a/claude-templates/.ai/workflows/helper-mode.org b/claude-templates/.ai/workflows/helper-mode.org
new file mode 100644
index 0000000..8ead37b
--- /dev/null
+++ b/claude-templates/.ai/workflows/helper-mode.org
@@ -0,0 +1,101 @@
+#+TITLE: Helper Mode Workflow
+#+AUTHOR: Craig Jennings & Claude
+#+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 three ways:
+
+- The =ai --helper= launcher routes here after the roster confirms a live agent (the deterministic path).
+- Startup's roster check finds the session is not alone and routes here instead of running normal startup (the safety net for a raw =claude= launch).
+- An explicit "you are a helper, follow helper-mode.org" instruction (the manual fallback).
+
+If none of those applies — the roster shows the session is alone — 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 routes through the cross-agent message form (=machine.project.agent-id=), or just gets reported to Craig. The helper leaves its tree changes for the primary's next commit, or describes them in a targeted message.
+
+* 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 (=.ai/scripts/agent-roster=) to learn whether a primary is still live.
+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 (=ai --helper=, startup's roster branch) and the [[file:wrap-it-up.org][wrap-it-up.org]] helper branch route here. Those wiring pieces ship behind the spec's bats-then-drills-then-pilot gate and are not yet live; until then, the manual "you are a helper" instruction is how a session adopts this contract.