Chidori (v3)
May 28, 2026 · View on GitHub
Chidori (v3)
An agent framework where TypeScript agents can checkpoint, replay, and resume by default.
Star us on GitHub! Join us on Discord.
About v3. Chidori began as a reactive runtime exploring how to build durable, debuggable agents. v3 is a ground-up rewrite that distills those ideas into a smaller, sharper core: a single Rust binary, TypeScript agent authoring, and replay as the foundation for tests, debugging, resume, and human-in-the-loop workflows. Earlier versions of Chidori live in the git history and on prior tags.
Contents
- 📖 About
- ⚡️ Quick Start
- ▶️ Try The Demo
- 🧩 Core Concepts
- 🚦 Running Modes
- 🐍 Python SDK
- ⏪ How Replay Works
- 🧪 Examples
- 🏗 Architecture
- 📦 Project Structure
📖 About
- Agents are TypeScript. Native async control flow, typed inputs, imports, and editor tooling with no template DSL.
- Deterministic execution. Every side effect goes through a host function the runtime can log, cache, and replay.
- Zero-cost checkpointing. Save a session's call log to disk, replay it later for identical output with zero LLM calls.
- Event-driven agents. Agents can run as HTTP servers that react to webhooks and other events.
- Rust core, TS and Python SDKs. The runtime is a single binary. SDKs talk to it over HTTP without native bindings.
⚡️ Quick Start
1. Write an agent
// agents/summarizer.ts
import type { Chidori } from "chidori";
export async function agent(input: { document: string }, chidori: Chidori) {
const summary = await chidori.prompt(
"Summarize in 3 bullets:\n" + input.document,
{ type: "summary" },
);
const actionItems = await chidori.prompt(
"Extract action items:\n" + summary,
{ type: "actions" },
);
return { summary, actionItems };
}
2. Run it
# Set up LLM provider (uses LiteLLM in this example)
export LITELLM_API_URL=http://localhost:4401/v1
export LITELLM_API_KEY=sk-litellm-master-key
# Or use providers directly
# export ANTHROPIC_API_KEY=sk-ant-...
# export OPENAI_API_KEY=sk-...
cargo build
./target/debug/chidori run agents/summarizer.ts \
--input document="Rust is a systems programming language..."
3. Try the example agents
# Interactive example picker
./target/debug/chidori demo
# Minimal agent — no LLM calls needed
./target/debug/chidori run examples/agents/hello.ts --input name=Colton
# Local TypeScript tool — no LLM calls needed
./target/debug/chidori run examples/agents/tool_use.ts \
--input query=chidori --tools examples/tools
# Summarizer with trace
./target/debug/chidori run examples/agents/summarizer.ts \
--input document="Rust is great." --trace
# Parallel host work
./target/debug/chidori run examples/agents/parallel.ts \
--input '{"topic": "runtime snapshots"}'
# Event-driven webhook handler
./target/debug/chidori serve examples/agents/webhook.ts --port 8080
▶️ Try The Demo
The easiest way to explore Chidori is the interactive demo picker:
cargo build
./target/debug/chidori demo
chidori demo shows a numbered list of runnable examples, including demos that
do not need an LLM provider and demos that exercise prompt tracing or streaming
when provider environment variables are configured. Choose Hello agent for
the fastest no-key path.
That demo runs a TypeScript agent, records a durable host-call log, and returns JSON. The direct command is:
./target/debug/chidori run examples/agents/hello.ts --input name=Colton
Expected output:
{
"greeting": "Hello, Colton!"
}
What this demonstrates:
examples/agents/hello.tsexportsagent(input, chidori).- The agent calls
chidori.log(...), so the runtime records a host call. - The agent returns plain JSON, which is what CLI, server, and SDK users receive.
- A checkpoint is written under
examples/agents/.chidori/runs/<run_id>/for trace/replay workflows.
You can inspect the most recent run:
RUN_ID=$(ls -t examples/agents/.chidori/runs | head -1)
./target/debug/chidori trace "$RUN_ID" --dir examples/agents
./target/debug/chidori snapshot "$RUN_ID" --dir examples/agents
Human-In-The-Loop Demo
This demo shows the session API pausing on chidori.input(...) and resuming
from the persisted VM state.
Start the server:
./target/debug/chidori serve examples/agents/input_pause.ts --port 8080
In another terminal, create a session:
curl -s http://localhost:8080/sessions \
-H "Content-Type: application/json" \
-d '{"input":{"request":"ship the TypeScript runtime"}}'
The response will have "status":"paused", an "id", and
"pending_prompt":"Approve this request?". Resume it with:
SESSION_ID=<paste id from the previous response>
curl -s http://localhost:8080/sessions/$SESSION_ID/resume \
-H "Content-Type: application/json" \
-d '{"response":"yes"}'
The completed response includes:
{
"output": {
"request": "ship the TypeScript runtime",
"approved": true
}
}
That flow is the core Chidori loop: TypeScript code runs until a durable host operation pauses, Chidori persists the run, and resume continues from the saved state.
🧩 Core Concepts
An agent is a .ts file that exports an async agent(input, chidori) function. The runtime provides a fixed set of host functions for side effects through the chidori object:
| Function | Purpose |
|---|---|
chidori.prompt(text, { type, ... }) | Send to an LLM, return string or parsed JSON; streamed prompt events carry the optional type |
chidori.template(strOrPath, vars) | Render a Jinja2 template with minijinja |
chidori.tool(name, args) | Invoke a registered tool |
chidori.callAgent(path, input) | Call a sub-agent |
chidori.parallel(fns) | Run functions concurrently |
chidori.input(msg, options) | Human-in-the-loop — pauses execution |
chidori.execJs(...), chidori.execPython(...), chidori.execWasm(...) | Run generated code in a sandbox |
chidori.http(url, options) | Make an HTTP request |
chidori.memory(action, ...) | Persistent storage (key-value + vector) |
chidori.log(msg, data) | Structured logging |
chidori.env(name) | Read environment variables |
chidori.retry(fn, options) | Retry with backoff |
chidori.tryCall(fn) | Capture errors without raising |
See llm.txt for the full API reference.
Streaming Prompt Progress
Agents can label prompt output streams with type so UIs can filter incremental
progress separately from final answers:
const status = await chidori.prompt("Say what work is starting", { type: "progress" });
const answer = await chidori.prompt("Write the final answer", { type: "final" });
When using --stream or POST /sessions/stream, prompt calls emit
prompt_start, prompt_delta, and prompt_end events with stream_id,
seq, and prompt_type. This also works for prompts inside
chidori.parallel(...) branches and chidori.callAgent(...) sub-agents. See
examples/agents/streaming_progress.ts.
🚦 Running Modes
1. One-shot CLI
chidori demo # pick from runnable examples
chidori run agents/my_agent.ts --input key=value
chidori run agents/my_agent.ts --input '{"complex": "input"}'
chidori check agents/my_agent.ts # validate without running
chidori tools --dir tools/ # list available tools
2. HTTP Server (event-driven + session API)
chidori serve agents/my_agent.ts --port 8080
Exposes:
GET /health— health checkANY /*— any request is passed toagent(event)as an event dictPOST /sessions— create a session and run the agent with given inputGET /sessions— list all sessionsGET /sessions/{id}— get session resultGET /sessions/{id}/checkpoint— get the call log and snapshot manifest metadataGET /sessions/{id}/snapshot— inspect snapshot manifest metadata without raw VM bytesPOST /sessions/{id}/resume— resume a pausedinput()or approval sessionPOST /sessions/{id}/replay— replay from a session's checkpointPOST /sessions/{id}/cancel— cancel a running or stored sessionPOST /sessions/stream— run a session with SSE call and prompt progress events
3. Event-Driven Agents
An agent can handle incoming HTTP events:
// agents/webhook.ts
import type { Chidori } from "chidori";
export async function agent(
input: { url: string; payload?: Record<string, unknown> },
chidori: Chidori,
) {
const response = await chidori.http(input.url, {
method: "POST",
body: input.payload ?? { source: "chidori" },
});
return { status: response.status, body: response.body };
}
chidori serve agents/webhook.ts --port 8080
curl -X POST http://localhost:8080/github \
-H "Content-Type: application/json" \
-d '{"action": "opened", "pull_request": {"title": "Add login"}}'
🐍 Python SDK
The Python SDK is a pure-stdlib HTTP client that talks to a running chidori serve instance. No pip install, no native bindings.
import sys
sys.path.insert(0, "sdk/python")
from chidori import AgentClient, Checkpoint
client = AgentClient("http://localhost:8080")
# Create a session (runs the agent with live LLM calls)
session = client.run({"document": "Rust is a systems language."})
print(session.output)
# {"summary": "...", "action_items": "..."}
# Save a checkpoint to disk
checkpoint = session.checkpoint()
checkpoint.save("/tmp/session.json")
Later, replay the session from disk — zero LLM calls:
from chidori import AgentClient, Checkpoint
client = AgentClient("http://localhost:8080")
cp = Checkpoint.load("/tmp/session.json")
# Replay: re-executes the agent but returns cached host-call results
replayed = client.replay(cp)
assert replayed.output == session.output # identical output
⏪ How Replay Works
TypeScript durable runs use deterministic runtime policy plus cached host-call results. Given the same inputs, compatible source hashes, and the same cached results for host calls, agent control flow is expected to produce the same outputs.
- Original run: Every
prompt(),tool(),http()call is logged with seq number + result. - Checkpoint: The call log is a JSON array — save it to disk, send it over the wire, commit it to git.
- Replay: Re-run the agent with the call log pre-loaded. Each host function call checks the log for its seq number — hit returns the cached result instantly, miss executes normally.
This means you can:
- Debug without spending money: save a failing session, replay locally with breakpoints.
- Run deterministic tests: check in a checkpoint, assert the agent's behavior hasn't changed.
- Resume after crashes: the runtime can persist checkpoints after each call; on restart, replay picks up where it left off.
- Pause for human approval:
input()suspends execution; when the human responds, the agent replays to that point and continues.
🧪 Examples
See examples/:
agents/hello.ts— minimal agent, no LLMagents/summarizer.ts— LLM summary pipelineagents/streaming_progress.ts— labelled prompt progress streamsagents/webhook.ts— event-driven HTTP handleragents/tool_use.ts— tool call examplesdk_demo.py— Python SDK with checkpointing + replayprompts/analysis.jinja— shared prompt templatetools/web_search.ts— simple tool definitionlegacy-starlark/— archived Starlark examples kept for migration reference
🏗 Architecture
┌─────────────────────────────────────────────────────┐
│ User code (.ts files, .jinja prompts, SDKs) │
└────────────────────────┬────────────────────────────┘
│
┌────────────────────────▼────────────────────────────┐
│ Rust Core Runtime │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ TypeScript │ │ Host Function│ │ Snapshot / │ │
│ │ Runtime │ │ Registry │ │ Replay │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ LLM Client │ │ Template │ │ HTTP Server │ │
│ │ (providers)│ │ (minijinja) │ │ (axum) │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘
- TypeScript runtime transpiles
.tsagents and exposes a deterministicchidorihost API. - Host functions are the only way agents touch the outside world.
- Snapshot/checkpoint engine records host calls and persists runtime metadata for resume.
- LLM providers (Anthropic, OpenAI, LiteLLM-compatible) are swappable via
reqwest. - Template engine uses
minijinjafor Jinja2 prompt templates. - HTTP server (
axum) powers theservecommand and session API.
See DESIGN.md for the full architecture and design rationale, and TODO.md for the implementation roadmap.
📦 Project Structure
chidori/
├── src/
│ ├── main.rs # CLI entry point
│ ├── server.rs # HTTP server (serve + session API)
│ ├── runtime/
│ │ ├── engine.rs # agent dispatch + runtime persistence
│ │ ├── typescript/ # TypeScript runtime, bindings, tools, transpile
│ │ ├── host_core.rs # language-neutral durable host behavior
│ │ ├── context.rs # Runtime context (call log + replay)
│ │ ├── call_log.rs # Checkpoint data structures
│ │ └── template.rs # minijinja integration
│ ├── providers/
│ │ ├── mod.rs # Provider registry, model routing
│ │ ├── anthropic.rs # Anthropic Messages API
│ │ └── openai.rs # OpenAI-compatible (incl. LiteLLM)
│ └── tools/
│ └── mod.rs # Tool discovery + JSON schema generation
├── sdk/
│ └── python/chidori/ # Python SDK (pure stdlib, no deps)
├── examples/
│ ├── agents/ # Example .ts agents
│ ├── prompts/ # Example .jinja templates
│ ├── tools/ # Example tools
│ ├── legacy-starlark/ # Archived .star examples
│ └── sdk_demo.py # Python SDK demo
├── DESIGN.md # Architecture & design rationale
├── TODO.md # Implementation roadmap
└── llm.txt # Complete API reference for LLM-assisted development