CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

honi

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.

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Critical

Do not install without reviewing

SKILL.md
Quality
Evals
Security

Honi — AI Agents on Cloudflare Workers

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.

Installation

npm install honidev
# or
bun add honidev

Minimal Agent

// 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 Config

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,
})

tool() Helper

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.

HTTP API

Every agent exposes:

EndpointMethodDescription
/chatPOSTSend a message, get a response
/historyGETRetrieve conversation history
/memoryGETInspect current memory context
/resetPOSTClear working memory
/mcpPOSTMCP 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"}'

Streaming

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.

Memory — Four Tiers

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 }
})

Multi-Agent Orchestration

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"]

MCP Server

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_SECRET

Claude Desktop with auth:

{
  "mcpServers": {
    "my-honi-agent": {
      "url": "https://my-agent.workers.dev/mcp",
      "headers": { "Authorization": "Bearer your-secret" }
    }
  }
}

Supported Models

PrefixProviderExample
claude-*Anthropicclaude-sonnet-4-5
gpt-*, o1, o3OpenAIgpt-4o
gemini-*Googlegemini-2.5-flash-preview
groq/*Groqgroq/llama-3.3-70b-versatile
deepseek-*DeepSeekdeepseek-chat
mistral-*Mistralmistral-large-latest
grok-*xAIgrok-2-latest
sonar*Perplexitysonar-pro
together/*Together AItogether/meta-llama/Llama-3-70b
command-*Coherecommand-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: ANTHROPIC_API_KEY
  • OpenAI: OPENAI_API_KEY
  • Google: GOOGLE_AI_API_KEY
  • Groq: GROQ_API_KEY
  • DeepSeek: DEEPSEEK_API_KEY
  • Mistral: MISTRAL_API_KEY
  • xAI: XAI_API_KEY
  • Perplexity: PERPLEXITY_API_KEY
  • Together: TOGETHER_API_KEY
  • Cohere: COHERE_API_KEY

Observability

createAgent({
  // ...
  observability: {
    enabled: true,
    aiGatewaySlug: 'my-gateway',  // Cloudflare AI Gateway slug
    collectEvents: true,           // Log tool calls + responses
  }
})

Common Patterns

Tool that calls an external API

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 that writes to D1

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 that writes to graph memory

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 }
})

Conditional memory based on content

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.

Project Layout

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

Quick Reference

TaskCode
Create agentcreateAgent({ name, model, system, tools })
Define tooltool(name, desc, schema, handler)
Access env in tool(params, ctx) => ctx.env.MY_BINDING
Access graph in tool(params, ctx) => ctx.graph.addNode(...)
Thread isolationx-thread-id header or ?threadId= query param
View historyGET /history?threadId=xyz
Reset sessionPOST /reset
Enable episodicmemory: { episodic: { enabled: true, dbBinding: 'DB' } }
Enable semanticmemory: { semantic: { enabled: true, indexBinding: 'VECTORIZE', aiBinding: 'AI' } }
Enable graphmemory: { graph: { enabled: true, binding: 'EDGRAPH', graphId: 'x' } }
MCP authmcp: { secretEnvVar: 'MCP_SECRET' }
Stream responseAccept: text/event-stream on POST /chat
Repository

stukennedy/honi-skill

Path

SKILL.md

Last updated
First committed

Is this your skill?

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.