car-runtime 0.51.0 → 0.52.1

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/GUIDE.md ADDED
@@ -0,0 +1,266 @@
1
+ # Building agents on CAR
2
+
3
+ Two copy-paste prompts that give an LLM (Claude / ChatGPT / Cursor) enough context
4
+ to produce a working CAR agent on the first try — one for a single agent, one for
5
+ a multi-agent system that learns from its own traces. Fill in the `TASK:` line and
6
+ paste the whole block. For the data shapes these reference, see [SPEC.md](./SPEC.md);
7
+ for runnable versions, see [`examples/`](./examples/).
8
+
9
+ ## Build your first agent
10
+
11
+ Paste everything in the block below into an LLM and fill in the `TASK:` line.
12
+
13
+ ````markdown
14
+ I want to build an AI agent using **Common Agent Runtime (CAR)**, a Rust-native
15
+ runtime where models propose actions and the runtime validates + executes them
16
+ deterministically.
17
+
18
+ TASK: <DESCRIBE WHAT YOU WANT THE AGENT TO DO — e.g. "read a directory of PDFs,
19
+ extract titles + abstracts, produce a JSON report">
20
+
21
+ Use the Python binding `car_runtime` (pip install car-runtime — import name is
22
+ `car_runtime`) unless I tell you otherwise. Keep everything in one file.
23
+
24
+ ## What CAR gives you
25
+
26
+ - `CarRuntime()` — a stateful runtime. Exposes `state_*`, `add_fact`,
27
+ `register_tool`, `register_policy`, `verify_proposal`, `execute_proposal`,
28
+ `infer_tracked`, `infer_stream`, plus persistence + memory graph.
29
+ - **You write the tools.** Tools are Python functions dispatched by a callback.
30
+ The runtime owns the DAG, state, policies, verification — not the tools.
31
+ - **Proposals are plans.** A proposal is JSON describing a list of actions and
32
+ their dependencies. Verify before executing.
33
+
34
+ ## Action / proposal shape
35
+
36
+ ```json
37
+ {
38
+ "actions": [
39
+ { "id": "a1", "type": "tool_call", "tool": "read_file",
40
+ "parameters": {"path": "/tmp/foo.txt"}, "dependencies": [] },
41
+ { "id": "a2", "type": "tool_call", "tool": "summarize",
42
+ "parameters": {"text_ref": "$a1.output"}, "dependencies": ["a1"] }
43
+ ]
44
+ }
45
+ ```
46
+
47
+ Valid `type` values: `tool_call`, `state_write`, `state_read`, `assertion`.
48
+
49
+ ## Tool callback contract
50
+
51
+ ```python
52
+ def tool_fn(call_json: str) -> str:
53
+ call = json.loads(call_json)
54
+ if call["tool"] == "read_file":
55
+ return json.dumps({"content": open(call["params"]["path"]).read()})
56
+ return json.dumps({"error": f"unknown tool: {call['tool']}"})
57
+ ```
58
+
59
+ The callback takes ONE argument — a JSON string describing the whole call
60
+ (`{"tool", "params", "action_id", "request_id", "timeout_ms", "session_id",
61
+ "attempt"}`), not the tool name and params as two separate arguments.
62
+ Return a JSON string. Errors are just a `{"error": "..."}` payload — the runtime
63
+ handles retries + replans if configured.
64
+
65
+ ## Skeleton to fill in
66
+
67
+ ```python
68
+ import json
69
+ import car_runtime
70
+
71
+ def build_agent():
72
+ rt = car_runtime.CarRuntime()
73
+
74
+ # 1. Register the tools your agent will use.
75
+ for tool_name in ["<TOOL_1>", "<TOOL_2>"]:
76
+ rt.register_tool(tool_name)
77
+
78
+ # 2. Add safety policies.
79
+ rt.register_policy("no_rm", "deny_tool_param",
80
+ target="shell", key="command", pattern="rm -rf")
81
+
82
+ # 3. Seed facts the agent should know (optional).
83
+ rt.add_fact("goal", "<WHAT THE AGENT IS TRYING TO DO>", "pattern")
84
+
85
+ # 4. Build a proposal (hand-written, or generated by infer_tracked).
86
+ proposal = {"actions": [ ... ]}
87
+
88
+ # 5. Verify first — cheap, catches bad plans before any tool runs.
89
+ check = json.loads(rt.verify_proposal(json.dumps(proposal)))
90
+ if not check["valid"]:
91
+ raise SystemExit(f"invalid plan: {check['issues']}")
92
+
93
+ # 6. Execute.
94
+ def tool_fn(call_json):
95
+ call = json.loads(call_json) # {"tool", "params", ...}
96
+ return json.dumps({"ok": True}) # IMPLEMENT ME
97
+
98
+ result = json.loads(rt.execute_proposal(json.dumps(proposal), tool_fn))
99
+ print(json.dumps(result, indent=2))
100
+
101
+ if __name__ == "__main__":
102
+ build_agent()
103
+ ```
104
+
105
+ ## Model-driven proposals (optional)
106
+
107
+ If the plan itself should come from an LLM, use `infer_tracked`:
108
+
109
+ ```python
110
+ out = json.loads(rt.infer_tracked(
111
+ f"Propose a JSON action plan for: {task}. "
112
+ f"Return ONLY a JSON object with an `actions` array.",
113
+ max_tokens=2048,
114
+ ))
115
+ proposal = json.loads(out["text"])
116
+ ```
117
+
118
+ ## Rules for the code you generate
119
+
120
+ - **No mocks.** Use real filesystem, real HTTP, real subprocess calls.
121
+ - **Verify before execute.** Every proposal goes through `verify_proposal`
122
+ first — show the check in the output.
123
+ - **One file.** Put everything in a single runnable `.py`.
124
+ - **Fail loud.** Don't swallow errors. Raise `SystemExit` with a useful message.
125
+ - **Print the final result as JSON** so it's easy to diff / test.
126
+
127
+ Now write the agent for my TASK above.
128
+ ````
129
+
130
+ **TypeScript version:** swap `car_runtime` (Python import) → `car-runtime` (npm);
131
+ `rt.register_tool` / `register_policy` → `await rt.registerTool` / `registerPolicy`;
132
+ `rt.execute_proposal(json, fn)` → `await executeProposal(rt, json, fn)`;
133
+ `rt.infer_tracked` → `await rt.inferTracked`.
134
+
135
+ ## Build a multi-agent system
136
+
137
+ For a pipeline, swarm, or supervisor — or anything that should learn from its own
138
+ traces — paste this into an LLM with the `TASK:` line filled in.
139
+
140
+ ````markdown
141
+ I want to build a multi-agent system using **Common Agent Runtime (CAR)** that
142
+ also learns skills from its own execution traces and evolves them over time.
143
+
144
+ TASK: <DESCRIBE WHAT THE SYSTEM SHOULD DO — e.g. "given a repo URL, use a scraper
145
+ agent to pull the README, a reviewer agent to identify the 3 biggest risks, and a
146
+ writer agent to draft an executive summary">
147
+
148
+ Use the Python binding `car_runtime` (pip install car-runtime — import name is
149
+ `car_runtime`). One file. No mocks.
150
+
151
+ ## CAR's multi-agent building blocks
152
+
153
+ Five coordination patterns are exposed as standalone functions:
154
+
155
+ - `run_pipeline(stages_json, task, agent_fn)` — linear chain, each stage feeds
156
+ the next. Staged refinement.
157
+ - `run_swarm(mode, agents_json, task, agent_fn, synthesizer_json=None)` — mode is
158
+ `"parallel"`, `"sequential"`, or `"debate"`. Exploration / multi-perspective.
159
+ - `run_supervisor(workers_json, supervisor_json, task, max_rounds, agent_fn)` — a
160
+ supervisor routes subtasks to workers over rounds. Long-horizon planning.
161
+ - `run_map_reduce(mapper_json, reducer_json, task, items_json, agent_fn)` — map
162
+ items in parallel, reduce to one answer. Batch work.
163
+ - `run_vote(agents_json, task, agent_fn, synthesizer_json=None)` — parallel +
164
+ voted/synthesized result. Higher-confidence answers.
165
+
166
+ Call `register_agent_runner(agent_fn)` once instead of passing `agent_fn` every
167
+ time; subsequent `run_*` calls use the stored callback.
168
+
169
+ ## AgentSpec + AgentOutput shape
170
+
171
+ ```python
172
+ spec = {
173
+ "name": "reviewer",
174
+ "system_prompt": "You review code for the 3 biggest risks.",
175
+ "tools": ["grep", "read_file"],
176
+ "max_turns": 5,
177
+ "metadata": {"model": "claude-opus-4-8", "temperature": 0.3},
178
+ }
179
+ ```
180
+
181
+ `agent_fn(spec_json, task)` is YOUR code. It MUST return a JSON string:
182
+
183
+ ```python
184
+ {
185
+ "name": spec["name"],
186
+ "answer": "...final answer text...",
187
+ "turns": 1,
188
+ "tool_calls": 0, # integer count — NOT an array
189
+ "duration_ms": 100.0,
190
+ "error": None, # or a string if the agent failed
191
+ }
192
+ ```
193
+
194
+ Produce `answer` however you like — Anthropic, OpenAI, local Qwen3 via
195
+ `rt.infer_tracked`, a deterministic tool chain.
196
+
197
+ ## Learning loop: trace → distill → evolve
198
+
199
+ ```python
200
+ trace = [
201
+ {"kind": "action_succeeded", "action_id": "step1", "tool": "scraper",
202
+ "data": {"task": task, "domain": "web"}, "reward": 1.0},
203
+ {"kind": "action_failed", "action_id": "step2", "tool": "scraper",
204
+ "data": {"task": task, "domain": "web"}, "reward": 0.0},
205
+ ]
206
+
207
+ skills_json = rt.distill_skills(json.dumps(trace)) # requires inference
208
+ rt.ingest_distilled_skills(skills_json)
209
+
210
+ rt.report_outcome("scrape_and_summarize", "success")
211
+ rt.report_outcome("scrape_and_summarize", "fail")
212
+
213
+ weak = rt.domains_needing_evolution(threshold=0.6)
214
+ for domain in weak:
215
+ rt.evolve_skills(json.dumps(trace), domain) # requires inference
216
+
217
+ repaired = rt.repair_skill("scrape_and_summarize")
218
+ ```
219
+
220
+ ## Skeleton to fill in
221
+
222
+ ```python
223
+ import json
224
+ import car_runtime
225
+
226
+ def main():
227
+ rt = car_runtime.CarRuntime()
228
+
229
+ def agent_fn(spec_json: str, task: str) -> str:
230
+ spec = json.loads(spec_json)
231
+ # CALL YOUR LLM HERE. Return the AgentOutput JSON shape.
232
+ return json.dumps({
233
+ "name": spec["name"], "answer": "IMPLEMENT ME",
234
+ "turns": 1, "tool_calls": 0, "duration_ms": 1.0,
235
+ })
236
+
237
+ car_runtime.register_agent_runner(agent_fn)
238
+
239
+ agents = json.dumps([
240
+ {"name": "<AGENT_1>", "system_prompt": "<ROLE>",
241
+ "tools": [], "max_turns": 5, "metadata": {"domain": "<DOMAIN>"}},
242
+ ])
243
+
244
+ result = json.loads(car_runtime.run_pipeline(agents, "<TASK>"))
245
+ print(json.dumps(result, indent=2))
246
+
247
+ if __name__ == "__main__":
248
+ main()
249
+ ```
250
+
251
+ ## Rules for the code you generate
252
+
253
+ - **Pick one coordination pattern** and justify it in a comment.
254
+ - **agent_fn does the LLM work.** Don't try to make CAR call an LLM directly.
255
+ - **AgentOutput shape is strict:** `name`, `answer`, `turns`, `tool_calls`
256
+ (integer count — NOT an array), `duration_ms`, `error`. Missing fields break
257
+ deserialization silently.
258
+ - **Synthesize traces from pipeline/swarm output** to feed the learning loop.
259
+ - **Report outcomes** (`rt.report_outcome`) as agents succeed/fail — that drives
260
+ `domains_needing_evolution`.
261
+ - **Skip distill_skills / evolve_skills if no inference is configured** — they'll
262
+ hang waiting for a model. Use hand-coded skills + `ingest_distilled_skills`.
263
+ - **One file. Print the final result as JSON.**
264
+
265
+ Now write the multi-agent system for my TASK above.
266
+ ````
package/docs/MCP.md ADDED
@@ -0,0 +1,382 @@
1
+ # CAR as an MCP server
2
+
3
+ > Describes CAR **{{VERSION}}**. Check yours with `car --version`; if it
4
+ > differs, prefer `car help <command>` on your own binary over this page.
5
+
6
+ Wire CAR's graph memory, skill storage, static plan verification, and policy
7
+ enforcement into Claude Code, Cursor, Claude Desktop, or any other
8
+ [MCP](https://modelcontextprotocol.io)-aware host as native tools. This is the
9
+ most direct way for an external coding agent to use CAR — no SDK, no runtime
10
+ embedding, just a binary on `PATH` and a few lines of client config.
11
+
12
+ Everything below is read from `car-rs/crates/car-mcp/src/schemas.rs` and
13
+ `server.rs`, and from `plugins/car/` — the actual source, not the protocol
14
+ aspiration. Where the two transports differ, or where a tool is weaker than
15
+ its name suggests, that's called out rather than smoothed over.
16
+
17
+ ## Two transports, two capability sets
18
+
19
+ CAR speaks MCP over **stdio** (`car-mcp` binary — one client per process) and
20
+ over **HTTP-streamable** (the `car-server` daemon's `/mcp` endpoint, default
21
+ `http://127.0.0.1:9102/mcp`). Same dispatch logic, same tool schemas — but they
22
+ are not equivalent, and the difference matters more than the transport choice:
23
+
24
+ | | stdio (`car-mcp`) | daemon (`/mcp`) |
25
+ |---|---|---|
26
+ | Tools advertised | 16 built-ins | 16 built-ins **+ 3** (`assistant_start`, `assistant_poll`, `assistant_cancel`) |
27
+ | Memory backing | Its own process, opens `<CAR_HOME>/memory/assistant.json` at startup | The daemon's live, already-running memgine — shared with everything else the daemon does (WS clients, `car do`) |
28
+ | Requires the daemon running? | No | Yes |
29
+ | Concurrent clients sharing state | No — one process per client | Yes |
30
+
31
+ Point a client at the running daemon instead of launching `car-mcp` and you
32
+ also get `assistant_start` / `assistant_poll` / `assistant_cancel` — a
33
+ poll-based handle onto CAR's flagship agent (the one behind `car do`), because
34
+ the daemon has the `Runtime` and inference engine to run it and the stdio
35
+ binary does not. `car-mcp-server` is `car-mcp` plus telemetry, nothing more;
36
+ it answers those three tool names with "unknown tool" rather than pretending
37
+ to run them. On stdio, delegate to the agent with `car do --json` directly
38
+ instead.
39
+
40
+ The daemon's `/mcp` endpoint validates the `Origin` header (present +
41
+ non-loopback → `403`) as its only access control — no bearer token. It does
42
+ not widen for a non-default `--mcp-bind`; front it with a reverse proxy if you
43
+ need to expose it beyond loopback.
44
+
45
+ ## What this does NOT give you
46
+
47
+ - **No proposal execution.** `verify`, `simulate`, `equivalent`, and
48
+ `optimize` all read an `ActionProposal` and run nothing — they check or
49
+ predict, they never execute a tool. Actually running a proposal needs
50
+ bidirectional tool callbacks, which MCP doesn't support cleanly; that's the
51
+ WebSocket protocol's job (`docs/websocket-protocol.md`).
52
+ - **No multi-agent patterns** (swarm, pipeline, supervisor) over MCP.
53
+ - **Most memory writes over stdio don't survive the process exiting.** See
54
+ below — only `memory_add_fact` is durable when you're talking to `car-mcp`
55
+ directly rather than the daemon.
56
+ - **The four verification tools are checks and predictions, not proofs.**
57
+ `verify`'s findings carry a `tier` (`decision_procedure` | `heuristic` |
58
+ `sampled`) precisely so a caller can tell an exact check from a rule of
59
+ thumb; `equivalent` samples two default states unless you supply your own
60
+ and a `true` only means none of the probed states diverged, not that none
61
+ ever would.
62
+
63
+ ## Memory durability — read this before relying on it
64
+
65
+ The stdio binary (`car-mcp`) opens `<CAR_HOME>/memory/assistant.json` — the
66
+ **same** durable note store `car do` reads and writes — at startup, and
67
+ `memory_query` sees whatever is already in it. But only **`memory_add_fact`**
68
+ appends back to that file. `memory_save_knowledge`, `memory_save_procedural`,
69
+ `memory_delete`, `memory_intervene`, and `skill_ingest` all operate on the
70
+ in-process graph only; the on-disk note format has no schema for them yet, so
71
+ anything written through those five tools is gone when the client
72
+ disconnects. There's also no lock on the store — the server re-reads
73
+ immediately before appending, which narrows but doesn't close a two-writer
74
+ race window.
75
+
76
+ The daemon's `/mcp` endpoint doesn't have this asymmetry: it shares the
77
+ daemon's one live `MemgineEngine`, and the daemon owns persisting it — a
78
+ second writer to the same file would just drop one side's appends, which is
79
+ exactly why the stdio binary and the daemon never both hold `store: Some(path)`
80
+ at once.
81
+
82
+ Relocate the store with `CAR_HOME` — every other daemon state path moves with
83
+ it, so an editor plugin that wants an isolated memory sets that one variable.
84
+
85
+ ## The tool list — 16 tools, verified against source
86
+
87
+ `memory_add_fact`, `memory_query`, `memory_update_status`,
88
+ `memory_save_knowledge`, `memory_save_procedural`, `memory_delete`,
89
+ `memory_intervene`, `memory_evaluate`, `skill_ingest`, `skill_list`,
90
+ `skill_find`, `verify`, `simulate`, `equivalent`, `optimize`, `policy_check` —
91
+ this is the complete, alphabetically-sorted list the crate's own test suite
92
+ asserts against (`server.rs`, `tool_names` test). There is also one MCP
93
+ **prompt** (not a tool), `car_context`.
94
+
95
+ Every entry carries all four MCP tool annotations
96
+ (`readOnlyHint`/`destructiveHint`/`idempotentHint`/`openWorldHint`) so a host
97
+ can auto-approve a read without prompting for a write — these are host UX
98
+ hints per the MCP spec, not a security boundary; CAR's own gate is
99
+ `policy_check` plus `.car/policies/`, and nothing in CAR's governance keys off
100
+ them.
101
+
102
+ ### Memory (8 tools)
103
+
104
+ | Tool | Read-only | Required params | Notes |
105
+ |---|---|---|---|
106
+ | `memory_add_fact` | no | `subject`, `body` | `kind`: `"pattern"` (default) or `"constraint"`. The one memory write durable over stdio. |
107
+ | `memory_query` | **yes** | `query` | `k` (1–50) caps results. Spreading-activation retrieval, returns nodes + activation scores. |
108
+ | `memory_update_status` | no | `body` | Session-local progress/risk status; a map slot, so a second call overwrites the first. `tenant_id` optional. |
109
+ | `memory_save_knowledge` | no | `subject`, `body` | Durable *proactive* knowledge (requirements, policies, verified environment facts). Additive — a repeat `id` mints `<id>-2` rather than overwriting. In-process only over stdio (see durability section). |
110
+ | `memory_save_procedural` | no | `subject`, `body` | Same shape as above, for procedural evidence (failed attempts, fixes, gotchas). Same in-process-only caveat. |
111
+ | `memory_delete` | no | `id` | Deletes a fact by id, or clears a status id like `proactive-status:global`. In-process only over stdio. |
112
+ | `memory_intervene` | no | *(none required)* | Selects at most one proactive reminder for the next action, or an explicit silent decision. Reads like a query but bumps the chosen fact's injection counter — not idempotent. |
113
+ | `memory_evaluate` | **yes** | `cases` (array of `{id, request, relevant_fact_ids?}`) | Evaluates proactive memory against selective / always-inject / passive-retrieval / no-memory baselines on labeled cases. |
114
+
115
+ `memory_save_knowledge` / `memory_save_procedural` share this input shape:
116
+
117
+ ```jsonc
118
+ {
119
+ "id": "string", // optional
120
+ "subject": "string", // required
121
+ "body": "string", // required
122
+ "tags": ["string"],
123
+ "confidence": "string",
124
+ "tenant_id": "string",
125
+ "is_constraint": false
126
+ }
127
+ ```
128
+
129
+ `memory_intervene` / `memory_evaluate`'s `request` shape:
130
+
131
+ ```jsonc
132
+ {
133
+ "query": "string",
134
+ "recent": ["string"],
135
+ "trigger": {
136
+ "repeated_failures": 0,
137
+ "tool_error": false,
138
+ "explicit_uncertainty": false,
139
+ "high_risk_action": false,
140
+ "context_shift": false
141
+ },
142
+ "force": false,
143
+ "max_candidates": 8, // 1-32
144
+ "tenant_id": "string"
145
+ }
146
+ ```
147
+
148
+ ### Skills (3 tools)
149
+
150
+ | Tool | Read-only | Required params | Notes |
151
+ |---|---|---|---|
152
+ | `skill_ingest` | no | `name`, `code` | Also takes `platform`, `persona`, `url_pattern`, `description`, `task_keywords`, `supersedes`. Marked *destructive*: naming an existing skill in `supersedes` flips it to deprecated. In-process only over stdio. |
153
+ | `skill_list` | **yes** | *(none)* | Optional `domain` filter — returns skills scoped Global or that Domain. |
154
+ | `skill_find` | **yes** | `task` | Optional `persona`, `url`, `k` (1–20). Top-k skills ranked by activation. |
155
+
156
+ ### Static verification (4 tools) — all stateless, no memory, no daemon
157
+
158
+ Each of these reads a `car_ir::ActionProposal` JSON object and runs nothing.
159
+
160
+ | Tool | Required params | What it answers |
161
+ |---|---|---|
162
+ | `verify` | `proposal` (+ optional `max_actions`, 1–1000) | Findings: dependency cycles, missing tools, simulated final state. Each issue has a `tier`. |
163
+ | `simulate` | `proposal` (+ optional `initial_state`) | The state an executor would be left holding, from *declared* `expected_effects` only — a declared effect is assumed to land, `failure_behavior` is not modelled. |
164
+ | `equivalent` | `proposal_a`, `proposal_b` (+ optional `test_states`, 1–256 items) | Whether two proposals leave the same state behind — **sampled**, not proven. Two trivial default states if you omit `test_states`; passing `[]` is treated as omitted, not a zero-probe `true`. |
165
+ | `optimize` | `proposal` | Rewrites the proposal to widen parallelism by dropping `state_dependency` entries naming keys nothing in the proposal writes. Returns `pruned` per action. Re-run `verify` on the result — a pruned dependency is one `verify` would flag as unavailable. |
166
+
167
+ ### Governance (1 tool) — also stateless
168
+
169
+ | Tool | Required params | What it answers |
170
+ |---|---|---|
171
+ | `policy_check` | `tool` (+ optional `params`) | Evaluates a proposed tool call against CAR's policy layer *before* it runs: allow/deny plus the rule that decided it. Merges `<CAR_HOME>/policies/` + `.car/policies/` (under the working directory — **neither is walked upward**) with CAR's stateless egress guardrail. |
172
+
173
+ Read `basis`, not just `decision`: `no_rules_configured` means nothing was
174
+ loaded — an allow with nothing behind it — versus `passed_rules`, real rules
175
+ that the call cleared. `policy_load_failed` denies rather than failing open on
176
+ an unparseable file. This is exactly what backs the Claude Code plugin's
177
+ `PreToolUse` hook below.
178
+
179
+ ### The prompt: `car_context`
180
+
181
+ Not a tool — an MCP *prompt*. Assembles CAR's four-layer context (identity →
182
+ constraints → facts → conversation → environment → known-unknowns) for a
183
+ query and returns it as one user message a host can prepend to its own
184
+ prompt. Arguments: `query` (required), `mode` — `"full"` (default) or
185
+ `"fast"` (skips embedding flush, skill lookup, PPR scoring).
186
+
187
+ ## Getting the binary
188
+
189
+ `car-mcp` (package name `car-mcp-server`) **ships in every release archive
190
+ today** — `.github/workflows/build.yml` builds it (`cargo build ... -p
191
+ car-mcp-server`) and sweeps the resulting `car-mcp` binary into every
192
+ per-platform tarball/zip alongside `car`, `car-server`, and
193
+ `car-memgine-eval`. It is not a separate download or a standalone release
194
+ asset; it comes from wherever you already get the CAR CLI:
195
+
196
+ ```bash
197
+ # install script (macOS + Linux) — the recommended CLI install today
198
+ curl -fsSL https://raw.githubusercontent.com/Parslee-ai/car-releases/main/install.sh | sh
199
+
200
+ # There is no Homebrew path — it was removed in May 2026 when the signed
201
+ # .pkg + Sparkle became the macOS install.
202
+
203
+ # manual tarball
204
+ curl -sL https://github.com/Parslee-ai/car-releases/releases/latest/download/car-darwin-arm64.tar.gz | tar -xz
205
+ ```
206
+
207
+ Confirm it's on `PATH`:
208
+
209
+ ```bash
210
+ which car-mcp
211
+ ```
212
+
213
+ `car-mcp` takes no CLI arguments — it starts the stdio JSON-RPC loop
214
+ immediately and blocks reading stdin, so don't run it bare to "check" it
215
+ (there's no `--help`/`--version` to print; a bare invocation just hangs until
216
+ stdin closes). Let your MCP client launch it.
217
+
218
+ See [DISTRIBUTION.md](./DISTRIBUTION.md) for every platform/package manager.
219
+
220
+ ## Client configuration
221
+
222
+ ### Claude Code — bare `.mcp.json`
223
+
224
+ Project- or user-scoped MCP config, independent of the plugin below:
225
+
226
+ ```json
227
+ {
228
+ "mcpServers": {
229
+ "car": {
230
+ "command": "car-mcp"
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Cursor
237
+
238
+ `~/.cursor/mcp.json`:
239
+
240
+ ```json
241
+ {
242
+ "mcpServers": {
243
+ "car": {
244
+ "command": "car-mcp"
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ ### Claude Desktop
251
+
252
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
253
+ (macOS):
254
+
255
+ ```json
256
+ {
257
+ "mcpServers": {
258
+ "car": {
259
+ "command": "car-mcp"
260
+ }
261
+ }
262
+ }
263
+ ```
264
+
265
+ If `car-mcp` isn't on the config-file reader's `PATH` (a common GUI-app
266
+ gotcha), use the absolute path from `which car-mcp` instead of the bare name.
267
+ Restart the client after editing — the CAR tools then appear in the tool list
268
+ automatically.
269
+
270
+ ### Pointing any of the above at the daemon instead
271
+
272
+ To get the 3 extra assistant tools, point the client at the running daemon's
273
+ HTTP endpoint (`http://127.0.0.1:9102/mcp` by default, `--mcp-bind` /
274
+ `CAR_MCP_BIND` to change it, `disabled` to turn it off) rather than launching
275
+ `car-mcp` per-client. The exact MCP-over-HTTP config stanza is client-specific
276
+ — consult that client's docs for "remote"/"HTTP" MCP servers.
277
+
278
+ ### Isolating memory per client
279
+
280
+ ```json
281
+ {
282
+ "mcpServers": {
283
+ "car": {
284
+ "command": "car-mcp",
285
+ "env": { "CAR_HOME": "/path/to/an/isolated/state/root" }
286
+ }
287
+ }
288
+ }
289
+ ```
290
+
291
+ One variable relocates every CAR state path together — journals,
292
+ `agents.json`, the memory store, policies — so the isolated instance is
293
+ coherent rather than half-moved.
294
+
295
+ ## The Claude Code plugin (`plugins/car/`)
296
+
297
+ Beyond raw MCP wiring, `plugins/car/` is a working Claude Code plugin that
298
+ bundles the MCP server with two subagents, three slash commands, and a policy
299
+ hook — install it and you get the tools plus opinionated glue in one step.
300
+
301
+ **Not installable from here today.** `plugins/car/` lives only in the CAR
302
+ source repository, which is private — nothing in the release pipeline
303
+ publishes it, so a `/plugin marketplace add` pointed at that repo 404s for
304
+ every reader of this document. What's below documents what the plugin does
305
+ and how it's laid out; treat it as a preview of what a future public
306
+ marketplace entry would provide, not a command you can run yet. Until then,
307
+ get the same tools with the bare `.mcp.json` config further up this page, and
308
+ write `.car/policies/rules.toml` — CAR's `car do` reads it directly, so
309
+ policy enforcement works without the plugin's hook.
310
+
311
+ Requires `car` and `car-mcp` on `PATH` from a CAR newer than v0.48.0 (the
312
+ first release with `car policy-check-hook`, which the hook below runs).
313
+
314
+ | Component | What it does |
315
+ |---|---|
316
+ | `@agent-car:car` | Delegates a task to CAR's autonomous agent (`car do --json` under the hood) — Docker sandbox, no network by default, validator + policy chain, returns tool receipts and a claim check |
317
+ | `@agent-car:car-reason` | CAR's code reasoning engine (`car reason`) — adaptive model routing over a graph of the codebase |
318
+ | `/car-do` | Shorthand for delegating to the `car` subagent |
319
+ | `/car-remember`, `/car-recall` | Write and search CAR's durable memory via the `memory_add_fact` / `memory_query` MCP tools |
320
+ | MCP server `car` | The 16 tools above, launched as `car-mcp` with `CAR_INVOKED_BY=claude-code` set (required — it seeds CAR's recursion guard so `car do` doesn't spawn a `claude` subprocess that loads this same plugin and calls back in) |
321
+ | `PreToolUse` hook | Runs **your** `.car/policies/*.toml` against Claude Code's own Bash/Write/Edit/WebFetch/NotebookEdit calls, via `car policy-check-hook` |
322
+
323
+ The hook is declarative — write `.car/policies/rules.toml`:
324
+
325
+ ```toml
326
+ deny_tool = ["WebFetch"]
327
+ deny_keyword = ["rm -rf /", "DROP TABLE"]
328
+ ```
329
+
330
+ Two behaviors worth knowing precisely: **an unparseable policy file blocks the
331
+ call** (an operator wrote a rule; silently enforcing nothing is the failure
332
+ declarative policy exists to prevent), but **the hook itself fails open** on
333
+ anything that isn't a rule decision — an unreadable payload or a missing
334
+ `tool_name` lets the call proceed with a note to stderr, because a hook that
335
+ fails closed on an install problem would make it look like a policy denial.
336
+ The hook command is literally `car policy-check-hook; exit 0`, so any exit
337
+ code the subcommand returns is discarded rather than read as a deny — that
338
+ guards a stale `car` too: an older binary that doesn't recognize the
339
+ subcommand used to exit 2, which Claude Code reads as *deny*, so every
340
+ matching call in the session was refused with clap's usage text as the stated
341
+ reason (Parslee-ai/car#993). Note this hook is a `car` subcommand
342
+ (`car_policy::tool_gate` in-process) — it doesn't shell out to `car-mcp` at
343
+ all, despite the MCP server enforcing the same policy engine. `.car/policies/`
344
+ is read from the working directory only and is **not walked upward**, matching
345
+ `car do`.
346
+
347
+ ### The plugin-authoring gotcha this repo has already hit
348
+
349
+ Plugin components — agents, commands, hooks, `.mcp.json` — must live at the
350
+ plugin **root**, not nested inside `.claude-plugin/`. Anything placed inside
351
+ `.claude-plugin/` silently fails to load, and `claude plugin validate` passes
352
+ it anyway; the only way to catch the mistake is `claude plugin details`. In
353
+ this repo, `.claude-plugin/` correctly holds only `plugin.json` — `.mcp.json`,
354
+ `agents/`, `commands/`, and `hooks/` all sit one level up, at
355
+ `plugins/car/`. If you're authoring a plugin of your own against this as a
356
+ reference, that layout is the one to copy.
357
+
358
+ ## The mirror image: CAR as an MCP client (`car-connectors`)
359
+
360
+ Everything above is CAR exposing its own tools to an external host. The
361
+ reverse also exists: `car-connectors` lets CAR add a **remote** MCP server as
362
+ a tool source, the way Claude or ChatGPT add connectors — connect over
363
+ HTTP-streamable, discover tools via `tools/list`, translate each tool's JSON
364
+ Schema into a `car_ir::ToolSchema`, and register it so it dispatches through
365
+ CAR's own validator/policy/eventlog like any other tool.
366
+
367
+ Worth knowing if you're evaluating this direction:
368
+
369
+ - A newly discovered connector tool is **disabled by default** and has no
370
+ route in the executor until a user enables it — structural, not a
371
+ permission flag someone could forget to set.
372
+ - Connector config (slug, name, URL, enabled tools, auth-header names)
373
+ persists to `~/.car/connectors.json`; secret header *values* go to the OS
374
+ keychain, never the JSON file.
375
+ - Phase 1 (unauthenticated / static-header servers) and Phase 2 (OAuth 2.1
376
+ with PKCE + token refresh) are the current scope. Connector management
377
+ itself is GUI-driven through CarHost, not a CLI flag.
378
+
379
+ This is the mirror of everything documented above, not a replacement for it —
380
+ use `car-mcp` / the daemon's `/mcp` endpoint to bring CAR's tools *into* an
381
+ external agent, and `car-connectors` to bring an external MCP server's tools
382
+ *into* CAR.