@agent-compose/sdk

中文文档
0.8.7发布于 2026/10/07MITTypeScriptES Module近 30 天下载 7774

Client library for agent-compose — define agents, runtimes, and workflows, and invoke them against an agent-compose server.

原始使用文档
文档目录21 节
  1. Installation
  2. Authoring a workflow
  3. What a step can do with ctx
  4. The agent loop
  5. Runtimes
  6. Built-in runtimes
  7. Custom runtime
  8. Registering a workflow
  9. Invoking a workflow
  10. Auto parent/child tracing
  11. Cancelling a run
  12. Streaming live events
  13. Factories
  14. Secrets
  15. API keys
  16. Usage
  17. Snapshots (replay-friendly sandboxes)
  18. Per-invocation overrides merge field by field
  19. Authentication
  20. Public exports: quick reference
  21. Errors

@agent-compose/sdk

TypeScript SDK for agent-compose. Use it to:

  • Author workflows that run agent loops inside isolated sandboxes
  • Use or define runtimes that drive a coding agent (Claude Code, Codex, Amp, OpenCode, Cursor, Droid, …) as an agent loop
  • Register, invoke, observe, and cancel workflows over the HTTP API (AgentComposeClient)
  • Manage factories, secrets, API keys, and snapshots programmatically

The hierarchy: a team (shown as a space in the dashboard) owns one or more factories; each factory owns workflow templates, secrets, runs and a drive. Workflows are versioned per (factory, name, version). Every team has a default factory, and every call that takes a factorySlug falls back to it.


Installation

npm install @agent-compose/sdk zod   # zod ^4 is a peer dependency

AgentComposeClient runs on Node ≥ 20 or Bun. Bundling a workflow for registration (bundleWorkflow, agentc register) needs Bun.


Authoring a workflow

A workflow is a typed chain of discrete steps: defineWorkflow({ id, input, output }).step(defineStep(...)).build(). Every step is a durable replay checkpoint: the engine records each step's validated output, so a retry or resume picks up after the last completed step instead of re-running the whole workflow.

// my-workflow.ts
import { defineWorkflow, defineStep, agent, claudeRuntime } from "@agent-compose/sdk";
import { z } from "zod";
import PROMPT from "./prompt.md" with { type: "text" };

const Input  = z.object({ repo: z.string() });
const Output = z.object({ ok: z.boolean(), summary: z.string().optional() });

const review = defineStep({
  name:   "review",
  input:  Input,
  output: Output,
  run: async (ctx) => {
    const result = await agent({
      sandbox: ctx.sandbox!,         // the run's own VM
      runtime: claudeRuntime,
      prompt:  `${PROMPT}\n\nRepository: ${ctx.input.repo}`,
      tools:   ["Bash", "Read", "Edit", "Write", "Grep", "Glob"],
      budget:  { turnsPerIteration: 40, maxIterations: 8 },
    });
    await ctx.setMetadata({ summary: result.lastStatus?.summary });
    return { ok: result.lastStatus?.exit_signal ?? false, summary: result.lastStatus?.summary };
  },
});

export default defineWorkflow({
  id:     "my-workflow",
  input:  Input,
  output: Output,
  // Optional: outbound network rules the runner sandbox enforces. Both
  // providers enforce the host list and inject the headers; only Vercel
  // also enforces per-host method/path rules. `$VAR` placeholders resolve
  // from the workflow's secrets at dispatch, and the value never enters
  // the sandbox.
  networkPolicy: {
    allow: {
      "*": [],
      "api.anthropic.com": [{ transform: [{ headers: { "x-api-key": "$ANTHROPIC_API_KEY" } }] }],
    },
  },
})
  .step(review)
  .build();

The builder chain checks at compile time that each step's input schema matches the previous step's output, and .build() verifies the final step's output is the same Zod instance as the workflow's declared output. networkPolicy, placeholders, snapshots, resources and the other definition fields become metadata the bundler picks up at registration.

The run-form (defineWorkflow({ run(ctx, sandbox) { … } })) has been removed: defineWorkflow throws when it sees a run key. Step-form is the only shape, and pausing only works there.

What a step can do with ctx

Each step's run(ctx) receives a StepContext:

