# Hono Adapter

URL: https://cogitator.app/docs/server-adapters/hono

> Deploy Cogitator agents to any JavaScript runtime — Node.js, Bun, Deno, and Cloudflare Workers — using the lightweight Hono adapter.

The `@cogitator-ai/hono` package provides `cogitatorApp`, a factory function that returns a Hono app with all Cogitator routes pre-configured. Hono's multi-runtime design means you can deploy the same agent API to Node.js, Bun, Deno, or Cloudflare Workers without code changes.

## Installation

```bash
pnpm add @cogitator-ai/hono hono
```

## Basic Setup

```typescript
import { serve } from '@hono/node-server';
import { Cogitator, Agent, tool } from '@cogitator-ai/core';
import { cogitatorApp } from '@cogitator-ai/hono';
import { z } from 'zod';

const cogitator = new Cogitator({
  llm: {
    defaultModel: 'anthropic/claude-sonnet-5-5',
    providers: {
      anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! },
    },
  },
});

const summarizer = new Agent({
  name: 'summarizer',
  description: 'Summarizes text in a few sentences',
  model: 'anthropic/claude-sonnet-5-5',
  instructions: 'You summarize text concisely. Keep it under 3 sentences.',
});

const app = cogitatorApp({
  cogitator,
  agents: { summarizer },
  enableSwagger: true,
});

serve({ fetch: app.fetch, port: 3000 });
```

## App Options

```typescript
interface CogitatorAppOptions {
  cogitator: Cogitator;
  agents?: Record<string, Agent>;
  workflows?: Record<string, Workflow>;
  swarms?: Record<string, SwarmConfig>;
  auth?: AuthFunction;
  enableSwagger?: boolean;
  swagger?: SwaggerConfig;
  enableWebSocket?: boolean;
  websocket?: WebSocketConfig; // { path?, maxPayloadSize?, upgradeWebSocket? }
  bodyLimit?: number; // max request body size in bytes, default 1 MiB
  sseHeartbeatMs?: number; // SSE heartbeat interval in ms, default 5000, 0 turns it off
}
```

## Request Handling

* Bodies are validated before anything reaches the runtime: `input` must contain more than whitespace (`""` and `"   "` are refused before the model is called), `context` an object, `threadId` a non-empty string, swarm `timeout` a positive number of at most `MAX_RUN_TIMEOUT_MS` (2147483647, the longest delay a timer can hold). Invalid bodies return `400 INVALID_INPUT`; bodies above `bodyLimit` return `413 PAYLOAD_TOO_LARGE`.
* Workflow `options` are limited to `maxConcurrency`, `maxIterations` (positive integers) and `checkpoint` (boolean); other options are dropped and wrongly typed ones return `400 INVALID_INPUT`. The body itself is optional.
* Thread messages accept the roles `user`, `assistant` and `system`; `metadata` is stored with the entry.
* Unknown agents, workflows and swarms return `404 NOT_FOUND`; workflow and swarm routes return `501 UNIMPLEMENTED` when `@cogitator-ai/workflows` or `@cogitator-ai/swarms` is not installed. `GET /swarms/:name/blackboard` returns the configured `blackboard.sections`, or `400` when the blackboard is disabled.
* The thread routes use `cogitator.getMemory()` and return `503 UNAVAILABLE` without memory; another user's thread returns `403 THREAD_ACCESS_DENIED`.
* `CogitatorError`s keep their message, HTTP status and code (for example `429 LLM_RATE_LIMITED`), `HTTPException`s keep their response, and other errors are logged and returned as `500` with the message `Internal server error`. A workflow that ends with an error is answered like the error itself.
* Disconnecting clients abort the running agent, workflow or swarm.
* `GET /agents` exposes each agent's `description`, never its `instructions`; `GET /tools` returns parameters as JSON Schema.

## Multi-Runtime Deployment

The same `cogitatorApp` works across runtimes. Only the server entry point changes.

### Node.js

```typescript
import { serve } from '@hono/node-server';

const app = cogitatorApp({ cogitator, agents: { summarizer } });
serve({ fetch: app.fetch, port: 3000 });
```

### Bun

```typescript
const app = cogitatorApp({ cogitator, agents: { summarizer } });
export default { fetch: app.fetch, port: 3000 };
```

#### Long runs on Bun

