Agent Canvas: child response returned once, but no...
# questions
f
Agent Canvas: child response returned once, but not on repeated attempts Hi everyone. I’m troubleshooting child responses in OpenHands Agent Canvas 1.25. My setup: • Canvas running locally in Docker on Windows 11. • A self-hosted
qwen3-coder-30b-fp8
model served by vLLM on a separate NVIDIA DGX Spark, connected through an OpenAI-compatible endpoint. • vLLM configured with
--tool-call-parser qwen3_coder
and
--enable-auto-tool-choice
. A simple parent–child smoke test worked once: the parent launched a child conversation, I watched the child run in Canvas, and its response returned to the parent. However, that success has not been repeatable despite many attempts. The problem I’m investigating is the child’s response not returning to the parent. Has anyone encountered this with Canvas child conversations? Which logs or events should I check to determine whether the child’s final response was delivered to the parent, versus the parent model failing to process it? Could the tool-call parser configuration affect this handoff? I’m asking specifically about Canvas child conversations, not SDK sub-agent delegation.
👀 1
l
Sorry, just to check, what do you mean by “returning to the parent”? I don’t think that this is OpenHands’s behavior to forward this by default
Typically the child will just run, and the parent needs to check back in on the child when it’s done. Potentially we might want to fix this though, so that the parent automatically gets a message when the child completes.
f
Thank you for responding Graham. My expectation came from the Canvas documentation’s Child Conversations section, which says: “The child remains linked to its parent, and its result is returned to the parent conversation.” And from asking Canvas to list tools and parameters which says: "finish - Signals the completion of the current task or conversation. Parameters: summary(string, optional), message(string required). And I saw this work as I expected exactly once. Other than that I have not bee able to return anything from a child conversation to the parent. If this is not the proper way to get a result from a child conversation back to the parent, then what is? An example would be greatly appreciated!
l
@fresh-machine-37269 Could you take a look at OpenHands/software-agent-sdk and OpenHands/OpenHands repositories and give me a summary of when, if ever, child messages are automatically returned to the parent without the parent explicitly querying the child’s conversation? Discuss both when calling the agents through TaskTool, and when the agent creates a child conversation, e.g. through the tool in the agent canvas.
👀 1
f
🤖 On it! View progress here: oss-agent-canvas.ngrok.dev/conversations/…
✅ Done! Here's what the two repos actually say. 1. TaskTool (software-agent-sdk) — returned automatically, because it's blocking The Task tool is synchronous, so there's no "push" involved: the parent's tool call is the wait.
TaskExecutor.__call__
calls
TaskManager.start_task(...)
, which runs the child
LocalConversation
to completion (
_run_task
→
send_message
→
run()
→
_run_until_finished
) and blocks until it reaches
FINISHED
. The child's final response is then extracted with
get_agent_final_response(events)
— the last
FinishAction.message
or the last agent
MessageEvent
— and handed straight back to the parent inside the
TaskObservation
(or as an error/partial result if it stopped for a run limit, stuck, etc.). The parent doesn't query anything; it receives the result as the return value of its own tool call in the same turn. The child conversation itself is persisted (under the parent's
subagents/
dir, or a temp dir), and
resume=task_id
re-runs it blocking again. So for TaskTool (and the sibling Delegate tool `spawn`/`delegate`, same blocking pattern) the answer is: always, by construction. 2. Canvas child conversations (
launch_child_conversation
) — only a launch receipt comes back, not the result
This path is deliberately fire-and-forget. The tool description in the Canvas repo is explicit: "The child runs on its own. It does not block you, you do not see its output, and it cannot see this conversation's history." What is posted back to the parent automatically is a launch acknowledgement, and it happens at launch time — not completion.
launch_child_conversation
is a client-side tool, so the agent-server acks the call immediately; the frontend then does the real work and posts a machine message
[child-conversation] {"status":"launched","conversation_id":…,"url":…,"initial_status":…}
into the parent (
reportLaunchResult
→
sendMessage
). That's the child's id/URL/initial status, not its final answer. When the child later finishes, nothing forwards its result into the parent. The parent/child relationship is persisted server-side (
parent_conversation_id
→ the parent's
sub_conversation_ids
, added in software-agent-sdk #4188 / agent-server ≥ 1.37.1), but that link is used for discovery, recovery and deletion bookkeeping — not message delivery. There's no completion hook that injects the child's final message into the parent. To get the child's output the parent has to explicitly check back in on that conversation id (or a human relays it). So Graham is right, and the docs' "its result is returned to the parent conversation" is misleading — it's really just the link/record that's returned. The one time it appeared to work was almost certainly the model (or you) going back and reading the child's conversation after the receipt, not a deterministic handoff. The tool-parser config can affect whether the model chooses to poll the child, but it can't create a delivery that the code never performs. Practical takeaway: if you need a child's result delivered to the parent, use the SDK Task tool (blocking, result in the observation). If you're using Canvas child conversations, treat them as independent and have the parent explicitly read the child's conversation when you need its output. This reply was drafted by an AI agent (OpenHands) on behalf of the user.
l
@fresh-machine-37269 check the OpenHands/docs and see if it is indeed confusing. If so, open an issue on that repository with clear acceptance criteria.
👀 1
f
✅ Done! Yes — the docs are genuinely confusing, and I've filed an issue. The offending line lives in
OpenHands/docs
→
openhands/usage/agent-canvas/conversations.mdx
, Child Conversations section:
"The child remains linked to its parent, and its result is returned to the parent conversation."
That sentence conflates two different things. What's automatically returned is the launch receipt — the
[child-conversation] {"status":"launched","conversation_id":…,"url":…}
message the frontend posts back at launch time. The child's final answer is not auto-delivered; the child runs independently and you have to open it to read its output. "Its result is returned to the parent conversation" reads naturally as the latter, which is exactly the misreading the user in the thread hit. It's made worse by the SDK Task tool genuinely being blocking and returning the sub-agent's result in the observation — same word "result", different behavior, no distinction drawn. Issue opened: OpenHands/docs#925 — github.com/OpenHands/docs/issues/925 It quotes the current text, explains the launch-receipt-vs-final-result conflation, and has concrete acceptance criteria: drop the "result is returned" claim or qualify it as the launch receipt only; distinguish the automatic launch acknowledgement from the non-delivered final result; state the child is non-blocking/independent; and (if a Task-tool contrast is added) note that Task-tool delegation is blocking and does return the final response. It also cites the implementing code in both
OpenHands/OpenHands
and
software-agent-sdk
for the maintainers to verify against. This reply was drafted by an AI agent (OpenHands) on behalf of the user.
l
hth @fancy-soccer-23711!
f
Thank you Graham. I came to the solution of using the SDK route about 2 hours before the posts here. I can confirm that route is useable, but a bit more convoluted and it is not as easy to check up on the children. I suggest that a "launch and wait for a child conversation to return a result" is something that probably has significant general applications. In my case I am playing around with doing complex refactors on a large code base (>1M lines, >500 files). I am using a child to process each file in the code base to prevent context overflow/compression issues which plagued my early attempts and made them completely unworkable. I don't quite have it working, but I think I am finally close. It is really exciting -- unlike the engineers working for me, OpenHands will probably not have a sick elderly relative every time I ask it to do cleanup on the code base 😉
😂 1
l
Awesome, if you could open an issue with the desired behavior we’ll be happy to take a look at implementing it!