fix: address codex review round 3

- Strict steer additionally requires an OPEN STEP: between steps the
  loop may be awaiting its continuation/turn-stop checkpoints, where
  pending steering was already folded and a terminal stop discards a
  later arrival. A message accepted during an open step is drained and
  recorded at that step's settlement before any terminal decision, so
  the acknowledged-then-discarded window is closed. New keyless test
  holds agent/turn-stop open and pins the rejection.
- tool-subagent-control README: distinguish synchronous not-delivered
  errors from started-Task failures (unknown/foreign/descriptor-less
  ids settle the started Task as failed), and drop the claim that the
  completion notice carries the child's response.
This commit is contained in:
Dudu-0223
2026-07-23 18:47:19 +08:00
committed by imccyu
parent 4eda48d002
commit 9e5ae0d12e
7 changed files with 49 additions and 7 deletions

View File

@@ -276,6 +276,19 @@ function driveTurn(
if (lastBoundary?.type !== 'turn/start') {
throw new Error(`subagent child "${childId}" turn has already closed; the message was not delivered`)
}
// Terminal turn-stops only run between steps: with no step open, the
// loop may be awaiting its continuation/turn-stop checkpoints, where
// pending steering was already folded and a terminal decision discards
// a later arrival. A message accepted during an OPEN step is instead
// drained and recorded at that step's settlement checkpoint before any
// terminal decision (cancellation remains the documented shared-outcome
// race).
const lastStep = child.session.events.findLast(
event => event.type === 'step/start' || event.type === 'step/end',
)
if (lastStep?.type !== 'step/start') {
throw new Error(`subagent child "${childId}" is between steps; the message was not delivered`)
}
// A committed structured capture makes the pending `agent/turn-stop`
// checkpoint terminal, and the loop then discards late steering. The
// capture is synchronously observable, so reject rather than