Skip to content

feat(spawn): bridge delegated sub-agent cost to the parent session - #155

Merged
Salil Das (sadlilas) merged 1 commit into
mainfrom
feat/bridge-delegated-subagent-cost
Aug 26, 2026
Merged

feat(spawn): bridge delegated sub-agent cost to the parent session#155
Salil Das (sadlilas) merged 1 commit into
mainfrom
feat/bridge-delegated-subagent-cost

Conversation

@sadlilas

@sadlilas Salil Das (sadlilas) commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

spawn_sub_session listed cost bridging as MVP out-of-scope and never called amplifier_foundation.bridge_child_cost. Delegated sub-agent spend therefore never reached the parent's session.cost channel.

Anything that asks the parent coordinator what a session cost — collect_contributions("session.cost"), and the sessionCostTotal the streaming hook emits from it — reported the parent's own LLM calls only. A turn that delegated most of its work looked nearly free.

Why this is small

bridge_child_cost is a top-level amplifier_foundation export. This package already depends on amplifier-foundation (pyproject.toml) and imports it in eleven places across src/ — including this very module, two lines above the change (generate_sub_session_id).

Nothing new is built. The call was simply never wired.

- from amplifier_foundation import generate_sub_session_id
+ from amplifier_foundation import bridge_child_cost, generate_sub_session_id

One wiring point covers both faces: _runtime.py and amplifier_agent_http/_session_runner.py each register session.spawn against this same function.

Placement

Before cleanup(). bridge_child_cost reads the child's contributions and re-registers the frozen total on the parent. cleanup() tears the child coordinator down and takes those contributions with it — bridging after it would silently bridge nothing and look like it worked.

Inside the try, not in finally. This matches amplifier_foundation's own spawn() (amplifier_foundation/bundle/_prepared.py:926-938), which bridges after execute and keeps finally teardown-only. spawn_sub_session is the second implementation of that capability; if the two disagree on when cost is bridged, the same delegating turn reports a different session total depending on which engine ran it. Spend from a failed delegation is not bridged. That is deliberate — it was the only thing a finally placement bought, and nothing needs it today.

try:
    response = await child_session.execute(instruction)
    await bridge_child_cost(
        child_session.coordinator,
        parent_session.coordinator,
        sub_session_id,
    )
finally:
    await child_session.cleanup()

Scope

This changes exactly one number: usage.sessionCostTotal on the NDJSON display stream, which the streaming hook computes from collect_contributions("session.cost") on the parent coordinator.

Per-call usage.cost is untouched. Child sessions already inherit the parent's display.emit capability and mount the same streaming hook, so per-call sub-agent costs — tagged with agentName — were already on the wire. The --output json envelope is untouched as well; it carries no cost field (docs/spec/envelope-and-errors.md).

Also in this change

Drops the two stale "OUT OF SCOPE for this MVP — Cost bridging" notes in the module and function docstrings.

Corrects the on_orchestrator_complete docstring in bundle/hook_streaming.py, which attributed sub-agent total discrepancies to "how the kernel accumulates contributions across the coordinator hierarchy." The kernel does no such thing — collect_contributions (amplifier-core/crates/amplifier-core/src/coordinator.rs:386) reads only the channels registered on that one coordinator. A parent's total includes delegated spend solely because of the bridge call this PR adds, and the docstring now says so.

Verification

ruff check src/          All checks passed
ruff format --check      74 files already formatted
pyright src/             0 errors, 0 warnings

No unit test accompanies this. tests/ in this repo carries e2e suites and nothing else, deliberately (pyproject.toml).

🤖 Generated with Amplifier

The PR wires bridge_child_cost into spawn_sub_session so delegated sub-agent
spend reaches the parent's session.cost channel. Without it,
collect_contributions("session.cost") — and the sessionCostTotal the streaming
hook emits from it — reported only the parent's own LLM calls, so a turn that
delegated most of its work looked nearly free.

A self-review found two problems with the original commit, both now fixed:

1. The original put the bridge_child_cost call in the finally block.
   amplifier_foundation's own spawn() (amplifier_foundation/bundle/_prepared.py:926-938)
   bridges inside the try after execute, keeping finally teardown-only.
   spawn_sub_session is the second implementation of that same capability, so the
   divergence meant the same delegating turn would report a different session
   total depending on which engine ran it. The call was moved inside the try,
   matching foundation. Capturing spend from failed delegations is deliberately
   dropped — no present need, and it was the only thing the finally placement
   bought.

2. hook_streaming.py's on_orchestrator_complete docstring claimed sub-agent
   totals differ "due to how the kernel accumulates contributions across the
   coordinator hierarchy." That is false — amplifier-core's collect_contributions
   (crates/amplifier-core/src/coordinator.rs:386) reads only the channels
   registered on that one coordinator; there is no hierarchy walk. The docstring
   now states the accurate mechanism: a parent's total includes delegated spend
   solely because spawn_sub_session calls bridge_child_cost.

The commit also drops two stale "OUT OF SCOPE for this MVP — Cost bridging"
notes from the module and function docstrings in spawn.py.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@sadlilas
Salil Das (sadlilas) force-pushed the feat/bridge-delegated-subagent-cost branch from 48e52d9 to e28475b Compare August 26, 2026 20:32
@sadlilas
Salil Das (sadlilas) merged commit 08bc421 into main Aug 26, 2026
4 checks passed
@sadlilas
Salil Das (sadlilas) deleted the feat/bridge-delegated-subagent-cost branch August 26, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant