Examples
Runnable scripts grouped by agent shape. The first five build Freelancers (hosted bots that serve many end users). The last two build Teammates (specialists that live in your workspace and do a role of work). All examples live in packages/kit/examples/ in the open repo and copy-paste cleanly into your own project. Not sure which to pick? See Roles.
Set your credentials once:
# Generate one at https://console.agentnava.com → API keys
export AGENTNAVA_API_KEY=ank_live_…
# Optional — defaults to https://api.agentnava.com
export AGENTNAVA_BASE_URL=https://api.agentnava.com
For Freelancers hosted bots
These build agents you publish to a URL and embed on a website: fan-out across many end users, one session per visitor.
1. Hello, agent
The smallest end-to-end lifecycle: push a spec, configure an agent, deploy it, start a session, send one message, stream the reply. About twenty lines.
import { AgentNava } from '@agentnava/kit';
const an = new AgentNava();
// 1) Push the spec. `instructions` is your AGENT.md — the system prompt.
const spec = await an.specs.push({
key: 'hello',
instructions: 'You are Hello — a friendly assistant. Be concise.',
});
// 2) Configure → a versioned agent.
const v = await an.agents.configure({
key: 'hello',
name: 'Hello',
modelClass: 'standard',
spec,
triggers: [{ kind: 'chat' }],
});
// 3) Deploy to a dev URL.
await an.agents.deployToTest(v.versionId);
// 4) Start a session and chat one turn.
const session = await an.sessions.start({ agentId: v.agentId, env: 'test' });
for await (const event of an.sessions.chat(session.id, 'Say hello.')) {
if (event.type === 'message-delta') process.stdout.write(event.delta);
}
Run: bun run examples/hello.ts
spec pushed: spec_4f1c9a@v1
agent version: ver_3da4cb (agt_43e7dc)
test url: http://localhost:8787/v1/test/agt_43e7dc
session: sess_1eb57f
Hello! I'm here to help — what's on your mind today?
2. Multi-turn memory
The runtime stores chat history per session. Send messages, the agent remembers what was said earlier. No extra wiring needed on your side.
// Same setup as above — push spec, configure, deploy, start session.
async function turn(session: { id: string }, message: string) {
process.stdout.write(`\n--- you → ${message} ---\n`);
for await (const e of an.sessions.chat(session.id, message)) {
if (e.type === 'message-delta') process.stdout.write(e.delta);
}
}
await turn(session, "My name is Sam. Please remember it.");
await turn(session, "What did I just tell you?");
await turn(session, "Repeat my name one more time.");
Run: bun run examples/multi-turn.ts
--- you → My name is Sam. Please remember it. ---
Got it, Sam. What can I help you with?
--- you → What did I just tell you? ---
You told me your name is Sam.
--- you → Repeat my name one more time. ---
Your name is Sam.
3. Versioned specs
Specs are immutable once pushed. Push a new version of the same key and you get spec_xyz@v2 alongside @v1. Old agent versions keep using the old spec until you reconfigure.
// v1
const v1Spec = await an.specs.push({
key: 'versioned-bot',
instructions: 'Respond in exactly five words. Always.',
});
const v1 = await an.agents.configure({
key: 'versioned-bot', name: 'Versioned', modelClass: 'standard',
spec: v1Spec, triggers: [{ kind: 'chat' }],
});
// v2 — same key, new instructions → new version
const v2Spec = await an.specs.push({
key: 'versioned-bot',
instructions: 'Respond in exactly ten words. Always.',
});
const v2 = await an.agents.configure({
key: 'versioned-bot', name: 'Versioned', modelClass: 'standard',
spec: v2Spec, triggers: [{ kind: 'chat' }],
});
console.log(`v1: ${v1.versionId} ← ${v1Spec.id}@v${v1Spec.version}`);
console.log(`v2: ${v2.versionId} ← ${v2Spec.id}@v${v2Spec.version}`);
Run: bun run examples/versioning.ts
Existing sessions pinned to v1 keep using the five-word spec. New sessions started against the agent's latest version use the ten-word spec. You can ship spec changes safely without disrupting running conversations.
4. Bring your own knowledge
Pass arbitrary text as a workflows file and the model will ground replies in it. Here we scrape a public site and feed it as the agent's source material.
import { AgentNava } from '@agentnava/kit';
const an = new AgentNava();
const URL = 'https://swatipetkar.com';
// 1) Scrape and convert to plain text.
const html = await fetch(URL).then((r) => r.text());
const text = htmlToText(html); // your own minimal stripper
// 2) Push a spec with the scraped content as a workflow file.
const spec = await an.specs.push({
key: 'aria',
instructions: 'You are Aria, a friendly assistant for Swati\'s website.',
workflows: [{ name: 'source', content: `# Source — ${URL}\n\n${text}` }],
});
const v = await an.agents.configure({
key: 'aria', name: 'Aria', modelClass: 'premium',
spec, triggers: [{ kind: 'chat' }],
});
await an.agents.deployToTest(v.versionId);
const session = await an.sessions.start({ agentId: v.agentId, env: 'test' });
for await (const e of an.sessions.chat(session.id, 'Who is Swati and what does she work on?')) {
if (e.type === 'message-delta') process.stdout.write(e.delta);
}
Run: bun run examples/aria-once.ts
For sites smaller than ~30 pages, the "stuff it into the system prompt" pattern works fine; modern models handle 100k+ tokens of context. For larger corpora, vector retrieval (planned in Knowledge) becomes necessary.
5. Claude Code-style skills
If you've structured your project like a Claude Code agent (AGENT.md at the root plus skills/<name>/SKILL.md folders), loadAgentDir reads the whole bundle in one call.
import { AgentNava, loadAgentDir } from '@agentnava/kit';
const an = new AgentNava();
// Reads:
// ./agents/concierge/AGENT.md (system prompt)
// ./agents/concierge/skills/*.md (auto-discovered)
// ./agents/concierge/subagents/*.md (delegated personas)
// ./agents/concierge/commands/*.md (user-invokable slash commands)
const spec = await an.specs.push(
loadAgentDir('./agents/concierge', { key: 'concierge' }),
);
const v = await an.agents.configure({
key: 'concierge', name: 'Concierge', modelClass: 'premium',
spec, triggers: [{ kind: 'chat' }],
});
await an.agents.deployToTest(v.versionId);
Run: bun run examples/with-claude-skills.ts
agents/concierge/
├── AGENT.md
├── skills/
│ ├── neighborhoods/SKILL.md
│ └── pricing/SKILL.md
└── commands/
└── compare-listings.md
For Teammates specialists in your workspace
These build agents that live in your workspace and do a role of work. No deployToTest / deployToProd — Teammates aren't published. After configure the Teammate appears in your workspace; you talk to it through the console (or, when several Teammates exist, through the auto-spawned Manager). See Roles → Teammates for the model and Teams & workflow for the day-to-day.
6. Hire a single Teammate
Configure one Teammate. The spec is the same shape as a Freelancer's; the difference is role: 'teammate' and the absence of a deploy step. As soon as configure returns, your Teammate is in the workspace.
import { AgentNava, loadAgentDir } from '@agentnava/kit';
const an = new AgentNava();
// Spec layout on disk:
// agents/code-reviewer/AGENT.md — role + voice + scope
// agents/code-reviewer/skills/lint.md — review conventions
// agents/code-reviewer/skills/style.md — style preferences
const spec = await an.specs.push(
loadAgentDir('./agents/code-reviewer', { key: 'code-reviewer' }),
);
// role: 'teammate' is the only thing distinguishing this from a Freelancer.
const v = await an.agents.configure({
key: 'code-reviewer',
name: 'Code Reviewer',
modelClass: 'standard',
role: 'teammate',
spec,
});
// No deployToTest, no deployToProd, no sessions.start.
// The Teammate is already in your workspace. Open it:
// https://console.agentnava.com/workspace/code-reviewer
console.log(`Code Reviewer ready: ${v.agentId} (${v.versionId})`);
Run: bun run examples/hire-teammate.ts
Your workspace now has a code-reviewer entry. Click into it and chat. Anything it writes to its workspace folder survives across conversations: paste a PR diff today, ask follow-up questions about it next week, the context is still there.
7. Hire a team (Manager auto-spawns)
Configure two Teammates with complementary roles. The platform auto-spawns a Manager once the second Teammate lands (your new front door). You talk to the Manager; it routes to the right Teammate and synthesizes the reply.
import { AgentNava, loadAgentDir } from '@agentnava/kit';
const an = new AgentNava();
// Teammate 1 — owns research
const researcherSpec = await an.specs.push(
loadAgentDir('./agents/researcher', { key: 'researcher' }),
);
await an.agents.configure({
key: 'researcher', name: 'Researcher',
modelClass: 'standard', role: 'teammate',
spec: researcherSpec,
});
// Teammate 2 — owns drafting. This second configure trips the threshold;
// the platform spawns a Manager automatically for your default team.
const writerSpec = await an.specs.push(
loadAgentDir('./agents/writer', { key: 'writer' }),
);
await an.agents.configure({
key: 'writer', name: 'Writer',
modelClass: 'standard', role: 'teammate',
spec: writerSpec,
});
console.log('Team ready. Open https://console.agentnava.com/workspace — the Manager is your front door.');
Run: bun run examples/hire-team.ts
Ask the Manager "find me three case studies on regional bank reorganisation and draft a one-pager summarising them." It hands the research half to researcher, takes the result, hands the drafting half to writer, then replies to you with the synthesized one-pager. Both Teammates share one workspace, so the researcher's notes file is sitting on disk when the writer goes to draft.
Verified end-to-end
The Freelancer examples are run against the live production runtime at api.agentnava.com on each SDK release. Teammate examples are run against a Premium-plan workspace. If something doesn't work for you, please tell us.