add mid-turn message injection and subagent done notifications#200
Closed
erikswedberg wants to merge 1 commit into
Closed
add mid-turn message injection and subagent done notifications#200erikswedberg wants to merge 1 commit into
erikswedberg wants to merge 1 commit into
Conversation
- send messages to agent while it's working (injected between tool calls) - parent conversation notified with summary when subagent finishes - always use wait:false for subagents (system prompt guideline) Co-authored-by: Shelley <shelley@exe.dev>
Contributor
Author
|
turns out shelley already has mid turn message injection |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mid-turn message injection and subagent done notifications
The problem
When the agent is working (executing tool calls), user messages are queued and only delivered after the entire turn ends. This means:
You can't course-correct mid-task. If you realize you forgot to mention something, or want to change direction, your message sits in a queue until the agent finishes its current chain of tool calls — which can be minutes. By then it may have gone down the wrong path.
Subagents force a bad choice: block or forget. With
wait: true(the default), the parent sits there stuck until the subagent finishes — serial execution, no parallelism. Withwait: false, the parent keeps working, but it never finds out the subagent finished. The results just sit there until the user manually prompts "check on that subagent." Neither option is good: you either waste time blocking, or you waste time chasing down results.Shelley is async — messages get queued and the UI shows them immediately. But the agent doesn't actually see them until its turn is over. It's async from the user's perspective but synchronous from the agent's.
What this changes
Mid-turn injection: When the user sends a message while the agent is working, instead of appending to
pendingMessagesfor post-turn draining, we inject directly into the loop'smessageQueueviaQueueUserMessage. The loop already checks this queue between tool calls (inexecuteToolCalls), so the message gets picked up at the next natural pause — between two tool executions, not after the entire turn. The DB flags (excluded_from_context, queued user_data) are cleared asynchronously so the message appears normally in history.The fallback path is preserved: if the loop doesn't exist yet or distillation is running, messages still queue the old way.
Subagent done notifications: When a subagent's
agentWorkingtransitions tofalse, anonDonecallback fires. This looks up the parent conversation, grabs the subagent's last assistant response (truncated to 500 chars), and injects a[Subagent 'slug' finished]\n<summary>user message into the parent's loop. The parent sees it immediately — between tool calls — and can act on the results, catch errors, or adjust its plan without waiting for its own turn to end and without the user having to prompt it.Done notifications make
wait: falseactually usable. The parent keeps working while subagents run, and gets notified with a summary when each one finishes — so it can react, catch errors, and incorporate results within the same turn. No blocking, no forgetting.What it feels like
Before: "I'll type this correction... ok it's queued... waiting... waiting... ok the agent finished, now it's reading my message, now it's redoing work."
And for subagents: you either block with
wait: trueand watch the parent do nothing for minutes, or usewait: falseand then spend time prompting "go check on that subagent" and discovering one of them went sideways after the fact.After: The agent reads your message within seconds (at the next tool boundary) and adjusts course. Subagent results flow into the parent automatically — the parent reacts to a subagent finishing, checks if the output makes sense, and incorporates it into its work, all within the same turn.
Changes
server/convo.go:QueueMessagerewritten to inject into active loop when available;onDonecallback field added toConversationManagerserver/server.go:notifyParentSubagentDonemethod;onDonewired ingetOrCreateSubagentConversationManagerAGENTS.md: guideline about preferringwait: falsefor subagentsWhat it doesn't change
loop.gois untouched —QueueUserMessageand the between-tool-calls check already existeddrainPendingMessagesis untouched — still used for the distillation/no-loop fallbackHow to test
Mid-turn injection:
Subagent done notifications:
wait: false— e.g. "launch two subagents: one to count the Go files in this repo, one to count the TypeScript files"[Subagent 'slug' finished]message appears in the parent's conversation with a summary of the resultPredictable model (no API key needed):
--predictable-onlyInjected user message into active loopNotified parent of subagent completion