Skip to content

feat(harness): add loop lifecycle seams and snapshot every turn - #108

Merged
sanchitrk merged 1 commit into
canaryfrom
claude/nice-cannon-750e5a
Oct 26, 2026
Merged

sanchitrk merged 1 commit into
canaryfrom
claude/nice-cannon-750e5a

Conversation

@sanchitrk

Copy link
Copy Markdown
Contributor

What

The session drain took one state snapshot per exchange and reused it for every tool-use and steer turn. A settings edit, a session model binding or a tool declared mid-chain only took effect at the next exchange, and nothing could step in between stages.

  • Snapshot every turn. The drain is one flat loop that takes a fresh snapshot before each turn. Ordering is unchanged: tool results and steers continue first, a follow-up only once both are exhausted.
  • Hooks service (runner/hooks.ts) with three seams, composed in fixed order in its live layer (no registry, no self-registering handlers):
    • prepareNextTurn(turn, next) runs between turns with the fresh snapshot and may replace it.
    • prepareRequest(request, { retry, failed? }) runs before every provider attempt, retries included.
    • finishTurn(turn) runs after the turn commits and returns "end" | "continue" | undefined. "end" wins over "continue" and leaves queued input for the next run. "continue" only sends a request if something was appended after the reply.
  • Completed.pending: what the next run would promote (queued steers, else the oldest follow-up), read without promoting it.
  • Resume ordering. An explicit resume or a recovered run answers a reply that stopped mid-chain before a queued follow-up goes in, and still promotes steers that land while it loads. Prompts and wakes are unchanged.

The seams are no-ops until a consumer is wired in.

Behaviour changes

  • Settings edits, session bindings and plugin changes made during a tool chain apply at the next turn instead of the next exchange. A turn's tool batch still runs on its own snapshot.
  • Every turn now re-reads settings and re-runs plugin setup. Not measured; expected to be small next to a provider request.
  • A snapshot failure can now happen between turns. The previous turn is already committed, so the run stops cleanly and a later run resumes from it.
  • Steers still promote all at once.

Verification

  • New cases in runner.loop.test.ts: tool declared mid-chain reaches the next request, finishTurn end and continue, prepareRequest on a retry (replacement model reaches the request), pending input shown without promotion, a steer landing while a resume loads. Artifact: test/__artifacts__/runner.hooks.json.
  • settings.loop.test.ts expectations updated from per-exchange to per-turn.
  • vp check, vp run typecheck and the full harness suite pass.

Not covered

  • finishTurn "continue" with an appended entry: nothing can append session entries yet.
  • The per-turn cost of MCP reconcile, until the MCP work lands on this branch.
  • A failing hook currently fails the turn; revisit when the first real consumer lands.

🤖 Generated with Claude Code

The drain took one state snapshot per exchange and reused it for every
tool-use and steer turn, so a settings edit, a session binding or a tool
declared mid-chain only landed at the next exchange, and nothing could step
in between stages.

The drain is now one flat loop that takes a fresh snapshot before each turn,
and a new Hooks service gives it three seams, composed in fixed order in one
place:

- prepareNextTurn runs between turns with the fresh snapshot and may replace it
- prepareRequest runs before every provider attempt, retries included, with
  the retry number and the previous failure
- finishTurn runs after a turn commits and can end the run or ask for one more
  request. It also sees the input the next run would promote (steers, else the
  oldest follow-up), read without promoting it.

An explicit resume or a recovered run now answers a reply that stopped
mid-chain before a queued follow-up goes in, and still promotes steers that
land while it loads. Prompts and wakes are unchanged. The seams are no-ops
until a consumer is wired in.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sanchitrk
sanchitrk merged commit 856ff02 into canary Oct 26, 2026
2 checks passed
@sanchitrk
sanchitrk deleted the claude/nice-cannon-750e5a branch October 26, 2026 20:44
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