Node Embedded Agent
The Node Embedded Agent is a small library you import directly into your Node.js Capsule Service. It speaks the Capsule Management Contract to the Opstage backend on your behalf.
- Repository:
xtrape-capsule-agent-node - Package:
@xtrape/capsule-agent-node - Runtime: Node.js 18+
When to use it
Use the embedded agent when your service is itself a Node.js process — an integration adapter, a Playwright worker, an AI Agent runtime, or any worker you can import into.
Other runtimes can integrate through custom wrappers today, while dedicated sidecar and standalone agents are planned on the roadmap.
Install
Public Review npm package
During Public Review, install @xtrape/capsule-agent-node from the public-review npm dist-tag. Pin an explicit prerelease version if you need reproducible builds.
pnpm add @xtrape/capsule-agent-node@public-reviewMinimal example
import { CapsuleAgent } from "@xtrape/capsule-agent-node";
const agent = new CapsuleAgent({
backendUrl: process.env.OPSTAGE_BACKEND_URL!,
registrationToken: process.env.OPSTAGE_REGISTRATION_TOKEN,
tokenStore: { file: "./data/agent-token.txt" },
service: {
code: "integration-worker",
name: "Example integration service",
version: "0.3.1",
runtime: "nodejs",
},
});
await agent.start();registrationToken is required only on first start. After the agent token has been persisted to tokenStore.file, restarting the process just re-uses it.
Health reporting
agent.health(async () => {
const ok = await ping();
return ok
? { status: "UP" }
: { status: "DOWN", message: "vendor API unreachable" };
});Health is sampled on the heartbeat cadence and surfaced in the Opstage console. See Health Reporting.
Agent health providers return protocol-level HealthStatus values: UP, DEGRADED, DOWN, UNKNOWN.
Opstage may derive an operator-facing effectiveStatus: HEALTHY, UNHEALTHY, STALE, OFFLINE.
Config reporting
agent.configs(() => [
{
key: "UPSTREAM_URL",
type: "string",
sensitive: false,
editable: false,
valuePreview: process.env.UPSTREAM_URL,
},
{
key: "UPSTREAM_TIMEOUT_MS",
type: "number",
sensitive: false,
editable: false,
valuePreview: String(process.env.UPSTREAM_TIMEOUT_MS ?? 15000),
},
]);Configuration is observed, not pushed. The service remains the source of truth. See Config Reporting.
Action model
agent.action({
name: "rotateKey",
label: "Rotate API key",
dangerLevel: "HIGH",
requiresConfirmation: true,
inputSchema: {
type: "object",
required: ["newKey"],
properties: { newKey: { type: "string", minLength: 8 } },
},
handler: async (payload) => {
await rotate(payload.newKey as string);
return { success: true, data: { rotatedAt: new Date().toISOString() } };
},
});See Action Model for the full lifecycle (ACTION_PREPARE → ACTION_EXECUTE, confirmation, schema-driven UI).
Command polling
The agent maintains a long-poll against GET /api/agents/commands and dispatches incoming commands to the registered action handlers. You don't have to manage the polling loop yourself — agent.start() runs it for you.
Typed errors
Since v0.2 the SDK throws typed error subclasses for failures that need operator action, so callers can instanceof-branch instead of parsing messages:
import {
CapsuleAgent,
RegistrationError,
AgentAuthError,
NetworkError,
} from "@xtrape/capsule-agent-node";
try {
await agent.start();
} catch (err) {
if (err instanceof RegistrationError) {
// Registration token invalid / single-use / expired.
// Not retried — operator must mint a fresh token.
} else if (err instanceof AgentAuthError) {
// Persisted agent token was revoked.
// Delete the token file, then start with a fresh registration token.
} else if (err instanceof NetworkError) {
// Transport-level failure. The SDK already retried inside its
// backoff budget; this rethrow signals "give up at the caller".
} else {
throw err;
}
}RegistrationError and AgentAuthError are non-retryable — the SDK throws them straight through. NetworkError is retryable and is what the internal backoff already iterates over before surfacing.
Structured logging
The SDK accepts an optional onLog sink that receives structured records instead of pre-formatted console lines. Records carry level, event, context, and data fields suitable for piping into a log aggregator:
const agent = new CapsuleAgent({
// ...
onLog: (record) => {
process.stdout.write(JSON.stringify(record) + "\n");
},
});When onLog is not supplied the SDK falls back to the existing console-shaped sink, so this is a pure addition — no breaking change.
Security notes
- Treat the registration token like a one-time secret. After first start, do not keep it in the environment.
- Persist the agent token file with restrictive file permissions (
chmod 600). - The agent only makes outbound connections; you do not need to expose any inbound port to Opstage.
- A leaked agent token can be revoked from the Opstage console — see Token Model.
Relationship to other repos
| Repo | Purpose |
|---|---|
xtrape-capsule-agent-node | This SDK |
xtrape-capsule-contracts-node | Shared Zod schemas / types used by SDK and backend |
xtrape-capsule-ce | The Opstage CE backend the SDK talks to |
→ Continue with the Capsule Management Contract for the wire-level view.