From 1411a445e1c892b62ba23c1cd515e20cc2f875b7 Mon Sep 17 00:00:00 2001 From: robert Date: Wed, 20 May 2026 19:41:22 -0300 Subject: [PATCH] fix: align agent-builder with longer pack timeouts --- agent.py | 14 ++++++++++++++ agent_builder/config.py | 4 +++- agent_builder/tools.py | 8 ++++++-- deploy/20-deployment.yaml | 4 ++++ requirements.txt | 2 +- tests/test_builder_prompt.py | 9 +++++++++ 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/agent.py b/agent.py index 892f631..7567c02 100644 --- a/agent.py +++ b/agent.py @@ -17,8 +17,10 @@ because it needs three things forwarded from the caller: """ from __future__ import annotations +import asyncio import json import re +from contextlib import suppress from typing import Any from pydantic import BaseModel @@ -65,6 +67,7 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]): "should do. Returns the live URL when ready." ), tags=["builder", "scaffold", "deepagents", "meta"], + stream=True, ) async def build( self, @@ -127,6 +130,13 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]): final_state: dict[str, Any] = {} deployed_url: str | None = None last_reply: str = "" + + async def _heartbeat() -> None: + while True: + await asyncio.sleep(20) + await ctx.emit_progress(f"agent-builder still working on {name!r}") + + heartbeat_task = asyncio.create_task(_heartbeat()) try: async for event in graph.astream_events( {"messages": [{"role": "user", "content": user_msg}]}, @@ -156,6 +166,10 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]): final_state = out except Exception as exc: # noqa: BLE001 return {"error": f"build graph failed: {type(exc).__name__}: {exc}"} + finally: + heartbeat_task.cancel() + with suppress(asyncio.CancelledError): + await heartbeat_task for msg in final_state.get("messages") or []: content = msg.get("content") if isinstance(msg, dict) else getattr(msg, "content", None) diff --git a/agent_builder/config.py b/agent_builder/config.py index 3660303..dcc98e8 100644 --- a/agent_builder/config.py +++ b/agent_builder/config.py @@ -9,6 +9,7 @@ from dataclasses import dataclass class Settings: sandbox_url: str sandbox_timeout_s: int + deploy_wait_timeout_s: int litellm_url: str litellm_key: str litellm_model: str @@ -24,7 +25,8 @@ def load_settings() -> Settings: sandbox_url=os.environ.get( "SANDBOX_URL", "http://sandbox.sandbox.svc.cluster.local:8000" ), - sandbox_timeout_s=int(os.environ.get("SANDBOX_TIMEOUT_S", "240")), + sandbox_timeout_s=int(os.environ.get("SANDBOX_TIMEOUT_S", "1200")), + deploy_wait_timeout_s=int(os.environ.get("DEPLOY_WAIT_TIMEOUT_S", "900")), litellm_url=os.environ.get( "A2A_LITELLM_URL", "http://litellm.llm.svc.cluster.local:4000" ), diff --git a/agent_builder/tools.py b/agent_builder/tools.py index b421021..57632c5 100644 --- a/agent_builder/tools.py +++ b/agent_builder/tools.py @@ -38,7 +38,7 @@ class ToolContext: async def _wait_for_live_card( url: str | None, expected_version: str | None, - timeout_s: float = 180.0, + timeout_s: float = 900.0, ) -> tuple[bool, dict[str, Any]]: """Poll ``{url}/.well-known/agent-card`` until ``version`` matches ``expected_version`` or ``timeout_s`` elapses. Returns @@ -435,7 +435,11 @@ def build_tools(ctx: ToolContext) -> list[Any]: # so the LLM can verify the input_schema actually matches the # code it wrote — catches the schema/code skew that causes # downstream 400s on call_agent. - live, live_card = await _wait_for_live_card(url_, version_) + live, live_card = await _wait_for_live_card( + url_, + version_, + timeout_s=settings.deploy_wait_timeout_s, + ) out: dict[str, Any] = { "ok": live, "name": name_, diff --git a/deploy/20-deployment.yaml b/deploy/20-deployment.yaml index b3b1a40..5343ee3 100644 --- a/deploy/20-deployment.yaml +++ b/deploy/20-deployment.yaml @@ -32,6 +32,10 @@ spec: value: registry.a2acloud.io/agents/agent-builder:f7da050814bf8856fb2df0949d8140b34d379864 - name: SANDBOX_URL value: http://sandbox.sandbox.svc.cluster.local:8000 + - name: SANDBOX_TIMEOUT_S + value: "1200" + - name: DEPLOY_WAIT_TIMEOUT_S + value: "900" - name: A2A_LITELLM_URL value: http://litellm.llm.svc.cluster.local:4000 - name: A2A_LITELLM_KEY diff --git a/requirements.txt b/requirements.txt index 1a839e8..1d7ec1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -a2a-pack>=0.1.12 +a2a-pack>=0.1.14 httpx>=0.27 boto3>=1.34 deepagents>=0.5.0 diff --git a/tests/test_builder_prompt.py b/tests/test_builder_prompt.py index d245e8d..4f7c56e 100644 --- a/tests/test_builder_prompt.py +++ b/tests/test_builder_prompt.py @@ -21,6 +21,15 @@ class BuilderPromptTests(unittest.TestCase): "config={\"recursion_limit\": DEEPAGENTS_RECURSION_LIMIT}", agent_source, ) + self.assertIn("stream=True", agent_source) + + def test_builder_defaults_raise_timeout_budget(self) -> None: + config_source = Path(__file__).resolve().parents[1].joinpath( + "agent_builder/config.py" + ).read_text() + + self.assertIn('sandbox_timeout_s=int(os.environ.get("SANDBOX_TIMEOUT_S", "1200"))', config_source) + self.assertIn('deploy_wait_timeout_s=int(os.environ.get("DEPLOY_WAIT_TIMEOUT_S", "900"))', config_source) def test_prompt_requires_resource_mirror_for_heavy_agents(self) -> None: self.assertIn("declare resources in BOTH places", SYSTEM_PROMPT)