diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-14 22:07:00 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-14 22:07:00 -0500 |
| commit | e0f914d510c081db45cafaf4fe5c8f7b65e46fec (patch) | |
| tree | 8ad851aea65e0e5fc1b6adbc70355b435a4dde0d | |
| parent | 16e64fb5f81bd8c3eaa2dc4c00a796ddbec01441 (diff) | |
| download | rulesets-e0f914d510c081db45cafaf4fe5c8f7b65e46fec.tar.gz rulesets-e0f914d510c081db45cafaf4fe5c8f7b65e46fec.zip | |
docs(ai): require an epoch on the tail of helper-agent ids
A helper agent's session-context file is .ai/session-context.d/<id>.org. A bare, reused id like "codex" makes the next run resolve to the previous run's leftover anchor, which it then mistakes for a crash to recover or clobbers. That bit on 2026-06-13: a codex run left codex.org for the next session to clean up.
The fix is a convention, not a resolver change. The spawner appends an epoch on the tail (host.project.runtime.<epoch>) so each run gets a fresh anchor. The epoch can't be minted inside session-context-path, since that resolver runs many times per session and must return the same path each call. I documented it in protocols.org, the wrap-up recommended-shape note, and the resolver header.
| -rw-r--r-- | .ai/protocols.org | 4 | ||||
| -rwxr-xr-x | .ai/scripts/session-context-path | 8 | ||||
| -rw-r--r-- | .ai/workflows/wrap-it-up.org | 2 | ||||
| -rw-r--r-- | claude-templates/.ai/protocols.org | 4 | ||||
| -rwxr-xr-x | claude-templates/.ai/scripts/session-context-path | 8 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/wrap-it-up.org | 2 |
6 files changed, 26 insertions, 2 deletions
diff --git a/.ai/protocols.org b/.ai/protocols.org index 6b1d873..caa5303 100644 --- a/.ai/protocols.org +++ b/.ai/protocols.org @@ -100,6 +100,10 @@ When two agents share one project at the same time, a single =session-context.or - =AI_AGENT_ID= unset or empty (the normal one-agent-per-project case): =.ai/session-context.org=, exactly as before. - =AI_AGENT_ID= set: =.ai/session-context.d/<id>.org= (id sanitized to filename-safe chars). Archived at wrap-up to =.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<description>.org= so concurrent agents don't collide on the archive name either. +The id must be unique per run, and the spawner makes it so by appending an epoch on the tail. The recommended shape is =host.project.runtime.<epoch>= (e.g. =velox.rulesets.claude.1718400000=); a fresh run of the same logical agent then resolves to a fresh anchor. A bare, reused id (just =codex=) makes the next run resolve to the previous run's leftover =codex.org= and either mistake it for a crashed session to recover or clobber it if both run at once. This happened on 2026-06-13: a =codex= run left =session-context.d/codex.org= behind, and the next session had to clean it up by hand. + +The epoch is baked into the id by the spawner, never minted inside =session-context-path=. That resolver is called many times per session (startup existence check, every log write, the wrap-up rename) and must return the same path on every call, so a self-generated =date +%s= would fragment the anchor across calls. The shell that runs each call doesn't carry env between calls either, so the agent can't export the epoch once at startup. The only stable source is the =AI_AGENT_ID= the spawner injects on every call. + 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. This file serves two purposes with one mechanism: diff --git a/.ai/scripts/session-context-path b/.ai/scripts/session-context-path index 8cc56f6..670a610 100755 --- a/.ai/scripts/session-context-path +++ b/.ai/scripts/session-context-path @@ -10,6 +10,14 @@ # instead of clobbering the singleton. The id is sanitized to filename-safe # characters so a stray value can't escape the .d/ directory. # +# The id must be unique per run; the spawner appends an epoch on the tail +# (recommended shape host.project.runtime.<epoch>) so a re-run of the same +# logical agent gets a fresh anchor instead of resolving to a prior run's +# leftover. The epoch is never minted here: this resolver is called many times +# per session and must return the same path each call, so it can't generate a +# new value. See protocols.org "Agent-scoped path". A bare, reused id (just +# "codex") is the bug that motivated this note. +# # Workflows call this to resolve the path; both startup (existence check) and # wrap-up (rename source) read/write through it. Callers should fall back to # .ai/session-context.org if this script isn't present yet (older checkouts diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org index 2d79795..139d612 100644 --- a/.ai/workflows/wrap-it-up.org +++ b/.ai/workflows/wrap-it-up.org @@ -84,7 +84,7 @@ idseg="${AI_AGENT_ID:+${AI_AGENT_ID}-}" mv "$sc" ".ai/sessions/${now}-${idseg}DESCRIPTION.org" #+end_src -Replace =DESCRIPTION= with your picked slug. (=AI_AGENT_ID= should be filename-safe; the recommended =host.project.runtime.shortid= shape already is.) +Replace =DESCRIPTION= with your picked slug. (=AI_AGENT_ID= should be filename-safe and unique per run; the recommended =host.project.runtime.<epoch>= shape is both. The epoch on the tail keeps a re-run of the same logical agent from resolving to a prior run's leftover anchor. See protocols.org "Agent-scoped path".) ** Step 3: todo.org cleanup (hygiene + archive completed work) diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org index 6b1d873..caa5303 100644 --- a/claude-templates/.ai/protocols.org +++ b/claude-templates/.ai/protocols.org @@ -100,6 +100,10 @@ When two agents share one project at the same time, a single =session-context.or - =AI_AGENT_ID= unset or empty (the normal one-agent-per-project case): =.ai/session-context.org=, exactly as before. - =AI_AGENT_ID= set: =.ai/session-context.d/<id>.org= (id sanitized to filename-safe chars). Archived at wrap-up to =.ai/sessions/YYYY-MM-DD-HH-MM-<id>-<description>.org= so concurrent agents don't collide on the archive name either. +The id must be unique per run, and the spawner makes it so by appending an epoch on the tail. The recommended shape is =host.project.runtime.<epoch>= (e.g. =velox.rulesets.claude.1718400000=); a fresh run of the same logical agent then resolves to a fresh anchor. A bare, reused id (just =codex=) makes the next run resolve to the previous run's leftover =codex.org= and either mistake it for a crashed session to recover or clobber it if both run at once. This happened on 2026-06-13: a =codex= run left =session-context.d/codex.org= behind, and the next session had to clean it up by hand. + +The epoch is baked into the id by the spawner, never minted inside =session-context-path=. That resolver is called many times per session (startup existence check, every log write, the wrap-up rename) and must return the same path on every call, so a self-generated =date +%s= would fragment the anchor across calls. The shell that runs each call doesn't carry env between calls either, so the agent can't export the epoch once at startup. The only stable source is the =AI_AGENT_ID= the spawner injects on every call. + 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. This file serves two purposes with one mechanism: diff --git a/claude-templates/.ai/scripts/session-context-path b/claude-templates/.ai/scripts/session-context-path index 8cc56f6..670a610 100755 --- a/claude-templates/.ai/scripts/session-context-path +++ b/claude-templates/.ai/scripts/session-context-path @@ -10,6 +10,14 @@ # instead of clobbering the singleton. The id is sanitized to filename-safe # characters so a stray value can't escape the .d/ directory. # +# The id must be unique per run; the spawner appends an epoch on the tail +# (recommended shape host.project.runtime.<epoch>) so a re-run of the same +# logical agent gets a fresh anchor instead of resolving to a prior run's +# leftover. The epoch is never minted here: this resolver is called many times +# per session and must return the same path each call, so it can't generate a +# new value. See protocols.org "Agent-scoped path". A bare, reused id (just +# "codex") is the bug that motivated this note. +# # Workflows call this to resolve the path; both startup (existence check) and # wrap-up (rename source) read/write through it. Callers should fall back to # .ai/session-context.org if this script isn't present yet (older checkouts diff --git a/claude-templates/.ai/workflows/wrap-it-up.org b/claude-templates/.ai/workflows/wrap-it-up.org index 2d79795..139d612 100644 --- a/claude-templates/.ai/workflows/wrap-it-up.org +++ b/claude-templates/.ai/workflows/wrap-it-up.org @@ -84,7 +84,7 @@ idseg="${AI_AGENT_ID:+${AI_AGENT_ID}-}" mv "$sc" ".ai/sessions/${now}-${idseg}DESCRIPTION.org" #+end_src -Replace =DESCRIPTION= with your picked slug. (=AI_AGENT_ID= should be filename-safe; the recommended =host.project.runtime.shortid= shape already is.) +Replace =DESCRIPTION= with your picked slug. (=AI_AGENT_ID= should be filename-safe and unique per run; the recommended =host.project.runtime.<epoch>= shape is both. The epoch on the tail keeps a re-run of the same logical agent from resolving to a prior run's leftover anchor. See protocols.org "Agent-scoped path".) ** Step 3: todo.org cleanup (hygiene + archive completed work) |
