Skip to content

feat(llm): resume the Claude Code session across a kill-chain phase - #149

Open
N3thunt3r69 wants to merge 1 commit into
elder-plinius:mainfrom
N3thunt3r69:feat/local-agent-session-resume
Open

feat(llm): resume the Claude Code session across a kill-chain phase#149
N3thunt3r69 wants to merge 1 commit into
elder-plinius:mainfrom
N3thunt3r69:feat/local-agent-session-resume

Conversation

@N3thunt3r69

Copy link
Copy Markdown
Contributor

Follow-up to #139/#140. That fix stopped AgentLoop's token budget check from being blind, but the deeper cost driver is architectural: every ReAct iteration resends the whole growing transcript as a brand-new claude -p process, and each of those spawns re-pays Claude Code's own CLAUDE.md/skills/MCP bootstrap tax from scratch. This uses Claude Code's --resume to carry one session across a kill-chain phase instead, so the repeated part is billed at cache-read pricing instead of resent.

Contribution Receipt

  • Change: Resume the Claude Code local-agent session (--resume) across a kill-chain phase, with a fallback to a fresh session if the resumed one is stale.
  • Scope class: authorized_live
  • Target authority: operator-owned (live verification ran against a target the operator personally owns and is actively building; authorization confirmed directly by the operator before the run)
  • Network use: authorized_external (live recon-phase mission against the operator's own external domain, plus loopback calls to the Claude Code CLI itself)
  • Run mode labels: local_agent, tool_backed, live_authorized
  • Model/harness labels: model=claude-opus-5, provider=Anthropic (via Claude Code CLI, no API key), agent_runtime=Claude Code, harness=manual-review + live mission run, tool_access=local_only
  • Commands run:
    • npm run typecheck -> pass
    • npm test -> pass (764/764 vitest, 11/11 ops-preflight, 19/19 model-matrix, refusal-frontier self-test)
    • npm run doctor -> pass (30/33, 3 warnings: semgrep/promptfoo missing, API health offline with the server not running — no blockers)
    • npm run verify-claims -> pass (27/27, no headline numbers changed)
  • Artifacts: none committed. Verification used two layers: (1) a throwaway script against the real CLI confirming --resume recall, the cost drop, and the stale-session failure shape; (2) a live recon-phase mission via /api/mission/start against an authorized external target, confirmed via ps that the session id held across all 4 recon tasks in one phase and that a newly spawned operator's first call in the next phase carried no --resume. Both deleted/stopped after use, no logs retained.
  • Redaction: not_applicable (mission findings from the live run are not part of this PR; only the code and its regression tests are)
  • Claims changed: none
  • Abstentions/refusals: none
  • Residual risk: the retry-without---resume fallback fires on any failure once a session id is in play, not only a confirmed-stale one; a real transient error on the resumed call pays one extra spawn before surfacing. Session scope is tied to advancePhase() only — Codex/Hermes/OpenCode/Oh My Pi are untouched (Claude-only, same as Claude Code local-agent responses carry no usage: AgentLoop's token budget check never fires #139/fix(llm): populate usage for the Claude Code local-agent path #140). Mission-level state (the running phase/task graph) still does not survive a server restart, so a resumed session id would already be moot in that case; this PR does not change that.

Every ReAct iteration flattens the growing transcript into a brand-new
prompt and spawns a fresh `claude -p` process. Within one task that is
a quadratic resend; across a mission it also means each spawn re-pays
Claude Code's own CLAUDE.md/skills/MCP bootstrap tax from scratch,
since T3MP3ST never told the CLI these calls belong to one session.

LocalAgentAdapter now tracks the Claude Code session id from the JSON
envelope and passes it back in with --resume on every later call, so
the CLI carries the accumulated transcript itself and the repeated
part is billed at cache-read pricing instead of resent. The session
is scoped to one kill-chain phase: TempestCommand drops it for every
operator right after advancePhase(), since local-agent operators are
spawned once for the whole mission and would otherwise carry one
session across all 7 phases. A stale or expired session id fails fast
(confirmed against the real CLI: nonzero exit, no JSON on stdout) and
localAgentChat retries once without --resume rather than failing the
task over it.

Verified against the real CLI: a resumed call recalled prior context
correctly and dropped from $0.151 to $0.008 (cache_read_input_tokens
absorbing the prior turn instead of cache_creation), and an invalid
--resume id triggered the fallback and still succeeded. Ran live
against an authorized target: the same session id held across all 4
recon tasks in one phase, then a newly spawned operator's first call
in the next phase carried no --resume, confirming both the resume and
the reset.

Full suite: 764/764, no regressions.
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.

1 participant