microsoft / microsoft/amplifier
[app-cli + foundation] Sub-agent session IDs always have an all-zero parent span, destroying delegate→parent attribution
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.1k
- Forks
- 261
- Avg merge
- 3h 28m
- Merged PRs (30d)
- 13
Description
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_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. - Lines 98-104 (the trace-ID fallback) are gated on
parent_trace_idbeing 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
AmplifierSessiona realtrace_idattribute, defaulting touuid.UUID(self.session_id).hexfor root sessions and inherited verbatim by children — so the twogetattrsites can no longer disagree. - Failing that, have both
session_spawner.py:409and:800use one shared helper that strips dashes:parent_session.session_id.replace("-", ""). - Optionally relax
_TRACE_ID_PATTERNintracing.py:24to accept a dashed UUID and normalize internally, making the API forgiving of the natural input. - Pass a trace ID from
tool-delegate/__init__.py:993too, 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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the three generate_sub_session_id cases from the issue, then read amplifier_app_cli/session_spawner.py, amplifier_foundation/tracing.py, amplifier_core/session.py, and amplifier_module_tool_delegate/init.py at the cited locations. Verify that root UUIDs become valid 32-hex trace IDs and that both spawning paths produce non-zero parent spans; existing metadata and session:fork attribution should remain compatible.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100