(subagents): attribute subagent activity to its parent from every source - #270
Merged
Merged
Conversation
Closes #247, step 5 of #246. A pure subagentParentFromParts() resolves a subagent transcript path to its parent for both layouts; the local-transcript and remote trackers emit a kind:'subagent' event on their existing channels, and the renderer adapters apply subagentSpawned to the parent's state with a separate decay. has-busy-agents is projected off the snapshot and survives a sidebar re-render for adapter-owned parents. Parents with a PTY keep the IPC path, so nothing is fed twice.
This was referenced Sep 12, 2026
Merged
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.
Closes #247. Step 5 of #246, after #269.
What
subagent-attribution.js: puresubagentParentFromParts(parts)→{ parentSessionId, agentId } | nullfor<parent>/subagents/agent-<id>.jsonland the legacy<parent>/agent-<id>.jsonl;nullfor a top-level transcript or anything else.local-transcript-activity.jsandremote-activity.jsuse it instead of dropping subagent legs: the existing channels (session-transcript-activity,remote-activity) carry either the previous{ sessionId, at }or{ parentSessionId, agentId, at, kind:'subagent' }(+ aliasfor remote). Local attribution is gated on the parent having no PTY in the app, sodetectSubagentTransitions()keeps owning PTY parents — nothing is fed twice.public/local-transcript-adapter.js,public/remote-activity-ui.js:subagentSpawnedon the parent's persistent state plus a separate 20 s decay →subagentCompleted({ stillActive:false }).session-activity-dom.js applyStateClassesprojectshas-busy-agentsoffsnapshot.agentsBusy(that rung never wins the icon whilebusyis active, but the row tint must still show).sidebar.js parentHasActiveSubagent()also consults the adapter states, so a periodicrenderProjects()(remote refresh 15–60 s) no longer wipes the tint before the decay.buildSubagentItem()already readssession.subagentType, populated for mirrored rows byreadSubagentMeta()like local ones (checked in code, DOM parity test added). The "SUB" seen on the VPS is therefore most likely the sidecar arriving a mirror cycle after its transcript — not verified live; if it persists after this, the next step is on the mirror ordering, not the renderer.session-state.md(all three kinds wired for subagent ports, status "steps 1-5 done"),subagent-observability.md,ipc-bridge.md.Proof
test/subagent-attribution.test.js(both layouts, top-level → null; mutation reporting the wrong segment as parent turns two tests red, reverted).has-busy-agents+session-icon--agents-busy; decay clears it.test/dom-sidebar-remote-subagent-attribution.test.js: remote subagent row shows the agent type like a local one; parent tint survives a re-render.task check: two stages green, 0 fail, pre-existing skips.Not done: seeding
agentsBusyat first paint (no persisted "active-at" signal for subagents exists to seed from) — live events only, as #247's acceptance requires.