-
Notifications
You must be signed in to change notification settings - Fork 70
feat: stream sub-task (child session) events through the bridge #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
When OpenCode's LLM calls the `task` tool (sub-agent), it creates a child session whose events were silently dropped by the bridge's session filter. Users saw "task tool running" → long silence → final result. Stream child session tool calls, step markers, and errors with `isSubtask: true` so the control plane can forward them to clients. Child text tokens are suppressed to avoid corrupting the web UI's single-ref text display. - Track child sessions via `session.created` (parentID match) and task tool metadata (covers resumed sessions with no `session.created`) - Session-scoped tool dedupe keys prevent parent/child callID collisions - Child errors forwarded without terminating the parent stream - Child idle/status events naturally ignored by existing parent guards - Extract `_extract_error_message()` helper to deduplicate error parsing
Terraform Validation Results
Pushed by: @ColeMurray, Action: |
Greptile OverviewGreptile SummaryAdds streaming support for sub-task (child session) events to prevent silent periods during sub-agent execution. The implementation tracks child sessions via dual discovery ( Key observations:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 files reviewed, 1 comment
Terraform Validation Results
Pushed by: @ColeMurray, Action: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a gap in the OpenCode SSE bridge where child session (sub-task) events were being filtered out, causing silent periods during sub-agent execution. It adds child session discovery and forwarding of non-text subtask events (with isSubtask: true) while suppressing child text tokens to avoid UI corruption.
Changes:
- Track direct child sessions via
session.createdand resumed child sessions viatasktoolmetadata.sessionId. - Forward child session tool/step/error events through the bridge with
isSubtask: true, while suppressing child text tokens. - Add test coverage for error-message extraction and subtask streaming behavior (including buffering and callID collision cases).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/modal-infra/src/sandbox/bridge.py | Implements child session tracking, subtask event forwarding, tool dedupe scoping by session, and _extract_error_message() helper. |
| packages/modal-infra/tests/test_bridge_sse.py | Adds tests for _extract_error_message() and subtask streaming (forwarding, suppression, buffering, and collision handling). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| elif error_session_id in tracked_child_session_ids: | ||
| error_msg = self._extract_error_message(props.get("error", {})) | ||
| self.log.warn( | ||
| "bridge.child_session_error", | ||
| error_msg=error_msg, | ||
| child_session_id=error_session_id, | ||
| ) | ||
| yield { | ||
| "type": "error", | ||
| "error": error_msg or "Sub-task error", | ||
| "messageId": message_id, | ||
| "isSubtask": True, | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Child session.error events are forwarded as {type: 'error', isSubtask: true}. In _handle_prompt(), any event with type == 'error' currently flips had_error=True and will make the final execution_complete.success false even if the parent stream recovers and completes normally. If the intent is “child errors don’t fail the parent execution”, consider emitting a different event type for subtask errors (or adjusting the parent error accounting to ignore isSubtask errors).
| metadata = part.get("metadata") or {} | ||
| child_sid = metadata.get("sessionId") |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metadata = part.get('metadata') or {} assumes metadata is a dict; if OpenCode ever sends metadata as a non-dict truthy value (e.g., string/list), metadata.get(...) will raise and abort streaming. Consider guarding with isinstance(metadata, dict) before accessing .get() to make the bridge resilient to schema changes.
| metadata = part.get("metadata") or {} | |
| child_sid = metadata.get("sessionId") | |
| metadata = part.get("metadata") | |
| if isinstance(metadata, dict): | |
| child_sid = metadata.get("sessionId") | |
| else: | |
| child_sid = None |
… format - Change child session.error log level from warn to error for consistency - Add test_grandchild_session_not_tracked to validate direct-children-only boundary - Apply ruff formatting to both changed files
Terraform Validation Results
Pushed by: @ColeMurray, Action: |
Add isinstance(metadata, dict) check before accessing .get() on task tool metadata, preventing AttributeError if metadata is ever a non-dict truthy value.
Terraform Validation Results
Pushed by: @ColeMurray, Action: |
Summary
tasktool (sub-agent), child session events were silently dropped by the bridge's session filter, causing users to see long silences during sub-task executionisSubtask: truefor future UI differentiationKey design decisions
session.createdevents (common case) + task toolmetadata.sessionId(covers resumed sessions viatask_id)tool:{sessionID}:{callID}:{status}prevents parent/child callID collisionsisSubtask: truebut parent stream continuestaskpermission on sub-agents, which is denied by default — YAGNI for nowisSubtaskflows through as generic JSONChanges
bridge.pyhandle_part()withis_subtask, expand session filter, route child events, extract_extract_error_message()helpertest_bridge_sse.py_extract_error_message, 8 forTestSubtaskStreaming(tool forwarding, text suppression, idle/status non-termination, error forwarding, buffering race, metadata discovery, callID collision)Test plan
tasktool invocation in a live session and confirm child tool events appear in the web UI event stream