aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-11 20:05:16 -0500
committerCraig Jennings <c@cjennings.net>2026-06-11 20:05:16 -0500
commit65b99e86d570e489bcd91f377b71193cd7eaba44 (patch)
tree346bce8950cb0832f14041ef4d3ab955960607fd
parent00fc6f10d132e61adde26613372cf845a5abe776 (diff)
downloadrulesets-65b99e86d570e489bcd91f377b71193cd7eaba44.tar.gz
rulesets-65b99e86d570e489bcd91f377b71193cd7eaba44.zip
docs(spec): deterministic helper spawn and session-end ordering rules
The launcher becomes the spawn mechanism: a shell script runs the roster check, assigns the id, and launches with the helper instructions in order, where a model-followed startup instruction can skip a step. The in-session roster check stays as the safety net for raw launches and still splits a live anchor into crashed versus concurrent. Session-end ordering was unhandled: a helper outliving the primary stranded a dirty worktree, since the helper may not commit and the agent allowed to is gone. The git ban on helpers is concurrency-scoped, so it lifts when the helper finds itself alone at wrap-up and the last agent out closes the door with the full wrap-up. The mirror case pauses too: a primary wrapping with live helpers stops at the commit and asks whether to sweep the helper's in-flight work, wait, or leave closing to the helper.
-rw-r--r--docs/design/2026-05-28-generic-agent-runtime-spec.org63
-rw-r--r--todo.org3
2 files changed, 52 insertions, 14 deletions
diff --git a/docs/design/2026-05-28-generic-agent-runtime-spec.org b/docs/design/2026-05-28-generic-agent-runtime-spec.org
index 243eac3..2b451cc 100644
--- a/docs/design/2026-05-28-generic-agent-runtime-spec.org
+++ b/docs/design/2026-05-28-generic-agent-runtime-spec.org
@@ -429,10 +429,21 @@ operator created deliberately and can manage manually.
not operator-triggerable by phrase; startup's detection is its entry
point, plus an explicit "you are a helper" instruction as the manual
fallback.
-- =ai --helper= remains as a convenience (exports =AI_AGENT_ID= and
- =AI_HELPER=1=, names the tmux window =<project>:helper-<id>=), but it is
- no longer the mechanism — detection is. The flag's value is a clearer
- tmux roster and skipping the self-assignment step.
+- The launcher is the deterministic spawn path (Craig, 2026-06-11, fourth
+ revision): a shell script can't skip a step the way a model-followed
+ instruction can. =ai --helper [project]= does three things in order —
+ (a) runs =agent-roster= for the target directory, (b) assigns and exports
+ the id (=AI_AGENT_ID=helper-<rand4>=, =AI_HELPER=1=) when the roster shows
+ a live agent, and (c) launches the agent in that directory with the
+ helper opening instruction (read and follow =helper-mode.org=) already in
+ the prompt. Window named =<project>:helper-<id>=. Roster empty → it warns
+ and launches a normal primary session instead.
+- The in-session startup check (previous subsection) stays as the safety
+ net, not the mechanism: it catches agents started raw (=claude= in a
+ terminal, no launcher) and is still what splits a live anchor into
+ crashed-vs-concurrent. Belt and suspenders: the launcher makes the common
+ path deterministic; the startup roster keeps the uncommon path safe.
+ Both call the same =agent-roster= script.
*** Read/write contract for shared files
@@ -520,13 +531,34 @@ Helper startup is deliberately light: resolve the context path, read
its Session Log — the context-contamination rule above), and start working.
No pulls, no rsync, no inbox processing, no staleness nudges.
-Helper wrap-up: archive its own file to
-=.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<desc>.org= (existing rule), skip the
-hygiene passes and the commit/push steps, and surface any uncommitted tree
-changes it left so the primary or Craig picks them up. A helper that dies
-uncleanly leaves its file in =session-context.d/= — the next startup's
-existence check surfaces it as an interrupted agent, same as the singleton
-today.
+Helper wrap-up forks on the roster, because session-end /ordering/ is not
+guaranteed — the primary may wrap and leave while helpers keep working
+(Craig, 2026-06-11):
+
+- /Others still live:/ archive its own file to
+ =.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<desc>.org=, skip the hygiene passes
+ and the commit/push steps, and surface any uncommitted tree changes so
+ the remaining agents or Craig pick them up. Today's helper contract.
+- /Alone (orphaned helper — the primary already wrapped):/ last agent out
+ closes the door. Without this, the helper's edits would strand as a dirty
+ worktree with nobody allowed to commit them. The git ban on helpers
+ exists for /concurrency/ (index-lock contention, interleaved staging),
+ and an orphaned helper has no concurrency — so the ban lifts, and the
+ helper assumes closing duties: the full wrap-up, including hygiene passes
+ (it is alone; the gate is satisfied), commit through the normal
+ review/voice flow, and push.
+
+The mirror case — the /primary/ wrapping while helpers still live — extends
+the hygiene gate to the wrap-up commit itself: a wrap-up commit sweeps the
+whole tree, including a helper's in-progress edits. With live helpers on the
+roster, the primary's wrap-up pauses and asks Craig: commit the helper's
+work-in-progress along with its own (the helper's log-before-write journal
+shows what was in flight), wait for the helper to finish, or wrap without
+the shared-file hygiene and leave the helper to close the door as above.
+
+A helper that dies uncleanly leaves its file in =session-context.d/= — the
+next session's roster-aware startup surfaces it as an interrupted agent,
+same as the singleton today.
** Hook and validation strategy
@@ -597,8 +629,13 @@ Independent of the phases 2-6 go/no-go; same-runtime only.
- =helper-mode.org= template workflow — the role contract: identity
self-assignment, the read/write tiers, light start, helper wrap-up.
INDEX entry marked auto-routed (no operator trigger phrase).
-- =ai --helper= flag as the optional convenience: =AI_AGENT_ID= + =AI_HELPER=
- export, tmux window naming.
+- =ai --helper= as the deterministic spawn path: roster check, id
+ assignment + export, launch with the helper-mode opening instruction,
+ tmux window naming, warn-and-run-primary when the roster is empty.
+- Wrap-up ordering rules: helper wrap-up re-runs the roster and assumes
+ full closing duties (hygiene + commit + push) when orphaned; primary
+ wrap-up with live helpers pauses at the commit and asks (commit helper
+ WIP / wait / leave closing to the helper).
- The shared-file read/write contract documented where agents will obey it
(protocols.org pointing at =helper-mode.org=, with startup.org and
wrap-it-up.org carrying the helper branches).
diff --git a/todo.org b/todo.org
index 941b837..9f7ec41 100644
--- a/todo.org
+++ b/todo.org
@@ -50,7 +50,8 @@ Implement Phase 1.5 of the generic-agent-runtime spec ([[file:docs/design/2026-0
- =agent-roster= detection script (the load-bearing piece, replaces operator action entirely): pgrep + /proc cwd match within project root + self-ancestry exclusion; verified live 2026-06-11 with 4 concurrent agents. Bats coverage.
- Startup detection-first: the roster check runs before Phase A.0's pulls; not-alone routes to the new =helper-mode.org= role contract and runs nothing else; alone keeps crashed-vs-fresh anchor logic (the roster also disambiguates crashed primary from live primary).
- =helper-mode.org= template workflow: identity self-assignment (helper-<rand4>, recorded in its own .d/ context file), the read/write tiers, light start, helper wrap-up. Auto-routed, no trigger phrase.
-- =ai --helper= flag demoted to convenience: AI_AGENT_ID + AI_HELPER export, tmux window naming.
+- =ai --helper= as the deterministic spawn path (Craig's shell-script point: a script can't skip the check): roster → id export → launch with the helper-mode opener; warn-and-run-primary on empty roster. The startup roster check stays as the safety net for raw launches.
+- Wrap-up ordering: helper wrap-up re-runs the roster — orphaned helper (primary already gone) assumes full closing duties incl. commit+push (the git ban is concurrency-scoped and lifts when alone; otherwise its edits strand as a dirty tree); primary wrap-up with live helpers pauses at the commit and asks (commit helper WIP / wait / leave closing to the helper).
- Shared-file read/write contract into protocols.org pointing at helper-mode.org (helper: scoped single-heading org edits only; file-wide passes, inbox processing, and all git mutation stay primary-only); helper branches in startup.org (light path, no pulls/rsync) and wrap-it-up.org (archive own file, skip hygiene + commit).
- Bats: launcher id assignment/sanitization, helper-vs-primary resolution, two simultaneous context files.
- Data-integrity items (spec second pass, 2026-06-11): live-helper gate before any file-wide hygiene pass (todo-cleanup/lint-org/wrap-org-table check session-context.d/, pause + ask on live files, surface stale ones); todo-cleanup.el brought up to the backup-to-/tmp invariant (lint-org and wrap-org-table already conform — verified); log-before-write journaling for helper shared-file edits; memory writes primary-only (MEMORY.md has no heading anchors — helpers log candidates instead); agent id in helper-originated inbox-send slugs (minute-resolution filenames can collide).