Lesson 03 — Raw Deep Agent
Instanciar un DeepAgent **crudo** — cero custom tools, cero dominio — y hacer que **falle explícitamente** en la primera pregunta. Vamos a leer las cuatro primitivas de DeepAgents mientras las vemos operar.
Starter prompt
Pegalo en tu coding agent (Claude Code, Cursor, Codex…) o usá
academy lesson prompt 3 --copy.
Rol: co-piloto Python, Lesson 03 del workshop.
Contexto: leé src/schemas.py (existe desde Lesson 02), .env.example (para OpenRouter),
y https://github.com/langchain-ai/deepagents (README).
Tarea:
1) Creá src/agent.py con:
- build_llm() -> BaseChatModel → configura langchain_openai.ChatOpenAI con
base_url="https://openrouter.ai/api/v1", api_key=env("OPENROUTER_API_KEY"),
model=env("OPENROUTER_MODEL", "anthropic/claude-haiku-4.5").
- build_agent(llm=None) → llama create_deep_agent(tools=[], instructions=..., model=llm)
con instructions cortas: "sos un analista de negocio para ACME Colombia. Si
no podés responder con evidencia, decilo explícito y reportá no_capability
con needed_skill en JSON. NO corras shell commands (no tenés esa capacidad
— es el coding agent quien opera el terminal)."
- write_receipt(event, payload) → append a .receipts/session.jsonl como Receipt validado.
2) Creá src/cli.py con typer:
- subcomando `ask` que toma una question: str
- construye el agent
- invoca agent.invoke({"messages": [{"role":"user","content": question}]})
- imprime la respuesta con rich.print
- si el agente no pudo responder, llama write_receipt("no_capability", {...})
3) Corré:
uv run python -m src.cli ask "¿cuánto facturó Café del Valle en Q3?"
Pegame el output y el contenido de .receipts/session.jsonl.
Reglas:
- mypy --strict verde.
- ./scripts/check.sh verde.
- No agregues tools custom todavía. El agente DEBE fallar en la pregunta de negocio.
- Escribí receipts usando el modelo Receipt de src/schemas.py.
Duración: 14 min · Prereq: Lesson 02 completa.
Challenge
Instanciar un DeepAgent crudo — cero custom tools, cero dominio — y hacer que falle explícitamente en la primera pregunta. Vamos a leer las cuatro primitivas de DeepAgents mientras las vemos operar.
Producto: src/agent.py + src/cli.py. Al correr uv run python -m src.cli ask "..." el agente responde algo, y .receipts/session.jsonl tiene al menos un evento no_capability.
Manual path
Referencia — src/agent.py
"""Raw DeepAgent — no custom tools, no domain knowledge."""
from __future__ import annotations
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from deepagents import create_deep_agent
from dotenv import load_dotenv
from langchain_core.language_models import BaseChatModel
from langchain_openai import ChatOpenAI
from src.schemas import Receipt
load_dotenv()
INSTRUCTIONS = """You are a business analyst assistant for ACME Colombia S.A.S.
You do NOT yet have any tools to query business data. If you cannot answer with
evidence, say so explicitly and do not fabricate numbers.
When you cannot answer, respond with a JSON block:
{"status": "no_capability", "reason": "<why>", "needed_skill": "<what would help>"}
## Scope
You are a domain agent (business analyst). You do NOT run shell commands, git
operations, or filesystem writes outside your own virtual FS. If the user asks
for any of those, respond with no_capability — that is the coding agent's job
(Claude Code, Pi, etc.), not yours. Ver `AGENTS.md` for the coding-agent policy.
"""
def build_llm() -> BaseChatModel:
api_key = os.environ["OPENROUTER_API_KEY"]
model = os.environ.get("OPENROUTER_MODEL", "anthropic/claude-haiku-4.5")
return ChatOpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=api_key, # type: ignore[arg-type]
model=model,
temperature=0.0,
)
def build_agent(llm: BaseChatModel | None = None) -> Any:
return create_deep_agent(
tools=[],
instructions=INSTRUCTIONS,
model=llm or build_llm(),
)
def write_receipt(event: str, payload: dict[str, Any]) -> Path:
receipts_dir = Path(os.environ.get("RECEIPTS_DIR", ".receipts"))
receipts_dir.mkdir(exist_ok=True)
path = receipts_dir / "session.jsonl"
receipt = Receipt(
ts=datetime.now(tz=timezone.utc),
actor="agent",
event=event,
payload=payload,
)
with path.open("a", encoding="utf-8") as f:
f.write(receipt.model_dump_json() + "\n")
return path
Referencia — src/cli.py
"""CLI entrypoint — one question at a time."""
from __future__ import annotations
import json
import typer
from rich import print as rprint
from src.agent import build_agent, write_receipt
app = typer.Typer(add_completion=False, no_args_is_help=True)
@app.command()
def ask(question: str) -> None:
"""Ask the raw DeepAgent a business question."""
agent = build_agent()
result = agent.invoke({"messages": [{"role": "user", "content": question}]})
reply = result["messages"][-1].content
rprint("[bold cyan]Agent:[/bold cyan]", reply)
if "no_capability" in reply:
try:
parsed = json.loads(reply[reply.index("{") : reply.rindex("}") + 1])
except (ValueError, json.JSONDecodeError):
parsed = {"raw_reply": reply}
path = write_receipt("no_capability", {"question": question, **parsed})
rprint(f"[dim]receipt → {path}[/dim]")
if __name__ == "__main__":
app()
Observe
Corriendo:
uv run python -m src.cli ask "¿cuánto facturó Café del Valle en Q3?"
Esperás algo como:
Agent: {"status": "no_capability", "reason": "no tools to query invoices data",
"needed_skill": "query_cognee"}
receipt → .receipts/session.jsonl
Y .receipts/session.jsonl con:
{"ts":"2026-...","actor":"agent","event":"no_capability","payload":{"question":"...", "reason":"...", "needed_skill":"query_cognee"}}
Discuss · ¿por qué el agente falló bien?
Pregunta para la sala: acabás de darle a un LLM cero herramientas y una pregunta que no puede responder. ¿Por qué respondió no_capability en vez de inventar un número?
Dos hipótesis:
- Hipótesis A · fue el system prompt. "Say so explicitly and do not fabricate numbers" es literalmente la instrucción. El LLM obedeció.
- Hipótesis B · fue el harness. DeepAgents impone estructura (planning tool, virtual FS, sub-agents, human-in-the-loop) que hace más difícil "improvisar" que "reportar honestamente".
La respuesta es "ambas" — pero la B es la que hace escalable el patrón. Un system prompt bueno se olvida a los 10k tokens; el harness sigue ahí siempre.
Aunque tu tools=[] esté vacío, DeepAgents ya usó estas 4 primitivas:
| Primitiva | Dónde apareció (o va a aparecer) |
|---|---|
Planning tool (write_todos) |
El LLM puede armar un TODO interno visible en result["messages"]. |
Virtual filesystem (write_file, read_file, ls) |
Aislado en el estado del agente. Lo explotamos en Lesson 04. |
| Sub-agents | Definimos skill_author en Lesson 04. |
Human-in-the-loop (interrupt) |
Lo usamos en Lesson 05 antes de cognee.cognify(). |
Reflexión: si sacaras estas primitivas y dejaras solo el LLM crudo con chat.completions.create, ¿cuánto de la disciplina que viste hoy sobreviviría?
Fade
En la próxima lección ya no te acompaño con "escribí una función Python". Asumo que sabés instanciar sub-agentes y leer/escribir el virtual FS.
🚀 Fastrack · saltear al checkpoint (⚠️ hace 1 llamada real a OpenRouter, ~USD 0.02)
Cinco pasos:
- Copiá
Referencia — src/agent.pyasrc/agent.py. - Copiá
Referencia — src/cli.pyasrc/cli.py. - Corré
academy check— verde. - Corré la primera invocación real:
bash uv run python -m src.cli ask "¿cuánto facturó Café del Valle en Q3?"Esperado: el agente respondeno_capability(no tiene tools). Se genera.receipts/session.jsonl. - Corré
academy validate 3.
Prompt one-shot (para tu coding agent):
Rol: implementador rápido, Lesson 03.
Leé workshop/deepagents-memory-skills/lessons/03-raw-deep-agent.md, aplicá
exactamente "Manual path — src/agent.py" y "Manual path — src/cli.py".
Corré ./scripts/check.sh. Confirmá verde. NO invoques el agente todavía —
el humano lo va a correr manualmente.
Trade-off honesto: te salteás el "aha" de ver al agente decir explícitamente "no puedo". Ese momento es la justificación de todo Lesson 04 — sin haberlo vivido, la lección siguiente pierde peso.
Siguiente → Lesson 04 — Skill generation
Validation prompt
Bash puro. Corré academy validate 3 para ejecutarlo, o pegalo en tu shell.
# 1) Existe agent + cli
test -f src/agent.py && test -f src/cli.py && echo "✅ agent + cli"
# 2) La rig sigue verde
./scripts/check.sh
# 3) El agente falla como esperamos
uv run python -m src.cli ask "¿cuánto facturó Café del Valle en Q3?"
# 4) Hay al menos un receipt no_capability
grep -q "no_capability" .receipts/session.jsonl && echo "✅ receipt no_capability presente"
Checkpoint
Marcá cada item cuando tengas evidencia. Se persiste en tu navegador (localStorage). No avanzás a la próxima lección hasta que estén todos verdes.