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/README.md +9 -2
- package/docs/ASSISTANT.md +491 -0
- package/docs/CLI.md +3173 -0
- package/docs/GUIDE.md +266 -0
- package/docs/MCP.md +382 -0
- package/docs/SPEC.md +169 -0
- package/docs/agent-ir-spec.md +677 -0
- package/docs/websocket-protocol.md +10 -0
- package/index.d.ts +17 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -74,8 +74,15 @@ const result = await executeProposal(rt, proposal, async (callJson) => {
|
|
|
74
74
|
});
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
Full API reference lives in [`index.d.ts`](./index.d.ts).
|
|
78
|
-
|
|
77
|
+
Full API reference lives in [`index.d.ts`](./index.d.ts). The package also
|
|
78
|
+
ships a `docs/` directory (`node_modules/car-runtime/docs/`) with prose
|
|
79
|
+
reference docs — `SPEC.md`, `GUIDE.md`, `CLI.md`, `ASSISTANT.md`, `MCP.md`,
|
|
80
|
+
`agent-ir-spec.md` — so an agent with this package installed and no network
|
|
81
|
+
access can still read how the `car` CLI, the flagship assistant's tools, and
|
|
82
|
+
`car-mcp` actually work. `docs/websocket-protocol.md` is a short pointer, not
|
|
83
|
+
the full reference (~510KB, deliberately not bundled) — read the real thing at
|
|
84
|
+
https://car.parslee.ai/websocket-protocol.md. High-level docs and examples
|
|
85
|
+
online: https://github.com/Parslee-ai/car-releases
|
|
79
86
|
|
|
80
87
|
## Environment variables
|
|
81
88
|
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# Parslee Core — the `car do` assistant
|
|
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
|
+
CAR ships with a general-purpose agent that works out of the box: no tools to
|
|
7
|
+
register, no proposal schema to write. Point it at a task and it uses files, a
|
|
8
|
+
real shell, the web, image/speech/music/video generation, browser control,
|
|
9
|
+
Microsoft 365, and a durable memory graph to get it done.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
car do "generate a 20-second commercial for a cold-brew coffee brand called Wanderlust Roasters, with a voiceover and music"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This is a different thing from the `CarRuntime` / `register_tool` / proposal
|
|
16
|
+
pattern in [SPEC.md](./SPEC.md) and [GUIDE.md](./GUIDE.md). Those docs are for
|
|
17
|
+
*building an agent on CAR* — you supply the tools, CAR supplies the DAG
|
|
18
|
+
executor, validator, and policy engine. `car do` is a finished agent CAR
|
|
19
|
+
already built that way: its tools are `car-server-core`'s own
|
|
20
|
+
`GeneralExecutor` plus a set of host-side delegates, driven through the same
|
|
21
|
+
`Runtime` (validator → policy → permission tiers → event log) any embedder
|
|
22
|
+
gets. If you want to write your own tools, read SPEC/GUIDE. If you want an
|
|
23
|
+
agent that already has real ones, this is it.
|
|
24
|
+
|
|
25
|
+
## Three ways to run it
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
car do "<goal>" # one-shot: run to a terminal outcome, print it, exit
|
|
29
|
+
car do # interactive REPL — multi-turn, in one process
|
|
30
|
+
car do --serve # supervised, conversational agent for CarHost / agents.chat
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`car do --serve` is what CarHost auto-starts: on daemon boot it registers
|
|
34
|
+
itself as agent id `parslee-core` (`car-assistant` is kept as a compatibility
|
|
35
|
+
alias for older clients) with `auto_start: true`, so a fresh CAR install has a
|
|
36
|
+
working conversational agent with no separate install step. Nothing else in
|
|
37
|
+
this document changes between the three modes — they share one system prompt,
|
|
38
|
+
one tool set, and one loop; they differ only in how a turn starts and how
|
|
39
|
+
output is delivered.
|
|
40
|
+
|
|
41
|
+
Full flag reference (`--local`, `--full-access`, `--until`, `--json`, …) is in
|
|
42
|
+
[CLI.md](./CLI.md#car-do).
|
|
43
|
+
|
|
44
|
+
## Execution posture: sandboxed by default
|
|
45
|
+
|
|
46
|
+
Unless you pass `--local`, `car do` binds a hardened Docker sandbox before
|
|
47
|
+
running anything:
|
|
48
|
+
|
|
49
|
+
- Container image `python:3.11` by default (override with `--image`) — the
|
|
50
|
+
full (non-slim) image, so git/gcc/make/curl are present without network
|
|
51
|
+
access to fetch them.
|
|
52
|
+
- `--network none`: file writes and shell execute **inside** the container;
|
|
53
|
+
network tools (`http_request`, `web_search`, browser control) run from the
|
|
54
|
+
**host**, because the container can't reach the network at all.
|
|
55
|
+
- If Docker isn't available, `car do` does not silently drop the sandbox — it
|
|
56
|
+
falls back to the local host with every write and shell call gated behind
|
|
57
|
+
human approval (see below), and tells the model why in the environment
|
|
58
|
+
description.
|
|
59
|
+
- `--local` skips the sandbox and runs directly against your filesystem.
|
|
60
|
+
`-y` / `--full-access` lifts the local-host approval gate.
|
|
61
|
+
|
|
62
|
+
Either way, execution goes through the runtime's admission gates before
|
|
63
|
+
anything runs: a **static verification gate** (tool exists, parameters are
|
|
64
|
+
well-formed) and, by default, an **information-flow gate** that blocks
|
|
65
|
+
confidential local data (files, recalled memories) from reaching an outbound
|
|
66
|
+
tool like `web_search` in the same proposal — CAR calls this out explicitly
|
|
67
|
+
because assistants that let persistent, high-privilege context flow straight
|
|
68
|
+
into outbound tools are a known failure class.
|
|
69
|
+
|
|
70
|
+
## What it can actually do
|
|
71
|
+
|
|
72
|
+
Every capability below is a real tool the model can call — read the parameter
|
|
73
|
+
names straight out of the source, not paraphrased. Several groups are
|
|
74
|
+
**conditionally advertised**: a tool never appears in the model's tool list
|
|
75
|
+
(and so never appears in the system prompt, since the prompt doesn't
|
|
76
|
+
re-enumerate tools — it points the model at the defs) on a host that can't run
|
|
77
|
+
it. That's deliberate: a tool that would only ever return an error is worse
|
|
78
|
+
than no tool.
|
|
79
|
+
|
|
80
|
+
### Files, shell, and planning — always available
|
|
81
|
+
|
|
82
|
+
| Tool | Parameters |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `read_file` | `path`, `offset?`, `limit?` |
|
|
85
|
+
| `list_dir` | `path` |
|
|
86
|
+
| `find_files` | `pattern` (glob, `**` spans directories), `path?` (default `.`), `max_results?` (default 1000) |
|
|
87
|
+
| `grep_files` | `pattern` (regex), `path?` (default `.`), `max_results?` (default 50) |
|
|
88
|
+
| `write_file` | `path`, `content`, `append?` — overwriting or appending to an existing file requires you to have read its full current content earlier in the session (read-before-write guard); creating a new file needs no prior read |
|
|
89
|
+
| `edit_file` | `path`, `old_text`, `new_text`, `replace_all?` — same read-before-edit guard; `old_text` must match exactly one place unless `replace_all: true` |
|
|
90
|
+
| `calculate` | `expression` (`^` for exponentiation, plus `+ - * / %`, parentheses, `sqrt`/`sin`/`ln`/…) — pure, no substrate access |
|
|
91
|
+
| `shell` | `command`, `timeout_secs?` (default 120, max 600) |
|
|
92
|
+
| `todo_write` | `items: [{text, status?: "open"\|"done"\|"dropped"}]` — replaces the whole checklist each call; it's live state that survives history compaction, not a transcript entry |
|
|
93
|
+
| `events_query` | `kinds?`, `action_id?`, `limit?` — queries this run's own append-only event log: what you already tried, what failed. Advertised whenever a run has a bound event log (every `car do` invocation) |
|
|
94
|
+
|
|
95
|
+
`write_file`, `edit_file`, and `shell` auto-run inside the sandbox or under
|
|
96
|
+
`--full-access`; on the local host without `--full-access` they need approval
|
|
97
|
+
(see [Approvals](#approvals-what-it-may-do-without-asking)).
|
|
98
|
+
|
|
99
|
+
### Web and delegated work — approval-gated by default
|
|
100
|
+
|
|
101
|
+
| Tool | Parameters |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `http_request` | `url`, `method?`, `headers?`, `body?`, `timeout_secs?` (default 30, max 120) |
|
|
104
|
+
| `web_search` | `query`, `max_results?` |
|
|
105
|
+
| `m365_task` | `task`, `conversation_id?` — delegates to the user's Microsoft 365 Parslee AI Employee (email, calendar, contacts, HubSpot CRM, meetings) over one chat call. Mutating actions (send mail, create an event) come back **drafted for approval**, never executed silently. Advertised only with a Parslee session (`car auth login`) |
|
|
106
|
+
|
|
107
|
+
These self-declare `"tier": "full_access"`, so — unlike file/shell — they need
|
|
108
|
+
approval even *inside* the sandbox unless the session was started with
|
|
109
|
+
`--full-access`. Network egress crosses the sandbox boundary; the container
|
|
110
|
+
walls it off from the filesystem, but not from the approval gate.
|
|
111
|
+
|
|
112
|
+
### Memory
|
|
113
|
+
|
|
114
|
+
| Tool | Parameters |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `remember` | `subject`, `body`, `kind?: "fact"\|"preference"\|"procedure"` (full_access, gated) |
|
|
117
|
+
| `recall` | `query` (no declared tier — never gated) |
|
|
118
|
+
|
|
119
|
+
See [Memory](#memory-what-actually-persists) below for what this backs and
|
|
120
|
+
what it explicitly does not.
|
|
121
|
+
|
|
122
|
+
### Creative and media generation
|
|
123
|
+
|
|
124
|
+
The capability a text-only coding agent structurally does not have. Two tiers:
|
|
125
|
+
local models (fast, always-available-if-installed) and Parslee Studio
|
|
126
|
+
(slower, needs `car auth login`, materially higher quality on some axes).
|
|
127
|
+
|
|
128
|
+
**Local (`sandbox_edit` tier — no approval needed once the run has edit
|
|
129
|
+
permission), advertised only when the matching local model is installed:**
|
|
130
|
+
|
|
131
|
+
| Tool | Parameters |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `generate_image` | `prompt`, `output_path?`, `width?` (≤1024, default 768), `height?` (≤1024, default 512), `seed?` |
|
|
134
|
+
| `generate_speech` | `text`, `output_path?`, `voice?` |
|
|
135
|
+
|
|
136
|
+
**Parslee Studio (`full_access` tier), advertised only with a Parslee
|
|
137
|
+
session:**
|
|
138
|
+
|
|
139
|
+
| Tool | Parameters |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `generate_music` | `prompt`, `duration_seconds?` (5–300, default 30), `output_path?` — ElevenLabs Music, instrumental/ambience |
|
|
142
|
+
| `generate_jingle` | `brand_name`, `style?`, `tagline?`, `output_path?` — short sonic branding |
|
|
143
|
+
| `generate_studio_image` | `prompt`, `aspect_ratio?` (default "16:9"), `quality?: "low"\|"medium"\|"high"`, `output_path?` — gpt-image-2; reliably renders legible in-image text where the local generator doesn't |
|
|
144
|
+
| `generate_song` | `prompt`, `duration_seconds?` (30–480, default 60), `style?`, `lyrics?`, `instrumental?`, `title?`, `output_path?` — Suno, full song with vocals, up to 8 minutes |
|
|
145
|
+
| `list_voices` | — lists Studio's stock voices plus any this org has cloned from real people; call before `generate_voiceover` when a specific voice matters |
|
|
146
|
+
| `generate_voiceover` | `text`, `voice?` (name or id — matched against `list_voices`), `rate?` (e.g. `"-10%"`; **omitting it is not neutral** — the tool description documents measured wpm at each setting and recommends `"-10%"` for narration), `output_path?` |
|
|
147
|
+
| `generate_video` | `prompt`, `image_path?` (omit for text-to-video, supply to animate a still), `duration_seconds?` (default 5), `provider?: "veo"\|"kling"\|"ltx"`, `output_path?` — image-to-video is a diffusion repaint of every frame, so it warps fine text/UI in the source image; the tool description warns against animating text-heavy slides with it |
|
|
148
|
+
| `produce_commercial` | `brief`, `duration_seconds?` (8–60, default 20), `voiceover_script?`, `voiceover_voice?` (default `"brian"`), `music?` (default true), `output_path?` — plans shots, generates keyframes/video, adds voiceover and a music bed, and assembles a finished MP4. Slow (real productions run up to ~25 minutes) |
|
|
149
|
+
|
|
150
|
+
Every generator returns a **file path under the working directory**, never
|
|
151
|
+
inline bytes — the artifact contract exists so a producer's output survives
|
|
152
|
+
the loop's observation-size cap; embed or read the path like any other file.
|
|
153
|
+
|
|
154
|
+
### Vision — reading images back
|
|
155
|
+
|
|
156
|
+
The consumer counterpart to the generators. `read_image_text` (OCR) runs via
|
|
157
|
+
the Apple Vision shim on macOS with a Tesseract fallback elsewhere;
|
|
158
|
+
`classify_image` runs via Apple Vision on macOS and a bundled MobileNetV2
|
|
159
|
+
model elsewhere (auto-downloaded and cached on first use off-macOS). Both are
|
|
160
|
+
read-only; each is advertised whenever its own backend is present.
|
|
161
|
+
|
|
162
|
+
| Tool | Parameters |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `read_image_text` | `image_path` — OCR: screenshots, scans, photographed documents, or a generated image containing words |
|
|
165
|
+
| `classify_image` | `image_path`, `top_k?` (default 5, max 20) — ranked content labels with confidence |
|
|
166
|
+
|
|
167
|
+
### Browser control and recording — `full_access` tier
|
|
168
|
+
|
|
169
|
+
Chromium launches lazily on first use, so a session that never browses pays
|
|
170
|
+
nothing for it.
|
|
171
|
+
|
|
172
|
+
| Tool | Parameters |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `browse_navigate` | `url` |
|
|
175
|
+
| `browse_click` | `element_id` (accessibility node id, e.g. `"el_5"`) |
|
|
176
|
+
| `browse_type` | `element_id`, `text` |
|
|
177
|
+
| `browse_scroll` | `delta_y` |
|
|
178
|
+
| `browse_keypress` | `key`, `modifiers?: ["shift"\|"control"\|"alt"\|"meta"]` |
|
|
179
|
+
| `browse_wait` | `condition` (`"page_loaded"` or `"url_changed"`), `timeout_ms?` (default 5000) |
|
|
180
|
+
| `browse_observe` | `include_screenshot?`, `ocr?` — screenshot + accessibility tree + a fused `ui_map`; OCR recovers labels a polished SPA's accessibility tree misses |
|
|
181
|
+
| `browser_await_answer` | `timeout_seconds?` (default 45) — blocks until a page stops changing after you submit something, so you don't screenshot mid-load |
|
|
182
|
+
| `browser_await_signin` | `url?`, `success_url_contains?`, `timeout_seconds?` (default 300, max 1800) — asks the human to complete a sign-in the agent can't (SSO, MFA); the session persists afterward |
|
|
183
|
+
| `browser_record_start` | `quality?` (JPEG 1–100, default 80) |
|
|
184
|
+
| `browser_record_stop` | `output_path?` — writes an MP4 (requires ffmpeg), captures only frames where the page actually changed |
|
|
185
|
+
|
|
186
|
+
### Desktop automation — `full_access` tier, one platform-native tool
|
|
187
|
+
|
|
188
|
+
Cannot be sandboxed by construction — it drives the real host GUI — so it
|
|
189
|
+
self-declares `full_access` and is approval-gated by the same tier rule as
|
|
190
|
+
`web_search`/browser control/`m365_task` (see
|
|
191
|
+
[Approvals](#approvals-what-it-may-do-without-asking)): gated unless the
|
|
192
|
+
session was started with `--full-access`.
|
|
193
|
+
|
|
194
|
+
| Platform | Tool | Parameters |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| macOS | `run_applescript` | `script`, `language?: "applescript"\|"javascript"` (JXA preferred — models generate it more cleanly) |
|
|
197
|
+
| Windows | `run_powershell` | `script` |
|
|
198
|
+
|
|
199
|
+
### Linked devices
|
|
200
|
+
|
|
201
|
+
| Tool | Parameters |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `linked_devices` | — (read-only, never gated) lists the user's linked CAR host devices and what they advertise (chat, approvals, notifications) |
|
|
204
|
+
| `notify_linked_device` | `device_id?`, `title`, `body` (full_access, gated) — a title/body push, nothing more; no contacts/location/photos/microphone access |
|
|
205
|
+
|
|
206
|
+
### Identity
|
|
207
|
+
|
|
208
|
+
| Tool | Parameters |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `set_assistant_name` | `name`, `spellings?` (alternate speech-to-text spellings), `user_name?` |
|
|
211
|
+
|
|
212
|
+
`set_assistant_name` is gated on **every** session regardless of tier —
|
|
213
|
+
`--full-access` does not exempt it. Every other gate in this document is about
|
|
214
|
+
what the *session* is allowed to do; this one is about where the instruction
|
|
215
|
+
could have come from. A rename request can arrive inside a fetched web page,
|
|
216
|
+
a file, or a recalled memory, and an assistant that silently starts answering
|
|
217
|
+
to a name it read somewhere is an identity-spoof surface — one approval tap is
|
|
218
|
+
the cheaper mistake. See [Identity](#identity-1) below.
|
|
219
|
+
|
|
220
|
+
## Approvals: what it may do without asking
|
|
221
|
+
|
|
222
|
+
Two independent axes decide whether a tool call runs immediately or stops for
|
|
223
|
+
a human:
|
|
224
|
+
|
|
225
|
+
**1. The environment tier**, set once per run:
|
|
226
|
+
|
|
227
|
+
| Environment | Standing tier | Meaning |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| Sandbox (default) | `SandboxEdit` | Writes/shell inside the container auto-run; nothing crosses the container boundary without approval |
|
|
230
|
+
| Sandbox, `--full-access` | `FullAccess` | Everything auto-runs, including network/browser/automation |
|
|
231
|
+
| Local host (`--local`) | `ReadOnly` | Even `write_file`/`edit_file`/`shell` need approval |
|
|
232
|
+
| Local host, `--full-access` | `FullAccess` | Everything auto-runs |
|
|
233
|
+
| Docker unavailable (auto fallback) | `ReadOnly` | Same as `--local` without it — never a silent unsandboxed run |
|
|
234
|
+
|
|
235
|
+
**2. Each tool's self-declared `tier`** in its schema (`sandbox_edit` or
|
|
236
|
+
`full_access`; a tool with no `tier` field is never gated by this rule). A
|
|
237
|
+
tool is routed to approval whenever its declared tier **exceeds** the run's
|
|
238
|
+
standing tier. This is why `web_search`, the browser tools, `run_applescript`
|
|
239
|
+
/ `run_powershell`, `m365_task`, the Studio media tools, `remember`, and
|
|
240
|
+
`notify_linked_device` all stop for approval inside the *default sandboxed
|
|
241
|
+
run* — they declare `full_access`, and the sandbox's standing tier is only
|
|
242
|
+
`SandboxEdit`. The local generators (`generate_image`, `generate_speech`)
|
|
243
|
+
declare `sandbox_edit`, so they run without asking inside the sandbox but are
|
|
244
|
+
gated on an unelevated local run.
|
|
245
|
+
|
|
246
|
+
**How the human is asked** depends on the surface:
|
|
247
|
+
|
|
248
|
+
- One-shot / REPL (`car do "<goal>"` or `car do`): a terminal prompt —
|
|
249
|
+
`⚠ Approve <tool>(<brief>)? [y/N]` — reading a single line from stdin.
|
|
250
|
+
Anything but `y` is a denial with reason `"declined by user"`.
|
|
251
|
+
- `car do --serve` / `agents.chat`: routed through the chat surface's
|
|
252
|
+
`approval_pending` → park → resolve flow. A connected host can present that
|
|
253
|
+
same pending approval as a reviewable control rather than a plain
|
|
254
|
+
yes/no modal — CarHost and the mobile apps do this — but the wire contract
|
|
255
|
+
`car do` itself guarantees is the `approval_pending` event; how a given host
|
|
256
|
+
renders it is that host's decision, not the assistant's.
|
|
257
|
+
|
|
258
|
+
**Changing the default posture** — the CLI counterpart to CarHost's
|
|
259
|
+
**Approvals** settings screen:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
car approvals get # show the current default posture
|
|
263
|
+
car approvals default cautious # ask before any edit or full-access action
|
|
264
|
+
car approvals default balanced # allow sandboxed edits without asking
|
|
265
|
+
car approvals default trusting # allow everything without asking
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This proxies the daemon's `agent_permissions.*` JSON-RPC surface: it moves the
|
|
269
|
+
**default** posture applied to every agent that has no explicit per-agent
|
|
270
|
+
override, so setting it once changes the fallback for every agent the daemon
|
|
271
|
+
supervises, not just one `car do` invocation. The three presets are the only
|
|
272
|
+
accepted values; anything else is rejected before it reaches the daemon.
|
|
273
|
+
|
|
274
|
+
## Memory: what actually persists
|
|
275
|
+
|
|
276
|
+
`recall` and `remember` are backed by CAR's graph memory engine
|
|
277
|
+
(`car-memgine`) — not a bespoke store built for the assistant. It's the same
|
|
278
|
+
durable note store `car-mcp` reads and writes (with a two-writer caveat if you
|
|
279
|
+
run both at once — see [MCP.md's memory-durability
|
|
280
|
+
section](./MCP.md#memory-durability--read-this-before-relying-on-it)).
|
|
281
|
+
|
|
282
|
+
- **What persists**: durable *facts*, written one at a time by `remember`
|
|
283
|
+
(`subject`, `body`, `kind`), stored as flat JSON notes at
|
|
284
|
+
`<CAR_HOME>/memory/assistant.json` (`~/.car/memory/assistant.json` by
|
|
285
|
+
default) and re-ingested into a fresh in-memory graph every time the
|
|
286
|
+
assistant opens. A `remember` call with a subject that already exists
|
|
287
|
+
**replaces** that note (case-insensitive match) rather than accumulating
|
|
288
|
+
duplicates. `recall` queries that graph and returns a relevance-scored
|
|
289
|
+
context string, not a raw list.
|
|
290
|
+
- **What does not persist**: the *conversation itself*. There is no
|
|
291
|
+
disk-backed transcript store behind `car do` — a prior, unrelated
|
|
292
|
+
`ConversationStore` mechanism was removed from CAR entirely, and nothing
|
|
293
|
+
replaced it as a session-replay feature. What "the assistant remembers the
|
|
294
|
+
conversation" cashes out to in practice is: whatever the model chose to
|
|
295
|
+
write with `remember` during that conversation, as a fact — not a replay of
|
|
296
|
+
what was said. Don't tell a user their chat history itself survives a
|
|
297
|
+
restart; only the facts it explicitly saved do.
|
|
298
|
+
- **Cross-device**: only on `car do --serve` (the daemon-attached path). There,
|
|
299
|
+
every `remember` is additionally mirrored into the daemon's synced knowledge
|
|
300
|
+
oplog, and `recall` pulls in facts a *different* device wrote for subjects
|
|
301
|
+
the local store doesn't already hold (local notes win on a subject both
|
|
302
|
+
devices have — this is deliberately conservative, not last-write-wins). A
|
|
303
|
+
one-shot `car do "<goal>"` run has no sync sink attached, so its `remember`
|
|
304
|
+
calls stay local to that machine.
|
|
305
|
+
- **`kind` changes retrieval behavior, not just categorization**: a `"fact"`
|
|
306
|
+
is retrieved when relevant to a query; a `"preference"` is surfaced in
|
|
307
|
+
*every* future session (a standing instruction, not a recall hit) — reserve
|
|
308
|
+
it for rules that should never be violated; a `"procedure"` records how a
|
|
309
|
+
task was done and whether it worked, for reuse.
|
|
310
|
+
|
|
311
|
+
## Identity
|
|
312
|
+
|
|
313
|
+
The product is **Parslee Core** — that name is fixed (it's what the product
|
|
314
|
+
is called in store copy) and never changes. What the user calls the agent day
|
|
315
|
+
to day is a nickname layered on top, defaulting to **Parslee** (so an install
|
|
316
|
+
that never sets a name still answers to something short and won't confuse
|
|
317
|
+
itself with the brand string in casual conversation). The agent is told in
|
|
318
|
+
its system prompt to answer to either without correcting the user.
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
car identity show # current name, spoken forms, where the record lives
|
|
322
|
+
car identity set Jarvis --also-hear jervis # rename it; --also-hear registers STT mis-hearings
|
|
323
|
+
car identity set-user Dana # tell it what to call YOU
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The record lives at `<CAR_HOME>/identity.json` — one file feeding the system
|
|
327
|
+
prompt, the voice wake-word matcher, and every host's addressing copy, so
|
|
328
|
+
`car identity show` reports the same name CarHost displays and the voice
|
|
329
|
+
pipeline wakes on. Saying "sure, I'll go by Friday" in conversation changes
|
|
330
|
+
nothing by itself: the model is instructed to call the gated
|
|
331
|
+
`set_assistant_name` tool (approval required — see above) to make a rename
|
|
332
|
+
actually take effect for the next session and the wake word; agreeing in
|
|
333
|
+
prose alone is explicitly called out in the prompt as *not* persisting
|
|
334
|
+
anything.
|
|
335
|
+
|
|
336
|
+
## Worked examples
|
|
337
|
+
|
|
338
|
+
These are **illustrative** — derived directly from the tool schemas and loop
|
|
339
|
+
behavior above, not captured output from an actual run.
|
|
340
|
+
|
|
341
|
+
### 1. A capability a text-only coding agent doesn't have
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
car do "Make a 20-second commercial for a cold-brew coffee brand called \
|
|
345
|
+
Wanderlust Roasters — warm, adventurous tone, with a voiceover and \
|
|
346
|
+
background music."
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Given a Parslee session, the model has `produce_commercial` in its tool list
|
|
350
|
+
and (per its schema) would call something like:
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{"tool": "produce_commercial", "parameters": {
|
|
354
|
+
"brief": "Wanderlust Roasters cold brew — warm, adventurous, morning-ritual tone",
|
|
355
|
+
"duration_seconds": 20,
|
|
356
|
+
"music": true
|
|
357
|
+
}}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`produce_commercial` self-declares `full_access`, so on a sandboxed run
|
|
361
|
+
without `--full-access` this call pauses for approval first. Once approved,
|
|
362
|
+
Studio plans shots, generates keyframes and video, adds a voiceover and a
|
|
363
|
+
music bed, and the tool result is an MP4 path under the working directory —
|
|
364
|
+
which the agent then references in its final summary rather than describing
|
|
365
|
+
in prose.
|
|
366
|
+
|
|
367
|
+
### 2. Approval prompt on the local host
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
car do --local "delete every file in this directory older than 30 days"
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`shell` (or `write_file`) is not `--full-access`, so before running the
|
|
374
|
+
deleting command the terminal shows:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
⚠ Approve shell(find . -mtime +30 -delete)? [y/N]
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Typing anything but `y` denies the call with `"declined by user"`, and the
|
|
381
|
+
model is told the boundary — the prompt instructs it not to retry the exact
|
|
382
|
+
same call, but to explain the limit and offer an alternative.
|
|
383
|
+
|
|
384
|
+
### 3. Machine-readable output (`--json`)
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
car do --json "summarize the CSV files in this directory"
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
emits exactly one JSON document on stdout (`schema: "car.do/1"`) — progress
|
|
391
|
+
goes to stderr as JSONL instead of human-readable text. The shape, per the
|
|
392
|
+
emitter that builds it:
|
|
393
|
+
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"schema": "car.do/1",
|
|
397
|
+
"status": "success",
|
|
398
|
+
"summary": "…",
|
|
399
|
+
"turns": 5,
|
|
400
|
+
"delegations": 0,
|
|
401
|
+
"model_used": "anthropic/claude-haiku-4-5:latest",
|
|
402
|
+
"receipts": {
|
|
403
|
+
"total": 11,
|
|
404
|
+
"failed": 0,
|
|
405
|
+
"by_tool": {"list_dir": 1, "read_file": 6, "grep_files": 4},
|
|
406
|
+
"sample": [
|
|
407
|
+
{"tool": "list_dir", "ok": true, "brief": "."},
|
|
408
|
+
{"tool": "read_file", "ok": true, "brief": "prices.csv"},
|
|
409
|
+
{"tool": "read_file", "ok": true, "brief": "orders.csv"},
|
|
410
|
+
{"tool": "read_file", "ok": true, "brief": "notes.csv"},
|
|
411
|
+
{"tool": "read_file", "ok": true, "brief": "invoices.csv"},
|
|
412
|
+
{"tool": "read_file", "ok": true, "brief": "returns.csv"},
|
|
413
|
+
{"tool": "read_file", "ok": true, "brief": "summary.csv"},
|
|
414
|
+
{"tool": "grep_files", "ok": true, "brief": "prices.csv"}
|
|
415
|
+
],
|
|
416
|
+
"sample_omitted": 3
|
|
417
|
+
},
|
|
418
|
+
"ungrounded_claims": [],
|
|
419
|
+
"sandbox": {
|
|
420
|
+
"mode": "docker",
|
|
421
|
+
"image": "python:3.11",
|
|
422
|
+
"network": "none",
|
|
423
|
+
"tier": "sandbox_edit",
|
|
424
|
+
"root": "/work",
|
|
425
|
+
"fallback_notice": null
|
|
426
|
+
},
|
|
427
|
+
"elapsed_seconds": 12.4
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
`sample` is capped at 8 receipts (failures first, then successes) regardless of
|
|
432
|
+
how many tool calls the run actually made; `sample_omitted` says how many were
|
|
433
|
+
left out of that cap so the array never silently reads as the complete list.
|
|
434
|
+
|
|
435
|
+
`ungrounded_claims` is the mechanism behind "receipts decide completion, not
|
|
436
|
+
the prose": before the run is reported as finished, operational claims in the
|
|
437
|
+
model's own summary ("I ran the tests", "the file is updated") are
|
|
438
|
+
cross-checked against the actual tool receipts from *this* run. An unmatched
|
|
439
|
+
claim is not silently trusted — a deterministic pass (e.g. `--until` goal
|
|
440
|
+
mode) only annotates the reply with a `[claim check]` note, while a
|
|
441
|
+
model-judged completion is failed closed on an unmatched claim. This array is
|
|
442
|
+
empty here because nothing in the summary asserted anything the receipts
|
|
443
|
+
didn't back up.
|
|
444
|
+
|
|
445
|
+
### The failure shape
|
|
446
|
+
|
|
447
|
+
The block above is a **successful** run. A failed one keeps the same
|
|
448
|
+
`schema` and swaps in an error triple — parse `status` first, not the presence
|
|
449
|
+
of `summary`, which a failed run does not carry:
|
|
450
|
+
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"schema": "car.do/1",
|
|
454
|
+
"status": "error",
|
|
455
|
+
"error": "AssistantLoopFailed",
|
|
456
|
+
"message": "inference failed: Not enough memory is free to start this model while preserving CAR's 6553 MB emergency reserve. Close memory-heavy apps or choose a smaller model.",
|
|
457
|
+
"turns": 1,
|
|
458
|
+
"model_used": "",
|
|
459
|
+
"elapsed_seconds": 0.007832208,
|
|
460
|
+
"suggestions": [
|
|
461
|
+
"Re-run the goal; the run failed mid-loop rather than completing with an answer.",
|
|
462
|
+
"Check `receipts` for what had already executed before the failure."
|
|
463
|
+
],
|
|
464
|
+
"receipts": {"total": 0, "failed": 0, "by_tool": {}, "sample": [], "sample_omitted": 0}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
- `status` is `"success"` or `"error"`. Branch on it.
|
|
469
|
+
- `error` is a stable machine-readable kind (e.g. `AssistantLoopFailed`);
|
|
470
|
+
`message` is the human-readable detail and is NOT stable enough to match on.
|
|
471
|
+
- `receipts` is always present, including on failure, and is how you see what
|
|
472
|
+
had already run before the failure — a partially-completed run reports the
|
|
473
|
+
tools it did execute.
|
|
474
|
+
- `model_used` is empty when the run failed before a model was selected.
|
|
475
|
+
- The process exits non-zero on `"error"`, so an exit-code check and a
|
|
476
|
+
`status` check agree; the JSON is still emitted.
|
|
477
|
+
|
|
478
|
+
The exit status and the JSON both being authoritative matters for scripting:
|
|
479
|
+
`car do --json` writes exactly one document to stdout either way, with progress
|
|
480
|
+
as JSONL on stderr, so redirecting stdout to a file gives a parseable result
|
|
481
|
+
even on failure.
|
|
482
|
+
|
|
483
|
+
## Where this fits with the rest of CAR
|
|
484
|
+
|
|
485
|
+
- Building your *own* agent with your *own* tools — [SPEC.md](./SPEC.md) /
|
|
486
|
+
[GUIDE.md](./GUIDE.md).
|
|
487
|
+
- Every CLI subcommand, including the rest of `car do`'s flags and every other
|
|
488
|
+
top-level command — [CLI.md](./CLI.md).
|
|
489
|
+
- Wiring CAR (including a poll-based handle onto this same assistant,
|
|
490
|
+
`assistant_start`/`assistant_poll`) into Claude Code, Cursor, or another
|
|
491
|
+
MCP host — [MCP.md](./MCP.md).
|