# Chidori TypeScript API Reference

Chidori runs TypeScript agents in a Rust runtime. Agents use normal async
TypeScript for orchestration, while all side effects go through the injected
`chidori` host object so the runtime can log, replay, stream, pause, and resume
work.

This file is optimized as an LLM-facing reference for generating Chidori agents
and tools.

## Agent Shape

An agent is a `.ts` file that exports `agent(input, chidori)`:

```ts
import type { Chidori } from "chidori:agent";

export async function agent(input: { document: string }, chidori: Chidori) {
  const summary = await chidori.prompt(
    "Summarize in three bullets:\n\n" + input.document,
    { type: "final" },
  );
  return { summary };
}
```

Rules:

- Export `async function agent(input, chidori)`, or import `{ run }` from
  `chidori:agent` and call `run(handler)` — the `agent` export is the fallback
  entrypoint when `run(...)` wasn't called.
- Return JSON-compatible values only.
- Use `chidori.*` for LLMs, tools, input, signals, memory, templates, workspace
  files, and logging. HTTP goes through the standard `fetch`, which the runtime
  captures.
- Prefer deterministic code. Durable runs use fixed `Date` and seeded
  `Math.random` policies by default.
- Local TypeScript imports are governed by runtime policy. Dynamic imports are
  rejected.

## CLI

```bash
chidori init my-agent --template chat      # scaffold a starter project (or: docs, worker)
chidori check agents/my_agent.ts
chidori run agents/my_agent.ts --input key=value
chidori run agents/my_agent.ts --input '{"document": "text"}'
chidori run agents/my_agent.ts --stream
chidori run agents/my_agent.ts --trace
chidori chat --system "You are a concise assistant." --model claude-sonnet
chidori chat agents/chat.ts                 # chat through a conversational agent file
chidori serve agents/webhook.ts --port 8080 --trusted
chidori resume agents/my_agent.ts <run_id>  # replay a recorded run byte-for-byte with zero model calls (durable resume)
chidori trace <run_id>
chidori snapshot <run_id>
```

`chidori init [dir] --template docs|chat|worker` scaffolds a starter project
(agent + README, plus a `tools/` dir for `worker` and a bundled docs corpus for
`docs`); omit `--template` to pick interactively. The `docs` template chats
with the bundled Chidori docs; the `chat` template is a conversational agent;
the `worker` template is an autonomous tool-using loop (see
`examples/agents/worker.ts`).

`chidori chat` is an interactive multi-turn REPL backed by `conversation()`. With
no agent file it chats with the model directly; pass a conversational agent file
(one accepting `{ messages, system?, model?, tools? }` and returning
`{ transcript }` or `{ history }`, like the `chat` template) to chat through it.
Each turn is a durable host call and streams its reply token-by-token; the prior
turns replay for free, so only your newest message reaches the provider. Flags:
`--system`, `--model`, `--tools <dir>`. Type `exit`/`quit` or Ctrl-D to end.

`chidori snapshot <run_id>` prints `runtime.snapshot.json` metadata without
printing raw VM snapshot bytes.

## Host Object

The runtime injects a `chidori` object with these methods.

### prompt

```ts
const text = await chidori.prompt("Write a concise answer", {
  type: "final",
  model: "claude-sonnet",
  maxTokens: 500,
  temperature: 0.2,
  tools: ["web_search"],
});
```

Options:

- `type`: label for streamed prompt output, such as `"progress"`, `"draft"`,
  `"subagent"`, or `"final"`.
- `model`: provider model override.
- `system`: system prompt for this call.
- `maxTokens`: output token cap.
- `maxTurns`: cap on provider tool-use turns.
- `temperature`: sampling temperature.
- `tools`: registered tool names available to provider tool-use loops.
- `format`: `"json"` parses the reply as JSON (falls back to the raw string).
- `cache`: prompt-cache posture. Defaults to on (`"5m"`): the runtime marks
  the stable request head (system, tools, conversation prefix) so providers
  bill repeated prefixes at the cached rate. `false` disables for this call;
  `"1h"` requests the extended TTL. Caching never changes a response.

When streaming is enabled, prompt events include `prompt_type`, `stream_id`,
and `seq` so UIs can filter progress streams separately from final-answer
streams.

### context

