Event-Driven Agents
Run a Chidori agent as a persistent HTTP server with chidori serve. Every incoming request is converted into an event object and passed to your agent(event, chidori) function. The return value becomes the HTTP response.
chidori serve agents/webhook.ts --port 8080This is how you build webhook handlers, chatbots, alert responders, and any agent that reacts to the outside world rather than being invoked ad-hoc.
The event object
Every HTTP request is converted to an object with this shape:
const event = {
method: "POST", // HTTP method
path: "/webhooks/github", // URL path
headers: { // all request headers
"content-type": "application/json",
"x-github-event": "pull_request",
},
query: { token: "abc123" }, // URL query parameters
body: { // parsed as JSON if possible, else string
action: "opened",
pull_request: { title: "..." },
},
}The response
Your agent's return value becomes the HTTP response. Return an object with status, body, and optional headers:
// Full control
return {
status: 200,
body: { result: "processed" },
headers: { "x-request-id": requestId },
}
// Error
return { status: 400, body: { error: "Missing required field" } }
// Shorthand: any object without a "body" key becomes a 200 with that object as the body
return { summary } // → 200 {"summary": "..."}A complete webhook handler
agents/webhook.ts
export async function agent(event, chidori) {
const path = event.path
if (path === "/ping") {
return { status: 200, body: { pong: true } }
}
if (path === "/github") {
return handleGithub(event, chidori)
}
if (path === "/alert") {
return handleAlert(event, chidori)
}
return { status: 404, body: { error: "Unknown path: " + path } }
}
async function handleGithub(event, chidori) {
const body = event.body
const action = body.action ?? "unknown"
const summary = await chidori.prompt(
"Summarize this GitHub event and suggest actions:\n" + JSON.stringify(body),
{ model: "claude-sonnet", maxTokens: 200 },
)
return { status: 200, body: { action, summary } }
}
async function handleAlert(event, chidori) {
const body = event.body
const severity = body.severity ?? "unknown"
const diagnosis = await chidori.prompt(
"You are an SRE. Diagnose this alert — root cause, mitigation, escalation:\n" +
JSON.stringify(body),
{ model: "claude-sonnet", maxTokens: 300 },
)
return { status: 200, body: { severity, diagnosis } }
}Testing
# Start the server
chidori serve agents/webhook.ts --port 8080
# Built-in health check
curl http://localhost:8080/health
# No-LLM ping
curl http://localhost:8080/ping
# GitHub webhook
curl -X POST http://localhost:8080/github \
-H "Content-Type: application/json" \
-d '{"action": "opened", "pull_request": {"title": "Add login", "user": {"login": "alice"}}}'
# Alert
curl -X POST http://localhost:8080/alert \
-H "Content-Type: application/json" \
-d '{"severity": "high", "service": "payments", "message": "p99 latency > 2s"}'Routing patterns
Because agent(event, chidori) is just a TypeScript function, routing is whatever you write:
export async function agent(event, chidori) {
const path = event.path
const method = event.method
// Path-based
if (path === "/webhooks/github") {
return handleGithub(event, chidori)
}
if (path === "/webhooks/slack") {
return handleSlack(event, chidori)
}
// Method-based
if (path === "/tasks" && method === "POST") {
return createTask(event, chidori)
}
if (path === "/tasks" && method === "GET") {
return listTasks(event, chidori)
}
// Header-based
if ("x-github-event" in event.headers) {
return handleGithub(event, chidori)
}
return { status: 404, body: { error: "Not found" } }
}Reading the event payload
The event object is plain typed JSON, so destructuring and optional fields work the way you'd expect:
// Optional field with a fallback
const action = event.body.action ?? "unknown"
// Destructure what you need
const { method, path, headers } = event
// Guard before reaching into nested data
if (event.body?.pull_request) {
// ...
}Sessions come for free
Every request handled by chidori serve is a session with an ID, a recorded call log, and a replay endpoint — so any webhook that hit production can be reproduced locally with zero LLM spend. See Checkpoint & Replay.
