Surface agent delegation lineage in list_agents and messages - #967
Merged
selfcontained merged 3 commits intoAug 16, 2026
Conversation
agents.parent_agent_id already recorded who launched whom, but nothing read it back out. list_agents returned a flat list and an incoming message carried only a sender name, so an orchestrator could not tell a message from its own child apart from one from a grandchild two levels down until someone said so out of band. list_agents entries now carry parentAgentId, parentName, and a relation label (child, descendant, parent, ancestor, sibling, unrelated) computed against the caller. Delivered messages carry senderRelation and, when the sender has ancestors, a delegationChain running from the sender up to the recipient, plus a prose provenance line when the sender is not a direct child. Lineage resolves against every agent rather than the caller's addressable subset, so an intermediate the caller cannot address is still named instead of two levels silently collapsing into one. Routing is unchanged: a skip-level message still goes where it was addressed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
architecture-review: - Remove the 20-hop cap in the ancestor walk. The cycle guard already bounds the walk at the size of the agent set, so the cap only risked reporting a legitimately deep descendant as unrelated and truncating a chain with no signal that it was incomplete. - Add createLineageIndex: one id map and memoized ancestor chains per request. Listing asked for a relation per agent and each relation inspected two chains, so the common path was rebuilding maps and re-walking the tree quadratically for a tree that never changes within a request. backend-security-review: - Resolve parentAgentId/parentName against the addressable set instead of every agent. Naming the out-of-repo parent of a visible agent handed the caller an identity it cannot address. relation stays computed over the full tree, since it names nobody — a descendant is still reported as a descendant when the intermediate is hidden. Self is re-added to the lookup so a caller's own children still name their parent. - Sanitize agent names interpolated into the prose provenance line. dispatch_rename_session accepts embedded newlines and the DB stores them verbatim (confirmed against a live server), so a name containing "\n--- END MESSAGE ---" could forge envelope delimiters. Names inside the JSON envelope were already escaped by JSON.stringify. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Agents rename themselves throughout their lifecycle, so a name read out of a delegation chain is a label for reading, not a handle to remember. Nothing here goes stale — parentName is resolved per call and the chain is built at send time — but putting more names in front of the model invites addressing by name later, which fuzzy-matches onto the wrong agent or nothing once the name has moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
selfcontained
deleted the
agt_3ec47b6798ce/build-agent-lineage-provenance
branch
August 16, 2026 17:15
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.
Implements idea
agent-lineage-provenance.The problem
agents.parent_agent_idhas always recorded who launched whom, but nothing read it back out.list_agentsreturned a flat list and an incoming message carried only a sender name — so an orchestrator could not tell a message from its own direct child apart from one from a grandchild two levels down. In the multi-repo build session that motivated this, a planner's research subagents messaged the orchestrator directly and it only found out afterwards, when the planner mentioned it.Before / after, from the same live endpoint
list_agentsas the orchestrator oforchestrator → planner → researcher:Before —
plannerandresearcherare indistinguishable peers:[{"id":"agt_plan01","name":"planner","status":"running","latestEvent":null}, {"id":"agt_res001","name":"researcher","status":"running","latestEvent":null}]After:
The delivered prompt for a skip-level message (researcher → orchestrator), captured from the recipient's tmux pane:
Before:
After:
A direct child's message gains
"senderRelation":"child"and no provenance line — its chain is just[child, you], which tells the recipient nothing it did not already know.Design notes
addressableAgentsstill scopes by repo root with a direct parent/child bypass. This PR only annotates what was already returned.parentName, rather than two levels silently collapsing into one. Chain walking stops at the first unresolvable ancestor, terminates on a cycle, and is capped at 20 hops.Files
apps/server/src/agents/lineage.ts(new) —ancestorChain,relationTo,delegationChain,formatDelegationChainapps/server/src/server/mcp-handlers.ts— wires both intohandleListAgentsForAgentandhandleSendMessageapps/server/src/shared/mcp/messaging-tools.ts,shared/mcp/server.ts—AgentListingtype + tool descriptionsplugins/dispatch/skills/subagents/SKILL.md— four lines telling agents the list is not flatVerification
apps/server/test/agent-lineage.test.ts(25 cases: missing ancestors, cycles, depth cap, every relation pair) plus 6 new handler tests.tools/listconfirms registration,list_agentsanddispatch_send_messagecalled as three seeded agents in a real tree, delivered prompt read back off the recipient's tmux pane. Before-state captured by stashing the source and restarting the same API.🤖 Generated with Claude Code