--- name: deepagents-implementation-patterns description: Implement DeepAgents inside a2a-pack agents with real deterministic tools, project skills, custom subagents, workspace-backed files, and caller-provided ChatOpenAI models. Use when editing create_deep_agent calls or deciding what belongs in tools, skills, or subagents. --- # DeepAgents Implementation Patterns DeepAgents supplies the inner agent loop. In this platform, the reliable shape is: caller-provided `ChatOpenAI`, A2A workspace backend, project DeepAgents skills, and a small number of exact tools. ## Implementation Shape ```python from __future__ import annotations import json from pathlib import Path from typing import Any from langchain_core.tools import tool from langchain_openai import ChatOpenAI from deepagents import create_deep_agent RUNTIME_SKILLS_ROOT = "/outputs/research-agent/.deepagents/skills/" SYSTEM_PROMPT = """\ You are a careful research agent. Use project skills for workflow rules and write durable files under /workspace/outputs/. """ def build_graph(ctx: Any) -> Any: creds = ctx.llm model = ChatOpenAI( model=creds.model, base_url=creds.base_url, api_key=creds.api_key, temperature=0.0, ) @tool def validate_citations(items_json: str) -> str: """Validate that each citation has title, url, and claim fields.""" items = json.loads(items_json) missing = [ i for i, item in enumerate(items) if not {"title", "url", "claim"} <= set(item) ] return json.dumps({"ok": not missing, "missing_indexes": missing}) backend = ctx.workspace_backend() skill_sources = seed_runtime_skills(backend) return create_deep_agent( model=model, backend=backend, skills=skill_sources or None, tools=[validate_citations], subagents=[ { "name": "source-reviewer", "description": "Reviews source quality and citation coverage.", "system_prompt": ( "Check whether sources support the claims. Return gaps " "and concrete fixes." ), "skills": skill_sources, } ], system_prompt=SYSTEM_PROMPT, ) def seed_runtime_skills(backend: Any) -> list[str]: root = Path(__file__).parent / "skills" if not root.exists(): return [] uploads: list[tuple[str, bytes]] = [] for path in root.rglob("*"): if path.is_file(): rel = path.relative_to(root).as_posix() uploads.append((RUNTIME_SKILLS_ROOT + rel, path.read_bytes())) if uploads: backend.upload_files(uploads) return [RUNTIME_SKILLS_ROOT] return [] ``` ## Choose The Right Primitive Use a DeepAgents skill when the agent needs reusable judgement, process, domain rules, examples, or a checklist. Put it under `skills//SKILL.md`. Use a subagent when a separate LLM pass can independently research, review, transform, or critique work. Custom subagents should receive their own `skills` field; do not assume they inherit every main-agent skill. Use a deterministic tool only for exact work: JSON validation, schema checks, filesystem transforms, API calls, calculations, sandbox commands, conversion commands, or database queries. Do not write tools whose body is prompt text, canned answers, or another hidden LLM call. That logic belongs in a DeepAgents skill or a subagent. ## Skill Files Each skill directory must contain `SKILL.md` with frontmatter: ```markdown --- name: market-research description: Run market research with source review, competitor mapping, and cited synthesis. Use for market questions, competitive analysis, or customer research. --- # Market Research Follow this workflow... ``` The `description` is the trigger text visible to the LLM before full skill loading. Keep it concrete. Put long examples or data dictionaries into `references/`, scripts into `scripts/`, and reusable assets into `assets/`. ## Backend Rules DeepAgents' default state backend is not enough for hosted agents because file outputs can disappear into graph state. Always pass `backend=ctx.workspace_backend()` for generated agents that read or write files. Use `skills=skill_sources or None`, not an empty list, so agents without a `skills/` directory still build cleanly. ## Invocation Budget Generated agents should use the same recursion budget as agent-builder: ```python state = await graph.ainvoke( {"messages": [{"role": "user", "content": prompt}]}, config={"recursion_limit": 500}, ) ``` For streaming/event loops, pass the same config to `graph.astream_events(...)`. This prevents normal multi-step skill/subagent workflows from failing at the default LangGraph recursion cap.