interface StepContext<TInput> {
  input:          TInput;                  // validated against the step's `input` schema
  stepName:       string;
  run:            { id: string };
  sandbox?:       SandboxProvider;         // the run's VM: pass to `agent({ sandbox: ctx.sandbox })`
  abortSignal:    AbortSignal;             // fires when the run is canceled
  requestContext: RequestContext;          // tenant identity, for processors
  agentEvents:    AgentEventSink;          // `agent({ events: ctx.agentEvents })`
  setMetadata(data: Record<string, unknown>): Promise<void>;
  step<T>(name: string, fn: () => Promise<T>): Promise<T>;       // durable named sub-step
  pause<T>(req: PauseRequest<T>): Promise<T>;                    // wait for a human, durably
  sleep(durationMs: number): Promise<void>;
  waitForEvent<T>(req: WaitForEventRequest<T>): Promise<T>;
  requestDecision(req: DecisionRequest): Promise<{ decision: string }>;
  invokeChild(name, input?, opts?): Promise<RunStatus>;          // run another workflow
}

ctx.step("phase-name", () => …) wraps a phase within a step for the run timeline. It memoises the result (a pause-resume re-entry restores it instead of re-running) and emits workflow_substep_started / workflow_substep_completed / workflow_substep_failed lifecycle events with the duration. Use it for setup, external API calls, or anything you want visible on the run page.

The agent loop

agent({
  sandbox,            // ctx.sandbox, the run's VM
  runtime,            // claudeRuntime, codexRuntime, …, or your own via defineRuntime
  prompt,             // raw markdown; a `--- frontmatter ---` block is stripped
  tools?,             // model tool allowlist (defaults inside agentLoop; [] disables tools)
  budget?,            // { turnsPerIteration, maxIterations }; omitted: no turn cap, up to 3 iterations
  workingDir?,        // every shell command runs here (default: the run's dir on the drive)
  responseSchema?,    // zod; when set, the loop demands a `<response>` block on exit
  mode?,              // "auto" (default) or "hitl" (a human can steer it mid-run)
  events?,            // lifecycle sink, e.g. ctx.agentEvents
  onAgentEvent?,      // per-message hook
  onIteration?,       // per-iteration hook with the parsed `<status>` block
  processors?,        // pre/post pipeline around the loop
})
// → AgentLoopResult { agentId, label, sessionId, lastStatus, iterations, response? }

The model emits XML-tagged blocks (<status>, <response>) that the loop parses. lastStatus is the last <status> block: { summary, completed, blockers, exit_signal }. See sdk/src/agent/protocol-suffix.md for the instructions appended to every prompt.


Runtimes

A runtime wraps an agent's execution model (usually a coding CLI) so agent can drive it. The SDK ships built-ins; you only need a custom one for a provider it does not cover.

Built-in runtimes

Each comes as a ready default and a create… factory that takes config:

DefaultFactoryDrives
claudeRuntimecreateClaudeRuntimeClaude through the Claude Agent SDK, from the runner process
claudeCodeRuntimecreateClaudeCodeRuntimethe Claude Code CLI
codexRuntimecreateCodexRuntimethe Codex CLI
ampRuntimecreateAmpRuntimeAmp
opencodeRuntimecreateOpencodeRuntimeOpenCode
cursorRuntimecreateCursorRuntimeCursor's cursor-agent CLI
droidRuntimecreateDroidRuntimeFactory's droid CLI
createVercelRuntimea model through the Vercel AI SDK

openAIDesktopRuntime is not in the package root: it pulls in sharp for screenshots, whose native binding cannot be cross-compiled. Import it from its subpath when you want it:

import openAIDesktopRuntime from "@agent-compose/sdk/runtimes/openai-desktop";

Custom runtime

import { defineRuntime, type AgentMessage } from "@agent-compose/sdk";

export default defineRuntime({
  create: (sandbox, opts) => ({
    // Return a ModelExecutionContract; see sdk/src/types/runtime.ts
    async *sendMessage({ prompt, sessionId, signal }): AsyncGenerator<AgentMessage> {
      // Run the model against `sandbox` and yield AgentMessage chunks the loop parses.
    },
  }),
});