```ts
const base = chidori
  .context()
  .system("You are a policy analyst.")
  .doc("policy-corpus", corpusText) // large stable reference block
  .cacheBreakpoint("5m");           // freeze the head as a cacheable prefix

let ctx = base;
for (const q of questions) {
  ctx = ctx.user(q);
  const { text, context } = await ctx.prompt({ type: "final" });
  ctx = context; // assistant turn appended; the prefix stays shared
}
```

An immutable, turn-structured prompt context. Builder methods (`system`,
`tools`, `doc`, `user`, `assistant`, `toolResult`, `cacheBreakpoint`) each
return a NEW context sharing the parent's segments, so `base.user("a")` and
`base.user("b")` are independent forks of the same prefix. Building is pure
in-VM work; only `prompt()` / `respond()` perform a durable host call. The
stable head is auto-marked for provider prompt caching, so each turn after the
first reads the shared prefix at the discounted cached rate.

- `prompt(options?)` → `{ text, context }`: send, return the answer plus the
  context extended with the assistant turn (including any tool-use exchange).
- `respond(options?)` → `{ response, context }`: one structured turn for
  author-driven tool loops (`response.toolCalls`, `response.blocks`).
- `digest()` → stable content hash of the assembled request, also recorded in
  each prompt's call-log args as `request_digest`.
- `estimateTokens()` → rough local size estimate for window budgeting.
- `compact(options?)` → `Promise<Context>`: explicit, opt-in window
  compaction. Summarizes the older conversation turns into ONE durable
  summary segment (a recorded `prompt` host call, so it replays
  deterministically) and returns a new context: stable head + summary +
  fresh cache breakpoint + the newest `keepTurns` turns (default 2) kept
  verbatim. `budgetTokens` makes it a pure no-op (no host call) while
  `estimateTokens()` is within budget, so loops can call it unconditionally;
  `model` / `instructions` / `maxTokens` / `ttl` tune the summarizer.
  Compaction is never automatic — it changes what the model sees, so it is
  always an author decision.

See `examples/agents/context_qa.ts` for a complete corpus-Q&A agent.

### conversation

```ts
const chat = chidori.conversation({
  system: "You are a concise, friendly assistant.",
  tools: ["web_search"],          // available on every turn
  compact: { budgetTokens: 8000 }, // opt-in per-turn window management
});

const reply = await chat.say("Hi, who are you?"); // one durable prompt call
await chat.say("What can you help with?");         // prefix read at cached rate

chat.length;       // number of completed exchanges
chat.history();    // [{ role, text }, ...]
chat.context;      // the underlying immutable Context, for the lower-level API
```

A stateful chat-assistant wrapper over `context()` — the most common agent
shape. It owns the running dialogue: the system/tools head is frozen once as a
cacheable prefix, and each `say(message)` appends the user turn, makes one
durable `prompt` host call, and threads the assistant turn back in for the next
message. So the whole conversation is recorded, replays for $0, and reads the
shared prefix at the cached rate each turn.

- `say(message, options?)` → `Promise<string>`: send a user message, return the
  assistant reply text; the dialogue advances in place. `options` are
  per-turn `PromptOptions` (override the conversation defaults).
- `respond(message, options?)` → `Promise<LlmResponseJson>`: like `say()` but
  returns the structured response (`toolCalls`, `blocks`) for author-driven
  tool loops; append results with `chat.context.toolResult(...)`, then `say()`.
- `loop(options?)` → `Promise<{role,text}[]>`: drive an interactive dialogue —
  read each human message via `chidori.input()` (terminal stdin under
  `chidori run`, a paused session resume under `chidori serve`), reply with
  `say()`, repeat until the user types an exit word (`"exit"`/`"quit"`) or
  `until` returns true. Options: `prompt`, `inputOptions`, `exit`, `maxTurns`,
  `skipEmpty`, `turn`, `onReply`, `until`.

`conversation(options)` accepts `system`, `tools`, default `type`/`model`/
`maxTokens`/`temperature`/`cache`, `cacheTtl`, and `compact` (a `CompactOptions`
applied before each turn — a no-op until the tail exceeds budget). See
`examples/agents/conversation.ts`.

Setting `CHIDORI_PROMPT_CACHE_DIR=<dir>` opts into a local content-addressed
prompt cache keyed on the assembled `request_digest`: an exact repeat of a
prompt (same model, system, tools, messages, cache layout) — even from a
different run — is served locally without calling the provider, then recorded
as a normal call-log entry with the identical result (and no token usage,
since nothing was billed). The cache is live-path only: replay always
short-circuits to the call log first and never consults it.

### input

