listTurnArtifacts filters on turn and status, never on source (packages/storage/src/artifact-store.ts:729). finalizeAndListChildTurnArtifacts passes the list straight through, and three call sites write all of it into the typed subagent / agent_swarm result as artifactIds (packages/runtime/src/session-manager.ts:2923, :3260, :3349).
So a field that reads as "what the child produced" is really "every row on the child's turn". Today that includes runtime-internal sources: synthesis_cache_block, history_compact_block, history_compact_source, session_effect, and — historically — provider_request_capture. #4738 has the evidence for the last one: the assertion it removed read childArtifacts.length === 1 with source === 'provider_request_capture', followed by deepEqual(typedSpawnResult.artifactIds, childArtifacts.map(a => a.id)). A child run that produced nothing reported one artifact to its parent.
#4738 fixed the consequence — every reader of these ids now drops a referent that is gone instead of failing the copy — so nothing is broken today. This issue is about the cause: the list should carry what the child produced.
It is not a one-line filter. Each source needs a decision about whether it should follow a fork: tool_result clearly should, provider_request_capture clearly should not, and the history-compaction blocks probably must, or a forked child loses history it needs to replay. The work is to trace, per source, who reads it after a copy — then either filter the list at its writers or give the field a name that matches what it holds.
Refs #4738.
listTurnArtifactsfilters on turn and status, never on source (packages/storage/src/artifact-store.ts:729).finalizeAndListChildTurnArtifactspasses the list straight through, and three call sites write all of it into the typedsubagent/agent_swarmresult asartifactIds(packages/runtime/src/session-manager.ts:2923,:3260,:3349).So a field that reads as "what the child produced" is really "every row on the child's turn". Today that includes runtime-internal sources:
synthesis_cache_block,history_compact_block,history_compact_source,session_effect, and — historically —provider_request_capture. #4738 has the evidence for the last one: the assertion it removed readchildArtifacts.length === 1withsource === 'provider_request_capture', followed bydeepEqual(typedSpawnResult.artifactIds, childArtifacts.map(a => a.id)). A child run that produced nothing reported one artifact to its parent.#4738 fixed the consequence — every reader of these ids now drops a referent that is gone instead of failing the copy — so nothing is broken today. This issue is about the cause: the list should carry what the child produced.
It is not a one-line filter. Each source needs a decision about whether it should follow a fork:
tool_resultclearly should,provider_request_captureclearly should not, and the history-compaction blocks probably must, or a forked child loses history it needs to replay. The work is to trace, per source, who reads it after a copy — then either filter the list at its writers or give the field a name that matches what it holds.Refs #4738.