refactor(agent-loop): simplify message machine
This commit is contained in:
86
packages/core/agent-loop/tests/MIGRATION.md
Normal file
86
packages/core/agent-loop/tests/MIGRATION.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Agent-loop test migration guide (naive-machine contract)
|
||||
|
||||
The loop was rewritten in the naive-agent shape. `packages/core/agent-loop/src/agent.ts`
|
||||
is the single source of truth — read it before migrating a spec. Key changes:
|
||||
|
||||
## Event seams (old → new)
|
||||
|
||||
| Old seam | Replacement |
|
||||
|---|---|
|
||||
| `agent/pre-step` (serial, before step/start) | `agent/step` (serial, before EVERY request derives; same position) |
|
||||
| `agent/post-step` (serial, after tools, before step/end) | REMOVED — use `agent/step` of the next step, or `agent/idle` after the turn |
|
||||
| `agent/session-prefix` (waterfall, request-only prefix) | REMOVED — requests carry no unlogged prefix; durable context via `agent.inject()` at `agent/session-start` |
|
||||
| `agent/step-result` (waterfall, rewrite assistant msg) | REMOVED — the assembled message is recorded as-is |
|
||||
| `agent/request-error` (waterfall, retry/fail decision) | REMOVED — observe `agent/idle` with `reason.kind === 'error'`, repair, then `agent.retry()` |
|
||||
| `agent/turn-continuation` (waterfall, ContinuationDecision) | `agent/continue` (waterfall of `boolean`; handler `(agent, turn, signal, next)`) |
|
||||
| `agent/turn-stop` (serial, terminal stop) | REMOVED — `agent/continue` returning `false` stops the turn |
|
||||
| `agent/request` `(agent, turn, step, config, signal, next)` | `(agent, turn, step, signal, next)` — the config comes only from `await next()` |
|
||||
| `agent/prompt-submit` | unchanged |
|
||||
|
||||
New emit: `agent/idle (agent, turn, reason: IdleReason)` fires once per closed turn
|
||||
(after turn/end + flush, with `busy` already false, so listeners may synchronously
|
||||
`retry()`/`send()`). `IdleReason = completed | aborted | { kind: 'error', error, failure? }`.
|
||||
|
||||
## Verb semantics
|
||||
|
||||
- `send()` — unchanged (queued FIFO, one turn each).
|
||||
- `steer()` while running — enters the outbox; taken whole at the next step
|
||||
boundary. Steering left when the turn closes becomes a queued prompt.
|
||||
There is NO terminal-stop discard of steering anymore.
|
||||
- `inject()` while the machine is busy — enters the outbox (a `context/message`
|
||||
appears at the NEXT step boundary, not immediately). While idle — writes a
|
||||
one-shot turn (`turn/start(injection)` + `context/message` + `turn/end`) and
|
||||
requests a flush. Enclosure is decided by `busy`, NOT by scanning the log for
|
||||
an open turn.
|
||||
- `retry()` — NEW verb: re-opens a turn on the current log with trigger
|
||||
`{ kind: 'retry' }`. Throws while busy ("cannot retry while busy") and after
|
||||
disposal. Legal from a synchronous `agent/idle` listener.
|
||||
- `cancel()` — unchanged surface. No more "pre-run cancelled" bookkeeping:
|
||||
clearing the queue before a run starts simply means no run starts.
|
||||
|
||||
## Machine shape (timing-sensitive tests)
|
||||
|
||||
- `kick()` runs SYNCHRONOUSLY from `send()` when idle: status flips to
|
||||
`running` inside the `send()` call. There is no parked driver loop, no
|
||||
waitForQueued, no microtask collection window.
|
||||
- One `run()` = one turn. The idle tail (`idle()`) runs after turn/end +
|
||||
flush: it sets `busy=false`, emits `agent/idle`, requeues leftover steering,
|
||||
then either kicks the next turn or settles `whenIdle` waiters and flips
|
||||
status to `idle`. Status stays `running` continuously across queued turns.
|
||||
- `step/end` is appended INSIDE the step (after tools + the in-step outbox
|
||||
drain), before `agent/continue` runs. The old `post-step → step/end`
|
||||
window no longer exists.
|
||||
- Request messages = `session.deriveMessages()` snapshot taken right before
|
||||
`step/start` — no `messagePrefix`. `request/header` events no longer carry
|
||||
a `messagePrefix` field.
|
||||
- Provider/model config waterfall (`agent/request`) runs INSIDE the step
|
||||
(after step/start), seeded from agent options (first request) or the folded
|
||||
logged header (later requests).
|
||||
- The assembled assistant message is recorded verbatim (with replayState when
|
||||
present); there is no rewrite path and no "content-less anchor on rejection".
|
||||
- A model failure (thrown by the adapter or a failure finish chunk) closes the
|
||||
turn: balanced step/end + turn/end `{ kind:'error', step, failure }` +
|
||||
`agent/error` emit + `agent/idle` `{ kind:'error', error, failure }`.
|
||||
There are no in-turn recovery steps.
|
||||
- Cancellation classification: signal reason `user`/`parent` → turn/end
|
||||
`aborted`; disposal → `disposed`. IdleReason for both is `aborted`.
|
||||
- A blocked prompt (`prompt-submit` → block) records `prompt/blocked`, closes
|
||||
a zero-step turn `rejected` in turn/end, and emits `agent/idle`
|
||||
`{ kind: 'completed' }` (rejection is a policy outcome, not an error).
|
||||
- Accept-validation error message is now
|
||||
"agent message content and source must be losslessly JSON-serializable".
|
||||
- `dispose()` (the prepared disposer / factory teardown) returns `undefined`
|
||||
when the machine is not busy — do not `.resolves` it unconditionally; use
|
||||
`await Promise.resolve(dispose())`.
|
||||
|
||||
## What to do with tests of removed seams
|
||||
|
||||
- Rewrite the scenario against the nearest new seam when the protected
|
||||
behavior still exists (e.g. turn-stop tests → `agent/continue` returning
|
||||
false; request-error retry tests → `agent/idle` + `retry()` flows).
|
||||
- Delete tests whose subject no longer exists at all (session-prefix
|
||||
reconstruction, step-result rewrite provenance, post-step ordering windows,
|
||||
pre-run-cancel bookkeeping). Do not keep zombie tests alive by weakening
|
||||
their assertions.
|
||||
- Keep the durable-log invariants strong: balanced turn/step boundaries,
|
||||
ordered tool call/result pairs, header change tracking — those still hold.
|
||||
Reference in New Issue
Block a user