# car-server-core
Transport-neutral library for the [Common Agent Runtime](https://github.com/Parslee-ai/car) JSON-RPC dispatcher.
## What it does
Holds the JSON-RPC dispatcher, per-client session state, and the WebSocket channel plumbing. Used by `car-server` (the standalone WebSocket binary) and by any future embedder that wants to expose CAR's protocol over a different transport (e.g. `tokhn-daemon`).
The standalone `car-server` binary is a thin wrapper that loads `~/.car/env`, initializes telemetry, spawns the dream loop, binds a TCP listener, and on each connection calls `run_dispatch`.
## Library boundary contract
This library MUST NOT:
- spawn the dream loop (caller decides),
- initialize telemetry (caller decides),
- load `~/.car/env` (caller decides).
Those bootstraps stay in the embedder's `main`. The contract prevents the dual-memgine bug: if the library silently spawned its own dream loop, embedded users would end up with two memgine engines (the embedder's plus the library's).
## Lock primitive
`ClientSession.memgine` uses `Arc<tokio::sync::Mutex<MemgineEngine>>` per the "one-wrapper rule" — dispatcher handlers can hold the lock across `.await` points without risking poisoning, and tokio's `Mutex` does not poison so a panicking handler does not poison the engine for sibling connections.
## Embedder usage
```rust,ignore
use car_server_core::{ServerState, ServerStateConfig, run_dispatch};
// Build the shared server state.
let state = ServerState::embedded(ServerStateConfig::default()).await?;
// On each accepted WebSocket connection:
tokio::spawn(async move {
run_dispatch(ws, state.clone()).await;
});
```
## The assistant (`car-server-core::assistant`)
The flagship batteries-included general agent behind `car do` — one-shot, REPL,
or `--serve` conversational.
- A `GeneralExecutor` (generalizes `coder::WorktreeExecutor` to any bound
`Substrate`, sharing one bounded `run_shell_on`) exposing files, a real shell,
`http_request` / `web_search`, `calculate`, and durable `remember` / `recall`
(memgine, `~/.car/memory/assistant.json`).
- Driven through a full `Runtime` — validator, policy, permission tiers, event
log. Sandbox-first (`car-sandbox`, `--network none`), with `--local` /
`--full-access` to widen it.
- Conversational via the native `agent.chat` harness (`chat::AssistantService`,
the first native Rust agent-side). Auto-registered in CarHost as
`car-assistant`.
- Tool *descriptions* are the single source of truth for what a tool does; the
system prompt (`assistant::prompt`) carries only posture and the rules no def
or policy expresses, and never re-enumerates the toolset.
- A per-turn **`<runtime-state>` block** carries live run state — the
`todo_write` task list and the subjects of facts written this run — so state
reaches the model whether or not it thinks to `recall`. It is appended to the
*last* message's content rather than added as a trailing `Message::System`,
because the Anthropic and Gemini handlers fold System messages into the
top-level system field and a trailing one would land in the cached prefix and
be rewritten every turn. It never enters durable history, and renders nothing
when there is nothing to say. Run *constants* — working directory, approval
tier — belong in the system prompt's `Environment:` sentence instead, which
compaction pins; putting them here would re-send them every turn and invite
drift from the substrate's own description.
### Verified goals
Live surfaces: `car do --until`, `car do --infer-until`, `agents.chat { goal }`,
`goal.suggest`, `goal.set` / `status` / `clear`, declarative-agent
`goal { check, max_iterations }`, and `goal_evaluated` events. (The macOS Chat
verified-goal strip and iOS push-to-talk both ride this path for chat-capable
agents.)
The goal loop requires `met && grounded`. Final-summary operational claims like
"tests passed" are cross-checked against same-run tool receipts: an unmatched
claim fails a model-judge-dependent verdict closed, while on a deterministic
pass it only annotates the reply with a `[claim check]` note — **completion is
decided by the receipts, not the prose.**
### Declarative agents
They advertise `capabilities: ["chat"]` and are reachable through `agents.chat`
in-daemon, with no spawned child process. Host `agents.chat*` is WS-only;
agent-side FFI exists via `registerChatHandler` / `register_chat_handler` and
`chatEvent` / `chat_event`.
## Where it fits
The full method reference is in [`docs/websocket-protocol.md`](../../../docs/websocket-protocol.md) — that document describes the wire format produced by this crate's dispatcher.