N AgentNavaKit
agentnava.com →
Read first · Where this runs

Your agents/<agent-name>/ folder lives on your computer — markdown files you edit, version-control, and grep. It's the local source-of-truth for the agent's spec.

The deploy script (agents/<agent-name>.ts) reads those files and calls an.specs.push(loadAgentDir(...)) to upload them to AgentNava, creating a new spec version on the backend. an.agents.configure assembles that spec into an agent version; deployToTest / deployToProd spins the agent up on AgentNava infrastructure.

The agent never runs on your machine. Once a version is pushed and deployed, your local files are no longer the runtime source — the pushed version is. To change behavior: edit locally → re-run the deploy script → redeploy. List or fetch what's been pushed with an.specs.list({ key }) / an.specs.get('spec_…@v2'), or browse them at console.agentnava.com → Specs.

How a spec becomes a running agent

Your machine · local Agent folder: agents/<agent-name>/ AGENT.md + skills/ + workflows/ + commands/ + (optional) subagents/ and knowledge/. You edit and version-control these.
your deploy script runs
@agentnava/kit · uploads an.specs.push(loadAgentDir(...)) Reads your folder, uploads each file, and stamps a new spec version on the backend (e.g. spec_4f1c9a@v2).
spec version pinned
@agentnava/kit · configure an.agents.configure({ spec, knowledge, connections, ... }) Assembles the pushed spec + KB + integrations + secrets into a new agent version (e.g. ver_3c5f). Idempotent on key.
deploy
AgentNava platform · runtime deployToTest(...) or deployToProd(...) The agent now runs on AgentNava infrastructure at <agent-name>.agents.agentnava.com. End users connect through sessions; tools and skills execute server-side.

The five object types

ObjectWhat it isHow to create it
Spec A bundle of behavior files — AGENT.md plus any skills, subagents, workflows, and slash commands. Same layout as Claude Code's .claude/. an.specs.push({ key, instructions, skills, subagents, workflows, commands }) or an.specs.push(loadAgentDir('./agents/foo')). Each push stamps a new spec version.
Knowledge RAG documents — .md, .pdf, .txt, images. Agents AND every workflow/subagent always read from the full base on every turn. an.knowledge.upload({ key, files }). Versioned per upload. See Knowledge.
Connections Pre-authorized integrations (Slack, Gmail, GitHub, MCP servers, …) — set up once in the workspace, referenced by key. Each connection registers tools. Dashboard, or connect.slack({ key: 'slack-acme' }) in code. Full catalog at Connections.
Secrets Workspace-level API keys / env values injected into tool handlers. Dashboard, or an.secrets.set('ZILLOW_API_KEY', '…'). Referenced from agents by name.
Plugins Pre-packaged bundles of skills, subagents, commands, and MCP servers. Claude Code plugins (.claude-plugin/plugin.json) install unchanged. an.plugins.install('github:owner/repo#tag') or local path. Reference by name in configure({ plugins }).
Widgets A typed event protocol for rendering anything in your UI. The agent emits ctx.emitWidget({ kind, props }) from a tool handler; your UI dispatches on kind and renders. No built-in catalog — every widget kind is yours. From inside a tool: ctx.emitWidget({ kind, props }). See Widgets for the full protocol.

File kinds inside a Spec

The Spec is a folder of markdown files. Five kinds — all first-class, all use the same frontmatter conventions Claude Code uses, so skills and subagents you've already built drop in unchanged.

AGENT.md instructions Required

The agent's core instructions — what it does, how it talks, what it won't do. The CLAUDE.md equivalent. Always sent to the model. One per spec.

# agents/concierge/AGENT.md
# BayHomes Concierge

You help prospective buyers explore listings.
Address the user as "you". Keep replies under two paragraphs unless asked.
Never quote a final price — always defer to a human agent for pricing.
skills/<name>/SKILL.md auto-invoked Optional

A focused, auto-invokable module the agent loads when the description matches the user's request. YAML frontmatter is the same as Claude Code skills — name, description, allowed-tools, model. Drop existing Claude Code skills in unchanged.

