diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-30 07:42:47 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-30 07:42:47 -0500 |
| commit | 84bd121683add4440a3652b12b70e6e2d45f5384 (patch) | |
| tree | b4a2cd0b2d9a3c862db02ae0216a9f7511dd246d /.ai/workflows/wrap-it-up.org | |
| parent | 73e8c01fa49f48766a283e2db933e38951f656ab (diff) | |
| download | rulesets-84bd121683add4440a3652b12b70e6e2d45f5384.tar.gz rulesets-84bd121683add4440a3652b12b70e6e2d45f5384.zip | |
feat(ai): add --helper for a second session in a live project
A helper is a second agent session in a project that already has a live one. It reads freely, makes only scoped single-heading edits, and never touches git. Until now the only way to get one was to tell a session by hand it was a helper, and nothing checked whether that was true.
--helper runs agent-roster first, so the claim gets verified rather than asserted. It opens its own tmux window and hands the session helper-mode.org instead of the normal startup workflow. Git prep is skipped because pulls belong to the primary. With no other agent live it warns and falls back to a primary launch.
When the roster can't answer, it opens a helper anyway. I picked that direction because a helper that turns out to be alone merely does less, while a primary that turns out not to be alone runs pulls and rsync underneath a live session.
Wrap-up gains a Step 0 branch. A helper archives its own context file and stops, and the primary's next commit carries its edits. An orphaned helper whose primary already exited runs the full wrap, because otherwise its edits strand as a dirty tree nobody owns.
That branch resolves the project root before consulting the roster. agent-roster defaults to $PWD and only matches agents at or inside it, so a call from a subdirectory would miss a primary at the root and report the helper orphaned. That is the one path that commits and pushes.
The agent id is sanitized to the charset session-context-path keeps, because it's interpolated into the command typed into the pane. A helper launching a helper gets a fresh id rather than inheriting its parent's. Two agents resolving to one context file is the collision the contract exists to prevent.
Startup still has no roster check, so a bare claude launched into a busy project runs full primary startup regardless. helper-mode.org and protocols.org now say so instead of listing the routing as though it works.
Diffstat (limited to '.ai/workflows/wrap-it-up.org')
| -rw-r--r-- | .ai/workflows/wrap-it-up.org | 32 |
1 files changed, 31 insertions, 1 deletions
diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org index a9a5895..ecd3d22 100644 --- a/.ai/workflows/wrap-it-up.org +++ b/.ai/workflows/wrap-it-up.org @@ -29,6 +29,8 @@ The wrap-up is complete when: The absence of =.ai/session-context.org= is the signal that the last session wrapped up cleanly. Its presence at session start means the previous session was interrupted. +*A helper session meets a shorter list.* Criteria 1 and 2 apply to its own context file (archived under =.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<description>.org=), and 6 applies. Criteria 3, 4, and 5 do not: hygiene, the Linear pass, and all git mutation belong to the primary, so a helper that satisfied criterion 5 would have violated its contract to get there. Step 0 routes this. + * Teardown mode (set from the trigger phrase) The wrap itself — Steps 1 through 5 — is identical in every mode. The trigger phrase only decides what Step 6 does once commit + push and the valediction are done. Resolve the mode from the phrase before starting: @@ -43,7 +45,35 @@ This depends on three functions in =.emacs.d/modules/ai-term.el= (=cj/ai-term-qu * The Workflow -** Step 0: Refuse if sentry is live +** Step 0: Helper branch — a helper wraps only itself + +Resolve first whether this session is a helper, because a helper's wrap is a different and much shorter workflow. Everything from Step 1 down — the hygiene passes, the inbox check, the commit, the push, the clean-tree certificate — is primary-only under the role contract in [[file:helper-mode.org][helper-mode.org]], and running any of it from a helper is exactly the concurrency failure that contract exists to prevent. + +A session is a helper when =AI_HELPER=1= in its environment (=ai --helper= sets it) or when it adopted helper-mode.org this session by instruction. If neither holds, this is a primary: skip to Step 0.5 and wrap normally. + +#+begin_src bash +echo "AI_HELPER=${AI_HELPER:-unset} AI_AGENT_ID=${AI_AGENT_ID:-unset}" +#+end_src + +For a helper, re-run the roster — the answer decides which wrap applies: + +#+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 + +Pass the project root explicitly. =agent-roster= defaults to =$PWD= and keeps only agents whose cwd is at or inside that root, so running it from a subdirectory hides a primary sitting at the root — and the "alone" that produces is read below as *orphaned*, which is the one branch that commits and pushes. Capture =rc= inside the branch too: =[ -x … ] && …; echo $?= reports the status of the whole list, so an absent script reads as 1 (others live) rather than 2 (unavailable). + +- *Primary still live (rc 1)* — the normal case. Finalize the =* Summary= in the helper's own context file — same contract as Step 1, KB receipt line included (resolve it with =AI_AGENT_ID=<id> .ai/scripts/session-context-path=), archive it to =.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<description>.org= so it can't collide with the primary's archive name, deliver the valediction, and stop. Do NOT commit, push, or run any hygiene pass. The helper's scoped edits stay in the tree and the primary's next commit carries them along with the archived file — say so in the valediction, so Craig knows the work is real but not yet pushed. +- *Alone (rc 0) — orphaned helper* — the primary exited first, so the git ban lifts: the concurrency that justified it is gone, and stopping here would strand the helper's edits as a dirty tree nobody owns. Run the full wrap below starting at Step 0.5, exactly as a primary would. +- *Roster unavailable (rc 2, or the script absent)* — take the archive-only path, the same as primary-still-live. Leaving work for the next session to commit is recoverable; guessing "orphaned" and committing underneath a live primary is not. + +** Step 0.5: Refuse if sentry is live Before anything else, check whether sentry is running in this project. Sentry holds the working tree on its =sentry/<date>-<host>= branch and commits unattended; wrapping underneath it would archive the session anchor and tear down the buffer while the loop is still firing into it. If sentry's single-runner lock is held, stop and point at the shutdown path: |
