Build AI agents with Honi (honidev) on Cloudflare Workers. Use when creating agents with tools, persistent memory, MCP servers, or multi-agent pipelines. Covers createAgent API, tool() helper, all memory tiers (working/episodic/semantic/graph), multi-agent routing, MCP auth, and all supported model providers.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Critical
Do not install without reviewing
honidev is a TypeScript framework for building persistent AI agents on Cloudflare Workers using Durable Objects. Zero cold starts. Global edge deployment. Layered memory that survives across sessions.
npm install honidev
# or
bun add honidev// src/index.ts
import { createAgent, tool } from 'honidev'
import { z } from 'zod'
export const { Agent, handler } = createAgent({
name: 'my-agent',
model: 'claude-sonnet-4-5',
system: 'You are a helpful assistant.',
tools: [
tool('get_weather', 'Get weather for a location', {
location: z.string()
}, async ({ location }) => {
return { temp: 22, condition: 'sunny', location }
})
]
})
export default handler
export class MyAgent extends Agent {}# wrangler.toml
name = "my-agent"
main = "src/index.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]
[[durable_objects.bindings]]
name = "AGENT"
class_name = "MyAgent"
[[migrations]]
tag = "v1"
new_classes = ["MyAgent"]createAgent({
name: string, // Worker name
model: string, // Model ID (see providers below)
system?: string, // System prompt
tools?: ToolDefinition[], // Array of tool() calls
binding?: string, // DO binding name (default: "AGENT")
maxSteps?: number, // Tool loop limit (default: 10)
memory?: MemoryConfig, // Memory tier config (see Memory section)
mcp?: McpConfig, // MCP server config (see MCP section)
observability?: ObservabilityConfig,
})import { tool } from 'honidev'
import { z } from 'zod'
const myTool = tool(
'tool_name',
'Description of what the tool does',
{
param1: z.string().describe('First param'),
param2: z.number().optional(),
},
async ({ param1, param2 }, ctx) => {
// ctx.env → raw Worker env (access bindings, secrets)
// ctx.graph → GraphMemory instance (if graph memory enabled)
return { result: 'value' }
}
)The handler receives (params, ctx?). The ctx argument is optional — tools without context still work fine.
Every agent exposes:
| Endpoint | Method | Description |
|---|---|---|
/chat | POST | Send a message, get a response |
/history | GET | Retrieve conversation history |
/memory | GET | Inspect current memory context |
/reset | POST | Clear working memory |
/mcp | POST | MCP JSON-RPC endpoint |
Thread isolation via header or query param:
curl -X POST https://my-agent.workers.dev/chat \
-H "x-thread-id: user-123" \
-H "Content-Type: application/json" \
-d '{"message": "Hello"}'curl -X POST https://my-agent.workers.dev/chat \
-H "Accept: text/event-stream" \
-d '{"message": "Tell me a story"}'Returns Server-Sent Events. The client receives data: {"type":"text","text":"..."} chunks.
Honi has four memory tiers. Enable only what you need.
memory: {
working: true, // Tier 1: Durable Object KV (always on)
episodic: { // Tier 2: D1 — conversation history
enabled: true,
dbBinding: 'DB', // D1 binding name in wrangler.toml
maxMessages: 50,
},
semantic: { // Tier 3: Vectorize — semantic search
enabled: true,
indexBinding: 'VECTORIZE',
aiBinding: 'AI', // Workers AI for embeddings
topK: 5,
},
graph: { // Tier 4: edgraph — knowledge graph
enabled: true,
binding: 'EDGRAPH', // Service binding OR
urlEnvVar: 'EDGRAPH_URL', // HTTP URL env var
apiKeyEnvVar: 'EDGRAPH_API_KEY',
graphId: 'my-graph',
contextDepth: 1, // BFS hops
maxContextEntities: 5,
}
}wrangler.toml for full memory stack:
[[durable_objects.bindings]]
name = "AGENT"
class_name = "MyAgent"
[[migrations]]
tag = "v1"
new_classes = ["MyAgent"]
[[d1_databases]]
binding = "DB"
database_name = "my-agent-db"
database_id = "<YOUR_D1_ID>"
[[vectorize]]
binding = "VECTORIZE"
index_name = "my-agent-index"
[ai]
binding = "AI"
[[services]]
binding = "EDGRAPH"
service = "edgraph"Graph memory in tools:
tool('remember_person', 'Store a person in the knowledge graph', {
name: z.string(),
role: z.string(),
}, async ({ name, role }, ctx) => {
await ctx.graph.addNode({ id: name, label: name, type: 'Person', properties: { role } })
return { stored: true }
})Route requests to specialised agents at the Worker level:
// src/index.ts
import { createAgent } from 'honidev'
export const { Agent: SupportAgent, handler: supportHandler } = createAgent({
name: 'support',
model: 'claude-sonnet-4-5',
system: 'You handle customer support queries.',
binding: 'SUPPORT_DO',
tools: [searchKnowledgeBase],
})
export const { Agent: AnalystAgent, handler: analystHandler } = createAgent({
name: 'analyst',
model: 'claude-sonnet-4-5',
system: 'You analyse data and produce reports.',
binding: 'ANALYST_DO',
tools: [runQuery, generateChart],
})
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url)
if (url.pathname.startsWith('/support')) return supportHandler.fetch(request, env)
if (url.pathname.startsWith('/analyst')) return analystHandler.fetch(request, env)
return new Response('Not found', { status: 404 })
}
}
export class SupportAgent extends SupportAgent {}
export class AnalystAgent extends AnalystAgent {}[[durable_objects.bindings]]
name = "SUPPORT_DO"
class_name = "SupportAgent"
[[durable_objects.bindings]]
name = "ANALYST_DO"
class_name = "AnalystAgent"
[[migrations]]
tag = "v1"
new_classes = ["SupportAgent", "AnalystAgent"]Every agent exposes /mcp as an MCP JSON-RPC endpoint. Tools are automatically registered.
Connect from Claude Desktop:
{
"mcpServers": {
"my-honi-agent": {
"url": "https://my-agent.workers.dev/mcp"
}
}
}Add authentication for remote connections (recommended):
createAgent({
// ...
mcp: { secretEnvVar: 'MCP_SECRET' }
})wrangler secret put MCP_SECRETClaude Desktop with auth:
{
"mcpServers": {
"my-honi-agent": {
"url": "https://my-agent.workers.dev/mcp",
"headers": { "Authorization": "Bearer your-secret" }
}
}
}| Prefix | Provider | Example |
|---|---|---|
claude-* | Anthropic | claude-sonnet-4-5 |
gpt-*, o1, o3 | OpenAI | gpt-4o |
gemini-* | gemini-2.5-flash-preview | |
groq/* | Groq | groq/llama-3.3-70b-versatile |
deepseek-* | DeepSeek | deepseek-chat |
mistral-* | Mistral | mistral-large-latest |
grok-* | xAI | grok-2-latest |
sonar* | Perplexity | sonar-pro |
together/* | Together AI | together/meta-llama/Llama-3-70b |
command-* | Cohere | command-r-plus |
@cf/* | Workers AI | @cf/meta/llama-3.1-8b-instruct |
Workers AI models require an [ai] binding in wrangler.toml. All other providers use their respective API key set via wrangler secret.
Environment variable names:
ANTHROPIC_API_KEYOPENAI_API_KEYGOOGLE_AI_API_KEYGROQ_API_KEYDEEPSEEK_API_KEYMISTRAL_API_KEYXAI_API_KEYPERPLEXITY_API_KEYTOGETHER_API_KEYCOHERE_API_KEYcreateAgent({
// ...
observability: {
enabled: true,
aiGatewaySlug: 'my-gateway', // Cloudflare AI Gateway slug
collectEvents: true, // Log tool calls + responses
}
})tool('search_docs', 'Search documentation', {
query: z.string(),
}, async ({ query }, ctx) => {
const apiKey = (ctx.env as any).DOCS_API_KEY
const res = await fetch(`https://api.example.com/search?q=${query}`, {
headers: { Authorization: `Bearer ${apiKey}` }
})
return res.json()
})tool('save_note', 'Save a note to the database', {
title: z.string(),
content: z.string(),
}, async ({ title, content }, ctx) => {
const db = (ctx.env as any).DB as D1Database
await db.prepare('INSERT INTO notes (title, content) VALUES (?, ?)')
.bind(title, content).run()
return { saved: true }
})tool('link_entities', 'Create a relationship between two entities', {
fromId: z.string(),
toId: z.string(),
relation: z.string(),
}, async ({ fromId, toId, relation }, ctx) => {
await ctx.graph.addEdge({ source: fromId, target: toId, label: relation })
return { linked: true }
})The memory stack is write-through by default. To promote selectively, skip memory config and write to tiers manually in a tool or post-processing step. An evaluator / significance filter is on the roadmap.
src/
index.ts # createAgent + export DO class + default handler
agents/
support.ts # Specialist agent definitions
analyst.ts
tools/
search.ts # Tool definitions
database.ts
wrangler.toml
package.json| Task | Code |
|---|---|
| Create agent | createAgent({ name, model, system, tools }) |
| Define tool | tool(name, desc, schema, handler) |
| Access env in tool | (params, ctx) => ctx.env.MY_BINDING |
| Access graph in tool | (params, ctx) => ctx.graph.addNode(...) |
| Thread isolation | x-thread-id header or ?threadId= query param |
| View history | GET /history?threadId=xyz |
| Reset session | POST /reset |
| Enable episodic | memory: { episodic: { enabled: true, dbBinding: 'DB' } } |
| Enable semantic | memory: { semantic: { enabled: true, indexBinding: 'VECTORIZE', aiBinding: 'AI' } } |
| Enable graph | memory: { graph: { enabled: true, binding: 'EDGRAPH', graphId: 'x' } } |
| MCP auth | mcp: { secretEnvVar: 'MCP_SECRET' } |
| Stream response | Accept: text/event-stream on POST /chat |
stukennedy/honi-skill
SKILL.md
d61c6be
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.