Running modes
Chidori agents run three ways: a one-shot CLI, an HTTP server with a session API, and event-driven HTTP handlers.
1. One-shot CLI
chidori init my-agent --template chat # scaffold a starter project (or: docs, worker)
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 chat --system "You are concise." # interactive multi-turn chat REPL
chidori chat agents/chat.ts # chat through a conversational agent file
chidori check agents/my_agent.ts # validate without runningchidori init [dir] --template docs|chat|worker scaffolds a starter project —
an agent and README. Omit --template to choose interactively. The docs
template chats with a bundled copy of the Chidori docs; the chat template is
a conversational agent; the worker template is an autonomous tool-using loop
whose tools are defined inline with defineTool.
chidori chat is a built-in conversational REPL backed by
chidori.conversation(). With no
agent file it chats with the model directly; pass a conversational agent file
(one accepting { messages, system?, model? } and returning
{ transcript } or { history }, like the chat init 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 and --model. Type exit/quit or Ctrl-D to end.
Every chat session is an ordinary durable run: the session id is announced at
start, each turn journals into .chidori/runs/<session_id> (next to the agent
file, or the cwd for the built-in agent), and the run's input.json always
holds the full dialogue state. chidori chat [FILE] --resume <session_id>
replays the journal — reprinting the transcript for $0, completing a turn that
a crash interrupted mid-generation — and continues the conversation in place.
chidori trace <session_id> inspects a session like any other run.
2. HTTP server (event-driven + session API)
chidori serve agents/my_agent.ts --port 8080The server is deny-by-default: unless you configure a policy
(CHIDORI_POLICY* env vars) or pass --trusted, gated effects (network
requests via fetch/node:http, workspace mutations) are refused — sessions
arrive from callers you may not control. Local chidori run is
ask-by-default: with nothing configured, gated effects pause for a y/a/N
prompt on your terminal (and fail closed without one); pass --trusted for
the permissive allow-all posture when running agents you wrote yourself. See
docs/sandbox-model.md.
It also binds loopback only (127.0.0.1) by default. To make it reachable
from the network, pass --host 0.0.0.0 (or set CHIDORI_HOST) — which
requires CHIDORI_API_KEY to be set, since an exposed unauthenticated server
would let anyone on the network execute agents. See
docs/deployment.md.
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 the durable journal-scaffold manifest metadata (no VM image — resume is call-log replay)POST /sessions/{id}/resume— answer a pausedinput()call and continue the runPOST /sessions/{id}/approve— approve or deny a policy-gated call that paused the runPOST /sessions/{id}/signal— deliver a signal{ name, payload?, from? }: resolves+resumes a run paused-waiting on that name (200); delivers in-memory to a live streaming run, resuming a matching pause in-process (202delivered_live); else enqueues into the durable mailbox (202queued); 409 for a terminal runPOST /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 eventsGET /sessions/{id}/stream— re-attach to a session's SSE events: replays everything already emitted (so a dropped client catches up), then follows a still-running streaming session live until it settles; for a settled session, replays the logged call records and closes with adoneevent carrying the final state
3. Event-driven agents
Any request to a non-session route is folded into an event dict and passed to the agent as its input:
{
"event": {
"method": "POST", // HTTP method
"path": "/alerts/pagerduty", // request path
"headers": { "content-type": "application/json", ... },
"query": { "key": "value" }, // query-string parameters
"body": { ... } // parsed JSON, or the raw string if not JSON
}
}The response mapping: an agent output of {status, body, headers?} becomes
the HTTP response (status code, JSON body, extra headers); any other output
returns as 200 JSON. Two behaviors to design for:
- Every request runs the whole agent — including health probes and
scanner traffic. Branch on
input.eventearly and return a cheap{status: 400, ...}for non-events before any model call, or the strays will cost tokens. (WithCHIDORI_API_KEYset, unauthenticated requests are rejected before the agent runs.) The server short-circuits the most common automatic noise for you: requests for/favicon.ico,/robots.txt,/apple-touch-icon*, and anything under/.well-known/get an immediate empty404without invoking the agent. If your agent genuinely serves those paths, setCHIDORI_SERVE_ALL_PATHS=1to route every path toagent(event)again. - A run that pauses becomes a session. If the agent reaches a
chidori.signal(...)listen point, aninput()call, or a policy approval gate, the server persists it as a real session and answers202 Acceptedwith the session view (id,status,pending_signal_names, ...). Deliver / resume / approve it through the normal/sessions/{id}/*endpoints — a webhook can open a long-lived, human-gated run and hand the caller the id to drive it with.
An agent can also make outbound requests while handling an event:
// agents/webhook.ts
import { run, type JsonObject } from "chidori:agent";
run(async (input: { url: string; payload?: JsonObject }) => {
// `fetch` is the runtime's captured networking surface — policy-gated,
// pausable for approval, and recorded for replay.
const response = await fetch(input.url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(input.payload ?? { source: "chidori" }),
});
return { status: response.status, body: await response.json() };
});chidori serve agents/webhook.ts --port 8080 --trusted # the agent makes a network call via fetch
curl -X POST http://localhost:8080/github \
-H "Content-Type: application/json" \
-d '{"action": "opened", "pull_request": {"title": "Add login"}}'