```ts
const answer = await chidori.input("Approve this request?", {
  type: "approval",
  choices: ["yes", "no"],
  default: "no",
});
```

In server mode, `input()` pauses the session. Resume it with
`POST /sessions/{id}/resume` or `AgentClient.resume(id, response)`.

### signal / pollSignal

```ts
// Pause at a named listen point until an outside party (human or agent)
// delivers { name, payload, from } via POST /sessions/{id}/signal. A durable
// per-run mailbox absorbs signals that arrive before the agent listens.
const review = await chidori.signal("review");

// With timeoutMs, resolves to { timedOut: true } after the deadline.
const r = await chidori.signal("review", { timeoutMs: 60000 });
if (r.timedOut) { /* nobody answered */ }

// Non-blocking: consume a queued signal or get null (recorded, replayable).
const steer = await chidori.pollSignal("steer");

// Fan-in: pass an array to pause until ANY listed name fires; result.name
// says which.
const fired = await chidori.signal(["review", "steer"]);
```

Every consumed signal is recorded in the call log, so multiplayer sessions
replay deterministically. Signals delivered to a run streaming over
`POST /sessions/stream` are pushed into the live agent's mailbox in-memory and
resume a matching pause in-process. See `docs/signals.md`.

### tool

```ts
const result = await chidori.tool("web_search", { query: "snapshot runtime" });
```

Tools discovered from disk are TypeScript `.ts` modules or MCP-backed remote
tools. Tool directory discovery ignores `.star` files.

### callAgent

```ts
const child = await chidori.callAgent("child.ts", { topic: "snapshots" });
```

Sub-agents share the parent runtime context and call log. Runtime dispatch
accepts TypeScript `.ts` sub-agents only.

### util.parallel

```ts
const [a, b] = await chidori.util.parallel([
  () => chidori.prompt("Draft option A", { type: "draft" }),
  () => chidori.prompt("Draft option B", { type: "draft" }),
]);
```

An in-VM helper with `Promise.all` semantics; `options.concurrency` caps
in-flight tasks. Everything under `chidori.util` is pure JavaScript control
flow and records nothing itself — only the durable calls made inside the
tasks appear in the journal.

### branch

```ts
const outcomes = await chidori.branch([
  { label: "outline-first", source: "strategies/outline_first.ts", input: { research } },
  { label: "draft-direct", source: "strategies/draft_direct.ts", input: { research } },
]);
const best = outcomes.filter((o) => o.status === "completed").reduce(pick);
```

Fork the run into one sub-run per variant from the current anchored state (the
parent's VFS plus each variant's explicit `input`). Each branch runs its own
source module (resolved like `callAgent` paths) on a fresh context whose
records occupy a reserved, disjoint sequence range nested under the `branch`
call, and returns `{ label, branchId, status, output?, pendingPrompt?,
error? }`. The whole fan-out is one recorded durable call: replay returns the
outcomes from the call log without re-running the branches. Variants run in
waves of `options.concurrency` worker threads (default 1 — sequential);
outcome order always follows variant order. Nested `chidori.branch` inside a
branch is rejected.

When the parent run persists, each branch is stored under
`.chidori/runs/<run>/branches/op-<seq>/branch-<k>/` (`source.ts`,
`checkpoint.json`, `branch.json`, plus the fork-time VFS anchor), making
branches independently operable after the parent moves on:

```bash
chidori branches <run-id>                                   # list branch stores
chidori branch-resume <run-id> <branch-id> --value "blue"   # answer a paused input()
chidori branch-rerun <run-id> <branch-id>                   # re-run edited source.ts from the anchor
```

A resumed or re-run branch updates only its own store; the parent's recorded
`branch` outcome is immutable history. See `docs/branching-execution.md`.

### actors: spawn / send / receive / join / stop / status / lookup

```ts
// Start an agent module as a supervised, addressable, concurrent process.
// spawn returns a HANDLE: the actor's address plus its lifecycle methods.
const worker = await chidori.actors.spawn("workers/researcher.ts", { topic }, {
  name: "researcher",      // optional registry name for actors.lookup / actors.send
  restart: "resume",       // "never" (default) | "clean" | "resume"
  maxRestarts: 3,
  backoffMs: 500,          // doubles per attempt
});

// Message passing (never blocks). String-addressed sends take a pid, a
// registered name, or "parent" (the sender's spawner).
await worker.send("focus", { region: "EU" });
await chidori.actors.send("researcher", "focus", { region: "EU" });

// Blocking in-place consumption, in delivery order; fan-in via an array.
const msg = await chidori.receive("draft");   // { name, payload, from: { kind, id } }
const any = await chidori.receive(["draft", "cancel"], { timeoutMs: 60000 });
if (any.timedOut) { /* deadline passed */ }

// Settle: fold the actor's records into this run's log and get the outcome.
const outcome = await worker.join();
// → { pid, status: "completed"|"failed"|"paused"|"stopped", output?, error?, restarts }
await worker.stop();                          // cooperative stop, then join
await worker.status();                        // { pid, status, restarts, mailbox, waitingFor? }
await chidori.actors.lookup("researcher");    // a handle, or null
```