`Bun.serve` closes a connection that stays silent for `idleTimeout` seconds, 10 by default, so without help a run that waits on a slow tool or model for longer ends with the connection closed. The adapter keeps both kinds of answer open: SSE streams write a heartbeat comment every `sseHeartbeatMs` (5 s by default), and the JSON routes (`run`, `resume`, workflow and swarm `run`) lift the idle timeout for their own request once its body is read, through the server Bun passes to `fetch`. If you set `idleTimeout`, keep it above `sseHeartbeatMs` and at 5 seconds or more, since Bun cuts connections at once below that whatever is written. See [Long-Running Runs](/docs/server-adapters#long-running-runs) for proxies and load balancers.

### Deno

```typescript
const app = cogitatorApp({ cogitator, agents: { summarizer } });
Deno.serve({ port: 3000 }, app.fetch);
```

### Cloudflare Workers

```typescript
const app = cogitatorApp({ cogitator, agents: { summarizer } });
export default { fetch: app.fetch };
```

Workers need a `compatibility_date` of 2026-08-04 or later (or the `nodejs_compat` flag), and a `Cogitator` with database memory is created per request. [Edge Runtimes](/docs/deployment/edge) covers setup, permissions on Deno and what needs a Node.js server.

## Authentication

The auth function receives Hono's `Context<HonoEnv>` and returns an `AuthContext`:

```typescript
const app = cogitatorApp({
  cogitator,
  agents: { summarizer },
  auth: async (c) => {
    const token = c.req.header('Authorization')?.replace('Bearer ', '');
    if (!token) throw new Error('No token');
    return { userId: 'user_123', roles: ['admin'] };
  },
});
```

The auth middleware applies to all routes via `app.use('*', ...)`, including `/health`, `/ready` and the WebSocket upgrade. Throwing answers `401 UNAUTHORIZED`; returning `undefined` lets the request through anonymously. The result is available as `c.get('cogitatorAuth')`, and its `userId` reaches agent and swarm runs and scopes the thread routes.

## Mounting Under a Prefix

Since `cogitatorApp` returns a standard Hono app, you can mount it under any path in a parent app:

```typescript
import { Hono } from 'hono';

const root = new Hono();

root.get('/', (c) => c.text('Welcome'));
root.route(
  '/cogitator',
  cogitatorApp({
    cogitator,
    agents: { summarizer },
    enableSwagger: true,
  })
);

serve({ fetch: root.fetch, port: 3000 });
```

All Cogitator endpoints are now available under `/cogitator/agents/...`, `/cogitator/docs`, etc.

## Swagger & OpenAPI

When `enableSwagger` is `true`, the app registers `/openapi.json` and `/docs` routes. The spec is generated from the registered agents, workflows, and swarms by the shared `@cogitator-ai/server-shared` generator on the first request and then cached. Its `servers` default to `/`, so set them to your mount path when you mount the app under a prefix.

```typescript
const app = cogitatorApp({
  cogitator,
  agents: { summarizer },
  enableSwagger: true,
  swagger: {
    title: 'Summarization API',
    version: '1.0.0',
    servers: [{ url: 'https://api.example.com/cogitator', description: 'Production' }],
  },
});
```

## Streaming

The `HonoStreamWriter` uses Hono's native streaming support, which works across all target runtimes:

```typescript
const response = await fetch('http://localhost:3000/agents/summarizer/stream', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    input: 'Summarize the history of the internet in detail.',
  }),
});

const reader = response.body!.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  process.stdout.write(decoder.decode(value));
}
```

## WebSocket

WebSocket upgrades depend on the runtime, so pass your runtime's `upgradeWebSocket` helper. The endpoint runs behind the same middleware as the REST routes, including `auth`.

```typescript
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { createNodeWebSocket } from '@hono/node-ws';

const root = new Hono();
const { upgradeWebSocket, injectWebSocket } = createNodeWebSocket({ app: root });

root.route(
  '/cogitator',
  cogitatorApp({
    cogitator,
    agents: { summarizer },
    enableWebSocket: true,
    websocket: { path: '/ws', upgradeWebSocket },
  })
);

injectWebSocket(serve({ fetch: root.fetch, port: 3000 }));
```

Use `upgradeWebSocket` from `hono/bun`, `hono/deno` or `hono/cloudflare-workers` on those runtimes. Without it the endpoint answers `501 UNIMPLEMENTED`.

Clients send:

* `run` with `payload: { type: 'agent' | 'workflow' | 'swarm', name, input, context?, threadId? }` — a workflow gets `{ input }` as its input state;
* `resume` with `payload: { name, threadId, decisions?, defaultDecision? }` to continue an agent run paused for approval;
* `stop` to abort the active run, and `ping`.

The server answers with `event` frames (`token`, `reasoning`, `tool-call`, `tool-result` for agents, then `complete` with the result or `cancelled`), `error` frames (`{ type: 'error', id, error }`) and `pong`. Each connection runs one thing at a time: a second `run` or `resume` answers `A run is already in progress`. There are no `subscribe` channels. Messages above `maxPayloadSize` (default 1 MiB) close the socket with code `1009`, and closing the socket aborts its active run.

## Context Variables

Routes behind the context middleware read the Cogitator context with `c.get()`. Add them to the app `cogitatorApp` returns, or put `createContextMiddleware` in front of your own app:

```typescript
import { Hono } from 'hono';
import { createContextMiddleware, type HonoEnv } from '@cogitator-ai/hono';

const app = new Hono<HonoEnv>();
app.use('*', createContextMiddleware({ cogitator, agents: { summarizer } }));

app.get('/custom', async (c) => {
  const { runtime, agents } = c.get('cogitator');
  const auth = c.get('cogitatorAuth');
  const requestId = c.get('cogitatorRequestId');

  const result = await runtime.run(agents.summarizer, {
    input: 'Summarize: Hono runs on every runtime.',
    userId: auth?.userId,
  });
  return c.json({ requestId, output: result.output });
});
```

## Exported Utilities

```typescript
import {
  cogitatorApp,
  createContextMiddleware,
  createAuthMiddleware,
  createBodyLimitMiddleware,
  errorHandler,
  createHealthRoutes,
  createAgentRoutes,
  createThreadRoutes,
  createToolRoutes,
  createWorkflowRoutes,
  createSwarmRoutes,
  createSwaggerRoutes,
  createWebSocketRoutes,
  handleWebSocketMessage,
  createClientState,
  HonoStreamWriter,
} from '@cogitator-ai/hono';
```

Individual route factories return Hono sub-apps, so you can compose only the routes you need.
