Skip to content

HTTP server and client ​

Durable runs, static graphs, and custom tasks are exposed through two opt-in subpaths. The existing @anvia/server and @anvia/client entrypoints do not load the durable runtime or SQLite, and the browser client uses the validated @anvia/durable/protocol entrypoint.

sh
pnpm add @anvia/durable @anvia/server @anvia/client

Server handler ​

ts
import { createDurableHandler } from '@anvia/server/durable'

const handleRequest = createDurableHandler({
  runtime,
  basePath: '/durable',
  maxBodyBytes: 65_536,
  authorize: async (request, resource) => {
    const user = await authenticate(request) // supplied by your application
    return user !== null && (await canAccessSession(user, resource.sessionId, resource.action))
  },
})
// Mount handleRequest in any Fetch-compatible server; keep runtime at application scope.

authorize is required for every operation, including reads and event subscriptions. It receives a DurableAuthorization resource with action (list, submit, inspect, events, respond, resolve-tool, retry, cancel, signal, or resolve-effect), the owning sessionId, and where applicable runId, agentId, graphId, taskId, rootTaskId, and taskName. Existing-run authorization uses the session stored in the database, never a client-supplied one. Owned agent runs map to their parent task's session. Graph children authorize against the graph's owning session, and graph-wide operations authorize every task. Listings require a sessionId. Your host supplies identity, CORS and CSRF policy, and lifecycle. SSE authorization is checked at connection time (task tree events reauthorize each emitting task).

Routes ​

MethodPath under basePathResult
POST/runsSubmit { agentId, sessionId, requestId, prompt, enqueue? }; returns a snapshot (202).
GET/runs?sessionId=...Summaries; optional agentId, status, after, limit.
GET/runs/:idAtomic snapshot.
GET/runs/:id/events?after=...SSE events; Last-Event-ID is the fallback cursor.
POST/runs/:id/respond{ interactionId, response } (204).
POST/runs/:id/resolve-tool{ operationId, output } (204).
POST/runs/:id/retryExplicit retry (204).
POST/runs/:id/cancelPersist cancellation (204).
POST/graphsSubmit a graph and return its snapshot (202).
GET/graphs?sessionId=...List graphs; optional after, limit.
GET/graphs/:idAtomic topology, node state, and cursor.
GET/graphs/:id/events?after=...SSE task events.
POST/graphs/:id/cancelCancel unfinished work (204).
POST/tasksSubmit { name, version, sessionId, requestId, input } to a registered definition.
GET/tasks?sessionId=...Paginated root tasks.
GET/tasks/:idAtomic snapshot and cursor.
GET/tasks/:id/graphEntire ownership tree.
GET/tasks/:id/eventsReconnectable tree SSE; after or Last-Event-ID.
POST/tasks/:id/signal{ name, requestId, value }.
POST/tasks/:id/resolve-effect{ key, value }.
POST/tasks/:id/retryResume a task needing attention.
POST/tasks/:id/cancelCancel the selected subtree.

JSON bodies default to a 64 KiB limit (maxBodyBytes). Errors map to 400 (validation), 403 (denied), 404, 409 (conflict), 413 (oversized body), 415 (media type), 429 (durable limit), and 500 without internal diagnostics. Responses disable caching, and SSE frames use the durable event sequence as their id. A transport error closes the stream without cancelling execution. Task cancellation returns 409 without mutating the tree if children appeared during authorization; retry to authorize the new tree.

Executable code is registered on the server. Clients only select registered names and versions and supply JSON, so allowlist the task definitions and agents each caller may use: granting submission or retry of a definition authorizes its spawning behavior. No global metrics or health route is exposed, because the authorization contract is session scoped. Export runtime.health() and runtime.metrics() through your own authenticated endpoint.

Browser client ​

ts
import { DurableClient } from '@anvia/client/durable'

const client = new DurableClient({
  endpoint: 'https://your-app.example/durable',
  headers: () => ({ Authorization: `Bearer ${getAccessToken()}` }),
})
const accepted = await client.submit(
  {
    agentId: 'researcher',
    sessionId: 'research-session',
    requestId: 'research-job-42',
    prompt: 'Summarize these notes...',
  },
  { enqueue: true },
)
const snapshot = await client.snapshot(accepted.run.id)
render(snapshot)
for await (const event of client.stream(accepted.run.id, { after: snapshot.cursor })) {
  renderEvent(event)
}

DurableClientOptions are endpoint (an absolute HTTP(S) base URL with no credentials, query, or fragment), fetch, headers (a value or an async function), and credentials.

AreaMethods
Runssubmit, snapshot, listRuns, stream, respond, resolveTool, retry, cancel
GraphssubmitGraph, graphSnapshot, listGraphs, streamGraph, cancelGraph
TaskssubmitTask, taskSnapshot, taskGraph, listTasks, streamTask, signalTask, resolveEffect, retryTask, cancelTask

Requests accept an abortSignal; aborting a stream detaches only that subscriber. The client validates incoming snapshots and events, rejects wrong-run, wrong-root, and out-of-order events, and throws DurableHttpError with the HTTP status on a failed response. It never retries mutations automatically. On a connection failure, reconnect from a fresh snapshot or from the last event cursor your application applied. Durable progress events are not the token-delta chat protocol used by the standard client transport.

Built for Anvia.