Repository navigation
Conversation
added 6 commits
October 9, 2026 13:03
An asynchronous AgentSpawn returns a started result while the child Session runs on, so the parent Turn's completion writer never saw the link that synchronous delegation stamps in memory. Remote record-stream consumers read persisted Turns only, which left the spawning tool item without any child Session fact. Link the child Session on the parent Turn's persisted tool item before the spawn start returns, materializing the running generation exactly like a relay read so the stamped item keeps the round and item identity the later completion merge preserves. The existing tool-completion record resync then publishes the link mid-Turn, with no protocol or client change.
…elay item records The record stream serializes the persisted tool item as it is, so a stamped parent AgentSpawn item must publish subagentSessionId, subagentDialogTurnId and subagentModelId under their camelCase wire names.
An Ultimate session delegates work to child agents, and the host keeps each worker in its own hidden session: the parent stream only carries the launch item, a child's steps exist on the child's own stream and carry no marker of their own. The client already knew how to draw a delegation card's branch; the records it drew came from a contract the host never published. The launching item now names its child session (`subagentSessionId`, the field the host stamps on the item data, camelCase as the record stream serializes it). A small coordinator follows the parent's records, opens that child session through the same `subscribeSession` call the session on screen uses, reduces its records with the same durable reducer, and attaches them as the branch of the item that launched it. Grouping comes from the subscription a record arrived on and never from a marker on the record, so a worker's steps cannot be mistaken for the parent's own rows. The coordinator publishes only through the controller's existing path, so the child's records reach the screen through the reducer, timeline store, projection, grouping policy and card that a parent record already uses. A launch whose item carries no child session id opens nothing: an older host keeps the plain tool rows it has always sent. A card is keyed by what changed in its message, and a launch item settles as soon as its worker starts — a background worker's launch settles while its stream says nothing new. The version this coordinator publishes therefore counts the launch's own record as well as its branch, or exactly that card would keep drawing a settled worker as running.
The visibility suite pinned a contract no host produces: child records carrying the child session id inside the parent stream, a nested shape the record stream has never sent. It is rewritten around what a live Ultimate session publishes. Pinned now: the launching tool item (`AgentSpawn`/`Task`) is the only place a child session id appears, a child stream's own records are unmarked and are grouped by the subscription they arrived on, a launch without that field opens no stream and stays a plain tool row, a settled launch repaints its card even when its worker says nothing new, and a host that still inlines a marked child in the parent stream is not drawn twice. The fixture drives the production chain — stream reader, controller, durable reducer, timeline store, keyed rows, projection, grouping policy — so the assertions cover the path the app runs rather than a preview-only seam. The three suites that load `ChatSessionController` from source now resolve the coordinator the controller constructs.
- keep branch expansion across row rebuilds via a keyed observed disclosure state instead of the card's local boolean - derive the card status from the child stream (worker lifecycle), not the launch record the host completes at spawn time; fall back to the launch status when the subscription failed before data - cap concurrent child streams at 8 with steal-oldest/waiters and lazy re-attach on expand; persist child branch transcripts through the phone transcript cache so a cold start restores the steps, and re-attach children named again by a later launch
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.
What
In 极致 (Ultimate) harness mode the agent delegates to swarm subagents, but a remote mobile client saw only two plain tool rows — "I spawned an agent" and "it answered" — while every intermediate step (94 thinking items / 172 tool calls in one measured worker) slept in hidden child sessions the phone could not even name. This PR makes the subagent process visible end-to-end:
bfe626e63+513453276): the asyncAgentSpawnpath now stampssubagent_session_id/subagent_dialog_turn_id/subagent_model_idonto the parent turn's persisted tool item before thestartedresult returns (the tool-completion record resync is the discovery trigger;SubagentSessionLinkeditself never resyncs records).SessionManagerpersists through the same materialized-rounds read the relay uses, so the link survives. Zero protocol change — the field already existed onToolItemDataand the record-stream serializer; it was simply never written on the async path. Old hosts without the field keep working (clients degrade to plain tool rows).SubagentStreamCoordinatordiscovers child ids from parent records, subscribes each child session's record stream (initial page backfills full history, then live records push), and groups steps into the owning task card by subscription identity — never byis_subagentmarkers, because live verification on a real Ultimate session proved child items carry none. Owner detection now acceptsAgentSpawnalongsideTask.@ObservedV2disclosure state, not a card-local boolean); the card status tracks the worker's lifecycle derived from the child stream (the host completes a spawn record when the worker starts); child streams are capped at 8 with steal-oldest + lazy re-attach, and child transcripts persist through the phone's transcript cache so a cold start restores the steps.subagent-process-visibilitytests asserted a host contract that never existed (child records carryingsubagentSessionIdinside the parent stream). They are rewritten to pin the real, live-verified contract, and the preview fixture now replays real shapes (async spawn with thestartedresult table, unmarked child items, real tool names).Verification
subagent-branch-lifecycle12/12,subagent-process-visibility31/31;harmony:architectureandmobile:ui:checkpass; cleanassembleHapcompile + install.running → completed/failedwhile spawn records are already completed, 10 workers under the 8-stream cap with all cards reachable, cold-start restore of card steps from the transcript cache with child streams muted.subagentSessionId, and a relay contract test asserting the publisheditem/...record;remote_connect_contracts75/75.Not verified