Actors are supervised siblings of branches (`docs/actors.md`): each runs its
own source module on an isolated VM and its records occupy a reserved,
disjoint sequence range, but an actor is detached and addressable — it runs
concurrently on its own thread with a durable mailbox. Inside an actor,
`chidori.signal`/`pollSignal` consume actor messages too (the mailbox uses
the signal envelope; `from.id` is the sending actor's pid, or `"run"`), and
`receive` waits in place without pausing the run. Restart strategies:
`clean` re-runs from scratch; `resume` replays the actor's accumulated log
minus the trailing failed records, so completed work returns from cache and
only the failing call retries. Every primitive is a durable call — replay of
the spawning run re-runs nothing — and a crash before a join re-creates the
actor on resume from the recorded spawn/send records.

Actors form **supervision trees**: an actor can spawn its own supervised
children (each level carves child sequence ranges out of its own, bounding
depth at four actor levels), `"parent"` addresses the sender's spawner (the
owning actor, or the run for a top-level actor), and join/stop are owner-only
— an actor is settled by whoever spawned it. Supervisors reap their children:
when an actor settles (or discards its log on a `clean` restart) its
still-live children are cooperatively stopped and their registered names
released; a `resume` restart keeps children (cached spawn records return the
same live pids). A run may spawn at most 128 actors in total, restarted
children included.

### agents: detached durable processes (spawn / send / join / stop / status / lookup)

```ts
// Start an agent module as a DETACHED durable process: its own run id and
// journal, a registered name that outlives this run, a durable mailbox, and
// a hibernate/wake lifecycle. spawn returns a handle like actors.spawn.
const svc = await chidori.agents.spawn("services/inbox-triager.ts", {}, {
  name: "inbox-triager",   // registry name; generated when omitted
  restart: "resume",       // "never" | "clean" | "resume" (default)
  maxRestarts: 3,
  backoffMs: 500,
});

await svc.send("email", { from: "a@x.com" });          // durable delivery; wakes a
await chidori.agents.send("inbox-triager", "email", p); // hibernating agent on match
await svc.status();   // { name, runId, status, restarts, waitingFor?, deadline?, output?, error? }
await svc.join({ timeoutMs: 30000 });   // waits for completed|failed|stopped|paused
await svc.stop();                        // cooperative stop
await chidori.agents.lookup("inbox-triager");   // { name, runId, status } handle, or null
```

Detached agents vs actors: an actor lives inside its spawning run (records
fold into the parent at a join; unjoined work dies with the run); a detached
agent is its own durable run and **outlives the spawner**. Inside the agent,
`chidori.signal(name)` is a hibernate point — the agent holds no thread and
no VM while waiting; a matching send (from any run, or `POST
/agents/detached/{name}/send` on the server) wakes it under resume-by-replay.
The fleet survives process restarts: `chidori serve` re-arms every
registered agent at boot. Requires persistence. Statuses: `running`,
`hibernating`, `paused` (interactive input/approval — no interactive
counterpart yet), `completed`, `failed`, `stopped`. See
`docs/detached-agents.md`.

### alarm

```ts
const fired = await chidori.alarm(24 * 60 * 60 * 1000);  // → { timedOut: true }
```

A durable timer on the signal machinery: the run (or detached agent)
hibernates and is woken at the deadline, surviving process restarts — the
deadline is persisted and re-armed at boot. In a detached agent this is the
idiomatic "do maintenance every N hours even with no traffic" primitive.

### http (fetch / node:http)

There is no `chidori.http`. Use the standard `fetch` API (or the
`node:http`/`node:https` client modules); the runtime replaces them with
captured versions backed by one policy-gated host op:

```ts
const response = await fetch("https://example.com/webhook", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ ok: true }),
});
const data = await response.json();
```

