car-runtime 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/SPEC.md ADDED
@@ -0,0 +1,169 @@
1
+ # CAR specification
2
+
3
+ The data shapes and semantics CAR guarantees across every binding (Python, Node,
4
+ Swift/Kotlin, the `car` CLI, and the `car-server` JSON-RPC protocol). These are
5
+ the contracts you code against; they're stable within a minor version (CAR is
6
+ pre-1.0 — see *Versioning*).
7
+
8
+ ## The model: propose → verify → execute
9
+
10
+ CAR treats a plan as first-class data. A **proposal** is a DAG of **actions**;
11
+ the runtime validates it, optionally verifies properties over it, enforces
12
+ policies, and then executes — dispatching each tool through *your* callback while
13
+ owning ordering, state, retries, and the audit trail.
14
+
15
+ ## Action / proposal shape
16
+
17
+ ```json
18
+ {
19
+ "actions": [
20
+ {
21
+ "id": "a1",
22
+ "type": "tool_call",
23
+ "tool": "read_file",
24
+ "parameters": { "path": "/tmp/foo.txt" },
25
+ "dependencies": []
26
+ },
27
+ {
28
+ "id": "a2",
29
+ "type": "tool_call",
30
+ "tool": "summarize",
31
+ "parameters": { "text_ref": "$a1.output" },
32
+ "dependencies": ["a1"]
33
+ }
34
+ ]
35
+ }
36
+ ```
37
+
38
+ - `id` — unique within the proposal; referenced by `dependencies` and by
39
+ `$<id>.output` interpolation.
40
+ - `type` — one of `tool_call`, `state_write`, `state_read`, `assertion`.
41
+ - `dependencies` — ids that must complete first. Actions with satisfied
42
+ dependencies run concurrently; the runtime owns the DAG scheduling.
43
+
44
+ The full IR (effect sets, read/write sets, cost metadata, invocation modes) is
45
+ documented in the source repo's `docs/agent-ir-spec.md`; the shape above is the
46
+ minimum every binding accepts.
47
+
48
+ ## Tool callback contract
49
+
50
+ You register a tool name and provide a dispatch callback; CAR never owns tool
51
+ implementations.
52
+
53
+ ```python
54
+ def tool_fn(call_json: str) -> str:
55
+ call = json.loads(call_json)
56
+ if call["tool"] == "read_file":
57
+ return json.dumps({"content": open(call["params"]["path"]).read()})
58
+ return json.dumps({"error": f"unknown tool: {call['tool']}"})
59
+ ```
60
+
61
+ - Input: ONE JSON string describing the whole call — `{"tool", "params",
62
+ "action_id", "request_id", "timeout_ms", "session_id", "attempt"}`. Not the
63
+ name and params as two arguments.
64
+ - Output: a JSON string. An error is just a `{"error": "..."}` payload — the
65
+ runtime handles retries / replans if configured.
66
+ - In Node the callback is async and returns a `Promise<string>`; in
67
+ `car-server` it's a bidirectional JSON-RPC callback.
68
+
69
+ ## Policies
70
+
71
+ Policies are enforced in **Rust, before any tool fires** — they can't be skipped
72
+ by the model or the tool code. Register them by kind:
73
+
74
+ | Kind | Effect |
75
+ |------|--------|
76
+ | `deny_tool` | Block a tool entirely. |
77
+ | `deny_tool_param` | Block a tool when a parameter matches a pattern (e.g. `shell` where `command` contains `rm -rf`). |
78
+ | `require_state` | Require a state key/precondition before an action runs. |
79
+
80
+ ```python
81
+ rt.register_policy("no_rm", "deny_tool_param",
82
+ target="shell", key="command", pattern="rm -rf")
83
+ ```
84
+
85
+ Beyond static policies, CAR classifies each action into a **risk tier**
86
+ (`read_only` / `sandbox_edit` / `full_access`) and gates it against a granted
87
+ standing tier with human-in-the-loop approval; per-agent postures
88
+ (always-allow / require-approval / deny) refine this per agent. Approvals are
89
+ recorded to a durable ledger.
90
+
91
+ ## Static verification
92
+
93
+ Before executing, you can check properties over a proposal — these are pure,
94
+ side-effect-free, and run in milliseconds. They are static analysis, not
95
+ solver-backed proof. The forward walk models only the effects an action
96
+ *declares* in `expected_effects`, so a declared effect is assumed to land and an
97
+ undeclared one is invisible:
98
+
99
+ - **`verify`** — structural + semantic validity: dependencies resolve,
100
+ preconditions are establishable against the supplied state, tools exist, and
101
+ tool-call parameters match the registered schema. Returns `{ valid, issues }`.
102
+ Write conflicts are reported as *warnings* — they do not make a proposal
103
+ invalid. Policy is enforced separately by `car-policy`, not here.
104
+ - **`simulate`** — forward-simulate the state transitions the plan would produce,
105
+ without running any tool.
106
+ - **`equivalent`** — spot-check that two proposals produce the same effects over
107
+ a set of test states (two trivial defaults unless you supply your own). Not a
108
+ proof of equivalence.
109
+ - **`optimize`** — prune phantom `state_dependencies` so independent actions can
110
+ run in the same DAG level. It does not reorder or remove actions.
111
+
112
+ "Deterministic" means: given the same proposal, policies, and tool outputs, the
113
+ runtime's ordering, validation, and state transitions are reproducible — the
114
+ non-determinism is confined to your tool implementations and the model.
115
+
116
+ ## Multi-agent shapes
117
+
118
+ Five coordination patterns are exposed as standalone functions:
119
+ `run_pipeline`, `run_swarm` (`parallel` / `sequential` / `debate`),
120
+ `run_supervisor`, `run_map_reduce`, `run_vote`. Each takes your agent callback.
121
+
122
+ **AgentSpec** (what you pass in):
123
+
124
+ ```python
125
+ {
126
+ "name": "reviewer",
127
+ "system_prompt": "You review code for the 3 biggest risks.",
128
+ "tools": ["grep", "read_file"],
129
+ "max_turns": 5,
130
+ "metadata": { "model": "claude-opus-4-8", "temperature": 0.3 }
131
+ }
132
+ ```
133
+
134
+ **AgentOutput** (what your `agent_fn(spec_json, task)` MUST return, as a JSON
135
+ string — the shape is strict; missing fields fail deserialization):
136
+
137
+ ```python
138
+ {
139
+ "name": "reviewer",
140
+ "answer": "...final answer text...",
141
+ "turns": 1,
142
+ "tool_calls": 0, # integer count, NOT an array
143
+ "duration_ms": 100.0,
144
+ "error": None # or a string if the agent failed
145
+ }
146
+ ```
147
+
148
+ The runtime doesn't care how you produce `answer` (Anthropic, OpenAI, local
149
+ Qwen3 via `infer_tracked`, a deterministic tool chain — anything).
150
+
151
+ ## Skills / learning loop
152
+
153
+ `TraceEvent` records (`action_succeeded` / `action_failed` with `tool`, `data`,
154
+ `reward`) feed `distill_skills` → `ingest_distilled_skills`; per-skill outcomes
155
+ (`report_outcome`) drive `domains_needing_evolution`, `evolve_skills`, and
156
+ `repair_skill`. A skill auto-degrades when `fail_count > success_count + 2`. Full
157
+ worked example: [GUIDE.md](./GUIDE.md) and `examples/python/multi_agent.py`.
158
+
159
+ ## Conformance
160
+
161
+ Execution semantics are portable: the **RuntimeBench** suite (`car-conformance`)
162
+ verifies every runtime capability against this spec, so an implementation either
163
+ passes the conformance tests or it isn't CAR-compatible.
164
+
165
+ ## Versioning
166
+
167
+ CAR is pre-1.0. Breaking changes between minor versions are possible — **pin to
168
+ exact versions** until the API stabilizes. Each release lists breaking changes in
169
+ its GitHub release notes and in `CHANGELOG.md`.