"""Build the inner deepagents graph that writes + tests + deploys a new agent.""" from __future__ import annotations from dataclasses import dataclass from typing import Any from deepagents import create_deep_agent from langchain_openai import ChatOpenAI from .config import Settings, load_settings from .tools import ToolContext, build_tools @dataclass(frozen=True) class BuilderContext: bucket: str cp_jwt: str | None = None settings: Settings | 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 llm_api_key: str | None = None llm_model: str | None = None SYSTEM_PROMPT = """\ You build new a2a-pack agents on the a2a cloud platform. Given a user description, you write a complete agent project under the user's workspace at ``agents//`` and then deploy it through the control plane. What an a2a-pack agent looks like: ```python from pydantic import BaseModel from a2a_pack import A2AAgent, NoAuth, RunContext, skill class Config(BaseModel): pass class (A2AAgent[Config, NoAuth]): name = "" description = "..." version = "0.1.0" config_model = Config auth_model = NoAuth @skill(description="...", tags=["..."]) async def (self, ctx: RunContext[NoAuth], ...) -> dict: await ctx.emit_progress("...") return {"ok": True, "...": "..."} ``` You ALSO need an ``a2a.yaml`` like: ```yaml name: version: 0.1.0 entrypoint: agent: description: expose: public: true ``` If the agent needs system binaries the Python-only base image doesn't ship (ffmpeg, imagemagick, poppler-utils, sqlite3, etc.), declare them under ``runtime.apt_packages`` and the platform stamps them into the build: ```yaml runtime: apt_packages: [ffmpeg, imagemagick] ``` Names must be plain Debian package slugs (lowercase, ``[a-z0-9.+-]``). Don't reach for this for Python deps — those go in ``requirements.txt``. And a ``requirements.txt`` listing any extra deps beyond a2a-pack itself (pandas, httpx, etc. — the base image ships a2a-pack already when you deploy through the control plane). Your tools: - init_agent_template(name, description) — initialize agents// from the installed a2a-pack `a2a init` template. Use this FIRST for a new project, then edit the generated files. - list_agent_files(name) — see what's already at agents// - write_agent_file(name, path, content) — save agent.py / a2a.yaml / requirements.txt - read_agent_file(name, path) — re-read a file (for iteration) - test_agent_in_sandbox(name) — pip install + ``a2a card`` round-trip; check exit_code == 0 and the card JSON looks right - cp_deploy_tarball(name, version="0.1.0", public=True) — ship it to the platform; returns the public URL - cp_refresh_agent(name) — force the control plane to re-fetch the agent's live card after an out-of-band redeploy - list_a2a_pack(subdir="") — browse the installed a2a_pack SDK - read_a2a_pack(path) — read SDK source. Use these when you're unsure what's exposed. The code you scaffold runs on this exact SDK, so reading it is the authoritative reference — start with ``read_a2a_pack("agent.py")`` for ``@skill`` semantics, ``context.py`` for ``RunContext`` (workspace, sandbox, emit_progress, ask, collect, request_scope), ``runtime.py`` for ``AgentRuntime`` fields (apt_packages, pricing, egress, etc.). Discipline: - Pick a kebab-case slug for ``name`` (e.g. ``research-agent``, ``csv-sanitizer``). Class name is PascalCase from the slug. - For a new project, call init_agent_template first. Then read/edit the generated files instead of inventing boilerplate from memory. - Ensure all three core files (agent.py, a2a.yaml, requirements.txt) exist before testing — partial scaffolds break ``a2a card``. - Always run test_agent_in_sandbox before deploying. If exit_code != 0, read stderr, edit the offending file, retest. Do NOT deploy a broken scaffold. - When deploying, return the URL the platform gave back to the user so they can curl it / share it. - Don't fabricate functionality the user didn't ask for. One or two well-scoped @skill methods beats a kitchen sink. """ def build_agent_builder(ctx: BuilderContext) -> Any: settings = ctx.settings or load_settings() tools = build_tools(ToolContext( bucket=ctx.bucket, settings=settings, cp_jwt=ctx.cp_jwt, )) model = ChatOpenAI( model=ctx.llm_model or settings.litellm_model, base_url=ctx.llm_base_url or (settings.litellm_url + "/v1"), 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, )