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 honoBasic 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:
inputmust contain more than whitespace (""and" "are refused before the model is called),contextan object,threadIda non-empty string, swarmtimeouta positive number of at mostMAX_RUN_TIMEOUT_MS(2147483647, the longest delay a timer can hold). Invalid bodies return400 INVALID_INPUT; bodies abovebodyLimitreturn413 PAYLOAD_TOO_LARGE. - Workflow
optionsare limited tomaxConcurrency,maxIterations(positive integers) andcheckpoint(boolean); other options are dropped and wrongly typed ones return400 INVALID_INPUT. The body itself is optional. - Thread messages accept the roles
user,assistantandsystem;metadatais stored with the entry. - Unknown agents, workflows and swarms return
404 NOT_FOUND; workflow and swarm routes return501 UNIMPLEMENTEDwhen@cogitator-ai/workflowsor@cogitator-ai/swarmsis not installed.GET /swarms/:name/blackboardreturns the configuredblackboard.sections, or400when the blackboard is disabled. - The thread routes use
cogitator.getMemory()and return503 UNAVAILABLEwithout memory; another user's thread returns403 THREAD_ACCESS_DENIED. CogitatorErrors keep their message, HTTP status and code (for example429 LLM_RATE_LIMITED),HTTPExceptions keep their response, and other errors are logged and returned as500with the messageInternal 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 /agentsexposes each agent'sdescription, never itsinstructions;GET /toolsreturns 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:
runwithpayload: { type: 'agent' | 'workflow' | 'swarm', name, input, context?, threadId? }— a workflow gets{ input }as its input state;resumewithpayload: { name, threadId, decisions?, defaultDecision? }to continue an agent run paused for approval;stopto abort the active run, andping.
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.