SDKCustom agents
Agents in your own apps
A support chat on your site, an intake form that talks, an assistant inside your product. A sealed agent can only use the tools your app gives it, so it can face the public.
How it fits together
- Visitoron your site
- Your websitechat widget
- Your gatewayholds the token, answers tools
- Sealed agenton your Swarm machine
- Modelyour provider
- Tool calls are checked against the tool's schema before your server sees them, and what your server answers goes back to the model marked as untrusted.
- Identity never comes from the model. Your gateway decides whose order a call may see; the model only supplies inputs like an order code.
- The token is narrow. It can start and use that agent's conversations and answer its tool calls. It can't list conversations, reach another agent, approve anything or change tools. If someone later gives the agent a built-in tool, the token stops working.
1 · Define the agent and its tools
A tool is a name, a description, a JSON Schema for its input and an effect. The agent names the tools it may call. Loaded on the machine with swarmctl setup sealed-agent --stdin, or from code with agents.defineClientTool and agents.defineSealedAgent.
{
"tools": [{
"name": "get_order",
"description": "Look up the visitor's order by its code.",
"input_schema": {
"type": "object",
"properties": { "order_id": { "type": "string", "pattern": "^[A-Z0-9]{6}$" } },
"required": ["order_id"],
"additionalProperties": false
},
"effect": "read",
"timeout_ms": 5000
}],
"agent": {
"name": "support_desk",
"prompt": "You answer visitors' questions about their orders.",
"tools": ["get_order"]
}
}2 · Serve it from your app
import { SwarmClient } from '@swarm-agent/sdk';
// The gateway token reaches this one agent and nothing else.
const swarm = new SwarmClient({ baseUrl: process.env.SWARM_API_URL, token: process.env.SWARM_GATEWAY_TOKEN });
// One conversation per visitor.
const chat = await swarm.sessions.create({ agent_name: 'support_desk', workspace_path: '/project', title: 'Visitor chat' });
// Per message: send it, answer the agent's tool calls, get the reply.
const { reply } = await swarm.agents.converse(chat.id, visitorMessage, {
// Who the visitor is comes from your session, never from the model.
get_order: ({ order_id }) => orders.findForVisitor(order_id, visitor),
});agents.converse is the whole loop a gateway needs per message. Lower level: agents.serve answers tool calls until stopped, or pendingCalls, answer and fail one at a time.
3 · A person can take over
When a conversation needs a human, someone on your team takes it: the agent stops replying, they answer as themselves, and hand it back.
// With an operator token for this agent:
await swarm.agents.takeOver(chat.id); // the agent stops replying
await swarm.agents.replyAsOperator(chat.id, 'Hi, this is Sam. I have refunded it.');
await swarm.agents.handBack(chat.id); // the agent answers the next messageLimits, whatever the model does
| Limit | Default |
|---|---|
| Model calls per message | 4 |
| Output tokens per call | 1,024 |
| History sent to the model | 40 messages |
| Time per reply | 60 seconds |
| Messages per gateway token | 120 a minute |
| New conversations per gateway token | 300 an hour |
| Spend per day | off until you set it |
Limits per visitor (per person, per IP address, bursts) belong in your gateway.
Deploy it safely
The sealed image has no shell. Run it read-only with every capability dropped.
The agent's port stays on loopback or a private network.
Block every other destination at the network.
Never in browser code or chat. Tokens expire; mint a new one before they do.
Put a support chat on my shop that can look up orders and start returns. Nothing else.
sealed-agentsdk-token --agentgateway.ts
It's running on staging. Try “where's my order?” with order K7Q2PL. Returns need a person to approve; I set that as a takeover.
Full details for your AI: ai/custom-agents.md.