Skip to content

[app-cli + foundation] Sub-agent session IDs always have an all-zero parent span, destroying delegate→parent attribution #348

Description

@Joi

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:

  1. 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.
  2. Failing that, have both session_spawner.py:409 and :800 use one shared helper that strips dashes: parent_session.session_id.replace("-", "").
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions