@cerebro-labs/agentnava-sdk
v0.26.0
Published
The AgentNava SDK. Configure an agent, start a conversation, and work with it.
Readme
@cerebro-labs/agentnava-sdk
Configure an agent, start a conversation, work with it.
npm install @cerebro-labs/agentnava-sdkNode 18 or later, or Bun, or any runtime with fetch. Both import and
require work, and TypeScript types ship with it.
import { AgentNava } from '@cerebro-labs/agentnava-sdk';
const ws = AgentNava.getWorkspace(); // reads AGENTNAVA_API_KEY
const agent = await ws.agents.create({
name: 'Refund checker',
instructions: 'Decide whether a late delivery qualifies for a refund.',
});
const conversation = await agent.start();
console.log(await conversation.ask('Order 4471 arrived nine days late.'));The full reference is at https://docs.agentnava.com. (It used to point here at a file path inside our own repository, which nobody who installs this package has.) This README covers only the things that are easy to get wrong.
Connecting an account, end to end
authorize() gives you a URL for your user to visit. waitUntilReady() tells
you when they are done, so you do not have to write a polling loop or work out
which field to believe.
const { url } = await conversation.authorize('gmail');
showToUser(url); // popup, redirect, QR code, your choice
await conversation.waitUntilReady(); // resolves once nothing required is outstandingHow you show the URL is yours: this SDK authenticates with an API key, so it runs on your server, and the browser half is your application's.
An OPTIONAL requirement does not hold the wait up, for the same reason it does
not make ready false: the conversation genuinely works without it.
On timeout it throws conversation_not_ready and the message names what is
still missing, e.g. still waiting on: connection gmail. It is safe to call
again; someone who has not finished signing in may just need longer.
await conversation.waitUntilReady({ timeoutMs: 120_000, intervalMs: 2_000, signal });0.2.0 is a breaking change. Upgrade from 0.1.1, do not stay on it.
"session" is gone from everything you touch. It survives only in our storage, which you never see.
| 0.1.1 | 0.2.0 |
|---|---|
| GET /v1/sessions | GET /v1/conversations (the old path now 404s) |
| ConnectionStatus.sessionId | ConnectionStatus.conversationId |
| conversation.authorize() sent sessionId | sends conversationId |
| scope: 'session' came back from the API | scope: 'conversation', the value the type always declared |
0.1.1 stops working once these reach production, because the API reads only the new field names. There is no compatibility shim: this package is private and pre-launch, so the old spellings were removed rather than deprecated.
If you are on 0.1.1 the change is mechanical: rename sessionId to
conversationId wherever you read it or pass it. Nothing else about the shape
moved.
Holding an object is free
ws.agent(id), ws.conversation(id) and ws.project(id) make no network
call. You get something you can call methods on, and the first request happens
when you call one.
Store the id, not the object. A returned object is a handle, not JSON.
Asking: one method, two shapes
const reply = await conversation.ask('...'); // just the answerfor await (const e of conversation.ask('...')) { // as it happens
if (e.type === 'text') append(e.text);
if (e.type === 'done') save(e.reply);
}The message is sent when you call ask, not when you await or iterate it.
Calling ask twice asks twice.
You may await the same turn more than once; it is a promise, and you get the same answer. You may not iterate it twice, or mix the two, because the events are a live stream rather than a stored list and the second consumer would receive nothing at all. That throws instead.
The turn is not your request. It runs on our side, so a dropped connection
does not cancel it. Read the answer back with conversation.messages() rather
than asking again.
Changing an agent publishes a version
There is no separate save. agent.update(), setWorkflow, removeWorkflow and
setTriggers each publish a new version.
Send only what changes. Everything you do not send is inherited from the current version. That is what makes changing one field safe, and it is not a convenience: a caller that rebuilds and resends a whole configuration drops whatever it forgot, which has caused four separate data-loss bugs in this codebase.
await agent.update({ instructions: 'Be brief.' }); // keeps everything elseA conversation pins its version
It pins the agent's version when it starts and keeps it. Publishing afterwards does not move a conversation that is already open.
Knowledge is the exception, and deliberately so: it is read by reference, so replacing a knowledge file changes what open conversations see on their next turn. A price list you could not correct without republishing the agent would be useless.
Two drives
| | The agent sees | Belongs to | You write | The agent writes |
|---|---|---|---|---|
| Knowledge | /knowledge | the agent | yes | no |
| Conversation | /workspace | one conversation | yes | yes |
An application stages material by writing it in before asking about it:
await conversation.writeFile('/input/contract.pdf', bytes);
await conversation.ask('What are the payment terms?');Anywhere under the root works. input/ is a convention here and nothing
enforces it, because the agent and your application share one namespace.
Credentials have two tiers, and they do not substitute
fixed is set once on the agent and used by every conversation. conversation
is supplied per conversation, so each user brings their own account.
An account connected on the agent does not satisfy a declaration scoped
conversation, and a conversation cannot supply a fixed one. Check
conversation.ready and conversation.requires to see what is outstanding.
Drafting tools from an API you already describe
importTools turns an OpenAPI document, a Postman collection or a working curl
command into draft httpTools, so nobody hand-writes one declaration per
endpoint.
import { importTools } from '@cerebro-labs/agentnava-sdk';
const draft = importTools({ openapi }, { include: (c) => !c.url.includes('/admin/') });
await ws.agents.create({ id: 'support', instructions, httpTools: draft.tools });It runs entirely on your machine. Nothing about the source is sent to
AgentNava, which matters because these files routinely contain a live token:
any credential found is replaced with a {{secrets.NAME}} placeholder and
listed in draft.secrets, with no value carried across. Each drafted tool also
declares the secrets it uses in its own secrets field, which is what lets the
runtime load them for the turn.
They are drafts. draft.warnings names what a human still has to decide, and
the big one is description-missing: none of the three formats contains the
sentence a model reads to pick a tool, and a bad description does not fail, it
makes the agent call the wrong thing.
Errors
Every failure throws AgentNavaError.
catch (err) {
if (err instanceof AgentNavaError) {
err.code; // 'agent_not_found', branch on THIS
err.status; // 404
err.retryable; // 5xx, 429, and network failures
err.retryAfter; // seconds, on a 429
}
}Branch on code, not on message. Codes are stable; messages are written for a
person reading a log.
retryable is the only thing worth automating on: a 400 will fail identically
forever, and a 404 will not become found.
404 covers both "no such thing" and "not yours", so a caller probing ids cannot tell another customer's agent from one that never existed. There is no 403.
Configuration
AgentNava.getWorkspace(); // AGENTNAVA_API_KEY
AgentNava.getWorkspace({ apiKey, baseURL, timeout });
AgentNava.getWorkspace({ fetch: myFetch }); // a proxy, or a testRuns anywhere fetch exists: Node, Bun, Deno, Cloudflare Workers, the browser.
loadWorkflows is the one exception; it reads a directory off disk and so needs
Node or Bun.
Licence
Apache License 2.0. See LICENSE, and NOTICE for the attribution that
section 4(d) asks you to carry along.
Free to use, modify and redistribute, including commercially, with an explicit patent grant. Section 6 grants no rights to the names: "AgentNava" and "Cerebro Labs" remain trademarks of Cerebro Labs, Inc.
