Skip to content

Integrate the TypeScript SDK

The TypeScript SDK provides Caracal.fromEnv() as the entry point, spawn() to open an agent session bound to an async context, delegate() to create authority edges to other agents, and transport() to get a fetch implementation that auto-injects W3C headers on every outbound call.

  • Node.js 20 or later.
  • A registered application in a Caracal zone with a client_secret.
  • The Caracal stack running with the Coordinator reachable.
Terminal window
npm install @caracalai/sdk

Set these in your process environment or .env file:

VariableRequiredDescription
CARACAL_COORDINATOR_URLYesBase URL of the Coordinator service (e.g., http://localhost:4000)
CARACAL_ZONE_IDYesZone identifier
CARACAL_APPLICATION_IDYesRegistered application ID
CARACAL_SUBJECT_TOKENYesAmbient bearer token for this application
CARACAL_GATEWAY_URLNoGateway base URL for resource-routed calls
CARACAL_RESOURCESNoComma-separated resource bindings: resourceId=upstreamPrefix,other=prefix
import { Caracal } from '@caracalai/sdk';
// Reads all CARACAL_* env vars
const caracal = Caracal.fromEnv();

To configure programmatically:

import { Caracal, AgentKind } from '@caracalai/sdk';
const caracal = new Caracal({
coordinator: { baseUrl: 'http://coordinator:4000' },
zoneId: 'my-zone',
applicationId: 'my-app',
subjectToken: process.env.MY_APP_TOKEN!,
gatewayUrl: 'http://gateway:8081',
resources: [
{ resourceId: 'resource://inventory', upstreamPrefix: 'http://inventory:8080' },
],
defaultKind: AgentKind.Instance,
defaultTtlSeconds: 3600,
});

spawn() opens an agent session on the Coordinator, binds it to the current async context via AsyncLocalStorage, runs the provided function, then closes the session when the function returns or throws.

await caracal.spawn(async () => {
const ctx = caracal.current();
console.log('agent session:', ctx?.agentSessionId);
// All awaited calls inside this function share the agent session.
});

Options:

await caracal.spawn(async () => { /* ... */ }, {
kind: AgentKind.Ephemeral, // 'service' | 'instance' | 'ephemeral'
ttlSeconds: 600,
metadata: { purpose: 'invoice-batch' },
});

Inside any spawn() callback — including in functions called at arbitrary depth — caracal.current() returns the live context:

function processInvoice(id: string) {
const ctx = caracal.current();
// ctx.agentSessionId, ctx.zoneId, ctx.delegationEdgeId, ctx.hop
}

delegate() creates a delegation edge from the current agent session to a target agent session, scoping authority to specific scopes. The child callback runs under the delegated context.

await caracal.spawn(async () => {
await caracal.delegate(
{
to: targetAgentSessionId,
toApplicationId: 'payments-service',
scopes: ['payment:submit', 'payment:read'],
ttlSeconds: 300,
},
async () => {
const ctx = caracal.current();
// ctx.delegationEdgeId is now set
// Outbound calls carry this edge in W3C Baggage
}
);
});

caracal.transport() returns a fetch-compatible function that reads the current context from AsyncLocalStorage and injects:

  • Authorization: Bearer <subjectToken>
  • traceparent: 00-{traceId}-{spanId}-01
  • baggage: caracal.agent_session=…,caracal.delegation_edge=…,caracal.hop=N
const transport = caracal.transport();
await caracal.spawn(async () => {
const res = await transport('http://inventory:8080/items', {
method: 'GET',
});
const items = await res.json();
});

If CARACAL_GATEWAY_URL and CARACAL_RESOURCES are set, the transport rewrites URLs for known resource prefixes and routes them through the Gateway.

When your service receives a request from another Caracal agent, extract the context from the request headers:

import express from 'express';
const app = express();
app.use(async (req, res, next) => {
await caracal.bindFromHeaders(req.headers, async () => {
// caracal.current() is populated from the inbound envelope
next();
});
});

Register callbacks that fire when agent sessions open and close:

caracal.onAgentStart((ctx) => {
console.log('session opened:', ctx.agentSessionId);
});
caracal.onAgentEnd((ctx) => {
console.log('session closed:', ctx.agentSessionId);
});
import { Caracal, AgentKind } from '@caracalai/sdk';
const caracal = Caracal.fromEnv();
async function runBatch(invoiceIds: string[]) {
await caracal.spawn(
async () => {
const ctx = caracal.current()!;
console.log(`processing ${invoiceIds.length} invoices in session ${ctx.agentSessionId}`);
const transport = caracal.transport();
for (const id of invoiceIds) {
const res = await transport(`http://invoices:8080/invoices/${id}`);
if (!res.ok) throw new Error(`invoice ${id} fetch failed: ${res.status}`);
const data = await res.json();
console.log(data);
}
},
{ kind: AgentKind.Ephemeral, ttlSeconds: 120 }
);
}
runBatch(['inv-001', 'inv-002', 'inv-003']);