diff --git a/examples/quota-plan-smoke.py b/examples/quota-plan-smoke.py index 5d14ea02..9b4ecbe5 100644 --- a/examples/quota-plan-smoke.py +++ b/examples/quota-plan-smoke.py @@ -1669,8 +1669,9 @@ def assert_goal_boundary_in_should_run() -> None: "production-action", "managed-mirror-sync", ], - "registered_agents": ["codex-main-control", "codex-side-bypass"], + "registered_agents": ["codex-main-control", "codex-side-bypass", "codex-side-reviewer"], "primary_agent": "codex-main-control", + "side_agent_handoff_agent": "codex-side-reviewer", }, "spawn_policy": { "mode": "multi_subagent", @@ -1724,8 +1725,12 @@ def assert_goal_boundary_in_should_run() -> None: assert "- automation_prompt_upgrade: required=True blocks_should_run=True" in markdown, markdown assert side_agent_decision["agent_identity"]["agent_id"] == "codex-side-bypass", side_agent_decision assert side_agent_decision["agent_identity"]["role"] == "side-agent", side_agent_decision + assert side_agent_decision["agent_identity"]["handoff_agent"] == "codex-side-reviewer", side_agent_decision assert "automation_prompt_upgrade" not in side_agent_decision, side_agent_decision - assert "agent_identity: agent_id=codex-side-bypass role=side-agent" in side_agent_markdown, side_agent_markdown + assert ( + "agent_identity: agent_id=codex-side-bypass role=side-agent " + "primary_agent=codex-main-control handoff_agent=codex-side-reviewer" + ) in side_agent_markdown, side_agent_markdown assert boundary["adapter"]["status"] == "connected-delivery", boundary assert boundary["write_scope"] == ["docs/design/**", "src/agent_harness/**", "tests/**"], boundary assert "production-action" in boundary["requires_parent_approval"], boundary diff --git a/loopx/agent_registry.py b/loopx/agent_registry.py index 779945e9..62d9e5ce 100644 --- a/loopx/agent_registry.py +++ b/loopx/agent_registry.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import Any -from .registry import read_json, registry_goals from .todo_contract import normalize_todo_claimed_by @@ -127,6 +126,8 @@ def agent_profile_for_goal(goal: dict[str, Any] | None, agent_id: str | None) -> def load_goal_from_registry(registry_path: Path, goal_id: str) -> dict[str, Any] | None: + from .registry import read_json, registry_goals + if not registry_path.exists(): return None registry = read_json(registry_path) diff --git a/loopx/quota.py b/loopx/quota.py index 1dc107e3..c7b7659a 100644 --- a/loopx/quota.py +++ b/loopx/quota.py @@ -11,6 +11,7 @@ from pathlib import Path from typing import Any +from .agent_registry import side_agent_handoff_agent_id_for_goal from .benchmark_core import compact_run_permission_policy_for_quota from .boundary_authority import checkpointed_boundary_authority_summary from .control_plane import ( @@ -4428,11 +4429,19 @@ def _quota_agent_identity(goal: dict[str, Any], *, agent_id: str | None) -> dict f"registered_agents={', '.join(registered_agents)}" ) primary_agent = _quota_primary_agent(goal) + handoff_agent = side_agent_handoff_agent_id_for_goal(goal, agent_id=normalized_agent_id) + if handoff_agent: + if handoff_agent not in registered_agents: + raise ValueError( + f"side_agent_handoff_agent={handoff_agent!r} is not registered; " + f"registered_agents={', '.join(registered_agents)}" + ) return { "agent_id": normalized_agent_id, "registered": True, "role": "primary-agent" if primary_agent and normalized_agent_id == primary_agent else "side-agent", "primary_agent": primary_agent, + "handoff_agent": handoff_agent, "registered_agents": registered_agents, } @@ -8307,7 +8316,8 @@ def render_quota_should_run_markdown(payload: dict[str, Any]) -> str: "- agent_identity: " f"agent_id={agent_identity.get('agent_id')} " f"role={agent_identity.get('role')} " - f"primary_agent={agent_identity.get('primary_agent')}" + f"primary_agent={agent_identity.get('primary_agent')} " + f"handoff_agent={agent_identity.get('handoff_agent')}" ) if payload.get("active_state_next_action"): lines.append(f"- active_state_next_action: {payload.get('active_state_next_action')}")