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 8080

This 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.

Was this page helpful?