From eee3de3d0a0f4ecb3c8c80e7ee550707d8e760cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:53:18 +0000 Subject: [PATCH 1/4] Initial plan From 25d00c681af8d1607b324c880aaabc194af95b91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:00:05 +0000 Subject: [PATCH 2/4] feat(agents): durably record antigravity backend receipts Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- .../agents/adapters/agent_orchestrator.py | 35 +++++++++++- tests/unit/test_antigravity_backend.py | 56 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/youtube_extension/services/agents/adapters/agent_orchestrator.py b/src/youtube_extension/services/agents/adapters/agent_orchestrator.py index 6ffd1b345..77f2eb4c5 100644 --- a/src/youtube_extension/services/agents/adapters/agent_orchestrator.py +++ b/src/youtube_extension/services/agents/adapters/agent_orchestrator.py @@ -56,11 +56,12 @@ class AgentOrchestrator: Handles task delegation, parallel processing, and result aggregation. """ - def __init__(self): + def __init__(self, audit_store: Any | None = None): """Initialize agent orchestrator""" self.logger = logging.getLogger("agent_orchestrator") self._agents: dict[str, BaseAgent] = {} self._agent_types: dict[str, type[BaseAgent]] = {} + self._audit_store = audit_store # Bounded: the module-level `orchestrator` singleton lives for the whole # process and every dispatch appends here, so an unbounded list would # grow without limit. maxlen evicts the oldest entries automatically. @@ -517,6 +518,38 @@ async def execute_antigravity_backend( }, ) ) + if self._audit_store is not None: + total_tokens = receipt.usage.get("total_tokens") + output_tokens = receipt.usage.get("output_tokens") + self._audit_store.append( + receipt.receipt_id, + agent_id="google_antigravity", + action="managed_backend_dispatch", + success=receipt.status == "completed" and receipt.error is None, + duration_ms=max(receipt.elapsed_seconds * 1000.0, 0.0), + details={ + "backend": "google_antigravity", + "provider": receipt.provider, + "agent": receipt.agent, + "status": receipt.status, + "receipt_id": receipt.receipt_id, + "request_sha256": receipt.request_sha256, + "interaction_id": receipt.interaction_id, + "environment_id": receipt.environment_id, + "budget_exceeded": receipt.budget_exceeded, + "mcp_servers": list(receipt.mcp_servers), + "policy": dict(receipt.policy), + "error": receipt.error, + }, + input_tokens=( + int(total_tokens) if isinstance(total_tokens, (int, float)) else None + ), + output_tokens=( + int(output_tokens) + if isinstance(output_tokens, (int, float)) + else None + ), + ) return receipt # --- Legacy local Antigravity-pattern workflow --- diff --git a/tests/unit/test_antigravity_backend.py b/tests/unit/test_antigravity_backend.py index 2286bde84..81e2d75be 100644 --- a/tests/unit/test_antigravity_backend.py +++ b/tests/unit/test_antigravity_backend.py @@ -39,6 +39,36 @@ async def create_interaction(self, payload: dict[str, Any]) -> Mapping[str, Any] return self.response +class FakeAuditStore: + def __init__(self) -> None: + self.entries: list[dict[str, Any]] = [] + + def append( + self, + run_id: str, + *, + agent_id: str, + action: str, + success: bool, + duration_ms: float, + details: dict[str, Any] | None = None, + input_tokens: int | None = None, + output_tokens: int | None = None, + ) -> None: + self.entries.append( + { + "run_id": run_id, + "agent_id": agent_id, + "action": action, + "success": success, + "duration_ms": duration_ms, + "details": details or {}, + "input_tokens": input_tokens, + "output_tokens": output_tokens, + } + ) + + def config(**changes: Any) -> AntigravityBackendConfig: values = { "enabled": True, @@ -213,6 +243,32 @@ async def test_orchestrator_records_managed_backend_dispatch() -> None: assert "context" not in entry +@pytest.mark.asyncio +async def test_orchestrator_records_managed_backend_receipt_durably() -> None: + from youtube_extension.services.agents.adapters.agent_orchestrator import ( + AgentOrchestrator, + ) + + audit_store = FakeAuditStore() + orchestrator = AgentOrchestrator(audit_store=audit_store) + receipt = await orchestrator.execute_antigravity_backend( + backend=AntigravityBackend(config(), FakeTransport()), + task="review", + context={"video_pack_id": "pack-3"}, + ) + + assert len(audit_store.entries) == 1 + entry = audit_store.entries[0] + assert entry["run_id"] == receipt.receipt_id + assert entry["agent_id"] == "google_antigravity" + assert entry["action"] == "managed_backend_dispatch" + assert entry["success"] is True + assert entry["duration_ms"] >= 0 + assert entry["details"]["receipt_id"] == receipt.receipt_id + assert entry["details"]["request_sha256"] == receipt.request_sha256 + assert "context" not in entry["details"] + + @pytest.mark.asyncio async def test_comparison_artifact_is_provider_neutral() -> None: transport = FakeTransport() From 469df0423891c6832537a2f8d5ec9bd01abd9e7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:03:56 +0000 Subject: [PATCH 3/4] fix(agents): add durable antigravity receipt audit hook Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- .../services/agents/adapters/agent_orchestrator.py | 4 +--- tests/unit/test_antigravity_backend.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/youtube_extension/services/agents/adapters/agent_orchestrator.py b/src/youtube_extension/services/agents/adapters/agent_orchestrator.py index 77f2eb4c5..a212ebd8e 100644 --- a/src/youtube_extension/services/agents/adapters/agent_orchestrator.py +++ b/src/youtube_extension/services/agents/adapters/agent_orchestrator.py @@ -16,6 +16,7 @@ from typing import Any, Optional from ..base_agent import AgentRequest, AgentResult, BaseAgent +from ..registry import get as get_agent_class @dataclass @@ -47,9 +48,6 @@ class OrchestrationResult: timestamp: datetime = field(default_factory=datetime.now) -from ..registry import get as get_agent_class - - class AgentOrchestrator: """ Centralized orchestration for AI agents. diff --git a/tests/unit/test_antigravity_backend.py b/tests/unit/test_antigravity_backend.py index 81e2d75be..7b028f052 100644 --- a/tests/unit/test_antigravity_backend.py +++ b/tests/unit/test_antigravity_backend.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Mapping +from collections.abc import Mapping +from typing import Any import pytest From ab25fe151009a5ec0f9bbd42e3169e0dd2e44cf9 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2026 09:05:32 +0000 Subject: [PATCH 4/4] Fix: Durable audit recording for the Antigravity backend stores `usage.total_tokens` in the `input_tokens` field, mislabeling token accounting (input = input+output). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the issue reported at src/youtube_extension/services/agents/adapters/agent_orchestrator.py:522 ## Bug In `execute_antigravity_backend` (`src/youtube_extension/services/agents/adapters/agent_orchestrator.py`, ~line 522), the durable audit-store append was populated as: ```python total_tokens = receipt.usage.get("total_tokens") output_tokens = receipt.usage.get("output_tokens") self._audit_store.append( ... input_tokens=(int(total_tokens) if isinstance(total_tokens, (int, float)) else None), output_tokens=(int(output_tokens) if isinstance(output_tokens, (int, float)) else None), ) ``` The `PipelineAuditStore.append` signature (`src/youtube_extension/services/pipeline_audit_store.py`, lines 29-40) records `input_tokens` and `output_tokens` as separate prompt vs. completion counts. The canonical mapping elsewhere (`hybrid_processor_service.py` lines 321-336) derives `input_tokens` from the prompt token count and `output_tokens` from the completion token count. Here `input_tokens` was fed `usage.get("total_tokens")`, which is the sum input + output. That mislabels the durable receipt: `input_tokens` ends up equal to input+output, corrupting cost/usage accounting derived from the audit trail. ## Trigger Any completed Antigravity backend dispatch where the provider `usage` dict reports `total_tokens` (e.g. `{"input_tokens": N, "output_tokens": M, "total_tokens": N+M}`): the audit record writes `input_tokens = total_tokens` instead of the real prompt count. ## Fix Read `usage.get("input_tokens")` (the actual prompt-token key, consistent with the sibling `output_tokens` key already read directly from `usage`) and record that into the audit store's `input_tokens` field: ```python input_tokens = receipt.usage.get("input_tokens") output_tokens = receipt.usage.get("output_tokens") ... input_tokens=(int(input_tokens) if isinstance(input_tokens, (int, float)) else None), ``` `total_tokens` remains used for budget-limit enforcement in `antigravity_backend.py`, which is unaffected. The existing test only sets `usage: {"total_tokens": 321}` and does not assert on recorded token values, so it neither breaks nor catches this — a follow-up test asserting per-direction token recording would be worthwhile. Co-authored-by: Vercel Co-authored-by: groupthinking --- .../services/agents/adapters/agent_orchestrator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/youtube_extension/services/agents/adapters/agent_orchestrator.py b/src/youtube_extension/services/agents/adapters/agent_orchestrator.py index a212ebd8e..1103ec43a 100644 --- a/src/youtube_extension/services/agents/adapters/agent_orchestrator.py +++ b/src/youtube_extension/services/agents/adapters/agent_orchestrator.py @@ -517,7 +517,7 @@ async def execute_antigravity_backend( ) ) if self._audit_store is not None: - total_tokens = receipt.usage.get("total_tokens") + input_tokens = receipt.usage.get("input_tokens") output_tokens = receipt.usage.get("output_tokens") self._audit_store.append( receipt.receipt_id, @@ -540,7 +540,7 @@ async def execute_antigravity_backend( "error": receipt.error, }, input_tokens=( - int(total_tokens) if isinstance(total_tokens, (int, float)) else None + int(input_tokens) if isinstance(input_tokens, (int, float)) else None ), output_tokens=( int(output_tokens)