Cogitator
Server Adapters

Hono Adapter

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

pnpm add @cogitator-ai/hono hono

Basic Setup

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

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.
  • CogitatorErrors keep their message, HTTP status and code (for example 429 LLM_RATE_LIMITED), HTTPExceptions 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

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

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

Bun

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 for proxies and load balancers.

Deno

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

Cloudflare Workers

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 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:

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:

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.

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:

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.

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:

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

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.

On this page