Replies: 1 comment
|
There is a working alternative implementation of exactly this mid-turn correction behavior in the Pi plugin route. It does not modify DSH native dsh plugin --profile <你的-profile> add pi2dsh
dsh plugin --profile <你的-profile> add @tintinweb/pi-subagentsThe acceptance is deliberately observable rather than a transcript-only claim. The initial child prompt contains one filename/content, while the later steer message contains a different filename/content that never appears in the spawn prompt. The parent is forbidden from running bash. After the real parent/child model run, only the steer-selected file exists with the steered content, proving the running child received and acted on the correction before completing its original turn. The bridge also preserves Reproduction and evidence: |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Delivery to a continuable background subagent is hardwired to FIFO next-turn queueing through
followup. There is no way to give a running child mid-turn guidance without destroying its current turn viainterrupt. Corrections sent throughsend_message(or the GUI subagent prompt) wait until the whole turn finishes, which is too late to change committed work. A long single-turn task cannot be corrected while it runs.The steering primitive exists at the Agent level, and the product exposes it today for exactly one direction: human input into the root session, through GUI Busy-Enter and the queue dock steer action. This proposal adds an opt-in delivery mode for parent-to-child sends.
Current behavior
send_messagecallsctx.subagents.followup()and ends atagent.followup(msg), which issend(msg, 'next-turn', true), an append to the FIFO turn queue. The control tool test states the intent directly: "A follow-up is its own later turn, never steering inside the first one."Locations involved:
packages/subagent/subagent/src/continuation.ts.submit()callsactivation.handle.agent.followup(message)unconditionally insideadmitWaking().packages/core/agent-loop/src/agent.tslines 122-132.followuptargets'next-turn',steertargets'next-step'. Both are waking sends.packages/subagent/tool-subagent-control/src/index.ts. The schema accepts onlysubagent_idplusmessage.packages/host/apiproxy/src/api-proxy.tsline 2662. The GUI subagent prompt uses the samectx.subagents.followup()path.SubagentReportDeliveryselects'next-step'among other presets (deliverReport/sendReport).Live reproduction
A child gets a multi-batch file-writing task ("write numbers 1 to 20"). A correction arrives while it runs ("must not contain 34"):
The child claims the queued message only after the last step of turn 1, and nothing retracts the numbers already written.
One more limitation:
interrupt_agentcancels the running turn but keeps parked messages (keepInbox: true). A redirect sent after the interrupt runs behind every stale item still queued, because a parent has no way to flush or reorder a child inbox. The GUI queue dock refuses sessions owned by subagents by design.Proposal
Add an opt-in delivery mode along the existing seam.
SubagentFollowupOptions.delivery?: 'next-turn' | 'next-step', defaulting to'next-turn'(today's behavior).ContinuationManager.submit(), choose.steer(message)or.followup(message)from that option. Both are waking sends inside the sameadmitWaking()wrapper, so lifecycle and settlement accounting stays unchanged.send_message(tool-subagent-control) and optionally on the GUIsubagents.promptRPC.'next-turn'(default)'next-step'A steered message also lands ahead of items already parked in the queue, so a parent can correct course without flushing anything.
Concurrency window to design for
Routed through the manager, steering changes no bookkeeping in the common path. A steered send aimed at a child that settles concurrently can still lose against disposal, and disposal clears an unclaimed inbox. Two options: reuse the retry pattern from
followup()(wait out the disposal transaction, then cold-resume), or document that a lost race degrades to a plain queued message. Both keep the feature no worse than what ships today.Alternatives considered
Prompt discipline alone (checkpointed prompting, self-superseding wording) works sometimes, but it depends on model behavior and cannot preempt a long turn. A side plugin calling
ctx.agents.get(id)?.steer(msg)works today, but it skipsadmitWaking()accounting and leaves the product gap open for everyone else.Environment
dsh 0.1.1-rc.2, master checkout. Verified against live in-process continuable children; claim ordering comes from session transcripts. Happy to contribute the reproduction as a test case.
All reactions