AgentRuntime is { create(sandbox, RuntimeOptions) → ModelExecutionContract }. There is no provider field on it: the runtime is bound to the workflow at author time (you pass it to agent), not selected by the server.


Registering a workflow

The agentc CLI bundles and registers in one step:

agentc register my-workflow.ts -n my-workflow

Under the hood that calls bundleWorkflow(workflowPath), which bundles the source with Bun.build, checks that the default export is a defineWorkflow(...) workflow, and returns the source with a manifest the server verifies against the source hash. Then it posts to /api/v1/factories/<slug>/templates. To register from your own build pipeline (under Bun):

import { AgentComposeClient, bundleWorkflow } from "@agent-compose/sdk";

const client = new AgentComposeClient({
  baseUrl: "https://your-server.example.com",   // default: AGENT_COMPOSE_URL, else https://api.agentcompose.ai
  apiKey:  process.env.AGENT_COMPOSE_API_KEY!,  // default: AGENT_COMPOSE_API_KEY
});

const bundled = await bundleWorkflow("./my-workflow.ts");
await client.register({
  name:         "my-workflow",
  source:       bundled.source,
  manifest:     bundled.manifest,      // required
  workflowPlan: bundled.workflowPlan,
  schedule:     "*/30 * * * *",        // optional cron (UTC)
  factorySlug:  "default",             // optional, defaults to "default"
  // pass bundled.networkPolicy, placeholders, snapshots, resources, … to keep
  // what the workflow declares
});

register() needs the manage scope on the caller's key.


Invoking a workflow

Two flavours:

// Fire-and-forget: returns the run id at once.
const { id } = await client.invoke("my-workflow", {
  repo: "owner/repo",
});

// Block until the run settles (default 30 min timeout, 1 s poll).
const status = await client.invokeAndWait("my-workflow", { repo: "owner/repo" }, {
  timeoutMs:      5 * 60_000,
  pollIntervalMs: 2000,
});
console.log(status.status);   // "success" | "failed" | "abandoned" | "canceled"
console.log(status.output);   // the workflow's return value

output is the final step's return value, validated against the workflow's declared output schema. setMetadata() writes to a separate metadata field, for side-channel facts (a PR URL, a plan URL) that don't belong in the structured return.

invoke and invokeAndWait take { factorySlug, snapshots, networkPolicy, placeholders, size, parentRunId, idempotencyKey } as the third argument. Per-invocation snapshots merges field by field with the registered default. factorySlug defaults to "default".

Auto parent/child tracing

The SDK reads process.env.RUN_ID (set by the runner sandbox on every dispatch) and threads it as parentRunId on invoke() calls. Workflows that fan out to other workflows get a parent/child tree in the dashboard for free. Pass parentRunId: null to opt out.

Cancelling a run

await client.cancelRun(runId);

Idempotent: cancelling a run that already ended returns its current state without throwing. The server marks the run canceled, emits a run_canceled event on the stream and kills its sandbox.

Streaming live events

streamRunLogs returns an async generator of RunEvents in real time, over SSE. Pass lastEventId (the highest seq you have processed) to resume after a reconnect.

for await (const ev of client.streamRunLogs(runId, { lastEventId: 0 })) {
  console.log(ev.event, ev.seq);
  if (ev.event === "run_complete" || ev.event === "run_failed" || ev.event === "run_canceled") {
    break;
  }
}

AbortSignal works too: pass { signal } and call controller.abort() to close the stream from the caller side.


Factories

Factories are containers within a team. Each has its own workflow templates, secrets, runs and drive, and API keys can be limited to one. New projects don't need to think about them: default exists in every team and is what the SDK uses when factorySlug is omitted.

// CRUD on factories
await client.createFactory({ slug: "ci-bots", name: "CI Bots", description: "…" });
const factories = await client.listFactories();
const f         = await client.getFactory("ci-bots");
await client.updateFactory("ci-bots", { name: "Continuous-Integration Bots" });
await client.deleteFactory("ci-bots");

// Templates: across factories, or scoped to one
const all    = await client.listTemplates();
const scoped = await client.listTemplates({ factorySlug: "ci-bots" });

