Node.js embedded Agent SDK for connecting Capsule Services to Opstage.
@xtrape/capsule-agent-node embeds an Opstage Agent inside a Node.js service.
The Agent registers with Opstage CE, persists its Agent token, sends heartbeats,
reports service metadata, exposes configs and health, and polls Commands for
operator-triggered Actions.
Package status: Xtrape Capsule is currently in Public Review before the
v0.1.0 Public Previewrelease. This package is published under thepublic-reviewdist-tag. APIs, contracts, deployment instructions, and SDK interfaces may still change.
During Public Review, install the prerelease package with:
pnpm add @xtrape/capsule-agent-node@public-reviewThe current Public Review version may change before v0.1.0.
For this repository itself:
pnpm install
pnpm buildThis example matches the current SDK API: create a CapsuleAgent, configure
providers with .health() / .configs(), register Actions with .action(),
then call start().
import { CapsuleAgent } from "@xtrape/capsule-agent-node";
const agent = new CapsuleAgent({
backendUrl: process.env.OPSTAGE_BACKEND_URL ?? "http://localhost:8080",
registrationToken: process.env.OPSTAGE_REGISTRATION_TOKEN,
tokenStore: { file: "./data/agent-token.txt" },
// Optional. If omitted, the SDK derives agent identity from `service`.
// Provide it explicitly when one Agent owns multiple Capsule Services on
// the same host, or when you want a stable agent code that survives
// service renames.
agent: {
code: "hello-capsule-agent",
name: "Hello Capsule Agent",
runtime: "nodejs",
},
service: {
code: "hello-capsule",
name: "Hello Capsule",
version: "0.1.0",
runtime: "nodejs",
description: "Minimal Capsule Service example",
},
});
agent.health(() => ({
status: "UP",
message: "ok",
details: {
uptimeSeconds: Math.floor(process.uptime()),
},
}));
agent.configs(() => [
{
key: "HELLO_MODE",
label: "Hello mode",
type: "string",
editable: false,
sensitive: false,
valuePreview: process.env.HELLO_MODE ?? "default",
},
]);
agent.action({
name: "echo",
label: "Echo",
description: "Return the submitted message.",
dangerLevel: "LOW",
requiresConfirmation: false,
inputSchema: {
type: "object",
required: ["message"],
properties: {
message: { type: "string", default: "hello" },
},
},
prepare: () => ({
initialPayload: { message: "hello" },
currentState: { service: "ready" },
}),
handler: async (payload) => ({
success: true,
data: { echo: payload.message },
}),
});
await agent.start();- An operator creates a single-use Registration Token in Opstage CE.
- The service starts with
registrationTokenand calls the Agent registration API. - Opstage returns an Agent ID and Agent token.
- The SDK stores the issued credentials in
tokenStore.fileas<agentId>:<agentToken>. - Future restarts reuse the stored Agent token; the Registration Token is not needed again.
If the token file is lost or the Agent is revoked, create a new Registration Token and restart the service with it.
The service option describes the Capsule Service reported to Opstage:
service: {
code: "hello-capsule", // stable unique service code
name: "Hello Capsule", // operator-facing display name
description: "...", // optional
version: "0.1.0", // service version
runtime: "nodejs", // nodejs | java | python | go | other
manifest: { labels: { team: "ai" } }, // optional passthrough metadata
}The SDK wraps this as a CapsuleService manifest with kind: "CapsuleService",
schemaVersion: "1.0", and agentMode: "embedded".
Use .health(provider) to report protocol-level health:
agent.health(async () => ({
status: "UP", // UP | DEGRADED | DOWN | UNKNOWN
message: "queue healthy",
details: { queueDepth: 0 },
}));The provider runs for heartbeats and service reports. Do not include secrets in
details.
Agent health providers return protocol-level HealthStatus values: UP,
DEGRADED, DOWN, UNKNOWN.
Opstage may derive an operator-facing effectiveStatus: HEALTHY, UNHEALTHY,
STALE, OFFLINE.
Use .configs(provider) to report observed config metadata:
agent.configs(() => [
{
key: "UPSTREAM_BASE_URL",
type: "string",
sensitive: false,
editable: false,
valuePreview: process.env.UPSTREAM_BASE_URL,
},
{
key: "UPSTREAM_API_KEY",
type: "secret",
sensitive: true,
editable: false,
valuePreview: "[REDACTED]",
secretRef: "env://UPSTREAM_API_KEY",
},
]);Configs are reported to Opstage for visibility; the current CE flow does not push config values from Opstage into the service.
Use .action() to expose an operator-triggerable operation:
agent.action({
name: "reload-cache",
label: "Reload Cache",
dangerLevel: "MEDIUM",
requiresConfirmation: true,
timeoutSeconds: 30,
handler: async () => {
await reloadCache();
return { success: true, message: "Cache reloaded." };
},
});Action metadata is published in the service report. The SDK intentionally strips runtime-only handler functions before reporting the Action Catalog.
Opstage uses two Command types for actions:
ACTION_PREPARE— created when the UI opens an Action panel. The SDK callsprepare()if present and returns dynamic form metadata such asinputSchema,initialPayload, andcurrentState.ACTION_EXECUTE— created when the operator confirms the Action. The SDK callshandler(payload)and reports the result.
If an Action has no prepare handler, the SDK returns a default prepare payload
based on the action metadata and inputSchema defaults.
The embedded Agent starts three loops by default:
| Loop | Default | Purpose |
|---|---|---|
| Heartbeat | 30 seconds | Agent liveness and latest health |
| Service report | 60 seconds | Manifest, configs, actions, health |
| Command poll | 5 seconds | Fetch and execute pending Commands |
You can override intervals:
new CapsuleAgent({
// ...
intervals: {
heartbeatMs: 30_000,
serviceReportMs: 60_000,
commandPollMs: 5_000,
},
});For tests, set autoStartLoops: false and call start() to perform one
registration/report/heartbeat/poll cycle.
The default token store is file-based via FileTokenStore. Store the token file
in a private data directory:
new CapsuleAgent({
// ...
tokenStore: { file: "./data/agent-token.txt" },
});Security recommendations:
- chmod the containing directory so only the service user can read it;
- never commit token files;
- rotate by revoking the Agent in Opstage and registering a new one;
- prefer secret managers for production wrappers when available.
- Registration Tokens are single-use bootstrap credentials.
- Agent Tokens are long-lived bearer tokens; treat them as secrets.
- Actions are remote operational capabilities. Use
dangerLevel,requiresConfirmation, and server-side validation in handlers. - Do not report raw passwords, API keys, cookies, OTPs, browser storage, or session files through health/config/action results.
- Logs are redacted by the SDK where possible, but service handlers remain responsible for avoiding secret leakage.
Main exports:
CapsuleAgentFileTokenStoreAgentApiClientAgentApiError- SDK option and provider types from
types.ts
Core methods:
| Method | Description |
|---|---|
new CapsuleAgent(options) |
Creates the embedded Agent. |
.health(provider) |
Registers a health provider. |
.configs(provider) |
Registers a config provider. |
.action(action) |
Registers an operator Action. |
.start() |
Registers if needed, reports service state, and starts loops. |
.stop() |
Stops background loops. |
.runHealth() |
Runs the configured health provider. |
| Package | Compatible with |
|---|---|
@xtrape/capsule-agent-node@0.1.x |
Opstage CE 0.1.x and @xtrape/capsule-contracts-node@0.1.x |
Use matching minor versions across CE, Agent SDK, and Contracts during Public
Review and Public Preview. The wire protocol may still change before v1.0.
The token is single-use and short-lived.
- Check the token has not already been consumed by an earlier successful start
(registration tokens flip to
USEDon first use). - Check the token has not expired. Operators set
expiresInSecondswhen creating the token; the default in CE is short. - Check the token has not been revoked from the Opstage console.
- Re-create a fresh token in the console and start the agent with it.
- Confirm
OPSTAGE_BACKEND_URLresolves from inside the container or host where the agent runs. The default ofhttp://localhost:8080only works if the agent runs on the same host as Opstage. - If you sit behind a reverse proxy, point
backendUrlat the proxy URL — not the internal Opstage container address. - Outbound HTTPS through corporate proxies: respect
HTTPS_PROXY/NO_PROXYenv vars (undicihonors them viasetGlobalDispatcher).
- File permissions: the agent process must be able to read
tokenStore.file. Runchmod 600(owner read/write) and ensure the process owner matches. - File contents: the SDK writes
<agentId>:<agentToken>plaintext. If the file exists but contains anything else, delete it and re-register with a fresh registration token. - Token revocation: an operator may have revoked the agent or the agent
token from the console. Inspect the agent's status in Opstage; if it shows
REVOKEDorDISABLED, that's the cause.
- Confirm the action
nameregistered withagent.action({ name: "..." })matches theactionNamethe backend dispatches in the command. The SDK reportsACTION_HANDLER_NOT_FOUNDif they differ. - Confirm
start()is awaited and not called repeatedly — multipleCapsuleAgentinstances on the same host with the same agent code will fight over heartbeats and command polls. - Check
commandPollMs/commandPollIntervalSecondsis not absurdly large; default is 5s.
- Site: https://xtrape-com.github.io/xtrape-capsule-site/
- Node embedded Agent guide: https://xtrape-com.github.io/xtrape-capsule-site/agents/node-embedded-agent
- Action model: https://xtrape-com.github.io/xtrape-capsule-site/agents/action-model
- Opstage CE: https://xtrape-com.github.io/xtrape-capsule-site/opstage-ce/overview
See CONTRIBUTING.md for development workflow and PR checks. See SECURITY.md for vulnerability reporting and token/action safety guidance.
Apache-2.0. "Xtrape", "Xtrape Capsule", and "Opstage" are trademarks of their respective owners; the open-source license does not grant trademark rights.