After a stop, steering still pending is claimed with the next prompt and the AgentLoop logs it first. Once the first of those rows is durable, the remaining echoes follow the new Turn's process control, which renders empty while the Turn runs. The 16px clearance after that control applied to them and moved each echo by 10px before its durable row replaced it. The clearance now applies after a visible control to every row, and after any control only to rows that are not local echoes, so the echoes keep the input gap. Also state that a switch lifted within a move's duration plays the rest of it, that the steering entrance approximates the status's previous position, and that the runtime-context notice placement follows from the README rule.
4.9 KiB
Agent Note: Precompute the steering batch start in Conversation Node Definition state
Status: implemented
English | 中文
Problem
Chat keeps a 44px slot, the height of the running status row, above the first steering row of each claim that lands after the running Turn's process output, so admitting steering swaps the status and the bubble without moving the bubble (conversation-nodes README). Deciding that a steering row starts such a batch needs two facts its own user/message event does not carry: whether it is the first human message of its next-step claim, and whether the same Turn already has durable visible output before it, which places the row after the Turn's process control. The answer must hold for live appends, replay, and loading earlier history, and must not scan the event window or the Chat Nodes.
Decision
The batch start is a fact in Conversation Node State, computed by Definitions through the existing reader.previous lookup:
inbox.tsrecordsfirstClaimedHuman, the first human message removed by eachnext-stepclaim, in the Client Inbox State.turn-process.tsregistersprocessEvidenceDefinition(kindprocess-evidence,publication: () => 'none'): one Context per Step with durable visible output, idturn:step, matched fromassistant/message,tool/call,tool/result, andllm/retrythrough the existingprocessEvidencetest. Streaming chunks never reach it. Its State holds the Turn number; the Context's matches accumulate that Step's evidence events, and nothing reads them.message.tscallsreader.previous('process-evidence')instart, only for the claim's first human message. When the nearest earlier record belongs to the same Turn, the steering State carriesbatchStart: true. Later appends have larger seqs and cannot change the answer; loading earlier history replays the dependency through the assembler's tracking ofreader.previous, as Message already does for Inbox.- The steering renderer reads only
data.batchStartand writesdata-chat-steering-batch="start"on its bubble root; CSS gives the containing row the slot.
The lookup costs O(log N) per claim-leading human message, where N is the number of Steps with output in the loaded window. The change adds no framework API and no persisted or wire data.
Alternatives considered
Compute it in the renderer from props. The renderer would need neighbouring rows and the Turn's process state, which puts a steering branch into the business-free node list and Seat, and the row would not re-render when only a neighbour changes.
Compute it in the Chat snapshot builder. A projector over the ordered entries would put a steering-specific rule and an extra pass into the generic build layer.
Add tracked Turn-data reads to the framework (reader.turnData). It changes the ConversationNodeBuildReader contract and the assembler's dependency tracking for one consumer, while reader.previous already gives the needed ordering, dependency, and replay.
Consequences
The decision lives in node data, follows replay and earlier-history loading through existing dependency tracking, and leaves the renderer, Seat, node list, and ui-conversation framework unchanged. It costs one more Definition and one Context per Step with output; each State is one number, and each Context holds references to its Step's evidence events. The slot follows the claim's first human message rather than its first rendered row, so a runtime-context notice logged before that message in the same claim sits above the slot; this follows from the README's rule that the claim's first human message keeps the slot.
Testing
- conversation-node-definitions.client.spec.ts:
records durable process evidence once per Step and ignores live chunks and invisible repliespins evidence matching;marks each claim lead and starts a batch only after the Turn control, run for replay, live appends, and prepends, pins batch starts for the first and a later claim, no batch start for the second row of a claim, an idle claim that opens a Turn, or a claim after a Step without output. - chat-view.client.spec.tsx:
marks the steering row whose data carries a batch start, and no other inputpins the rendered attribute; turn-tail-spacing.client.spec.ts pins the 44px slot rule.