Summary
Every sub-agent session ID has an all-zero parent span: 0000000000000000-<child>_<agent>. The {parent-span}-{child-span} format from amplifier_foundation.tracing never carries real lineage, so tooling that reads session directory names cannot attribute a delegate session to its parent. On one machine, 1394 of 1394 sub-session directories are zeroed — not a single counter-example.
This is two bugs that mask each other; fixing either alone leaves the behavior unchanged.
Bug A: parent_trace_id is always None
amplifier_app_cli/session_spawner.py:405-410:
sub_session_id = generate_sub_session_id(
agent_name=agent_name,
parent_session_id=parent_session.session_id,
parent_trace_id=getattr(parent_session, "trace_id", None),
)
AmplifierSession (amplifier_core/session.py:24) has no trace_id attribute — grep -rn trace_id amplifier_core/ returns nothing. The getattr therefore always yields None.
In amplifier_foundation/tracing.py:generate_sub_session_id, both ways of obtaining a parent span then fail:
- Lines 89-94 match
parent_session_id against _SPAN_PATTERN = ^([0-9a-f]{16})-([0-9a-f]{16})_. Root session IDs are dashed uuid4 (amplifier_core/session.py:71), e.g. 071a2b27-7562-4242-8ea6-8911809a5a28: 8 hex in the first group, no underscore. No match.
- Lines 98-104 (the trace-ID fallback) are gated on
parent_trace_id being truthy, so they never run.
- Line 87's
parent_span = _DEFAULT_PARENT_SPAN ("0" * 16) survives.
amplifier_module_tool_delegate/__init__.py:993 has the same hole independently: it passes parent_session_id only, never a trace ID.
Bug B: the stored trace_id cannot satisfy _TRACE_ID_PATTERN
session_spawner.py:800 performs the same lookup with a different default, and persists the result as metadata.json's trace_id (line 812):
parent_trace_id = getattr(parent_session, "trace_id", parent_session.session_id)
Since the fallback is session_id, every stored trace ID is a dashed 36-char UUID, while _TRACE_ID_PATTERN is ^[0-9a-f]{32}$ (tracing.py:24). Measured across 1355 metadata.json files: 0 valid 32-hex trace IDs. So aligning line 409's default with line 800's is not sufficient — the UUID must be normalized to hex before it reaches generate_sub_session_id.
Why it is unconditional
The line 89-94 path can only succeed if the parent is itself a sub-session, which requires nested delegation. tool-delegate defaults exclude_tools = ["tool-delegate"] (amplifier_module_tool_delegate/__init__.py:129), so spawned agents do not inherit the delegate tool and nesting effectively never occurs. Result: the parent component is dead weight in 100% of real sessions.
Repro
from amplifier_foundation.tracing import generate_sub_session_id as g
root = "071a2b27-7562-4242-8ea6-8911809a5a28" # a real root session id
# 1. what session_spawner.py:409 does today
g(agent_name="explorer", parent_session_id=root)
# -> '0000000000000000-2ed57072cf184b17_explorer' BUG
# 2. passing the dashed uuid that metadata.json stores as trace_id
g(agent_name="explorer", parent_session_id=root, parent_trace_id=root)
# -> '0000000000000000-68ba3ed2d49b44c4_explorer' STILL BUGGED (Bug B)
# 3. passing the uuid hex with dashes stripped
g(agent_name="explorer", parent_session_id=root, parent_trace_id=root.replace('-', ''))
# -> '756242428ea68911-85d3baf91ef2496a_explorer' correct
Or on any existing install:
find ~/.amplifier/projects -mindepth 3 -maxdepth 3 -type d -path '*/sessions/*' \
| sed 's|.*/sessions/||' | grep -E '^[0-9a-f]{16}-[0-9a-f]{16}_' \
| grep -vc '^0000000000000000-'
# -> 0
Suggested fix
Normalize the root UUID into a 32-hex trace ID once, at session creation, and propagate it:
- Give
AmplifierSession a real trace_id attribute, defaulting to uuid.UUID(self.session_id).hex for root sessions and inherited verbatim by children — so the two getattr sites can no longer disagree.
- Failing that, have both
session_spawner.py:409 and :800 use one shared helper that strips dashes: parent_session.session_id.replace("-", "").
- Optionally relax
_TRACE_ID_PATTERN in tracing.py:24 to accept a dashed UUID and normalize internally, making the API forgiving of the natural input.
- Pass a trace ID from
tool-delegate/__init__.py:993 too, so the foundation-internal path is fixed independently of app-cli.
Existing sessions remain attributable — parent_id is already recorded in both metadata.json and the session:fork event — so a fix needs no data migration, only forward correctness.
Related
#314 (session IDs truncated in session list --format json — same family, different path), #252 (closed; parent_session_id persisted for sub-recipes).
Versions: amplifier-app-cli 0.1.1 (a0c13cd1aa4b1bf9e3e155d4e12b306a87baa09a), amplifier-foundation 1.0.0 (0d5c5204befc33febfc2bdb8cd8d72ff0a3e6c84), amplifier-core 1.6.0. macOS 26 (Darwin 25.5.0), Python 3.12.
Summary
Every sub-agent session ID has an all-zero parent span:
0000000000000000-<child>_<agent>. The{parent-span}-{child-span}format fromamplifier_foundation.tracingnever carries real lineage, so tooling that reads session directory names cannot attribute a delegate session to its parent. On one machine, 1394 of 1394 sub-session directories are zeroed — not a single counter-example.This is two bugs that mask each other; fixing either alone leaves the behavior unchanged.
Bug A:
parent_trace_idis alwaysNoneamplifier_app_cli/session_spawner.py:405-410:AmplifierSession(amplifier_core/session.py:24) has notrace_idattribute —grep -rn trace_id amplifier_core/returns nothing. Thegetattrtherefore always yieldsNone.In
amplifier_foundation/tracing.py:generate_sub_session_id, both ways of obtaining a parent span then fail:parent_session_idagainst_SPAN_PATTERN = ^([0-9a-f]{16})-([0-9a-f]{16})_. Root session IDs are dasheduuid4(amplifier_core/session.py:71), e.g.071a2b27-7562-4242-8ea6-8911809a5a28: 8 hex in the first group, no underscore. No match.parent_trace_idbeing truthy, so they never run.parent_span = _DEFAULT_PARENT_SPAN("0" * 16) survives.amplifier_module_tool_delegate/__init__.py:993has the same hole independently: it passesparent_session_idonly, never a trace ID.Bug B: the stored
trace_idcannot satisfy_TRACE_ID_PATTERNsession_spawner.py:800performs the same lookup with a different default, and persists the result asmetadata.json'strace_id(line 812):Since the fallback is
session_id, every stored trace ID is a dashed 36-char UUID, while_TRACE_ID_PATTERNis^[0-9a-f]{32}$(tracing.py:24). Measured across 1355metadata.jsonfiles: 0 valid 32-hex trace IDs. So aligning line 409's default with line 800's is not sufficient — the UUID must be normalized to hex before it reachesgenerate_sub_session_id.Why it is unconditional
The line 89-94 path can only succeed if the parent is itself a sub-session, which requires nested delegation.
tool-delegatedefaultsexclude_tools = ["tool-delegate"](amplifier_module_tool_delegate/__init__.py:129), so spawned agents do not inherit the delegate tool and nesting effectively never occurs. Result: the parent component is dead weight in 100% of real sessions.Repro
Or on any existing install:
Suggested fix
Normalize the root UUID into a 32-hex trace ID once, at session creation, and propagate it:
AmplifierSessiona realtrace_idattribute, defaulting touuid.UUID(self.session_id).hexfor root sessions and inherited verbatim by children — so the twogetattrsites can no longer disagree.session_spawner.py:409and:800use one shared helper that strips dashes:parent_session.session_id.replace("-", "")._TRACE_ID_PATTERNintracing.py:24to accept a dashed UUID and normalize internally, making the API forgiving of the natural input.tool-delegate/__init__.py:993too, so the foundation-internal path is fixed independently of app-cli.Existing sessions remain attributable —
parent_idis already recorded in bothmetadata.jsonand thesession:forkevent — so a fix needs no data migration, only forward correctness.Related
#314 (session IDs truncated in
session list --format json— same family, different path), #252 (closed; parent_session_id persisted for sub-recipes).Versions: amplifier-app-cli 0.1.1 (
a0c13cd1aa4b1bf9e3e155d4e12b306a87baa09a), amplifier-foundation 1.0.0 (0d5c5204befc33febfc2bdb8cd8d72ff0a3e6c84), amplifier-core 1.6.0. macOS 26 (Darwin 25.5.0), Python 3.12.