@agent-compose/cli 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -102,11 +102,14 @@ Claude Code session running inside this repo:
102
102
  | Skill | What it does |
103
103
  |---|---|
104
104
  | `/ac:setup` | Walks first-time setup against an existing server |
105
- | `/ac:demo` | Guided first-run walkthrough |
105
+ | `/ac:tutorial` | Hands-on tutorial — register, invoke, schedule, emit events, capture snapshots (~10 min) |
106
106
  | `/ac:register` | Bundle + register the current workflow |
107
107
  | `/ac:invoke` | Dispatch a registered workflow with input |
108
108
  | `/ac:register-invoke` | Register-then-invoke combo for fast iteration |
109
+ | `/ac:schedule` | Create / list / delete cron schedules on a registered workflow |
109
110
  | `/ac:logs` | Tail logs for a running run |
111
+ | `/ac:events` | Send / list run events |
112
+ | `/ac:snapshots` | List / inspect / delete captured sandbox snapshots |
110
113
  | `/ac:secrets` | Manage per-workflow secrets |
111
114
  | `/ac:generate-workflow` | Scaffold a new workflow from a plain-English description |
112
115
  | `/ac:generate-agent` | Scaffold an agent prompt + iteration logic |
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/index.ts
6
- import { readFileSync as readFileSync5 } from "node:fs";
7
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6
+ import { readFileSync as readFileSync6 } from "node:fs";
7
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
8
8
  import { program } from "commander";
9
9
 
10
10
  // src/commands/register.ts
@@ -183,7 +183,7 @@ var registerCommand = new Command("register").description("Register a workflow w
183
183
  console.error(`Error: workflow not found at ${workflowPath}`);
184
184
  process.exit(1);
185
185
  }
186
- const { source, manifest, networkPolicy, placeholders, snapshots, memory, postRunHooks, inputSchema, outputSchema, workflowPlan } = await bundleWorkflow(workflowPath);
186
+ const { source, manifest, description, networkPolicy, placeholders, snapshots, memory, postRunHooks, inputSchema, outputSchema, workflowPlan } = await bundleWorkflow(workflowPath);
187
187
  if (networkPolicy)
188
188
  console.log(`[register] Network policy detected — credentials will be brokered via Vercel firewall`);
189
189
  if (snapshots?.bootFrom)
@@ -200,6 +200,7 @@ var registerCommand = new Command("register").description("Register a workflow w
200
200
  factorySlug: opts.factory,
201
201
  ...opts.version ? { version: opts.version } : {},
202
202
  ...opts.schedule ? { schedule: opts.schedule } : {},
203
+ ...description ? { description } : {},
203
204
  ...networkPolicy ? { networkPolicy } : {},
204
205
  ...placeholders ? { placeholders } : {},
205
206
  ...snapshots !== undefined ? { snapshots } : {},
@@ -1159,14 +1160,95 @@ var listCommand2 = new Command13("list").description("List events for a run, or
1159
1160
  });
1160
1161
  var eventsCommand = new Command13("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
1161
1162
 
1162
- // src/update-check.ts
1163
+ // src/commands/schedule.ts
1164
+ import { Command as Command14 } from "commander";
1165
+ var sharedOpts2 = (cmd) => cmd.option("--factory <slug>", `Factory the schedule lives in (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey);
1166
+ var scheduleCommand = new Command14("schedule").description("Manage cron schedules attached to registered workflows");
1167
+ sharedOpts2(scheduleCommand.command("create <name>").description("Create a new schedule for a workflow").requiredOption("--workflow <name>", "The registered workflow this schedule should fire").requiredOption("--cron <expr>", "Cron expression (UTC) — e.g. '0 9 * * *'")).action(async (name, opts) => {
1168
+ const client = makeClient(opts);
1169
+ const created = await client.createSchedule({
1170
+ name,
1171
+ workflow: opts.workflow,
1172
+ cron: opts.cron,
1173
+ factorySlug: opts.factory
1174
+ });
1175
+ console.log(`✓ Schedule "${created.name}" → workflow "${created.workflow}" @ "${created.cron}" (${created.id})`);
1176
+ });
1177
+ sharedOpts2(scheduleCommand.command("list").description("List schedules in a factory")).action(async (opts) => {
1178
+ const client = makeClient(opts);
1179
+ const rows = await client.listSchedules(opts.factory);
1180
+ if (rows.length === 0) {
1181
+ console.log(`No schedules in factory "${opts.factory}"`);
1182
+ return;
1183
+ }
1184
+ console.log(`Schedules in factory "${opts.factory}":`);
1185
+ for (const s of rows) {
1186
+ const next = s.nextFireAt ? new Date(s.nextFireAt).toLocaleString() : "—";
1187
+ console.log(` ${s.name.padEnd(24)} ${s.workflowName.padEnd(20)} ${s.cron.padEnd(16)} next: ${next} ${s.id}`);
1188
+ }
1189
+ });
1190
+ sharedOpts2(scheduleCommand.command("delete <id>").description("Delete a schedule by id")).action(async (id, opts) => {
1191
+ const client = makeClient(opts);
1192
+ await client.deleteSchedule(id, opts.factory);
1193
+ console.log(`✓ Schedule ${id} deleted from factory "${opts.factory}"`);
1194
+ });
1195
+
1196
+ // src/commands/upgrade.ts
1197
+ import { Command as Command15 } from "commander";
1163
1198
  import { spawnSync } from "node:child_process";
1164
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
1199
+ import { readFileSync as readFileSync4 } from "node:fs";
1200
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1201
+ import pc4 from "picocolors";
1202
+ var PACKAGE_NAME = "@agent-compose/cli";
1203
+ var upgradeCommand = new Command15("upgrade").description("Update the agentc CLI to the latest published version").option("--check", "Check for updates without installing", false).action(async (opts) => {
1204
+ const pkg = JSON.parse(readFileSync4(fileURLToPath2(new URL("../../package.json", import.meta.url)), "utf8"));
1205
+ const current = pkg.version;
1206
+ const controller = new AbortController;
1207
+ const timeout = setTimeout(() => controller.abort(), 5000);
1208
+ let latest = null;
1209
+ try {
1210
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
1211
+ signal: controller.signal,
1212
+ headers: { Accept: "application/json" }
1213
+ });
1214
+ if (res.ok) {
1215
+ const body = await res.json();
1216
+ if (typeof body.version === "string")
1217
+ latest = body.version;
1218
+ }
1219
+ } catch {} finally {
1220
+ clearTimeout(timeout);
1221
+ }
1222
+ if (!latest) {
1223
+ console.error(`${pc4.red("✗")} Couldn't reach the npm registry. Check your connection and try again.`);
1224
+ process.exit(1);
1225
+ }
1226
+ if (latest === current) {
1227
+ console.log(`${pc4.green("✓")} You're on the latest version (${pc4.bold(current)}).`);
1228
+ return;
1229
+ }
1230
+ console.log(`${pc4.yellow("●")} agentc ${pc4.bold(latest)} is available (you have ${current}).`);
1231
+ if (opts.check) {
1232
+ console.log(` Run ${pc4.cyan("agentc upgrade")} to install, or ${pc4.cyan("npm install -g " + PACKAGE_NAME + "@latest")} manually.`);
1233
+ return;
1234
+ }
1235
+ console.log(` Running ${pc4.cyan("npm install -g " + PACKAGE_NAME + "@latest")} …`);
1236
+ const result = spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { stdio: "inherit" });
1237
+ if (result.status !== 0) {
1238
+ console.error(`${pc4.red("✗")} Install failed. Run the command above manually, or check for permission issues (npm prefix / sudo).`);
1239
+ process.exit(1);
1240
+ }
1241
+ console.log(`${pc4.green("✓")} Installed ${pc4.bold(`agentc ${latest}`)}.`);
1242
+ });
1243
+
1244
+ // src/update-check.ts
1245
+ import { spawnSync as spawnSync2 } from "node:child_process";
1246
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync } from "node:fs";
1165
1247
  import { homedir as homedir3 } from "node:os";
