Chidori (v3)

May 28, 2026 · View on GitHub

  Chidori (v3)  

An agent framework where TypeScript agents can checkpoint, replay, and resume by default.

GitHub Last Commit crates.io version PyPI version npm version License Apache-2.0


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

  • 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.ts exports agent(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:

FunctionPurpose
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 check
  • ANY /* — any request is passed to agent(event) as an event dict
  • POST /sessions — create a session and run the agent with given input
  • GET /sessions — list all sessions
  • GET /sessions/{id} — get session result
  • GET /sessions/{id}/checkpoint — get the call log and snapshot manifest metadata
  • GET /sessions/{id}/snapshot — inspect snapshot manifest metadata without raw VM bytes
  • POST /sessions/{id}/resume — resume a paused input() or approval session
  • POST /sessions/{id}/replay — replay from a session's checkpoint
  • POST /sessions/{id}/cancel — cancel a running or stored session
  • POST /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.

  1. Original run: Every prompt(), tool(), http() call is logged with seq number + result.
  2. Checkpoint: The call log is a JSON array — save it to disk, send it over the wire, commit it to git.
  3. 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/:

🏗 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 .ts agents and exposes a deterministic chidori host 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 minijinja for Jinja2 prompt templates.
  • HTTP server (axum) powers the serve command 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