What people call it. Shown wherever the agent appears.
Agents
One object describes the agent. There is no separate spec and no deploy step.
What an agent is
An agent is one object. There is no separate spec, no manifest and no deploy step: the fields below are the agent, and it is usable the moment you create it.
const agent = await ws.agents.create({
name: 'Refund checker',
instructions: 'You help a support agent decide whether a refund is warranted.',
});Everything else has a default. Add workflows when it needs procedures, connections when it needs an account, knowledge when it needs to read something. Not before.
An agent shows in the portal as Live the moment it exists: it answers your calls, your team's tests and its chat page now, and there is nothing to switch on. That is true however it was made, from code or in the portal. Who can use its website chat is a separate setting, Restricted to your team until you open it, and you never need it to use the agent from code.
Fields
Who the agent is, what it is for, and how it should answer. This is the system prompt.
Your own stable id. Unique in your workspace. Omit it and we mint one.
What the agent says before anyone types. Omit it and the agent waits. Costs nothing: no model runs.
What the platform enforces, even when someone tries to talk the agent out of it. See Guardrails.
Which model the agent thinks with, and what each turn costs. Standard, the default, is the fastest and costs 0.02 credits a turn; lite costs 0.01, premium 0.05 (it thinks harder, more slowly) and best 0.20, where one credit is one US dollar. The old name fast still works and means standard.
drive gives every conversation a durable filesystem. none means the agent acts only through its tools, which suits an agent whose whole job is calling them.
Named procedures. See Workflows.
The apps it uses. Each is a name, an app, and whose account: Connection.creatorAccount(app, account) for the agent creator's account, or Connection.endUserAccount(app) for each end user's own. See Connections.
Keys it needs.
Typed values collected when a conversation starts.
What starts a run. See Triggers.
Platform capabilities to switch on, such as 'web_search'.
Whether the agent answers with cards, tables and charts where the chat can show them, such as the website chat. Set false for an agent that should answer in text only. In the builder this is the Widgets switch under Version, and the Agent Builder can set it when asked.
MCP servers whose tools it may call. See Tools.
Your own endpoints, described so the agent can call them.
Changing an agent
Updating publishes a new version. Fields you leave out keep their current value, so a wording change is one field.
await agent.update({
instructions: '... Be strict about perishable goods.',
});Conversations already running keep the version they started on. New ones get the new version. That is what makes it safe to edit an agent while people are talking to it.
await agent.versions(); // the agent at each version
await agent.get({ version: 3 }); // as it was| Change this | Reaches a conversation already open |
|---|---|
update, setWorkflow, removeWorkflow | No. Each publishes a version |
knowledge.put, knowledge.remove | Yes, on the next turn |
connections.add, connections.change, connections.remove | No. Each publishes a version |
| An account signed in again, or disconnected | Yes, on the next turn |
attach({ secrets }) | Yes, for scope: 'fixed' |
A stored secret replaced or deleted (update, delete) | Yes, on the next tool call, in every conversation it is attached to |
setTriggers | Not applicable. A trigger starts a new run |
A version pins a reference. Instructions and workflows are the thing, so a version freezes them. Knowledge is a folder, and a connection points at an account, so the version pins which one and what is on the other end stays live. Correcting a policy document reaches a conversation in flight; rewording an instruction does not.
Guardrails
An agent's instructions are what it tries to do. Its guardrails are what the platform enforces, so a visitor cannot argue the agent past them.
await agent.update({
guardrails: {
stayOnTopic: true,
rules: [
'Never offer discounts or change prices.',
'Never send email to anyone outside @example.com.',
],
},
});stayOnTopic is on unless you set it to false. It declines anything outside the
agent's job, judged from its own instructions: other work like code or homework, "ignore your
instructions", requests for its prompt or files, and "I'm the owner" claims made to get special treatment.
Each rule is one plain-English sentence, at most 20 rules. Every rule is checked in three places:
- The message. A message asking the agent to break a rule is declined. The agent never reads it and replies politely in its own voice instead.
- Each action. A tool call that breaks a rule, an email to the wrong address for example, does not happen. The agent is told and explains.
- The reply. A reply that breaks a rule is taken back and answered again within it.
The message is checked before the agent reads it, which adds about 0.2 seconds, so a message that breaks a
guardrail never reaches the agent and nothing it asked for is done. The check reads the whole message, the
agent's instructions and workflows, and the conversation so far, so a follow-up like "make it cheaper" is judged
in context and a request one of its workflows covers is not called off-topic. Set checkMessagesFirst: false
to answer at once instead: the agent then reads every message, and its actions and replies are still checked.
Replies are checked as they stream, so they cost no wait.
When a guardrail fires, the turn's stream says so with a guardrail event naming the rule, and a
reply that was taken back arrives as a reset before the new words.
Guardrails are part of the configuration, so changing them publishes a version, and a conversation already
in progress keeps the version it began on. In the web app they are the Guardrails tab of the
Build screen; checkMessagesFirst is set from the SDK only.
Website chat
Every agent has a chat page of its own, the share link, and an embed script that puts it on your website. Who can chat is one setting, in Google Docs' words:
| Who can chat | The share link | Other websites |
|---|---|---|
| Restricted (every new agent) | Members of your workspace, signed in to AgentNava | Only the allowed websites below |
| Anyone with the link | Anyone | Any website |
Restricted is how a team tests an agent: copy the link, send it to a teammate, and they sign in and chat. Members are not held to the daily message limit a visitor has. Open it to anyone when the agent is ready for the public; each visitor's turns are charged to your workspace.
await agent.websiteChat.access.get(); // 'restricted' for a new agent
await agent.websiteChat.access.set('anyone'); // anyone with the link, on any website
const chat = await agent.websiteChat.get();
chat.chatUrl; // the share link, to send or put in an iframe
chat.embedAgentId; // what the embed script's data-agent-id takes
chat.access; // who can chatPublishing a new version moves the website chat to it, as it moves new conversations. An agent that needs each user's own account cannot have a website chat, because a visitor has none to bring: its page serves nobody, and a version that adds such a need leaves the page on the last version it could serve. Pausing an agent stops it everywhere, its website chat included.
Allowed websites. While the agent is Restricted, its chat appears inside another website, through the embed script, only on the websites you list, and visitors there chat without signing in. A listed domain covers its subdomains, and an empty list means no other website. The same setting and list are in the portal, in the Website chat panel on the agent's page and in the Publish dialog.
How strong each setting is: Restricted with no websites is enforced by our servers for every request. Restricted with websites relies on the website the visitor's browser reports, which a script can fake, like CORS. Anyone with the link is open.
await agent.websiteChat.allowedWebsites.set(['https://acme.com/help', 'localhost']);
// ['acme.com', 'localhost']: each entry becomes its domain; localhost is for testing
await agent.websiteChat.allowedWebsites.get();Coming back to an agent
You will not hold the object between requests. Store the id and pick it up again:
const agent = ws.agent(row.agentId); // no network call
const conversation = await agent.start({ endUserId: 'u_ananya' });ws.agent(id) makes no request. You get something you can call methods on, and
the first request happens when you call one.