Write normal Hono route handlers. Use direct Agentuity clients inside handlers by default, or add @agentuity/hono when middleware is the convenient place to share clients and telemetry across routes.
Create a Starter
npm create agentuity -- --name my-hono-api --framework hono --services keyvalue
cd my-hono-apiThe starter includes a translation example in src/index.ts that uses AIGatewayClient for model calls. Adding --services keyvalue installs @agentuity/keyvalue and updates the starter checklist, but it does not add another route by itself.
Key files:
| File | Purpose |
|---|---|
src/index.ts | Hono app, API route, and server entry |
src/landing.tsx | Rendered page shell for / |
package.json | scripts for dev, build, start, and deploy |
For an existing Hono app, add the CLI and link the project:
npm install -D @agentuity/cli
npx agentuity project import --validate-only
npx agentuity project importnpm install -D installs the CLI as a project-local dev dependency, not a global install. Use npx to run that local CLI. For Bun, pnpm, and Yarn equivalents, see Local versus global CLI.
See Add Agentuity to an existing app for validation, import, the Invalid project folder deploy case, and first deploy.
What Agentuity adds
| Area | Hono keeps | Agentuity adds |
|---|---|---|
| routing | new Hono() and normal route handlers | direct service clients or @agentuity/hono middleware |
| local dev | your Hono dev script | agentuity dev supplies service credentials |
| build | your Node-targeted build command | agentuity build runs the build and writes generic launch metadata |
| deploy | the built server entry | agentuity deploy ships the packaged app |
Use Service Clients in Routes
Direct service clients work in any Hono handler. They read AGENTUITY_SDK_KEY (with AGENTUITY_CLI_KEY as a fallback) from the process environment, so agentuity dev can supply the linked project key for local runs.
Install what the example uses:
npm install hono @agentuity/aigateway @agentuity/keyvalue valibotThis route translates input text, persists the last five translations under a session cookie, and calls AI Gateway with the project SDK key. Use a provider SDK directly only when the provider-specific API is the point of the route.
import { AIGatewayClient } from '@agentuity/aigateway';
import { KeyValueClient } from '@agentuity/keyvalue';
import { Hono } from 'hono';
import type { Context } from 'hono';
import { getCookie, setCookie } from 'hono/cookie';
import * as v from 'valibot';
const DEFAULT_MODEL = 'openai/gpt-5.4-mini';
const HISTORY_NAMESPACE = 'translation-history';
const SESSION_COOKIE = 'agentuity_session';
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 30;
const requestSchema = v.object({
model: v.optional(v.string()),
text: v.string(),
toLanguage: v.string(),
});
interface HistoryEntry {
readonly model: string;
readonly sessionId: string;
readonly text: string;
readonly timestamp: string;
readonly toLanguage: string;
readonly translation: string;
}
interface HistoryState {
readonly history: readonly HistoryEntry[];
readonly translationCount: number;
}
const kv = new KeyValueClient();
const gateway = new AIGatewayClient();
const app = new Hono();
function getSessionId(c: Context): string {
const existing = getCookie(c, SESSION_COOKIE);
if (existing) return existing;
const sessionId = crypto.randomUUID();
setCookie(c, SESSION_COOKIE, sessionId, {
httpOnly: true,
path: '/',
sameSite: 'Lax',
secure: new URL(c.req.url).protocol === 'https:',
maxAge: SESSION_TTL_SECONDS,
});
return sessionId;
}
app.post('/api/translate', async (c) => {
const body: unknown = await c.req.json();
const parsed = v.safeParse(requestSchema, body);
if (!parsed.success) {
return c.json({ error: 'text and toLanguage are required' }, 400);
}
const input = parsed.output;
const sessionId = getSessionId(c);
const model = input.model ?? DEFAULT_MODEL;
const result = await gateway.completeText({
model,
messages: [
{ role: 'user', content: `Translate to ${input.toLanguage}:\n\n${input.text}` },
],
});
if (!result.hasText) {
return c.json({ error: 'model returned no text' }, 502);
}
const translation = result.text;
const previous = await kv.get<HistoryState>(HISTORY_NAMESPACE, sessionId);
const history = [
...(previous.exists ? previous.data.history : []),
{
model,
sessionId,
text: input.text,
timestamp: new Date().toISOString(),
toLanguage: input.toLanguage,
translation,
},
].slice(-5);
const translationCount = (previous.exists ? previous.data.translationCount : 0) + 1;
await kv.set(HISTORY_NAMESPACE, sessionId, { history, translationCount }, { ttl: SESSION_TTL_SECONDS });
return c.json({ sessionId, model, toLanguage: input.toLanguage, translation, history, translationCount });
});
export default app;KeyValueClient.get() returns a discriminated result: check result.exists before accessing result.data to keep the read path type-safe.
Use the model id exactly as it appears in the AI Gateway model catalog. The default example keeps the model in code, but you can move it to app configuration or request input.
Use @agentuity/hono for Shared Clients
When most routes need the same Agentuity clients, @agentuity/hono can initialize them once and expose them on c.var.*. Keep direct clients for a single route, one-off script, or code you want to move across frameworks. Use middleware when the app benefits from shared clients, telemetry defaults, and route-local access through c.var. If your unbundled dev runner has trouble resolving the telemetry package, keep direct clients in dev or bundle the server entry before using the middleware path.
npm install @agentuity/honoimport { agentuity } from '@agentuity/hono';
import type { Logger, Services } from '@agentuity/hono';
import { Hono } from 'hono';
import * as v from 'valibot';
type Variables = Pick<Services, 'kv'> & {
logger: Logger;
};
const app = new Hono<{ Variables: Variables }>();
app.use('*', agentuity());
app.post('/api/events', async (c) => {
const body: unknown = await c.req.json();
const parsed = v.safeParse(v.object({ title: v.string() }), body);
if (!parsed.success) {
return c.json({ error: 'title is required' }, 400);
}
const id = crypto.randomUUID();
const event = { id, title: parsed.output.title, createdAt: new Date().toISOString() };
await c.var.kv.set('events', id, event, { ttl: null });
c.var.logger.info('event recorded', { id });
return c.json(event, 201);
});
export default app;| Direct clients | @agentuity/hono middleware | |
|---|---|---|
| Setup | Instantiate in the module that needs the client | One app.use('*', agentuity()) |
| Scope | Works anywhere (scripts, workers, other frameworks) | Hono apps only |
| Telemetry | Use your existing setup or @agentuity/telemetry directly | Agentuity telemetry defaults through c.var.logger and c.var.tracer |
| Best for | Single route, script, or cross-framework module | Most routes need multiple services |
Run Locally
agentuity dev runs your package manager's dev script. When the linked project key is available, the CLI supplies AGENTUITY_SDK_KEY to the Hono process.
agentuity devPass a custom script when the dev entry is named differently:
agentuity dev --script dev:apiSmoke-test before debugging the UI:
curl -X POST http://localhost:3000/api/translate \
-H "content-type: application/json" \
-d '{"text":"Hello","toLanguage":"Spanish"}'Deployed Environment Variables
Configure the variables your deployed app needs. Direct AIGatewayClient calls use AGENTUITY_SDK_KEY; the model ID in this example is normal app configuration or request input.
| Variable | Used by |
|---|---|
AGENTUITY_SDK_KEY | Agentuity service clients and @agentuity/aigateway |
DATABASE_URL | database routes |
AWS_* | object storage routes; see Object Storage for exact variables |
Validate Before Deploy
The current starter uses Hono's Node template and adds explicit build and start scripts so the deploy can launch a single process.
{
"scripts": {
"build": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node22 --outfile=dist/src/index.cjs",
"start": "node dist/src/index.cjs",
"deploy": "agentuity deploy"
}
}Then validate the packaging. Hono apps are packaged through Agentuity's generic adapter, so the start script is the contract for what the deployed process runs.
npm run build
npx agentuity build
cat .agentuity/launch.jsonDeploy only after .agentuity/launch.json points at the documented web process and a local packaged validation of / plus one API route passes:
agentuity deploy --confirmCommon Gotchas
| Symptom | Check |
|---|---|
| Service client returns auth errors | Run with agentuity dev or set AGENTUITY_SDK_KEY |
| Model call returns an auth error | Verify AGENTUITY_SDK_KEY is available to the server process. See AI Gateway |
| Cookies not set or missing flags | Import getCookie/setCookie from hono/cookie; setting cookies directly on the response object bypasses Hono's helper |
| Route works in dev but not after build | Re-inspect .agentuity/launch.json after agentuity build. See Hono on Node.js for the adapter shape |
| Deploy starts the wrong file | Confirm scripts.start in package.json points at the built server entry (dist/src/index.cjs) |
c.var.kv is undefined | Add app.use('*', agentuity()) before any route that reads from c.var |
c.var.* types are missing | Declare the Variables generic on new Hono<{ Variables: ... }>() |
Framework Docs
- Hono on Node.js: confirm the
src/index.tsentry and@hono/node-serveradapter setup - Hono Routing: use standard Hono routing helpers
- Hono Cookies: configure
getCookie,setCookie, and signed cookies
Next Steps
- Agents: wrap model calls in reusable functions called from Hono routes
- Chat and Streaming: return streamed model output from Hono routes
- Key-Value Storage: store compact state by namespace and key
- AI Gateway: route provider SDK calls through Agentuity
- Observability: choose app-owned logging and tracing or Agentuity telemetry defaults