# agents/concierge/skills/format-listing/SKILL.md
---
name: format-listing
description: Format a property listing card with price, beds, sqft, and one-line pitch
allowed-tools: [Read, lookup_zestimate]
---
When showing a listing, render…

The platform also bundles built-in capabilities (file ops, web search, structured-output helpers, etc.) that work without any declaration.

workflows/<name>.md multi-step playbook Optional

A multi-step playbook the agent runs end-to-end — e.g. "morning digest", "weekly report", "intake → qualify → handoff". Frontmatter declares the trigger (chat-keyword, schedule, webhook) and required connections. Every workflow has full access to the agent's Knowledge base and Connections — no extra wiring.

# agents/concierge/workflows/morning-digest.md
---
name: morning-digest
description: Daily 7am email summarizing new listings + price changes
trigger: { kind: schedule, cron: '0 7 * * 1-5' }
requires: [gmail, hubspot]
---
1. Query HubSpot for active buyers…
2. Match against new MLS listings…
3. Draft + send digest emails…
commands/<name>.md user-invokable Optional

A slash command the end user types in chat (/reset, /refresh-kb, /handoff). Same format as Claude Code commands — argument-hint, allowed-tools, optional model.

# agents/concierge/commands/handoff.md
---
description: Hand the conversation off to a human agent
argument-hint: "[reason]"
---
Tell the user a human will follow up about $ARGUMENTS, then emit a handoff event.

On every push the entire bundle is uploaded. specs.push stamps a new version only if content actually changed.

Custom subagents — advanced

