Status: implemented
English | 中文
Agent.followup() identifies and queues a user message, but one follow-up does not own the activity that follows it. Steering, injected context, tool continuations, recovery, and later queued messages can all contribute before the agent next becomes idle. A MessageId can therefore prove inbox admission, but it cannot identify which assistant message or turn/end is the result of that input.
The one-send-one-turn decision already rejects a per-send completion handle in the core API. Protocol and SDK layers that pair one prompt request with a turn result manufacture that missing relationship downstream. The pairing becomes ambiguous as soon as activity admits more input, and it exposes turn mechanics as if they were a prompt-level outcome.
Keep Agent.followup(message): void as an enqueue-only operation. Agent.whenIdle() and agent/status remain whole-agent lifecycle observations; neither settles an individual message. Inbox durability records the identified message and its admission or cancellation, without assigning later output to it.
The low-level SDK protocol answers session/prompt as soon as enqueue succeeds with { messageId }. It streams durable facts through session.event, publishes whole-agent transitions through session.status, and has no session.finished. A low-level client may observe that receipt and later idleness, but receives no prompt result.
High-level automation APIs return a RunResult only when they explicitly own an activity interval. The TypeScript and Python SDK run() methods collect from the submitted message's durable inbox receipt through the next whole-agent idle; their final response is the last committed assistant message in that interval, not a response causally attributed to the submitted prompt. The Python SDK also reports the last root turn's reason kind as the run-level finish_reason, without attributing it to the submitted prompt. The one-shot CLI owns the analogous idle-to-idle interval. An isolated child-agent run may report a result because its caller owns the complete child lifecycle and any steering belongs to that run.
ACP must return a protocol stopReason. Its bridge serializes one in-flight prompt per ACP session and owns the interval from admission through whole-Agent idle and ordered update delivery. It correlates the turn that admits the identified ACP message without claiming that every activity in the interval was caused only by that message. A correlated token-limit ending maps to standard max_tokens; a correlated model error rejects at the same quiescence boundary; a turnless slot settles as cancelled alongside explicit ACP cancellation or disposal. Other normal quiescence reports end_turn.
Goal continuation retains MessageId only to recognize its durable queued and admitted goal message. It advances from durable goal state at whole-agent idle, without mapping the message to a turn result.
Map MessageId to the turn that admits it. A turn may consume steering and injected context and may continue through multiple model/tool steps. The mapping identifies admission, not causal ownership of the resulting output or stop reason.
Return a per-follow-up completion handle. A handle would imply a result boundary that the shared agent lifecycle does not have. It would either omit work that influenced the activity or silently absorb unrelated later input.
Use the last turn/end observed before idle. This is a useful run-level observation for an explicitly owned interval, but naming it as the submitted message's outcome recreates the false causal claim.
- Agent and inbox tests pin enqueue-only follow-up, durable admission or cancellation, and whole-agent idle observation.
- SDK protocol, TypeScript SDK, and Python SDK tests pin the
{ messageId }receipt,session.status, the absence ofsession.finished, and receipt-to-idleRunResultcollection without prompt-levelstatusorreason; Python SDK tests separately pin its run-levelfinish_reasonobservation. - ACP, one-shot CLI, goal continuation, and subagent tests pin the distinct activity ownership each integration possesses.
- Consumer tests pin that no production integration derives a follow-up result by correlating
MessageIdwithturn/end.
An owned activity interval can include steering, injected context, or other work submitted before idleness, so its final response, finish reason, and events are deliberately broader than the initiating message. Prompt-level model error and token-limit classifications remain absent from the low-level DSH SDK result. ACP projects the correlated turn into its required standard error or max_tokens stop reason at interval quiescence, without adding a DSH-specific result or claiming exclusive causality. Concurrent automation on one session requires an explicit serialization or ownership policy rather than an implicit per-follow-up result.