// Register, invoke and secret calls all accept factorySlug
await client.invoke("scrape", { url: "…" }, { factorySlug: "ci-bots" });
await client.setSecret("scrape", "GH_TOKEN", "ghp_…", { factorySlug: "ci-bots" });

CLI equivalents: agentc factory list | create | use | delete | show, plus --factory <slug> on most other commands.


Secrets

Secret values live in GCP Secret Manager and are write-only: the API only returns metadata (key, timestamps). A run sees its factory's secrets plus its workflow's own, the workflow's winning on a clash. A secret the workflow's networkPolicy references as $VAR is brokered: the platform adds it to matching requests at the network layer and the sandbox env holds only a placeholder, or nothing. Any other secret is a plain env var in the runner sandbox.

// Per-workflow
await client.setSecret("my-workflow", "ANTHROPIC_API_KEY", process.env.ANTHROPIC_API_KEY!);
const list = await client.listSecrets("my-workflow"); // [{ key, createdAt, updatedAt }]
await client.deleteSecret("my-workflow", "STALE_KEY");

// Factory-wide (every workflow in the factory inherits them)
await client.setFactorySecret("SLACK_WEBHOOK_URL", "https://hooks.slack.com/…", { factorySlug: "ci-bots" });

Factory secrets need the manage scope. Changing a workflow's secrets needs invoke plus write access to that workflow.


API keys