Drop one or more markdown files at subagents/<name>.md using the same format as Claude Code subagents (.claude/agents/*). Each one defines a focused specialist — a compliance reviewer, a sales-tone checker, a structured research role. The frontmatter name + description are what the parent agent sees; the body is the subagent's own instructions.

# agents/concierge/subagents/researcher.md
---
name: researcher
description: Look up market comparables and neighborhood data for a given address
---
You research recent comparable sales…

When the spec ships at least one subagent the runtime auto-registers a Task tool on the parent. The model calls Task({ subagent_type: 'researcher', prompt: '…' }) to delegate a focused side-quest with its own context window. The parent's session history, knowledge base, and HTTP tools all stay isolated — the subagent gets its own ephemeral run and returns a single final string. Nesting is capped (depth 3) to keep cost bounded.

Import from Claude Code

Existing Claude Code work is directly portable. No converter, no edits — the spec bundle uses the same file layout and frontmatter conventions Claude Code uses.

Skills + commands — copy into your local spec folder

Your agents/<agent-name>/ directory is the local source-of-truth that gets uploaded on the next specs.push (see Where this runs). Copy existing Claude Code files into it:

cp -r ~/projects/my-tool/.claude/skills/*   agents/<agent-name>/skills/
cp -r ~/projects/my-tool/.claude/commands/* agents/<agent-name>/commands/

On the next an.specs.push(loadAgentDir(...)) the files are uploaded to AgentNava as part of a new spec version; after configure + deploy, the agent running on AgentNava infrastructure picks them up. Everything works as-is:

  • Skills — YAML frontmatter (name, description, allowed-tools, model, disable-model-invocation) is interpreted identically.
  • Commands$ARGUMENTS substitution, argument-hint, and allowed-tools are honored. Users invoke them with /name in chat.

Subagents (.claude/agents/*) port the same way but are an advanced concept — most agents don't need them. See Custom subagents.

The one Claude-Code-specific concept we don't carry over is CLAUDE.md — we call its equivalent AGENT.md instead. If you're porting a Claude Code project, rename CLAUDE.mdAGENT.md and you're done.

Plugins — install once, attach by name

Claude Code plugins (.claude-plugin/plugin.json bundles of skills, subagents, commands, hooks, and MCP servers) install at the workspace and are referenced from any agent:

// 1) Install at the workspace (one-time, idempotent on tag).
await an.plugins.install('github:owner/repo#v1');
// → { name: 'pr-helper', version: 'v1', skills: 3, subagents: 1, mcpServers: 1 }

// 2) Reference by name from configure.
await an.agents.configure({
  // ...
  plugins: ['pr-helper'],
});

Install sources we accept:

  • github:owner/repo, github:owner/repo#v1, github:owner/repo#commit-sha
  • https://example.com/plugin.tgz (signed tarball)
  • ./vendor/my-plugin (local path)

Once installed, the plugin's bundled skills, subagents, commands, hooks, and MCP servers all become available on every agent that lists it.

MCP servers

List HTTP / SSE MCP servers in mcpServers on the spec — same shape as Claude Code's mcpServers in ~/.claude/settings.json, minus the stdio variant (the runtime can't spawn subprocesses). At session start the runtime connects to each server, calls tools/list, and merges those tools into the model's surface namespaced as <server-name>__<tool>.

const spec = await an.specs.push({
  key: 'concierge',
  instructions: '…',
  mcpServers: [
    { name: 'linear', url: 'https://mcp.linear.app/sse', transport: 'sse' },
    {
      name: 'jira',
      url: 'https://mcp.example.com',
      headers: { Authorization: 'Bearer {{secrets.JIRA_MCP_TOKEN}}' },
    },
  ],
});

If you keep your spec as a folder, drop the same array as mcp-servers.json alongside AGENT.md and loadAgentDir picks it up. See configure → mcpServers for the full field reference.

The lifecycle in TypeScript

The whole flow is a single file. Each step is an SDK call.

// agents/concierge.ts
import { AgentNava, connect, loadAgentDir } from '@agentnava/kit';

const an = new AgentNava();   // reads process.env.AGENTNAVA_API_KEY

// 1) Push the Spec bundle (AGENT.md + skills + subagents + workflows + commands).
//    loadAgentDir reads the folder and infers each file's kind from its path.
const spec = await an.specs.push(
  loadAgentDir('./agents/concierge', { key: 'concierge' }),
);
// → { id: 'spec_4f1c9a', version: 2 }

// 2) Upload the Knowledge base (.md, .pdf, .txt, images).
const kb = await an.knowledge.upload({
  key:   'bayhomes-handbook',
  files: ['./knowledge/handbook.pdf', './knowledge/policies.md', './knowledge/floorplan.png'],
});
// → { id: 'kb_8b07', version: 3 }

// 3) Configure → returns a new agent version that pins to spec@v2 and kb@v3.
const v = await an.agents.configure({
  key:         'bayhomes-concierge',
  name:        'BayHomes Concierge',
  modelClass:  'standard',
  spec,                                                  // bundle from step 1
  knowledge:   [kb],                                     // agents AND workflows always see this
  triggers:    [{ kind: 'chat' }],
  connections: [connect.slack({ key: 'slack-bayhomes' })],
  secrets:     ['ZILLOW_API_KEY'],
});
// → { agentId: 'agt_2b8e', versionId: 'ver_3c5f' }

// 4) (Optional) deploy to test. Dev-only URL.
const test = await an.agents.deployToTest(v.versionId);
console.log('test:', test.url);

// 5) Deploy to prod when you're happy.
const prod = await an.agents.deployToProd(v.versionId);
console.log('prod:', prod.url);

Re-running the file is safe and idempotent. specs.push only stamps a new version if content changed. agents.configure upserts on key. deployToTest/deployToProd always produce a new instance.

Explicit fields, if you prefer

loadAgentDir is a convenience over the explicit form. Both are valid:

const spec = await an.specs.push({
  key: 'concierge',
  instructions: readFileSync('./agents/concierge/AGENT.md', 'utf8'),
  skills: [
    { name: 'format-listing', content: readFileSync('./agents/concierge/skills/format-listing/SKILL.md', 'utf8') },
  ],
  workflows: [
    { name: 'morning-digest', content: readFileSync('./agents/concierge/workflows/morning-digest.md', 'utf8') },
  ],
  commands: [
    { name: 'handoff',        content: readFileSync('./agents/concierge/commands/handoff.md', 'utf8') },
  ],
  // subagents: [...]  // advanced — see Custom subagents below
});

What configure takes

Every field below is a property of the object you pass to an.agents.configure(...).

key string Required

A dev-chosen, stable identifier for this agent in your workspace. The backend uses it to upsert — first configure with a new key creates the agent (and assigns an opaque agt_… ID); later calls with the same key update the existing one.

key: 'bayhomes-concierge'

You control key. The backend controls agentId. Use key in source code; use agentId for sessions, deploys, and rollbacks at runtime.

name string Required

What users address the agent as in chat (e.g. "Hey Concierge, …") and how it introduces itself. Also appears in the workspace header, embed widget, and catalog cards — but the conversational identity is what matters.

name: 'BayHomes Concierge'
spec Spec | string Required

The Spec object you got from an.specs.push(...), or its ID + version as a string ('spec_4f1c9a@v2'). The agent version pins to whichever spec version you pass — re-pushing the spec doesn't change deployed agents.

spec  // pass the object directly
// or
spec: 'spec_4f1c9a@v2'
// or
spec: 'spec_4f1c9a'   // resolves to latest at configure time, then pinned
knowledge (KnowledgeBase | string)[] Optional

One or more Knowledge bases for RAG (.md, .pdf, .txt, images). The runtime indexes them and retrieves relevant chunks on every turn. The agent and every workflow, subagent, and skill it spawns always have full access — you do not need to re-declare it anywhere. Pin to specific KB versions the same way as Spec.

knowledge: [kb, 'kb_legal@v5']

End users (or you, post-deploy) can also upload files into a Knowledge base — see Knowledge.

modelClass 'standard' | 'premium' | 'advanced' Required

How much reasoning the agent applies on each turn. Higher classes think longer, can chain more tool calls before replying, and cost more.

ClassWhen to pick itDefault route
standardShort replies, FAQ, simple Q&A. One shot, at most one tool call.GLM-4.7 tier
premiumDrafts, multi-step reasoning, RAG across several sources, summarization with citations.Sonnet tier
advancedPlanning across many tools and steps — research, code generation, complex workflows.Opus tier

Pin a snapshot if you want behavior frozen across model upgrades:

modelClass: 'standard'           // rolling — auto-upgrades over time
modelClass: 'standard@2026-05'   // pinned to the May 2026 snapshot
modelClass: 'premium@v3'         // pinned to integer release v3
role 'freelancer' | 'teammate' | 'manager' Optional default: 'freelancer'

The role this agent plays for its user. AgentNavaKit infers the compute model and persistence from it — you don't pick those directly. The choice doesn't change how you write the spec; it changes which tools become available, whether state survives between sessions, and how the platform organises the agent inside a team.

RoleTool surface · persistencePick it for
freelancer HTTP tools, MCP, web search, knowledge base, widgets, subagent delegation. Per-session, ephemeral — no shared state across sessions, fan-out across users. Chat, research, scheduled summaries, anything that talks to APIs and serves many people in parallel. Fast cold-start, cheap, edge-distributed.
teammate Everything in freelancer plus Read, Write, Edit, and Bash against a real Linux filesystem and shell. Per-user persistent workspace at /workspace that survives between sessions. Code review, scripting, anything where the agent needs to read/write files, run a process, or remember work between sessions for the same user.
manager Same compute as a Teammate. One Manager per team. You don't normally pick this — the platform auto-spawns a Manager when a team has 2+ Teammates to coordinate them and route incoming work.
// loadAgentDir option:
const input = loadAgentDir('./agents/code-reviewer', {
  key: 'code-reviewer',
  role: 'teammate',
});
// or explicit on the SpecInput:
const spec = await an.specs.push({
  key: 'code-reviewer',
  instructions: '…',
  role: 'teammate',
});

Teammate / Manager sessions get a persistent workspace at /workspace. When the session goes idle, the underlying container hibernates — cost drops to storage only.

teamId string Optional default: workspace's default team

The team this agent belongs to. Teams group related agents — a Manager and its Teammates always live in the same team, and the Manager's Delegate tool only sees Teammates that share its team. Workspaces start with a single default team, so most setups never need to pass this explicitly.

ValueWhat happens
omitted The platform stamps the workspace's default team id. This is what you want unless you've explicitly created a second team.
'tm_…' Pin the agent to a specific team. Used when you want a separate Manager + Teammate group for a different purpose (e.g. a "marketing" team and a "support" team in the same workspace).
const v = await an.agents.configure({
  key: 'researcher',
  name: 'Researcher',
  modelClass: 'standard',
  spec,
  triggers: [{ kind: 'chat' }],
  // teamId omitted — joins the default team.
});

// Or stamp explicitly:
await an.agents.configure({
  key: 'marketing-writer',
  // ...
  teamId: 'tm_marketing',
});

The platform auto-spawns a Manager once a team has its second Teammate. Without an explicit teamId, all Teammates join the same default team and the same auto-spawned Manager.

provider 'auto' | 'bedrock' | 'azure-openai' | 'vertex' | 'openrouter' Optional default: 'auto'

Whose account pays for the model calls. 'auto' bills the managed runtime. Any other value routes inference through your own cloud account — tokens billed there. See Operate → BYOK.

triggers Trigger[] Required

When the agent wakes up. Without at least one trigger, the agent never runs.

triggers: [
  { kind: 'chat' },
  { kind: 'webhook' },
  { kind: 'schedule', cron: '0 7 * * 1-5' },
  { kind: 'loop', everySeconds: 600, maxRuns: 24 },
  { kind: 'manual' },
]
KindThe agent runs when
chatA user sends a message via the embed widget, hosted chat page, or your own UI.
webhookAn external service POSTs to the agent's URL — Stripe events, GitHub webhooks, your own backend.
scheduleA cron expression matches. Daily standup digests, hourly polls, weekly reports.
loopA self-driving loop runs the agent every N seconds, optionally capped. Useful for monitoring tasks.
manualYour code calls an.agents.run(agentId, { ... }). Useful for batch jobs.
sessionMode 'per-user' | 'per-call' Optional default: 'per-user'

How sessions are scoped. Think of the agent as someone the user hires — same person, same continuous relationship.

ModeBehaviorPick it for
per-user Each user.id has one continuous session with the agent. Same user returning next week picks up the same history, same knowledge-base entries, same sandbox state. New users get their own independent session. Default. Assistants, support bots, coaches, anything the user comes back to. With one user (yourself) you also get a single continuous thread.
per-call Every invocation creates a fresh session that closes shortly after. Stateless webhooks, smoke tests, fire-and-forget reports.

Cron firings on a per-user agent fan out across users. A real-estate assistant with 1,000 active users + cron: '0 9 * * MON' = 1,000 parallel Monday-morning runs, each on its own per-user session. Agents that nobody has interacted with yet don't fire.

When the user sends a message through chat or your code calls an.agents.run(...), pass user: { id: '…' } — that's the join key. On per-user agents the request fails fast if user.id is missing.

connections ConnectionRef[] Coming

OAuth-managed third-party connectors (Slack, Gmail, GitHub, Stripe, …) — coming. Today the same outcomes are reached through mcpServers + declarative HTTP tools; the connector layer will sit on top of those primitives once the OAuth flow ships. See Connections.

mcpServers McpServerSpec[] Optional

HTTP/SSE Model Context Protocol servers the agent connects to at session start. Every tool the MCP server advertises becomes a callable tool for the model, namespaced as <server-name>__<tool>. Header values may reference workspace secrets so auth tokens never live in the spec.

mcpServers: [
  // public MCP server, no auth
  { name: 'echo', url: 'https://mcp.example.com/echo' },

  // authenticated — secret name is resolved at session start
  {
    name: 'linear',
    url: 'https://mcp.linear.app/sse',
    transport: 'sse',
    headers: { Authorization: 'Bearer {{secrets.LINEAR_TOKEN}}' },
  },
]

If you keep your spec as a folder, drop a mcp-servers.json file alongside AGENT.md with the same array — loadAgentDir picks it up automatically. Stdio MCP servers are not supported (the runtime can't spawn subprocesses).

secrets string[] Optional

Names of workspace secrets the agent declares it needs. Where you actually see them depends on the runtime:

  • HTTP tools and MCP headers use the {{secrets.NAME}} placeholder — the runtime substitutes the decrypted value at call time.
  • Teammate / Manager roles additionally expose every declared secret as a real environment variable inside the container, so a Bash call can read $TOKEN directly (e.g. git clone https://x-access-token:$GITHUB_TOKEN@github.com/…).
secrets: ['GITHUB_TOKEN', 'STRIPE_KEY']

Set the actual values once in the workspace console. Only the names listed here are exposed — never the full workspace secret store.

tools ToolDef[] Optional

Custom functions you write in TypeScript. The agent decides when to call them. Use this for behavior no connection covers — calling your internal API, computing something, or hitting a SaaS the catalog doesn't yet support.

import { defineTool, t } from '@agentnava/kit';

const lookupZestimate = defineTool({
  name: 'lookup_zestimate',
  description: 'Get the Zillow zestimate for an address',
  input: t.object({ address: t.string() }),
  handler: async ({ address }, ctx) => {
    // ctx.secrets, ctx.sessionId, ctx.user available here
  },
});

tools: [lookupZestimate]
plugins PluginRef[] Optional

Pre-packaged bundles of skills, subagents, commands, and MCP servers — installed once at the workspace and referenced by name. Claude Code plugins (.claude-plugin/plugin.json) install unchanged: an.plugins.install('github:owner/repo#v1'), then list them here.

plugins: ['claude-code-review', 'pr-helper@v2']
hooks Hook[] Optional

Event hooks fired by the runtime around the agent loop. Same event names and shape as Claude Code hooks — PreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionStart. Use them for audit logging, PII scrubbing, or gating risky tool calls.

hooks: [
  { event: 'PreToolUse',  match: { tool: 'Bash' }, run: async (ctx) => { /* approve or block */ } },
  { event: 'PostToolUse', match: { tool: '*' },    run: async (ctx) => { /* log */ } },
]
allowedTools string[] Optional

Whitelist of built-in tools the agent (and its subagents) can call. If unset, every built-in is available. Built-ins mirror Claude Code: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch, Task, TodoWrite.

allowedTools: ['Read', 'WebSearch', 'Task']
// disallowedTools also accepted
permissionMode 'default' | 'acceptEdits' | 'plan' | 'bypass' Optional default: 'default'

How aggressively the agent is allowed to act without confirmation. Same semantics as Claude Code's permission modes — default asks for risky tool calls, acceptEdits auto-approves file edits, plan only proposes, bypass never asks. Surface a UI affordance to your users if you change this from default.

meta Record<string, unknown> Optional

Anything you set here is readable from tool handlers via ctx.meta.X. Use it for per-deploy config the agent doesn't reason about — brand colors, deploy targets, feature flags.

meta: {
  deployTarget: 'embed',
  brandColor: '#B91C1C',
  region: 'eu-west',
}

Event stream

The agent runtime emits a typed SSE stream that any host UI can consume. The same shape comes back whether you're hitting a test or a prod deploy.

type AgentEvent =
  | { type: 'message-start'; messageId: string }
  | { type: 'message-delta'; messageId: string; delta: string }
  | { type: 'message-end'; messageId: string; final: string }
  | { type: 'tool-start'; toolId: string; name: string; input: unknown }
  | { type: 'tool-end'; toolId: string; ok: boolean; result?: unknown; error?: string }
  | { type: 'phase'; label: string; state: 'start' | 'end' }
  | { type: 'error'; message: string }
  | { type: 'done' };