Because the capture lives at the base networking layer, every request — even
one made inside a dependency — is policy-checked, logged, and replayed from the
call log when available. `fetch` returns a standard `Response`
(`status`/`ok`/`headers`/`json()`/`text()`).

### template

```ts
const prompt = await chidori.template("prompts/summary.jinja", {
  document: input.document,
});
```

Use templates for reusable prompt text. Inline templates are also supported.

### memory

```ts
await chidori.memory.set("draft", { text: "..." });
const draft = await chidori.memory.get("draft");
const keys = await chidori.memory.list();
await chidori.memory.delete("draft");
await chidori.memory.clear();
```

Memory actions are logged and replay-aware.

### workspace

```ts
const entries = await chidori.workspace.list({ completeOnly: true });
const text = await chidori.workspace.read("notes/draft.md");
const entry = await chidori.workspace.write("notes/draft.md", "...", { language: "markdown" });
await chidori.workspace.delete("notes/draft.md", "superseded");
const manifest = await chidori.workspace.manifest();
```

Durable file store rooted at the run's workspace directory
(`CHIDORI_WORKSPACE_ROOT` when set). Entries carry `{ path, status, sha256,
bytes }`. Every action is policy-gated (`workspace:write` /
`workspace:delete` are refused under `untrusted`) and recorded in the call
log. `remove` is an alias for `delete`.

### appData

```ts
await chidori.appData.write("insert into notes (body) values ($1)", ["hi"]);
const rows = await chidori.appData.query("select * from notes", []);
```

Host-brokered writes/queries against a run-bound app-data cluster (generative
UI). Params are bound server-side, never string-concatenated; the guest never
holds a DB credential. Journaled like `http`. Requires a host-side
`CHIDORI_APP_DATA` binding; without one, calls return
`{ appDataError: { kind: "no_cluster", ... } }`.

### renderDOM

```ts
document.body.appendChild(document.createElement("div"));
const batch = chidori.renderDOM();
```

Agents get a virtual `document` / `window`. `renderDOM()` flushes the pending
DOM mutation batch as a journaled `dom_render` effect — recorded live, served
from the journal on replay. See `docs/dom-runtime-prototype.md`.

### mark

```ts
await chidori.mark("after-draft", { tokens: 120 });
```

`mark()` records a labelled call-log marker — an annotation for the trace,
nothing more (the durable VALUE checkpoint is `chidori.step`). Persisted runs
also refresh snapshot manifest metadata at durable host safepoints.

### step

```ts
const plan = await chidori.step("plan", () => buildPlanDeterministically(input));
```

`step(name, fn)` is a durable value checkpoint: `fn` runs once and its
JSON-serializable result is journaled; replay and resume return the recorded
value (or re-throw the recorded error) without re-running `fn`. Wrap expensive
deterministic computation in a step so resuming a long run does not re-pay it.
The callback must be pure, synchronous compute: host effects, captured
randomness, filesystem writes, timers, and async callbacks throw inside a step.
See `docs/value-checkpoints.md`.

### log

```ts
await chidori.log("Fetched candidates", { count: 3 });
```

Use structured logs for progress and debugging. Logs are call-log records.

### util.retry and util.tryCall

```ts
const value = await chidori.util.retry(
  () => fetch("https://example.com").then((r) => r.json()),
  { attempts: 3 },
);

const result = await chidori.util.tryCall(() => chidori.tool("maybe_fails", {}));
if (!result.ok) {
  await chidori.log("Tool failed", { error: result.error });
}
```

`RetryOptions` also accepts `delayMs` and `backoff`, but the helper
retries immediately — no delay is applied between attempts.

## TypeScript Tool Shape

A TypeScript tool exports JSON-compatible metadata and an async
`run(args, chidori)` function. Tool discovery evaluates the exported `tool`
value in a restricted metadata VM context before registration.

```ts
import type { Chidori, ToolDefinition } from "chidori:agent";

export const tool: ToolDefinition = {
  name: "web_search",
  description: "Search the web for a short query.",
  parameters: {
    type: "object",
    properties: {
      query: { type: "string", description: "Search query" },
    },
    required: ["query"],
  },
};

export async function run(args: { query: string }, chidori: Chidori) {
  await chidori.log("Running web_search", { query: args.query });
  return { query: args.query, results: [] };
}
```

Tool metadata must be JSON-compatible. The `parameters` field is a JSON Schema
object and is used for provider tool calling.

## Streaming

CLI:

```bash
chidori run examples/agents/streaming_progress.ts --stream
```

