From de2887ff6f177adc5003ad38cc831be9cf8c2712 Mon Sep 17 00:00:00 2001 From: robert Date: Sun, 17 May 2026 18:57:29 -0300 Subject: [PATCH] fix: persist builder deepagent files --- Dockerfile | 2 +- agent.py | 1 + agent_builder/builder.py | 57 +++++++++++++++++++++++++++++++++++-- agent_builder/tools.py | 3 +- requirements.txt | 2 +- tests/test_template_init.py | 1 + 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3679162..48325c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -RUN python -c "import importlib.resources as r, a2a_pack; t = r.files('a2a_pack.cli.templates').joinpath('agent.py.tmpl').read_text(); missing = [m for m in ('LLMProvisioning.CALLER_PROVIDED', 'ctx.llm', 'create_deep_agent', 'wrap_model_call') if m not in t]; assert not missing, f'a2a-pack {a2a_pack.__version__} has stale init template; missing {missing}'" +RUN python -c "import importlib.resources as r, a2a_pack; t = r.files('a2a_pack.cli.templates').joinpath('agent.py.tmpl').read_text(); missing = [m for m in ('LLMProvisioning.CALLER_PROVIDED', 'ctx.llm', 'ctx.workspace_backend()', 'create_deep_agent', 'wrap_model_call') if m not in t]; assert not missing, f'a2a-pack {a2a_pack.__version__} has stale init template; missing {missing}'" COPY . . diff --git a/agent.py b/agent.py index f1252ac..3f208ff 100644 --- a/agent.py +++ b/agent.py @@ -108,6 +108,7 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]): graph = build_agent_builder(BuilderContext( bucket=bucket, cp_jwt=cp_jwt, + project_prefix=f"agents/{name}", llm_base_url=creds.base_url, llm_api_key=creds.api_key, llm_model=creds.model, diff --git a/agent_builder/builder.py b/agent_builder/builder.py index 965ab0c..2d17e15 100644 --- a/agent_builder/builder.py +++ b/agent_builder/builder.py @@ -16,6 +16,7 @@ class BuilderContext: bucket: str cp_jwt: str | None = None settings: Settings | None = None + project_prefix: str | None = None # Optional caller-provided LLM creds (when the outer platform Card # declares llm_provisioning=caller_provided). Falls back to settings. llm_base_url: str | None = None @@ -128,6 +129,58 @@ def build_agent_builder(ctx: BuilderContext) -> Any: api_key=ctx.llm_api_key or settings.litellm_key, temperature=0.0, ) - return create_deep_agent( - model=model, tools=tools, system_prompt=SYSTEM_PROMPT, + kwargs: dict[str, Any] = { + "model": model, + "tools": tools, + "system_prompt": SYSTEM_PROMPT, + } + backend = _build_project_backend(ctx, settings=settings) + if backend is not None: + kwargs["backend"] = backend + return create_deep_agent(**kwargs) + + +def _build_project_backend(ctx: BuilderContext, *, settings: Settings) -> Any | None: + """Persist DeepAgents built-in file tools into this project directory. + + The explicit builder tools remain the preferred path, but this prevents + a model-selected DeepAgents ``write_file`` from disappearing into + LangGraph state. Scope it to the project prefix, not the whole bucket. + """ + if not ctx.project_prefix: + return None + prefix = ctx.project_prefix.strip("/") + if not prefix: + return None + try: + from a2a_pack import Grant, WorkspaceAccess, WorkspaceMode + from a2a_pack.deepagents import WorkspaceBackend + from a2a_pack.workspace import MinIOWorkspaceClient + except Exception: # noqa: BLE001 + return None + + workspace = MinIOWorkspaceClient( + bucket=ctx.bucket, + endpoint_url=settings.minio_endpoint, + access_key_id=settings.minio_access_key, + secret_access_key=settings.minio_secret_key, + access=WorkspaceAccess.dynamic( + max_files=2000, + allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY), + require_reason=False, + ), + issuer="agent-builder", ) + workspace.install_grant( + Grant( + grant_id=f"agent-builder-{prefix.replace('/', '-')}", + issuer="agent-builder", + audience="agent-builder", + bucket=ctx.bucket, + mode=WorkspaceMode.READ_WRITE_OVERLAY, + allow_patterns=(f"{prefix}/**", "outputs/**"), + deny_patterns=(), + outputs_prefix=None, + ) + ) + return WorkspaceBackend(workspace) diff --git a/agent_builder/tools.py b/agent_builder/tools.py index 64d732e..b3a36fe 100644 --- a/agent_builder/tools.py +++ b/agent_builder/tools.py @@ -599,6 +599,7 @@ def _assert_current_deepagents_scaffold( "agent.py": ( "LLMProvisioning.CALLER_PROVIDED", "ctx.llm", + "ctx.workspace_backend()", "create_deep_agent", "wrap_model_call", "ChatOpenAI", @@ -624,7 +625,7 @@ def _assert_current_deepagents_scaffold( except Exception: # noqa: BLE001 version = "unknown" raise RuntimeError( - f"{source} is stale; expected the 0.1.3+ " + f"{source} is stale; expected the 0.1.4+ " "DeepAgents caller-LLM scaffold. " f"a2a_pack={version}; missing markers: {', '.join(missing)}" ) diff --git a/requirements.txt b/requirements.txt index 02a658c..b37ca6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -a2a-pack>=0.1.3 +a2a-pack>=0.1.4 httpx>=0.27 boto3>=1.34 deepagents>=0.5.0 diff --git a/tests/test_template_init.py b/tests/test_template_init.py index 417cc9c..fa3ec64 100644 --- a/tests/test_template_init.py +++ b/tests/test_template_init.py @@ -23,6 +23,7 @@ class TemplateInitTests(unittest.TestCase): self.assertIn('description = "Research helper"', files["agent.py"]) self.assertIn("LLMProvisioning.CALLER_PROVIDED", files["agent.py"]) self.assertIn("ctx.llm", files["agent.py"]) + self.assertIn("ctx.workspace_backend()", files["agent.py"]) self.assertIn("create_deep_agent", files["agent.py"]) self.assertIn("name: research-agent", files["a2a.yaml"]) self.assertIn("entrypoint: agent:ResearchAgent", files["a2a.yaml"])