Mint and list keys programmatically (the caller's key needs admin). A new key is returned once, in the same response as its metadata: copy the ac_… value at once.

const created = await client.createApiKey({
  name:      "ci-dispatcher",
  scopes:    ["read", "invoke"],          // omitted = ["read"]
  expiresAt: new Date(Date.now() + 30 * 86_400_000).toISOString(),  // 30 days
  // factorySlug: "ci-bots"               // optional: limit the key to one factory
});
console.log(created.key);   // "ac_…", the only time you will see it

const page = await client.listApiKeys();

Scopes: read, invoke (dispatch runs), manage (operate a factory: templates, secrets, repos) and admin (members, roles, keys). A key can only mint scopes its holder has. CLI equivalent: agentc keys create <name> --scopes read,invoke --expires-in 30d.


Usage

const usage = await client.getUsage(
  new Date(Date.now() - 30 * 86_400_000),
  new Date(),
);
// usage.data: [{ eventType, unit, total, tags }]

CLI equivalent: agentc usage.


Snapshots (replay-friendly sandboxes)

A workflow can capture its runner sandbox as a snapshot on success (snapshots: { saveLatest: true }), on Vercel or E2B. Other runs boot from it with snapshots.bootFrom into the same prepared VM (deps installed, repo cloned) instead of repeating the setup.

// Capture per-invocation:
await client.invoke("my-workflow", input, { snapshots: { saveLatest: true } });

// Boot from a captured snapshot (take the id from `agentc snapshot list`
// or the dashboard):
await client.invoke("my-workflow", input, {
  snapshots: { bootFrom: { snapshotId: "snap_…" } },
});

// Set a default in the definition:
defineWorkflow({ id, input, output, snapshots: { saveLatest: true } }).step(…).build();

// Keep every step's snapshot (not just the latest):
defineWorkflow({ id, input, output, snapshots: { saveLatest: true, retainSteps: true } }).step(…).build();

// Browse and clean up:
const page  = await client.listSnapshotsPage({ workflow: "my-workflow", limit: 50 });
const snaps = page.data;
await client.deleteRunSnapshot(snaps[0].runId, snaps[0].snapshotId);

CLI equivalents: agentc snapshot list, agentc snapshot show <run-id>, agentc snapshot delete <run-id> [snapshot-id].

Per-invocation overrides merge field by field

The snapshots object on invoke() is merged with the registered template's snapshots config: you can override bootFrom alone without losing saveLatest, or the other way round.


Authentication

The SDK sends a Bearer API key (ac_…). Mint one in the dashboard: sign in, open the space's settings, Credentials → API keys. A new key gets read by default; tick invoke to dispatch runs, manage to register workflows or set secrets, and admin only to mint keys or manage members.

const client = new AgentComposeClient({
  baseUrl: process.env.AGENT_COMPOSE_URL,
  apiKey:  process.env.AGENT_COMPOSE_API_KEY,
});

Both options fall back to those env vars (and the URL to https://api.agentcompose.ai), so new AgentComposeClient() works inside a run sandbox. The dashboard itself uses a session cookie; the SDK is for programmatic and server-to-server callers.


Public exports: quick reference

ExportWhat
defineWorkflowTyped step-workflow builder: defineWorkflow({ id, input, output }).step(...).build()
defineStepDeclare one typed workflow step ({ name, input, output, run })
defineRuntimeWrap an agent execution provider as an AgentRuntime
defineSandboxEnvironmentDeclare a workflow whose purpose is to build a snapshot for others to boot from
agent / agentLoopEmbed an agent loop inside a workflow
runWorkflowLocal engine for running a workflow in-process (test harness)
bundleWorkflowBundle and validate a workflow for registration (Bun)
claudeRuntime, codexRuntime, … and their create… factoriesBuilt-in runtimes (table above)
AgentComposeClientHTTP client: register, invoke, cancel, stream, factories, snapshots, secrets, API keys, usage, conversations, sessions, projects
AgentComposeErrorThrown by every non-2xx HTTP response
parseAgentStatus / parseAgentResponse / AgentStatusSchema / AgentMessageSchemaProtocol parsers
parseSseStreamGeneric SSE decoder (used by streamRunLogs)
createSandbox / reconnectSandbox / killAllSandboxes / killSandboxById / getSandboxQuotas / listOwnedSandboxes / deleteSandboxSnapshotSandbox-provider helpers (Vercel and E2B)

Type exports include Workflow, Step, StepContext, WorkflowBuilder, WorkflowHooks, AgentBudget, AgentRuntime, RuntimeOptions, ModelExecutionContract, McpServerConfig, AgentMessage (and its variants), AgentStatus, RunStatus, RegisterResult, RunEvent, FactoryRow, SnapshotListEntry, ApiKey, ApiKeyCreated, UsageRollupRow, UsageResponse, CancelRunResponse, AgentLoopResult, AgentOpts, SandboxProvider, DesktopSandboxProvider, SandboxNetworkPolicy, SandboxCreateOpts, OwnedSandbox and BundledWorkflow.

For the exact signatures, follow your IDE's go-to-definition into @agent-compose/sdk: sdk/src/index.ts is the public surface, and the files it re-exports from carry full docstrings.


Errors

Every non-2xx HTTP response throws AgentComposeError. It carries status and a message taken from the server's { error: string } body when present (else the HTTP status text), plus code and capability on an authorization denial and retryAfterMs when the server sent Retry-After:

import { AgentComposeError } from "@agent-compose/sdk";

try {
  await client.invoke("missing-workflow");
} catch (err) {
  if (err instanceof AgentComposeError && err.status === 404) {
    // template not registered
  }
}

invokeAndWait throws AgentComposeError(504, …) on timeout.

版本与发布记录

@agent-compose/sdk 共发布 28 个版本,首次发布于 2026/04/25;过去 12 个月发布了 28 个版本。

版本发布日期距今
0.8.7latest2026/10/071 个月前
0.8.62026/10/061 个月前
0.8.52026/09/151 个月前
0.8.42026/08/232 个月前
0.8.32026/08/232 个月前
0.8.22026/08/152 个月前

关于 @agent-compose/sdk 的常见问题

@agent-compose/sdk 是什么?

Client library for agent-compose — define agents, runtimes, and workflows, and invoke them against an agent-compose server.

如何安装 @agent-compose/sdk?

在项目目录运行 npm install @agent-compose/sdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@agent-compose/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@agent-compose/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@agent-compose/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@agent-compose/sdk 可以商用吗?

@agent-compose/sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@agent-compose/sdk 的下载量有多少?

根据 npm 官方下载统计,@agent-compose/sdk 在 2026/10/05 – 2026/11/03 这 30 天内被下载 7,774 次,日均约 259 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@agent-compose/sdk 还在维护吗?

最新版本 0.8.7 发布于 2026/10/07(1 个月前);至今共发布 28 个版本,其中过去 12 个月发布了 28 个;首次发布于 2026/04/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。