1166
1248
  import { join as join3 } from "node:path";
1167
1249
  import { createInterface } from "node:readline/promises";
1168
- import pc4 from "picocolors";
1169
- var PACKAGE_NAME = "@agent-compose/cli";
1250
+ import pc5 from "picocolors";
1251
+ var PACKAGE_NAME2 = "@agent-compose/cli";
1170
1252
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
1171
1253
  var FETCH_TIMEOUT_MS = 2000;
1172
1254
  function cachePath() {
@@ -1201,7 +1283,7 @@ function isNewer(latest, current) {
1201
1283
  }
1202
1284
  function readCache() {
1203
1285
  try {
1204
- const raw = readFileSync4(cachePath(), "utf8");
1286
+ const raw = readFileSync5(cachePath(), "utf8");
1205
1287
  const parsed = JSON.parse(raw);
1206
1288
  if (typeof parsed.checkedAt !== "number")
1207
1289
  return null;
@@ -1225,7 +1307,7 @@ async function fetchLatest() {
1225
1307
  const controller = new AbortController;
1226
1308
  const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
1227
1309
  try {
1228
- const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
1310
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME2}/latest`, {
1229
1311
  signal: controller.signal,
1230
1312
  headers: { Accept: "application/json" }
1231
1313
  });
@@ -1263,35 +1345,35 @@ async function checkForUpdate(currentVersion) {
1263
1345
  const latest = await resolveLatest();
1264
1346
  if (!latest || !isNewer(latest, currentVersion))
1265
1347
  return;
1266
- const headline = `${pc4.yellow("●")} agentc ${pc4.bold(latest)} is available (you have ${currentVersion}).`;
1348
+ const headline = `${pc5.yellow("●")} agentc ${pc5.bold(latest)} is available (you have ${currentVersion}).`;
1267
1349
  process.stderr.write(`${headline}
1268
1350
  `);
1269
1351
  if (!process.stdin.isTTY) {
1270
- process.stderr.write(` Run ${pc4.cyan("npm install -g " + PACKAGE_NAME + "@latest")} to update.
1352
+ process.stderr.write(` Run ${pc5.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} to update.
1271
1353
  `);
1272
1354
  return;
1273
1355
  }
1274
- const confirm = await promptYesNo(` Install now? [y/${pc4.bold("N")}] `);
1356
+ const confirm = await promptYesNo(` Install now? [y/${pc5.bold("N")}] `);
1275
1357
  if (!confirm)
1276
1358
  return;
1277
- process.stderr.write(` Running ${pc4.cyan("npm install -g " + PACKAGE_NAME + "@latest")} …
1359
+ process.stderr.write(` Running ${pc5.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} …
1278
1360
  `);
1279
- const result = spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], {
1361
+ const result = spawnSync2("npm", ["install", "-g", `${PACKAGE_NAME2}@latest`], {
1280
1362
  stdio: "inherit"
1281
1363
  });
1282
1364
  if (result.status !== 0) {
1283
- process.stderr.write(`${pc4.red("✗")} Install failed. Run the command above manually.
1365
+ process.stderr.write(`${pc5.red("✗")} Install failed. Run the command above manually.
1284
1366
  `);
1285
1367
  return;
1286
1368
  }
1287
- process.stderr.write(`${pc4.green("✓")} Installed ${pc4.bold(`agentc ${latest}`)}. ` + `Re-run your command to use the new version.
1369
+ process.stderr.write(`${pc5.green("✓")} Installed ${pc5.bold(`agentc ${latest}`)}. ` + `Re-run your command to use the new version.
1288
1370
  `);
1289
1371
  process.exit(0);
1290
1372
  }
1291
1373
 
1292
1374
  // src/index.ts
1293
- var pkg = JSON.parse(readFileSync5(fileURLToPath2(new URL("../package.json", import.meta.url)), "utf8"));
1294
- program.name("agentc").description("CLI for agent-compose register, invoke, and monitor workflows").version(pkg.version);
1375
+ var pkg = JSON.parse(readFileSync6(fileURLToPath3(new URL("../package.json", import.meta.url)), "utf8"));
1376
+ program.name("agentc").description("Coordinate agent production lines.").version(pkg.version);
1295
1377
  program.hook("preAction", async () => {
1296
1378
  try {
1297
1379
  await checkForUpdate(pkg.version);
@@ -1300,14 +1382,34 @@ program.hook("preAction", async () => {
1300
1382
  program.addCommand(registerCommand);
1301
1383
  program.addCommand(invokeCommand);
1302
1384
  program.addCommand(listCommand);
1385
+ program.addCommand(cancelCommand);
1386
+ program.addCommand(scheduleCommand);
1303
1387
  program.addCommand(logsCommand);
1304
- program.addCommand(initCommand);
1388
+ program.addCommand(eventsCommand);
1389
+ program.addCommand(factoryCommand);
1390
+ program.addCommand(snapshotCommand);
1391
+ program.addCommand(secretsCommand);
1305
1392
  program.addCommand(keysCommand);
1306
1393
  program.addCommand(authCommand);
1307
- program.addCommand(secretsCommand);
1308
1394
  program.addCommand(usageCommand);
1309
- program.addCommand(snapshotCommand);
1310
- program.addCommand(factoryCommand);
1311
- program.addCommand(cancelCommand);
1312
- program.addCommand(eventsCommand);
1395
+ program.addCommand(initCommand);
1396
+ program.addCommand(upgradeCommand);
1397
+ program.addHelpText("after", `
1398
+ Topics:
1399
+ Workflows register, invoke, list, cancel
1400
+ Scheduling schedule
1401
+ Run inspection logs, events
1402
+ Resources factory, snapshot, secrets
1403
+ Account keys, auth, usage
1404
+ Setup init, upgrade
1405
+
1406
+ Examples:
1407
+ $ agentc register ./workflows/pipeline.ts
1408
+ $ agentc invoke pipeline --follow
1409
+ $ agentc schedule create nightly --workflow pipeline --cron '0 9 * * *'
1410
+ $ agentc logs <run-id>
1411
+
1412
+ Docs:
1413
+ https://github.com/Layr-Labs/agent-compose
1414
+ `);
1313
1415
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-compose/cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Command-line interface for agent-compose — register, invoke, and monitor workflows from your terminal.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,21 +35,21 @@
35
35
  "node": ">=20"
36
36
  },
37
37
  "scripts": {
38
- "typecheck": "tsc --noEmit",
39
- "test": "bun test src/__tests__",
40
- "build": "bun run clean && bun run build:js && bun run build:chmod",
41
- "build:js": "bun build src/index.ts --outdir dist --target node --format esm --packages external --banner='#!/usr/bin/env bun'",
42
- "build:chmod": "chmod +x dist/index.js",
43
- "clean": "rm -rf dist",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "bun test src/__tests__",
40
+ "build": "bun run clean && bun run build:js && bun run build:chmod",
41
+ "build:js": "bun build src/index.ts --outdir dist --target node --format esm --packages external --banner='#!/usr/bin/env bun'",
42
+ "build:chmod": "chmod +x dist/index.js",
43
+ "clean": "rm -rf dist",
44
44
  "prepublishOnly": "bun run build"
45
45
  },
46
46
  "dependencies": {
47
- "@agent-compose/sdk": "^0.3.0",
48
- "commander": "^12.0.0",
49
- "picocolors": "^1.1.1"
47
+ "@agent-compose/sdk": "^0.5.0",
48
+ "commander": "^12.0.0",
49
+ "picocolors": "^1.1.1"
50
50
  },
51
51
  "devDependencies": {
52
- "bun-types": "latest",
52
+ "bun-types": "latest",
53
53
  "typescript": "^5.8.0"
54
54
  },
55
55
  "publishConfig": {
@@ -44,7 +44,13 @@ about "step-form vs run-form" — that's an internal call.
44
44
  6. **Should it run on a schedule?**
45
45
  If yes, ask for the cadence in plain English ("every weekday at 9am
46
46
  ET", "every hour", "the 1st of every month"). Convert to cron
47
- yourself.
47
+ yourself (UTC). Schedules are *separate from workflow source*: you
48
+ don't put cron in `defineWorkflow(...)`. After register, run
49
+ `agentc schedule create <name> --workflow <wf> --cron '<expr>'`
50
+ (or, for the simple "one cron per workflow" case, pass
51
+ `--schedule '<expr>'` to `agentc register` and the CLI auto-creates
52
+ a same-named schedule). Read the cron expression back to the user
53
+ to confirm.
48
54
 
49
55
  7. **Anything else worth recording?**
50
56
  Optional. Most workflows are fine without this.
@@ -337,6 +343,10 @@ export default defineWorkflow({
337
343
  - (If you added a sandbox env companion: "First register the
338
344
  `playwright-env` workflow with `--build` so the snapshot is
339
345
  captured, then register and invoke `pr-triage`.")
346
+ - (If the user asked for a schedule at step 6:
347
+ "To run it on the schedule: `agentc schedule create pr-triage-daily
348
+ --workflow pr-triage --cron '<expr>'` — the cron pattern we
349
+ worked out earlier.")
340
350
 
341
351
  ## When the user asks for memory extraction
342
352
 
@@ -7,6 +7,13 @@ allowed-tools: Bash(agentc *)
7
7
 
8
8
  # Invoke Workflow
9
9
 
10
+ A one-shot dispatch — the CLI POSTs to `/api/v1/factories/<slug>/templates/<name>/invoke`,
11
+ the server creates a run row, the worker boots a sandbox, and the
12
+ workflow runs. Scheduled runs go through the same dispatch path but
13
+ are triggered by a cron tick instead; they're tagged with
14
+ `_scheduleId` + `_scheduleName` on metadata so the dashboard shows
15
+ "schedule by <name>" on the row. This skill is the manual path.
16
+
10
17
  ## Context
11
18
 
12
19
  Registered workflows:
@@ -18,7 +25,8 @@ Registered workflows:
18
25
 
19
26
  The user wants to invoke: **$ARGUMENTS**
20
27
 
21
- If the line above is blank, ask which workflow to invoke. Use the registered workflows list above as suggestions.
28
+ If `$ARGUMENTS` is empty, ask which workflow to invoke. Use the
29
+ registered workflows list above as suggestions.
22
30
 
23
31
  Once you have the workflow name, dispatch it:
24
32
 
@@ -26,17 +34,31 @@ Once you have the workflow name, dispatch it:
26
34
  agentc invoke <name> --follow
27
35
  ```
28
36
 
37
+ Common extra flags:
38
+ - `--input '{"k":"v"}'` — JSON input matching the workflow's `inputSchema`.
39
+ - `--factory <slug>` — invoke into a non-default factory.
40
+ - `--snapshot <snap_…>` — per-invocation `bootFrom` override (per `/ac:snapshots`).
41
+
29
42
  ## Output
30
43
 
31
44
  Stream events as they arrive. When complete, report:
32
- - Final outcome (success / failed)
33
- - Final wall time (`run_complete` includes elapsed seconds)
34
- - Any PR URL, plan URL, or artifact produced (visible in the streamed events
35
- or via `agentc logs <run-id>` after the fact)
45
+ - Final outcome (success / failed / cancelled / abandoned)
46
+ - Wall time (`run_complete` includes elapsed seconds)
47
+ - Any PR URL, plan URL, or artifact emitted (visible in the streamed
48
+ events or via `agentc logs <run-id>` after the fact)
36
49
 
37
50
  ## Next Steps
38
51
 
39
- - **Success** → Report the outcome and any output.
40
- - **Failed** → Show the failure reason. Offer to debug: "Run `/ac:logs <run-id>` for the full event log."
41
- - **Secrets missing** → "Run `/ac:secrets set <workflow> <KEY> <value>` to configure the required secret."
42
- - **Template not found** → "Run `/ac:register <workflow.ts>` to register it first."
52
+ - **Success** → Report outcome + any output emitted.
53
+ - **Failed** → Show the failure reason. Offer `/ac:logs <run-id>` for
54
+ the full event stream.
55
+ - **Secrets missing** → "Run `/ac:secrets set <workflow> <KEY> <value>`
56
+ to configure the required secret."
57
+ - **Template not found** → "Run `/ac:register <workflow.ts>` to
58
+ register it first."
59
+ - **Snapshot missing (`bootFrom` 503)** → The snapshot id this
60
+ workflow boots from was deleted. List captured snapshots
61
+ (`/ac:snapshots`) and update the workflow's `snapshots: { bootFrom }`
62
+ with a current id, then re-register.
63
+ - **Want it to run on a schedule?** → `/ac:schedule create <name>
64
+ --workflow <wf> --cron '<expr>'`.
@@ -7,22 +7,59 @@ allowed-tools: Bash(agentc *)
7
7
 
8
8
  # Register and Invoke
9
9
 
10
- Register the workflow at `$0`, then invoke it.
10
+ Bundle + register a workflow, then dispatch it once and stream the run.
11
+ Tight inner-loop for verifying source-level changes against the server.
12
+
13
+ ## Instructions
14
+
15
+ The user passed: **$ARGUMENTS**
16
+
17
+ If `$ARGUMENTS` is empty, ask for the workflow file path before
18
+ proceeding.
19
+
20
+ Parse out the workflow file (first positional arg, ends in `.ts`).
21
+ Anything after it (`--input`, `--factory`, etc.) is passed through to
22
+ `agentc invoke`.
11
23
 
12
24
  ## Plan
13
25
 
14
26
  ```bash
15
- agentc register $0
16
- agentc invoke $(basename "$0" .ts) --follow $1
27
+ # Register the file:
28
+ agentc register <workflow.ts>
29
+
30
+ # Then invoke it by name (basename minus .ts) and stream:
31
+ agentc invoke <workflow-name> --follow [...invoke flags]
32
+ ```
33
+
34
+ If the user wants the workflow to run on a schedule instead of (or in
35
+ addition to) the one-shot test, suggest `/ac:schedule create` AFTER the
36
+ register completes — schedules live independently of register/invoke
37
+ and shouldn't be mixed into this iteration loop.
38
+
39
+ For the shorthand path (one schedule per workflow, named after the
40
+ workflow), pass `--schedule '<cron>'` to `agentc register`:
41
+
42
+ ```bash
43
+ agentc register <workflow.ts> --schedule '0 9 * * *'
17
44
  ```
18
45
 
19
- If `$ARGUMENTS` is empty, ask for the workflow path first.
46
+ That creates + reconciles the schedule at register time; you still
47
+ need a manual `agentc invoke` to test it without waiting for the next
48
+ tick.
20
49
 
21
50
  ## Output
22
51
 
23
- Show register output, then stream the run. When complete, report outcome and any artifacts.
52
+ Show register output, then stream the run live. When complete, report:
53
+ - Final outcome (success / failed / abandoned)
54
+ - Wall time (from `run_complete`)
55
+ - Any PR URL, plan URL, or artifact emitted along the way
24
56
 
25
57
  ## Next Steps
26
58
 
27
- - **Registration failed** → Fix the error and re-run.
28
- - **Run failed** Show failure reason from the final event. Offer `/ac:logs <run-id>`.
59
+ - **Registration failed** → Re-read the bundler / validator error.
60
+ Usually a missing import path, malformed `defineWorkflow(...)`, or a
61
+ manifest mismatch. Fix and re-run.
62
+ - **Run failed** → Show failure reason from the final event. Offer
63
+ `/ac:logs <run-id>` for the full event stream.
64
+ - **Need a schedule** → `/ac:schedule create <name> --workflow <wf>
65
+ --cron '<expr>'` after this completes.
@@ -36,7 +36,10 @@ Confirm:
36
36
 
37
37
  ## Next Steps
38
38
 
39
- - **Success** → "Registered. Run `/ac:invoke <name>` to test it."
39
+ - **Success** → "Registered. Run `/ac:invoke <name>` to test it.
40
+ Want it on a schedule? `/ac:schedule create <name> --workflow <wf>
41
+ --cron '<expr>'` (or pass `--schedule '<expr>'` next time you
42
+ register for the auto-shorthand)."
40
43
  - **Bundle failed** → Re-read the bundler error; usually a missing import path
41
44
  or a runtime that couldn't be resolved.
42
45
  - **Validation error** → Fix the reported issue in the source file and re-run.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: ac:schedule
3
+ description: Create, list, and delete cron schedules attached to registered workflows.
4
+ argument-hint: <create|list|delete> [args]
5
+ allowed-tools: Bash(agentc *)
6
+ ---
7
+
8
+ # Schedules
9
+
10
+ Cron schedules attached to a registered workflow. A workflow can have N
11
+ named schedules with distinct cadences; each tick dispatches a fresh
12
+ run tagged with the schedule's id + name so the dashboard attributes
13
+ the run to its trigger and filters Runs by schedule.
14
+
15
+ > **Mental model.** A schedule is a `(name, workflow, cron)` triple
16
+ > scoped to a factory. Names are unique per factory; ids are stable
17
+ > across re-registers (which makes "show me every run this schedule
18
+ > ever fired" work even when the target template gets new versions).
19
+ > Schedules are independent of workflow lifecycle — deleting a schedule
20
+ > doesn't touch the workflow, deleting a workflow cascades to its
21
+ > schedules.
22
+
23
+ ## Context
24
+
25
+ Registered workflows:
26
+ ```
27
+ !`agentc list 2>/dev/null | awk '{print $1}'`
28
+ ```
29
+
30
+ Existing schedules:
31
+ ```
32
+ !`agentc schedule list 2>/dev/null`
33
+ ```
34
+
35
+ ## Plan
36
+
37
+ Based on `$ARGUMENTS`:
38
+
39
+ | Arguments | Action |
40
+ |---|---|
41
+ | `create <name> --workflow <wf> --cron <expr>` | Create a schedule |
42
+ | `list` | List schedules in the active factory |
43
+ | `delete <id>` | Delete a schedule by id |
44
+ | *(none)* | Ask which subcommand and walk through args |
45
+
46
+ ### Create
47
+
48
+ ```bash
49
+ agentc schedule create <name> --workflow <wf> --cron '<expr>'
50
+ ```
51
+
52
+ - `<name>` — kebab-case, unique per factory (e.g. `nightly-triage`).
53
+ - `--workflow <wf>` — must be a registered workflow in the same factory.
54
+ - `--cron '<expr>'` — UTC, standard 5-field cron. Quote it (shells eat
55
+ the wildcards otherwise). Common patterns:
56
+ - `'0 9 * * *'` — daily at 09:00 UTC
57
+ - `'*/15 * * * *'` — every 15 minutes
58
+ - `'0 * * * 1-5'` — hourly on weekdays
59
+ - `--factory <slug>` — pin to a specific factory (default: active).
60
+
61
+ Read `--cron` aloud back to the user to confirm — the symbol soup
62
+ hides bugs. If unsure, use `cronstrue` (the dashboard renders the
63
+ human form on the Schedules tab).
64
+
65
+ ### List
66
+
67
+ ```bash
68
+ agentc schedule list
69
+ agentc schedule list --factory my-factory
70
+ ```
71
+
72
+ Output is one row per schedule:
73
+
74
+ ```
75
+ <name> <workflow> <cron> next: <ISO> <schedule-id>
76
+ ```
77
+
78
+ The dashboard's `/factories/<slug>/workflows` → **Schedules** tab is
79
+ the richer view: human cadence (cronstrue), next/last fire times,
80
+ delete button, click-through to the Runs view filtered by schedule.
81
+
82
+ ### Delete
83
+
84
+ ```bash
85
+ agentc schedule delete <schedule-id>
86
+ ```
87
+
88
+ Confirm with the user first — "Delete schedule `<name>`? It will stop
89
+ firing immediately; existing runs are unaffected." Schedule ids come
90
+ from `agentc schedule list` or the dashboard.
91
+
92
+ ## Shorthand on register
93
+
94
+ `agentc register <file> --schedule '<cron>'` auto-creates a schedule
95
+ **named after the workflow** at register time. Useful when a workflow
96
+ has exactly one cadence and you don't need a separate name:
97
+
98
+ ```bash
99
+ agentc register pipeline.ts --schedule '0 9 * * *'
100
+ # → registers "pipeline" + creates schedule "pipeline" at 09:00 UTC
101
+ ```
102
+
103
+ Re-registering with the same cron is a no-op; changing the cron
104
+ updates the existing schedule's row. Drop `--schedule` (or pass
105
+ `null`) to clear the auto-shorthand schedule. Schedules created via
106
+ `agentc schedule create` with a different name are left alone.
107
+
108
+ For workflows with multiple cadences (e.g. weekday + weekend), use
109
+ `agentc schedule create` explicitly — the register shorthand only
110
+ manages a single same-name schedule.
111
+
112
+ ## Filtering runs
113
+
114
+ Every run dispatched by a schedule carries `_scheduleId` +
115
+ `_scheduleName` on its metadata. The dashboard's Schedules tab makes
116
+ the schedule name a link straight into a filtered Runs view; the
117
+ chip on each row links back. From the CLI, the metadata is visible
118
+ on `agentc logs <run-id>` and via the SDK's `getRunById`.
119
+
120
+ ## Next Steps
121
+
122
+ - **Schedule created** → "Wait for the first tick (next fire shown in
123
+ `agentc schedule list`); the dispatched run shows up in the dashboard
124
+ Runs view with a `schedule by <name>` link."
125
+ - **`workflow "..." not found`** → Register the target template first
126
+ (`/ac:register <file.ts>`) — schedules can only target workflows
127
+ that already exist.
128
+ - **`schedule "..." already exists`** → Name collision. Either use
129
+ `agentc schedule delete <id>` first or pick a new name.
130
+ - **Schedule never fires** → Check the workflow's `bootFrom`
131
+ (`/ac:snapshots`) — runs against a missing snapshot fail at
132
+ dispatch. The schedule still ticks; only the workflow run fails.
@@ -86,6 +86,9 @@ This lists registered workflows. If it returns without error, setup is complete.
86
86
 
87
87
  > "You're all set. Here's what you can do now:"
88
88
  >
89
- > - `/ac:demo` — watch AI agents build a browser game end-to-end
89
+ > - `/ac:tutorial` — hands-on walkthrough of register, invoke, schedule, events, and snapshots (~10 min)
90
90
  > - `agentc invoke <workflow> --follow` — run any registered workflow
91
91
  > - `/ac:generate-workflow` — build your own workflow from a description
92
+ > - `/ac:schedule create <name> --workflow <wf> --cron '<expr>'` — fire
93
+ > a workflow on a cron schedule (each tick shows up in the Runs view
94
+ > with a `schedule by <name>` link)
@@ -0,0 +1,375 @@
1
+ ---
2
+ name: ac:tutorial
3
+ description: Hands-on tutorial — register, invoke, schedule, emit events, capture snapshots. ~10 minutes against a running stack.
4
+ effort: high
5
+ allowed-tools: Bash(agentc *) Bash(git *) Bash(open *) Read Write Edit
6
+ ---
7
+
8
+ # Tutorial — agent-compose
9
+
10
+ A hands-on walkthrough of every surface that matters. Builds one tiny
11
+ workflow and uses it to demonstrate the five things you'll actually
12
+ care about in production:
13
+
14
+ 1. **Author + register** a workflow.
15
+ 2. **Invoke** it and watch the run live.
16
+ 3. **Schedule** it to fire on a cron.
17
+ 4. **Emit events** from inside the workflow + inspect them.
18
+ 5. **Snapshot** the sandbox state + boot another workflow from it.
19
+
20
+ Should take ~10 minutes against the hosted server (a bit longer
21
+ locally — the runner sandbox boots fresh each time).
22
+
23
+ ## Pre-flight
24
+
25
+ ```
26
+ !`agentc auth status`
27
+ ```
28
+
29
+ If the line above shows no API key, run `/ac:setup` first.
30
+
31
+ ## Mental model
32
+
33
+ Three primitives. Read once, refer back when something doesn't make
34
+ sense:
35
+
36
+ > **Workflow** — the unit of work you author. Two shapes:
37
+ > `async (ctx, sandbox) => T` (run-form, single body) or
38
+ > `defineWorkflow({...}).step(s1).step(s2).build()` (step-form, typed
39
+ > pipeline). This tutorial uses run-form because it's the smaller
40
+ > mental load; step-form is the right shape when the work decomposes
41
+ > into typed phases.
42
+
43
+ > **Agent loop** — `agent({ runtime, prompt, ... })`. Lives inside the
44
+ > workflow body. Drives one iteration of an LLM agent against the
45
+ > runner sandbox until the model emits `exit_signal: true` or hits
46
+ > the budget.
47
+
48
+ > **Runtime** — `claudeRuntime` (built-in, wraps the Claude CLI).
49
+ > `openAIDesktopRuntime` (computer-use). Write your own with
50
+ > `defineRuntime`.
51
+
52
+ Everything else — schedules, events, snapshots, secrets — wraps these
53
+ three.
54
+
55
+ ## Step 1 — Scaffold
56
+
57
+ Create `hello-agent.ts` next to where the user wants to work. Walk
58
+ them through the file before writing — point at `ctx`, `sandbox`,
59
+ `agent`, the prompt, the tool allowlist, the budget, and the
60
+ `ctx.reportEvent` call (we'll use that in step 4). Confirm before
61
+ saving.
62
+
63
+ ```ts
64
+ // hello-agent.ts
65
+ import { defineWorkflow, agent, claudeRuntime } from "@agent-compose/sdk";
66
+
67
+ const PROMPT = `
68
+ You're verifying agent-compose is wired up correctly.
69
+
70
+ Run \`echo "hello from sandbox $(hostname)"\` once, then emit a
71
+ <status> block with a summary describing what you observed and
72
+ exit_signal: true.
73
+ `;
74
+
75
+ export default defineWorkflow({
76
+ async run(ctx, sandbox) {
77
+ const result = await agent({
78
+ sandbox,
79
+ runtime: claudeRuntime,
80
+ prompt: PROMPT,
81
+ tools: ["Bash"],
82
+ budget: { turnsPerIteration: 6, maxIterations: 1 },
83
+ });
84
+
85
+ // Emit a custom event so step 4 has something to inspect.
86
+ await ctx.reportEvent("tutorial.verified", {
87
+ summary: result.status?.summary ?? "(no summary)",
88
+ attributes: { completed: !!result.status?.completed },
89
+ });
90
+
91
+ return { ok: !!result.status?.completed, summary: result.status?.summary };
92
+ },
93
+ });
94
+ ```
95
+
96
+ ## Step 2 — Register and invoke
97
+
98
+ ```bash
99
+ agentc register hello-agent.ts
100
+ agentc invoke hello-agent --follow
101
+ ```
102
+
103
+ > "The CLI bundles the workflow source + the runtime source via
104
+ > `bundleWorkflow`, then POSTs to `/api/v1/factories/<slug>/templates`.
105
+ > The server creates a runner sandbox, drops the bundle in, and starts
106
+ > Node. Inside, our workflow runs `agent` once — Claude spawns, runs
107
+ > the bash command, emits its `<status>`, and exits. Server marks the
108
+ > run complete; the CLI prints elapsed time and the final outcome."
109
+
110
+ Watch the SSE-streamed events live — point out:
111
+ - `step_started` / `step_completed` (timeline phases).
112
+ - `agent_event` lines (model output streaming live).
113
+ - `event_reported` for the `tutorial.verified` event we emitted (step 4
114
+ picks this up).
115
+ - `run_complete` with elapsed time.
116
+
117
+ Copy the run id from the final output — needed for step 4.
118
+
119
+ ## Step 3 — Schedule it
120
+
121
+ Schedules fire your workflow on a cron. Names are unique per factory
122
+ and stable across re-registers, so historical runs stay attributed
123
+ even when the target template gets new versions.
124
+
125
+ ```bash
126
+ agentc schedule create hello-every-2min \
127
+ --workflow hello-agent \
128
+ --cron '*/2 * * * *'
129
+ ```
130
+
131
+ Wait two minutes, then list runs (or open the dashboard's Runs view):
132
+
133
+ ```bash
134
+ agentc list # registered templates
135
+ # In the dashboard, /factories/<slug>/workflows → Schedules tab shows
136
+ # `hello-every-2min` with the cadence in plain English and the next
137
+ # fire time. Clicking the name filters Runs to just this schedule.
138
+ ```
139
+
140
+ When you're done, clean up:
141
+
142
+ ```bash
143
+ agentc schedule list # find the id
144
+ agentc schedule delete <schedule-id>
145
+ ```
146
+
147
+ > "Schedules are independent of the workflow — deleting a schedule
148
+ > doesn't touch the template, deleting the template cascades to its
149
+ > schedules. A workflow can have N named schedules for different
150
+ > cadences (weekday vs weekend, hourly vs daily, etc)."
151
+
152
+ ## Step 4 — Inspect events
153
+
154
+ `ctx.reportEvent(...)` inside the workflow body emits structured
155
+ events the dashboard timeline renders, downstream workflows subscribe
156
+ to, and analytics queries scan. List the events on the run we did in
157
+ step 2:
158
+
159
+ ```bash
160
+ agentc events list <run-id>
161
+ ```
162
+
163
+ You should see `tutorial.verified` with the summary we emitted.
164
+
165
+ Send a synthetic event from the CLI to verify the write path (useful
166
+ for testing downstream subscribers without re-running the workflow):
167
+
168
+ ```bash
169
+ agentc events send <run-id> tutorial.poke \
170
+ --body '{"source":"tutorial"}' \
171
+ --summary "manual poke from /ac:tutorial"
172
+ ```
173
+
174
+ List again — `tutorial.poke` is now there too.
175
+
176
+ > "Events are idempotent on `(run, name, idempotency-key)`, propagate
177
+ > to subscribers, and live in the same timeline as lifecycle events.
178
+ > Production workflows emit them via `ctx.reportEvent` — the CLI form
179
+ > is for testing + verification."
180
+
181
+ ## Step 5 — Snapshots
182
+
183
+ A snapshot is the on-disk state of a runner VM at the end of a
184
+ successful step. Other workflows boot from it instead of running
185
+ their own setup. Useful for:
186
+
187
+ - Pre-baked sandbox environments (Claude CLI installed, deps fetched,
188
+ caches warm).
189
+ - Re-running a workflow with the same starting state every time.
190
+
191
+ For the tutorial, capture one from the next `hello-agent` run by
192
+ adding `snapshots: { saveLatest: true }` to the workflow:
193
+
194
+ ```ts
195
+ export default defineWorkflow({
196
+ snapshots: { saveLatest: true }, // ← add this
197
+ async run(ctx, sandbox) { /* unchanged */ },
198
+ });
199
+ ```
200
+
201
+ Re-register and invoke:
202
+
203
+ ```bash
204
+ agentc register hello-agent.ts
205
+ agentc invoke hello-agent --follow
206
+ ```
207
+
208
+ After the run completes, list captured snapshots:
209
+
210
+ ```bash
211
+ agentc snapshot list --workflow hello-agent
212
+ ```
213
+
214
+ Copy the `snap_…` id. Now author a *second* workflow that boots from
215
+ it instead of starting from a blank sandbox:
216
+
217
+ ```ts
218
+ // hello-consumer.ts
219
+ import { defineWorkflow } from "@agent-compose/sdk";
220
+
221
+ export default defineWorkflow({
222
+ snapshots: { bootFrom: { snapshotId: "snap_PASTE_ID_HERE" } },
223
+ async run(ctx, sandbox) {
224
+ const result = await sandbox.commands.run("ls -la /home/user");
225
+ return { listing: result.stdout };
226
+ },
227
+ });
228
+ ```
229
+
230
+ ```bash
231
+ agentc register hello-consumer.ts
232
+ agentc invoke hello-consumer --follow
233
+ ```
234
+
235
+ `hello-consumer` starts in the exact state `hello-agent` left the
236
+ sandbox in — same files, same processes, same env. No setup re-runs.
237
+
238
+ > "Snapshot ids are the unit of identity. There's no 'latest of
239
+ > workflow X' resolution at dispatch time — operators pick an id from
240
+ > `agentc snapshot list` (or the dashboard's snapshots page) and
241
+ > paste it. Deleting a snapshot makes any workflow referencing it 503
242
+ > at dispatch; the consuming workflow needs to be re-registered with
243
+ > a different id."
244
+
245
+ ## Step 6 — From your own code (SDK)
246
+
247
+ Everything you just did with the CLI is also available programmatically
248
+ via `@agent-compose/sdk`. The CLI is a thin wrapper around the same
249
+ `AgentComposeClient` — what you see in `agentc <verb>` is what your
250
+ backend code does. Useful for:
251
+
252
+ - Triggering workflows from another service (webhook handlers, queue
253
+ consumers, internal dashboards).
254
+ - Listing runs / schedules from your own admin UI.
255
+ - Wiring agent-compose into a CI pipeline without shelling out to
256
+ `agentc`.
257
+
258
+ Install it as a dependency of the calling project (it's a public npm
259
+ package):
260
+
261
+ ```bash
262
+ npm install @agent-compose/sdk # or bun add @agent-compose/sdk
263
+ ```
264
+
265
+ Construct a client with an API key (mint one in the dashboard's
266
+ Settings → API Keys; the CLI uses the same shape):
267
+
268
+ ```ts
269
+ import { AgentComposeClient } from "@agent-compose/sdk";
270
+
271
+ const client = new AgentComposeClient({
272
+ baseURL: process.env.AGENTC_URL ?? "http://localhost:8080",
273
+ apiKey: process.env.AGENTC_API_KEY!, // ac_…
274
+ });
275
+ ```
276
+
277
+ > "The SDK is Bearer-authed (Authorization: ac_…). It's the same
278
+ > entry point the CLI uses — `agentc invoke foo` is literally
279
+ > `client.invoke('foo', …)`. Browser code talks to the server via a
280
+ > cookie-bound dashboard-internal fetch wrapper instead, but that's
281
+ > a dashboard-only seam; server-to-server is the SDK."
282
+
283
+ ### Invoke a workflow + wait for the result
284
+
285
+ ```ts
286
+ // Fire-and-forget — returns a runId you can stream later.
287
+ const { runId } = await client.invoke("hello-agent", { input: {} });
288
+
289
+ // Block until the run settles, return the typed output:
290
+ const result = await client.invokeAndWait<{ ok: boolean; summary?: string }>(
291
+ "hello-agent",
292
+ { input: {}, timeoutMs: 120_000 },
293
+ );
294
+ console.log(result.outcome, result.output?.summary);
295
+ ```
296
+
297
+ ### Schedules (matches `agentc schedule …`)
298
+
299
+ ```ts
300
+ const schedules = await client.listSchedules(); // → ScheduleRow[]
301
+ await client.createSchedule({
302
+ name: "nightly",
303
+ workflow: "hello-agent",
304
+ cron: "0 9 * * *", // UTC
305
+ });
306
+ await client.deleteSchedule(scheduleId);
307
+ ```
308
+
309
+ ### Events (matches `ctx.reportEvent` inside the workflow + `agentc events`)
310
+
311
+ ```ts
312
+ // Read what a run emitted:
313
+ const events = await client.listRunEvents(runId);
314
+
315
+ // Or factory-wide (audit / cross-workflow analytics):
316
+ const recent = await client.listFactoryEvents({ limit: 200 });
317
+
318
+ // Send a synthetic event into a run from outside:
319
+ await client.reportEvent(runId, {
320
+ name: "deploy.completed",
321
+ summary: "deploy to prod succeeded",
322
+ body: { sha: "abc123" },
323
+ });
324
+ ```
325
+
326
+ ### Snapshots (matches `agentc snapshot list`)
327
+
328
+ ```ts
329
+ const captured = await client.listSnapshots({ workflowName: "hello-agent" });
330
+ const perRun = await client.listRunSnapshots(runId);
331
+ ```
332
+
333
+ To boot another workflow from a captured snapshot, set
334
+ `snapshots: { bootFrom: { snapshotId: "snap_…" } }` in
335
+ `defineWorkflow(...)` — same shape the CLI's register step writes.
336
+
337
+ ### Stream a run's logs
338
+
339
+ ```ts
340
+ for await (const line of client.streamRunLogs(runId)) {
341
+ console.log(`[${line.stream}] ${line.line}`);
342
+ }
343
+ ```
344
+
345
+ The full set of methods is on `AgentComposeClient` — the same surface
346
+ the dashboard playground and `/ac:logs` skill use. See the SDK's
347
+ [`README`](../../sdk/README.md) for the type-checked reference.
348
+
349
+ ## Step 7 — Where to go next
350
+
351
+ > "You've now used every primitive. To go further:"
352
+ >
353
+ > - `/ac:generate-workflow` — describe a multi-phase workflow in
354
+ > plain English; Claude scaffolds the file with `agent` blocks per
355
+ > phase and the right input/output schemas.
356
+ > - `/ac:generate-agent` — scaffold one agent loop's prompt + snippet
357
+ > to slot into an existing workflow.
358
+ > - `/ac:secrets` — wire up brokered network secrets (GCP Secret
359
+ > Manager → injected at the Vercel firewall).
360
+ > - `/ac:schedule` — the dedicated schedules walkthrough.
361
+ > - `/ac:snapshots` — list, inspect, delete captured snapshots.
362
+ > - `/ac:events` — full events reference.
363
+ > - Read [`docs/how-it-works.md`](../../docs/how-it-works.md) for the
364
+ > multi-agent example (plan + parallel implementers).
365
+
366
+ ## Cleanup
367
+
368
+ ```bash
369
+ agentc schedule delete <hello-every-2min-id> # if you created one
370
+ agentc snapshot delete <run-id> <snap-id> # if you captured one
371
+ rm hello-agent.ts hello-consumer.ts # local files
372
+ ```
373
+
374
+ Or keep them as references — registered templates stay in the team
375
+ factory until you `agentc factory ...` them out.
package/skills/ac:demo.md DELETED
@@ -1,138 +0,0 @@
1
- ---
2
- name: ac:demo
3
- description: First-run walkthrough — verify auth, scaffold a sample workflow, register it, invoke, and watch the agent run live.
4
- effort: high
5
- allowed-tools: Bash(agentc *) Bash(git *) Bash(open *) Read Write Edit
6
- ---
7
-
8
- # Demo — agent-compose
9
-
10
- A guided first-run walkthrough. Verify auth, generate a tiny sample
11
- workflow, register it, dispatch it, and watch a single agent loop run
12
- end-to-end. Should take ~5 minutes against the hosted server (a bit
13
- longer locally — the runner sandbox boots fresh).
14
-
15
- ## Context
16
-
17
- ```
18
- !`agentc auth status`
19
- ```
20
-
21
- If the line above shows no API key, run `/ac:setup` first (sign in to
22
- the dashboard, mint an `ac_…` key, `agentc auth login <key>`). That
23
- skill walks the full bootstrap.
24
-
25
- ## Steps
26
-
27
- ### 1. Briefly explain the model
28
-
29
- Make sure the user understands the three primitives before you start
30
- typing. Keep it brief — they can read [`docs/how-it-works.md`](../../docs/how-it-works.md)
31
- for the full version.
32
-
33
- > **Workflow** — the unit of work you author. Two shapes are valid:
34
- > a single `async (ctx, sandbox) => T` body (run-form, agent-driven)
35
- > or a typed pipeline of `defineStep(...)` objects composed with
36
- > `defineWorkflow({...}).step(s1).step(s2).build()` (step-form). The
37
- > demo below uses run-form because the body is a single agent loop;
38
- > step-form is the right shape when the work decomposes into phases
39
- > with typed handoffs.
40
-
41
- > **Agent loop** — `agent({ runtime, prompt, ... })`. Embedded inside
42
- > the workflow body. Drives one iteration cycle of an LLM agent against
43
- > the runner sandbox until the model emits `exit_signal: true` (or
44
- > hits the iteration budget).
45
-
46
- > **Runtime** — `claudeRuntime` (built-in) wraps the Claude CLI.
47
- > `openAIDesktopRuntime` wraps OpenAI computer-use. You can write your
48
- > own with `defineRuntime`.
49
-
50
- ### 2. Scaffold a sample workflow
51
-
52
- Create a temporary `hello-agent.ts` next to where the user wants to
53
- work. Keep it minimal — one phase, no fancy plumbing:
54
-
55
- ```ts
56
- // hello-agent.ts
57
- import { defineWorkflow, agent, claudeRuntime } from "@agent-compose/sdk";
58
-
59
- const PROMPT = `
60
- You're verifying agent-compose is wired up correctly.
61
-
62
- Run \`echo "hello from sandbox $(hostname)"\` once, then emit a
63
- <status> block with summary describing what you observed and
64
- exit_signal: true.
65
- `;
66
-
67
- export default defineWorkflow({
68
- async run(ctx, sandbox) {
69
- const result = await agent({
70
- sandbox,
71
- runtime: claudeRuntime,
72
- prompt: PROMPT,
73
- tools: ["Bash"],
74
- budget: { turnsPerIteration: 6, maxIterations: 1 },
75
- });
76
- await ctx.setMetadata({ summary: result.status?.summary });
77
- return { ok: !!result.status?.completed, summary: result.status?.summary };
78
- },
79
- });
80
- ```
81
-
82
- Walk the user through the file — point at `ctx`, `sandbox`,
83
- `agent`, the prompt, the tool allowlist, the budget. Confirm before
84
- writing.
85
-
86
- ### 3. Register
87
-
88
- ```bash
89
- agentc register hello-agent.ts
90
- ```
91
-
92
- > "The CLI bundles the workflow source + the runtime source via
93
- > `bundleWorkflow`, then POSTs to `/api/v1/templates`. Once registered,
94
- > anyone with an `invoke`-scoped API key can call it."
95
-
96
- ### 4. Invoke and watch live
97
-
98
- ```bash
99
- agentc invoke hello-agent --follow
100
- ```
101
-
102
- > "The server creates a runner sandbox, drops the bundle in, and starts
103
- > Node. Inside the sandbox, our workflow runs `agent` once — Claude
104
- > spawns, runs the bash command, emits its `<status>`, and exits.
105
- > Server marks the run complete; the CLI prints the final outcome and
106
- > elapsed time."
107
-
108
- Watch the SSE-streamed events together — point out:
109
- - `step_started` / `step_completed` (timeline phases).
110
- - `agent_event` lines (model output streaming live).
111
- - `run_complete` with elapsed time.
112
-
113
- ### 5. Browse the run in the dashboard
114
-
115
- Once the run finishes, open the run detail page:
116
-
117
- ```bash
118
- # pull the dashboard URL from auth status; substitute the run id
119
- open "$(agentc auth status | awk '/^Dashboard:/ {print $2}')/workflows/<run-id>"
120
- ```
121
-
122
- The dashboard shows the same lifecycle events plus the captured
123
- artifacts (logs, metadata) the workflow emitted.
124
-
125
- ### 6. What to build next
126
-
127
- > "That was the simplest possible workflow. To go further:"
128
- >
129
- > - `/ac:generate-workflow` — describe a multi-phase workflow in plain
130
- > English; Claude scaffolds the full file with `agent` blocks per phase.
131
- > - `/ac:generate-agent` — scaffold one phase's prompt + `agent` snippet
132
- > to slot into an existing workflow.
133
- > - `/ac:generate-runtime` — write a custom runtime for a model the SDK
134
- > doesn't ship.
135
- > - Read [`docs/how-it-works.md`](../../docs/how-it-works.md) for the
136
- > multi-agent example (plan + parallel implementers).
137
-
138
- Clean up: `rm hello-agent.ts` (or keep it as a reference).