Skip to content

fix: deterministic session resolution for Inference --auto-state#1467

Closed
jbmml wants to merge 1 commit into
danielmiessler:mainfrom
jbmml:fix/inference-auto-state-session-resolution
Closed

fix: deterministic session resolution for Inference --auto-state#1467
jbmml wants to merge 1 commit into
danielmiessler:mainfrom
jbmml:fix/inference-auto-state-session-resolution

Conversation

@jbmml

@jbmml jbmml commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Part 1 of 2 for #1466 (the Inference.ts half; the AlgoPhase.ts fail-closed change follows in a separate PR).

What

synthesizeAdvisorState() (the --mode advisor --auto-state path) read work.json expecting active/current/activeSession top-level keys. The registry's actual shape is {sessions: {slug: row}} — those keys never exist — so resolution always fell through to "most recently modified directory in MEMORY/WORK/". That fallback is usually right by accident, but it carries no session identity: under concurrency or right after another session completes, it attaches an unrelated session's ISA as the advisor state, and the reviewer reviews the wrong work.

How

Deterministic resolution, documented in the function's doc comment:

  1. Row matching CLAUDE_SESSION_ID, any phase — a phase: complete row of the SAME session is still this session, and the verification doctrine fires the advisor at the completion boundary itself. Set-but-unmatched returns no state rather than falling through to a guess. (Honesty note: Claude Code does not export CLAUDE_SESSION_ID to tool subprocesses today, so this path is forward-looking defense-in-depth; the operative resolution today is steps 2–4.)
  2. No env: a complete row updated within the last 5 minutes is treated as the just-finished caller making its mandatory completion-boundary advisor call — the most recent such row wins, UNLESS any fresh live row also exists (then we cannot tell which session is calling → ambiguous). Without this window, fixing the mtime guess would have broken the doctrine's advisor-after-complete flow for every single-session user.
  3. No env, no recent-complete: exactly ONE live (non-complete, non-native) row updated within the last 60 minutes. Older non-complete rows are crash leftovers — the registry stale sweep only prunes them after ~7 days — and are never guessable.
  4. Anything else returns an ADVISOR STATE UNAVAILABLE marker and warns loudly on stderr. Wrong state is worse than no state for a reviewer, so the mtime-based directory fallback is removed rather than fenced.

Behavior change disclosure: previously --auto-state always attached SOME ISA. Now, when the caller's session cannot be determined (multiple live sessions, stale-only registry, unmatched env), the advisor runs with an explicit no-state marker instead of a possibly-wrong blob.

No CLI contract change: flags, positional args, output framing, and exit codes are untouched.

Verification

Self-contained bun suite added alongside the tool (Inference.autostate.test.ts, runs in-process against a throwaway $HOME): env-match incl. complete row; env unmatched → no state; single fresh live → its ISA; two live → no state; recent-complete alone → used; two recent-completes → most recent; recent-complete + live → no state; 1h-old complete → no state; 2h-idle live → no state; native placeholder rows excluded. 10/10 pass; tsc --noEmit --strict clean.

…tically

synthesizeAdvisorState() read work.json expecting active/current/
activeSession top-level keys, but the registry's shape is
{sessions: {slug: row}} — those keys never exist, so resolution always
fell through to the most-recently-modified MEMORY/WORK/ directory. That
fallback carries no session identity: it can attach a completed,
unrelated session's ISA as the advisor state blob.

Resolution is now deterministic and fail-safe:
1. Row matching CLAUDE_SESSION_ID, any phase (a complete row of the same
   session is still this session — the advisor fires at the completion
   boundary). Set-but-unmatched returns no state instead of guessing.
   Note: Claude Code does not export CLAUDE_SESSION_ID to tool
   subprocesses today; this path is forward-looking defense-in-depth.
2. No env: a complete row updated within the last 5 minutes is the
   just-finished caller's completion-boundary advisor call — most recent
   wins, unless a fresh live row also exists (ambiguous).
3. Else exactly one live row updated within the last 60 minutes (older
   non-complete rows are crash leftovers; the registry stale sweep only
   prunes them after ~7 days).
4. Anything else returns an ADVISOR STATE UNAVAILABLE marker and warns
   on stderr — wrong state is worse than no state for a reviewer.

Adds a self-contained bun test suite covering all resolution branches.
@danielmiessler

Copy link
Copy Markdown
Owner

Thank you @jbmml — the deterministic session-resolution intent is sound. This specific path (Inference.ts --mode advisor --auto-state / synthesizeAdvisorState) was retired in the v8 modes/tiers consolidation, so the target function no longer exists in current source and there's nothing to port. Closing as superseded by that redesign, not as a rejection of the idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants