{% raw %}
# Building agents on CAR

Two copy-paste prompts that give an LLM (Claude / ChatGPT / Cursor) enough context
to produce a working CAR agent on the first try — one for a single agent, one for
a multi-agent system that learns from its own traces. Fill in the `TASK:` line and
paste the whole block. For the data shapes these reference, see [SPEC.md](./SPEC.md);
for runnable versions, see [`examples/`](./examples/).

## Build your first agent

Paste everything in the block below into an LLM and fill in the `TASK:` line.

````markdown
I want to build an AI agent using **Common Agent Runtime (CAR)**, a Rust-native
runtime where models propose actions and the runtime validates + executes them
deterministically.

TASK: <DESCRIBE WHAT YOU WANT THE AGENT TO DO — e.g. "read a directory of PDFs,
extract titles + abstracts, produce a JSON report">

Use the Python binding `car_runtime` (pip install car-runtime — import name is
`car_runtime`) unless I tell you otherwise. Keep everything in one file.

## What CAR gives you

- `CarRuntime()` — a stateful runtime. Exposes `state_*`, `add_fact`,
  `register_tool`, `register_policy`, `verify_proposal`, `execute_proposal`,
  `infer_tracked`, `infer_stream`, plus persistence + memory graph.
- **You write the tools.** Tools are Python functions dispatched by a callback.
  The runtime owns the DAG, state, policies, verification — not the tools.
- **Proposals are plans.** A proposal is JSON describing a list of actions and
  their dependencies. Verify before executing.

## Action / proposal shape

```json
{
  "actions": [
    { "id": "a1", "type": "tool_call", "tool": "read_file",
      "parameters": {"path": "/tmp/foo.txt"}, "dependencies": [] },
    { "id": "a2", "type": "tool_call", "tool": "summarize",
      "parameters": {"text_ref": "$a1.output"}, "dependencies": ["a1"] }
  ]
}
```

Valid `type` values: `tool_call`, `state_write`, `state_read`, `assertion`.

## Tool callback contract

```python
def tool_fn(call_json: str) -> str:
    call = json.loads(call_json)
    if call["tool"] == "read_file":
        return json.dumps({"content": open(call["params"]["path"]).read()})
    return json.dumps({"error": f"unknown tool: {call['tool']}"})
```

The callback takes ONE argument — a JSON string describing the whole call
(`{"tool", "params", "action_id", "request_id", "timeout_ms", "session_id",
"attempt"}`), not the tool name and params as two separate arguments.
Return a JSON string. Errors are just a `{"error": "..."}` payload — the runtime
handles retries + replans if configured.

## Skeleton to fill in

```python
import json
import car_runtime

def build_agent():
    rt = car_runtime.CarRuntime()

    # 1. Register the tools your agent will use.
    for tool_name in ["<TOOL_1>", "<TOOL_2>"]:
        rt.register_tool(tool_name)

    # 2. Add safety policies.
    rt.register_policy("no_rm", "deny_tool_param",
                       target="shell", key="command", pattern="rm -rf")

    # 3. Seed facts the agent should know (optional).
    rt.add_fact("goal", "<WHAT THE AGENT IS TRYING TO DO>", "pattern")

    # 4. Build a proposal (hand-written, or generated by infer_tracked).
    proposal = {"actions": [ ... ]}

    # 5. Verify first — cheap, catches bad plans before any tool runs.
    check = json.loads(rt.verify_proposal(json.dumps(proposal)))
    if not check["valid"]:
        raise SystemExit(f"invalid plan: {check['issues']}")

    # 6. Execute.
    def tool_fn(call_json):
        call = json.loads(call_json)          # {"tool", "params", ...}
        return json.dumps({"ok": True})       # IMPLEMENT ME

    result = json.loads(rt.execute_proposal(json.dumps(proposal), tool_fn))
    print(json.dumps(result, indent=2))

if __name__ == "__main__":
    build_agent()
```

## Model-driven proposals (optional)

If the plan itself should come from an LLM, use `infer_tracked`:

```python
out = json.loads(rt.infer_tracked(
    f"Propose a JSON action plan for: {task}. "
    f"Return ONLY a JSON object with an `actions` array.",
    max_tokens=2048,
))
proposal = json.loads(out["text"])
```

## Rules for the code you generate

- **No mocks.** Use real filesystem, real HTTP, real subprocess calls.
- **Verify before execute.** Every proposal goes through `verify_proposal`
  first — show the check in the output.
- **One file.** Put everything in a single runnable `.py`.
- **Fail loud.** Don't swallow errors. Raise `SystemExit` with a useful message.
- **Print the final result as JSON** so it's easy to diff / test.

Now write the agent for my TASK above.
````

**TypeScript version:** swap `car_runtime` (Python import) → `car-runtime` (npm);
`rt.register_tool` / `register_policy` → `await rt.registerTool` / `registerPolicy`;
`rt.execute_proposal(json, fn)` → `await executeProposal(rt, json, fn)`;
`rt.infer_tracked` → `await rt.inferTracked`.

## Build a multi-agent system

For a pipeline, swarm, or supervisor — or anything that should learn from its own
traces — paste this into an LLM with the `TASK:` line filled in.

````markdown
I want to build a multi-agent system using **Common Agent Runtime (CAR)** that
also learns skills from its own execution traces and evolves them over time.

TASK: <DESCRIBE WHAT THE SYSTEM SHOULD DO — e.g. "given a repo URL, use a scraper
agent to pull the README, a reviewer agent to identify the 3 biggest risks, and a
writer agent to draft an executive summary">

Use the Python binding `car_runtime` (pip install car-runtime — import name is
`car_runtime`). One file. No mocks.

## CAR's multi-agent building blocks

Five coordination patterns are exposed as standalone functions:

- `run_pipeline(stages_json, task, agent_fn)` — linear chain, each stage feeds
  the next. Staged refinement.
- `run_swarm(mode, agents_json, task, agent_fn, synthesizer_json=None)` — mode is
  `"parallel"`, `"sequential"`, or `"debate"`. Exploration / multi-perspective.
- `run_supervisor(workers_json, supervisor_json, task, max_rounds, agent_fn)` — a
  supervisor routes subtasks to workers over rounds. Long-horizon planning.
- `run_map_reduce(mapper_json, reducer_json, task, items_json, agent_fn)` — map
  items in parallel, reduce to one answer. Batch work.
- `run_vote(agents_json, task, agent_fn, synthesizer_json=None)` — parallel +
  voted/synthesized result. Higher-confidence answers.

Call `register_agent_runner(agent_fn)` once instead of passing `agent_fn` every
time; subsequent `run_*` calls use the stored callback.

## AgentSpec + AgentOutput shape

```python
spec = {
    "name": "reviewer",
    "system_prompt": "You review code for the 3 biggest risks.",
    "tools": ["grep", "read_file"],
    "max_turns": 5,
    "metadata": {"model": "claude-opus-4-8", "temperature": 0.3},
}
```

`agent_fn(spec_json, task)` is YOUR code. It MUST return a JSON string:

```python
{
    "name": spec["name"],
    "answer": "...final answer text...",
    "turns": 1,
    "tool_calls": 0,      # integer count — NOT an array
    "duration_ms": 100.0,
    "error": None,        # or a string if the agent failed
}
```

Produce `answer` however you like — Anthropic, OpenAI, local Qwen3 via
`rt.infer_tracked`, a deterministic tool chain.

## Learning loop: trace → distill → evolve

```python
trace = [
    {"kind": "action_succeeded", "action_id": "step1", "tool": "scraper",
     "data": {"task": task, "domain": "web"}, "reward": 1.0},
    {"kind": "action_failed", "action_id": "step2", "tool": "scraper",
     "data": {"task": task, "domain": "web"}, "reward": 0.0},
]

skills_json = rt.distill_skills(json.dumps(trace))   # requires inference
rt.ingest_distilled_skills(skills_json)

rt.report_outcome("scrape_and_summarize", "success")
rt.report_outcome("scrape_and_summarize", "fail")

weak = rt.domains_needing_evolution(threshold=0.6)
for domain in weak:
    rt.evolve_skills(json.dumps(trace), domain)      # requires inference

repaired = rt.repair_skill("scrape_and_summarize")
```

## Skeleton to fill in

```python
import json
import car_runtime

def main():
    rt = car_runtime.CarRuntime()

    def agent_fn(spec_json: str, task: str) -> str:
        spec = json.loads(spec_json)
        # CALL YOUR LLM HERE. Return the AgentOutput JSON shape.
        return json.dumps({
            "name": spec["name"], "answer": "IMPLEMENT ME",
            "turns": 1, "tool_calls": 0, "duration_ms": 1.0,
        })

    car_runtime.register_agent_runner(agent_fn)

    agents = json.dumps([
        {"name": "<AGENT_1>", "system_prompt": "<ROLE>",
         "tools": [], "max_turns": 5, "metadata": {"domain": "<DOMAIN>"}},
    ])

    result = json.loads(car_runtime.run_pipeline(agents, "<TASK>"))
    print(json.dumps(result, indent=2))

if __name__ == "__main__":
    main()
```

## Rules for the code you generate

- **Pick one coordination pattern** and justify it in a comment.
- **agent_fn does the LLM work.** Don't try to make CAR call an LLM directly.
- **AgentOutput shape is strict:** `name`, `answer`, `turns`, `tool_calls`
  (integer count — NOT an array), `duration_ms`, `error`. Missing fields break
  deserialization silently.
- **Synthesize traces from pipeline/swarm output** to feed the learning loop.
- **Report outcomes** (`rt.report_outcome`) as agents succeed/fail — that drives
  `domains_needing_evolution`.
- **Skip distill_skills / evolve_skills if no inference is configured** — they'll
  hang waiting for a model. Use hand-coded skills + `ingest_distilled_skills`.
- **One file. Print the final result as JSON.**

Now write the multi-agent system for my TASK above.
````

{% endraw %}
