Skip to main content
Agent serves an HTTP webhook and runs your handler once per dispatch. It runs under an active execution context, so defineTool, rebunoFetch, and step() record durably.

The handler

The handler is the async function you hand to agent.serve() or agent.bind(), named process in the examples below. It takes one argument, the execution’s input object, passed through unchanged:
The return value becomes the execution’s output and has to be JSON-serializable.

Validating input

Pass an inputSchema, any Standard Schema validator such as Zod, Valibot, or ArkType, to validate and coerce input:
Validation happens before your handler runs. A failure fails the execution with the collected issue messages. Your handler receives the validated and coerced value. Without an inputSchema, input is passed through as-is.

serve() vs fetch

serve() binds the handler and serves it with node:http. It blocks, resolving only when the server closes:
You can also bind separately and serve later:
Use agent.fetch to mount the agent into an existing service or an edge runtime. It is a Web-standard (Request) => Promise<Response> handler with the webhook logic already wired:
agent.fetch reads the request body, verifies the signature, and returns the response, so it doesn’t care what server calls it. agent.serve is a thin node:http wrapper around it.

Dispatch and resume

Each webhook POST carries an execution_id, a dispatch_id, and a dispatch_attempt. The agent:
  1. Verifies the Rebuno-Signature header (see Signing). A bad or missing signature gets a 401.
  2. Acknowledges immediately. The handler runs in a background task and the webhook returns 200 right away, so delivery isn’t held open for the whole execution.
  3. Aborts the handler still running for that execution when the webhook supersedes it, so a re-delivery doesn’t leave two copies racing. The superseded run’s kernel client is scoped to the aborted signal, so it can neither renew the lease nor write for a dispatch it no longer owns. A repeat of the attempt already running, or of one the kernel has moved past, is ignored.
  4. Skips terminal executions. Nothing to do if it is already completed, failed, or cancelled.
  5. Runs. It fetches the execution’s input, validates it, and calls your handler under the ambient execution context.
The same handler runs on every dispatch, and each recorded step returns its stored result instead of executing again. See How it works for what breaks this. A Blocked or Terminated your handler swallows still ends the dispatch correctly, because the execution context remembers it and Agent re-throws it. A denial or rate limit has no such backstop and has to escape the handler, but it may arrive wrapped in a provider SDK’s own error type, which raiseForRefusal() unwraps.

What happens on failure

The agent maps outcomes from your handler onto the execution: See Errors for what each class means.

Lifecycle

serve() keeps running until the server closes. Call these directly only when you drive agent.fetch yourself and want to drain in-flight handlers before shutdown.