From 27c14ac22f433e599ec7c193f580f79ce41d1fa6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:34:08 +0000 Subject: [PATCH 1/2] fix: include delegate orchestration agent in manifest.agents Delegate orchestrations are built as a new Agent forked from the entry agent's blueprint and report token usage under the orchestration name. build_manifest now appends an AgentDescriptor for every delegate Agent in orchestrators, so consumers can look up the model for any token event by matching the orchestration name in manifest.agents. --- src/strands_compose/manifest.py | 21 +++++++++++- tests/unit/test_manifest.py | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/strands_compose/manifest.py b/src/strands_compose/manifest.py index 49aebcd..75dc9d1 100644 --- a/src/strands_compose/manifest.py +++ b/src/strands_compose/manifest.py @@ -235,6 +235,9 @@ def _orchestration_descriptor(name: str, orch: Node) -> OrchestrationDescriptor: # ── Entry resolution ───────────────────────────────────────────────────────── +# ── Entry resolution ───────────────────────────────────────────────────────── + + def _resolve_entry( entry: Node, agents: dict[str, Agent], @@ -268,6 +271,13 @@ def build_manifest( Pure function: no I/O, no network calls, no mutation of inputs. + Delegate orchestrations are built as a new :class:`strands.Agent` forked + from an entry agent's blueprint. Because the delegate agent reports token + usage under the orchestration name, it is included in ``manifest.agents`` + (in addition to the orchestration entry) so consumers can trace any token + event back to a model descriptor. Swarm and Graph orchestrations do not + report token usage directly and are therefore not duplicated. + Args: agents: Resolved agents keyed by name. orchestrators: Resolved orchestrations keyed by name. @@ -280,8 +290,17 @@ def build_manifest( Raises: ValueError: If *entry* cannot be resolved by object identity. """ + agent_descriptors = [_agent_descriptor(name, agent) for name, agent in agents.items()] + + # Delegate orchestrations are Agents themselves — they report token usage + # under the orchestration name, so include them in agents so consumers can + # look up the model for any incoming token event by name. + for name, orch in orchestrators.items(): + if isinstance(orch, Agent): + agent_descriptors.append(_agent_descriptor(name, orch)) + return SessionManifest( - agents=[_agent_descriptor(name, agent) for name, agent in agents.items()], + agents=agent_descriptors, orchestrations=[ _orchestration_descriptor(name, orch) for name, orch in orchestrators.items() ], diff --git a/tests/unit/test_manifest.py b/tests/unit/test_manifest.py index cf571dd..21103c5 100644 --- a/tests/unit/test_manifest.py +++ b/tests/unit/test_manifest.py @@ -245,10 +245,15 @@ def test_manifest_entry_not_found_raises_value_error(self): ) def test_manifest_orchestration_kind_delegate(self): - """Agent orchestration → kind='delegate'.""" + """Agent orchestration → kind='delegate'; delegate also appears in agents.""" agent = _mock_agent() delegate = Mock(spec=Agent) delegate._session_manager = None + delegate.description = None + delegate.model = Mock() + delegate.model.get_config.return_value = {} + delegate.model.__class__.__module__ = "strands.models" + delegate.model.__class__.__qualname__ = "TestModel" manifest = build_manifest( agents={"agent1": agent}, @@ -261,6 +266,10 @@ def test_manifest_orchestration_kind_delegate(self): assert manifest.orchestrations[0].nodes == [] assert manifest.orchestrations[0].edges is None assert manifest.orchestrations[0].entry_node_id is None + # Delegate agent is also included in manifest.agents under its orch name + agent_names = [a.name for a in manifest.agents] + assert "agent1" in agent_names + assert "delegate1" in agent_names def test_manifest_orchestration_kind_swarm(self): """Swarm orchestration → kind='swarm'.""" @@ -546,10 +555,15 @@ def test_manifest_graph_entry_node_id_none_when_empty(self): assert manifest.orchestrations[0].entry_node_id is None def test_manifest_delegate_empty_topology(self): - """Delegate orchestration has empty topology.""" + """Delegate orchestration has empty topology; delegate appears in agents.""" agent = _mock_agent() delegate = Mock(spec=Agent) delegate._session_manager = None + delegate.description = None + delegate.model = Mock() + delegate.model.get_config.return_value = {} + delegate.model.__class__.__module__ = "strands.models" + delegate.model.__class__.__qualname__ = "TestModel" manifest = build_manifest( agents={"agent1": agent}, @@ -561,6 +575,45 @@ def test_manifest_delegate_empty_topology(self): assert orch_desc.nodes == [] assert orch_desc.edges is None assert orch_desc.entry_node_id is None + assert len(manifest.agents) == 2 + assert {a.name for a in manifest.agents} == {"agent1", "delegate1"} + + def test_manifest_delegate_agent_descriptor_uses_orchestration_name(self): + """Delegate added to agents uses the orchestration name, not the entry agent name.""" + agent = _mock_agent(model_id="claude-3") + delegate = Mock(spec=Agent) + delegate._session_manager = None + delegate.description = "orchestrator" + delegate.model = Mock() + delegate.model.get_config.return_value = {"model_id": "claude-3"} + delegate.model.__class__.__module__ = "strands.models" + delegate.model.__class__.__qualname__ = "TestModel" + + manifest = build_manifest( + agents={"manager": agent}, + orchestrators={"main": delegate}, + entry=delegate, + ) + + delegate_agent = next(a for a in manifest.agents if a.name == "main") + assert delegate_agent.model.model_id == "claude-3" + + def test_manifest_non_delegate_orchestration_not_added_to_agents(self): + """Swarm and Graph orchestrations are not added to manifest.agents.""" + agent = _mock_agent() + swarm = Mock(spec=Swarm) + swarm.nodes = {} + swarm.entry_point = None + swarm.session_manager = None + + manifest = build_manifest( + agents={"agent1": agent}, + orchestrators={"swarm1": swarm}, + entry=swarm, + ) + + assert len(manifest.agents) == 1 + assert manifest.agents[0].name == "agent1" # ── first_session_id ───────────────────────────────────────────────────────── From 9d526e7c1c77ca256dd989019e857dec7bd14271 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:56:45 +0000 Subject: [PATCH 2/2] Remove duplicate Entry resolution comment in manifest.py --- src/strands_compose/manifest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/strands_compose/manifest.py b/src/strands_compose/manifest.py index 75dc9d1..e8c8d5c 100644 --- a/src/strands_compose/manifest.py +++ b/src/strands_compose/manifest.py @@ -235,9 +235,6 @@ def _orchestration_descriptor(name: str, orch: Node) -> OrchestrationDescriptor: # ── Entry resolution ───────────────────────────────────────────────────────── -# ── Entry resolution ───────────────────────────────────────────────────────── - - def _resolve_entry( entry: Node, agents: dict[str, Agent],