HTTP:

```ts
for await (const event of client.stream({ topic: "snapshots" })) {
  if (event.type === "prompt_delta" && event.prompt_type === "progress") {
    process.stdout.write(event.delta);
  }
}
```

Stream event types:

- `call`: a host call record.
- `prompt_start`: prompt stream started.
- `prompt_delta`: incremental token text.
- `prompt_end`: prompt stream ended.
- `paused`: the run paused at a `signal()` listen point and
  stays live; a delivered signal (or the timeout deadline) resumes it on the
  same stream.
- `done`: run completed, failed, or paused.

Prompt labels work inside sub-agents and parallel branches because prompt
events are emitted through the shared runtime context.

## Sessions

Start a server:

```bash
chidori serve examples/agents/webhook.ts --port 8080 --trusted
```

`chidori serve` is deny-by-default: without `--trusted` or explicit
`CHIDORI_POLICY*` configuration, gated effects (network requests via
`fetch`/`node:http`, workspace mutations) are refused under the built-in
`untrusted` profile (read-only
workspace introspection stays allowed). `--untrusted` forces that profile over
any env configuration; `chidori run` keeps the permissive default.

Session endpoints:

- `POST /sessions`: create and run a session.
- `GET /sessions`: list sessions.
- `GET /sessions/{id}`: get session state.
- `GET /sessions/{id}/checkpoint`: get call log plus optional
  `snapshot_manifest`.
- `GET /sessions/{id}/snapshot`: get only snapshot manifest metadata.
- `POST /sessions/{id}/resume`: resume a paused `input()` session.
- `POST /sessions/{id}/signal`: deliver `{ name, payload?, from? }` — resolves
  a matching signal pause (200), delivers live to a streaming run
  (202 `delivered_live`), or enqueues into the durable mailbox (202 `queued`).
- `POST /sessions/{id}/approve`: approve or deny a policy-gated call that
  paused the run as `awaitingapproval`.
- `POST /sessions/{id}/cancel`: cancel a running session.
- `POST /sessions/{id}/replay`: replay from a call-log checkpoint.
- `POST /sessions/stream`: run with SSE events.

Use `CHIDORI_DB_PATH=/path/to/chidori.sqlite` to persist sessions across server
restarts.

## TypeScript SDK

```ts
import { AgentClient, Checkpoint } from "@1kbirds/chidori";

const client = new AgentClient("http://localhost:8080");
const session = await client.run({ document: "Rust is a systems language." });

const checkpoint = await session.checkpoint();
const replayed = await client.replay(checkpoint);

if (checkpoint.snapshotManifest) {
  console.log(checkpoint.snapshotManifest.pending?.kind);
}

const manifest = await client.getSnapshotManifest(session.id);
```

`Checkpoint` contains the replay call log and optional snapshot manifest
metadata. It does not contain raw VM snapshot bytes.

## Runtime Policy

Durable TypeScript runs record policy in the snapshot manifest:

- `typescript_imports`: `none`, `relative`, or `project`.
- `date`: `disabled`, `fixed`, or `host`.
- `random`: `disabled`, `seeded`, or `host`.
- `maps_sets`: `reject` or `serialize`.

Environment overrides:

```bash
CHIDORI_TS_IMPORTS=relative
CHIDORI_TS_DATE=fixed
CHIDORI_TS_RANDOM=seeded
CHIDORI_SNAPSHOT_MAPS_SETS=reject
```

Durable snapshot runs reject host clock and host randomness.

For local smoke tests without provider credentials, set
`CHIDORI_TEST_LLM_RESPONSE` to a static response string. This registers a
catch-all test provider and avoids external network calls.

## Snapshot And Replay Notes

Current resume/replay uses the call log plus persisted host-promise metadata:
the agent re-executes and host calls return cached records. Persisted
TypeScript runs also write `runtime.snapshot` and `runtime.snapshot.json`
metadata. Replay **is** the resume mechanism by design — there is no direct VM
continuation from `runtime.snapshot`; the manifest carries journal/scaffold
metadata rather than serialized VM bytes.

Snapshot manifests record:

- ABI version and engine fork.
- Runtime policy.
- Entry source hash and imported module hashes.
- Pending host operation, if any.
- Host promise table records, including pending/resolved/rejected state.
- Call-log length and snapshot blob filename.

Resume rejects incompatible source hashes, policy, or ABI before trusting
snapshot metadata or raw VM snapshot bytes.
