TypeScript SDK for the AgentRail Task Lifecycle API.
The SDK is a client library. It does not start AgentRail, connect GitHub, CircleCI, or Linear, create local config, or wake local coding agents. Start an AgentRail API first, then point the SDK at it.
For the full guide, see AgentRail SDK Guide.
npm install @agentrail-core/sdkRequires Node.js 18 or newer because it uses native fetch.
For local development against this repository before publication:
cd /path/to/agentrail/sdk/typescript
npm install
npm run build
cd /path/to/your-harness
npm install /path/to/agentrail/sdk/typescriptUse the CLI to create the local runtime, agent credentials, and provider setup:
npm install -g @agentrail-core/cli
agentrail init
agentrail server startIn another terminal:
agentrail agent create
source ~/.agentrail/agent.env
agentrail doctorThe generated env file provides AGENTRAIL_BASE_URL and AGENTRAIL_API_KEY.
Use the ar_live_... secret as the bearer token. Values that start with
akey_ are key ids, not secrets.
When you have a hosted AgentRail API base URL, pass that as baseUrl.
import { AgentRailClient } from "@agentrail-core/sdk";
const apiKey = process.env.AGENTRAIL_API_KEY;
if (!apiKey) {
throw new Error("Set AGENTRAIL_API_KEY before using the SDK.");
}
const client = new AgentRailClient({
baseUrl: process.env.AGENTRAIL_BASE_URL ?? "http://127.0.0.1:3000",
apiKey,
});
const tasks = await client.listMyTasks({ status: "todo", limit: 10 });
for (const task of tasks.data) {
console.log(`${task.identifier}: ${task.title}`);
console.log(`Next actions: ${task.availableActions.join(", ")}`);
}baseUrl is required. Passing it explicitly avoids a hidden dependency on
local defaults.
Use adapter_managed mode for real provider automation. In this mode AgentRail
creates or reuses the provider PR and returns PR metadata.
const tasks = await client.listMyTasks({ status: "in_progress", limit: 1 });
const task = tasks.data[0];
if (!task) {
process.exit(0);
}
const detail = await client.getTask(task.id);
if (detail.data.availableActions.includes("submit")) {
const submission = await client.submitTask(
task.id,
{
summary: "Implemented the task and pushed commits to the task branch.",
mode: "adapter_managed",
pullRequest: {
title: `Submit ${detail.data.identifier}`,
draft: false,
},
},
`submit-${task.id}-v1`,
);
console.log(`PR: ${submission.data.prUrl}`);
}
const ci = await client.getTaskCiStatus(task.id);
const review = await client.getTaskReviewFeedback(task.id);
const refreshed = await client.getTask(task.id);
const headSha =
ci.data.headSha ??
review.data.latestDecision.headSha ??
refreshed.data.headSha;
if (
refreshed.data.availableActions.includes("ship") &&
ci.data.overallStatus === "passed" &&
review.data.latestDecision.outcome === "approved" &&
headSha
) {
await client.shipTask(
task.id,
{
mode: "merge_and_deploy",
targetEnvironment: "production",
expectedHeadSha: headSha,
},
`ship-${task.id}-${headSha}`,
);
}Always follow availableActions instead of guessing the next lifecycle step.
Gate shipping on ship, green CI, an approved review decision, and the latest
head SHA.
const controller = new AbortController();
for await (const event of client.streamEvents({
eventTypes: ["task.updated", "task.reviewed", "task.shipped"],
heartbeatSeconds: 30,
signal: controller.signal,
})) {
console.log(event.id, event.type);
}After reconnecting, pass the last event id as cursor.
import { parseWebhookEvent } from "@agentrail-core/sdk";
const sub = await client.createEventSubscription(
{
url: "https://my-app.example.com/webhooks/tasks",
eventTypes: ["task.awaiting_user", "task.updated", "task.reviewed", "task.shipped"],
secret: "whsec_my_secret_at_least_16",
},
"event-sub-v1",
);
console.log(sub.data.id);
const event = parseWebhookEvent(rawBody, "whsec_my_secret_at_least_16", {
"x-agentrail-subscription-id": req.headers["x-agentrail-subscription-id"],
"x-agentrail-event-id": req.headers["x-agentrail-event-id"],
"x-agentrail-event-type": req.headers["x-agentrail-event-type"],
"x-agentrail-delivery-id": req.headers["x-agentrail-delivery-id"],
"x-agentrail-delivery-attempt": req.headers["x-agentrail-delivery-attempt"],
"x-agentrail-signature": req.headers["x-agentrail-signature"],
});
console.log(event.type, event.data);Verify signatures against the raw request body before parsing JSON.
The client retries 429, 500, 502, 503, and 504 responses with
exponential backoff. Rate-limited responses respect Retry-After.
const client = new AgentRailClient({
baseUrl: process.env.AGENTRAIL_BASE_URL ?? "http://127.0.0.1:3000",
apiKey: process.env.AGENTRAIL_API_KEY!,
retry: {
maxAttempts: 5,
initialDelayMs: 500,
maxDelayMs: 60_000,
retryableStatusCodes: [429, 500, 502, 503, 504],
},
});import {
AgentRailClient,
ConflictError,
NotFoundError,
RateLimitError,
ValidationError,
} from "@agentrail-core/sdk";
try {
await client.shipTask(taskId, request, idempotencyKey);
} catch (err) {
if (err instanceof ConflictError) {
console.log("Task is not shippable:", err.details);
} else if (err instanceof NotFoundError) {
console.log("Task not found");
} else if (err instanceof RateLimitError) {
console.log(`Retry after ${err.retryAfterSeconds}s`);
} else if (err instanceof ValidationError) {
console.log("Bad request:", err.details);
}
}Most users should create keys through agentrail init and
agentrail agent create. Use these methods from admin tooling only.
const key = await client.createApiKey(
{
agent: {
id: "agt_my_agent",
displayName: "My Agent",
role: "developer",
externalIdentities: [{ provider: "github", subject: "my-bot" }],
},
scopes: ["tasks:read", "tasks:write", "ci:read", "reviews:read", "ship:write"],
rateLimit: { windowSeconds: 60, maxRequests: 600 },
},
"create-my-agent-key-v1",
);
console.log(key.data.apiKey);
const rotated = await client.rotateApiKey(
key.data.id,
{ expiresAt: "2026-07-01T00:00:00Z" },
"rotate-my-agent-key-v1",
);
const usage = await client.getApiKeyUsage(rotated.data.id);
console.log(usage.data.totals.accepted);