@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 +4 -1
- package/dist/index.js +126 -24
- package/package.json +11 -11
- package/skills/ac:generate-workflow.md +11 -1
- package/skills/ac:invoke.md +31 -9
- package/skills/ac:register-invoke.md +44 -7
- package/skills/ac:register.md +4 -1
- package/skills/ac:schedule.md +132 -0
- package/skills/ac:setup.md +4 -1
- package/skills/ac:tutorial.md +375 -0
- package/skills/ac:demo.md +0 -138
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:
|
|
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
|
|
7
|
-
import { fileURLToPath as
|
|
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/
|
|
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 {
|
|
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
|
|
1169
|
-
var
|
|
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 =
|
|
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/${
|
|
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 = `${
|
|
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 ${
|
|
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/${
|
|
1356
|
+
const confirm = await promptYesNo(` Install now? [y/${pc5.bold("N")}] `);
|
|
1275
1357
|
if (!confirm)
|
|
1276
1358
|
return;
|
|
1277
|
-
process.stderr.write(` Running ${
|
|
1359
|
+
process.stderr.write(` Running ${pc5.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} …
|
|
1278
1360
|
`);
|
|
1279
|
-
const result =
|
|
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(`${
|
|
1365
|
+
process.stderr.write(`${pc5.red("✗")} Install failed. Run the command above manually.
|
|
1284
1366
|
`);
|
|
1285
1367
|
return;
|
|
1286
1368
|
}
|
|
1287
|
-
process.stderr.write(`${
|
|
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(
|
|
1294
|
-
program.name("agentc").description("
|
|
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(
|
|
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(
|
|
1310
|
-
program.addCommand(
|
|
1311
|
-
program.
|
|
1312
|
-
|
|
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.
|
|
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":
|
|
39
|
-
"test":
|
|
40
|
-
"build":
|
|
41
|
-
"build:js":
|
|
42
|
-
"build:chmod":
|
|
43
|
-
"clean":
|
|
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.
|
|
48
|
-
"commander":
|
|
49
|
-
"picocolors":
|
|
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":
|
|
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
|
|
package/skills/ac:invoke.md
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
34
|
-
- Any PR URL, plan URL, or artifact
|
|
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
|
|
40
|
-
- **Failed** → Show the failure reason. Offer
|
|
41
|
-
|
|
42
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
agentc
|
|
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
|
-
|
|
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
|
|
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** →
|
|
28
|
-
|
|
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.
|
package/skills/ac:register.md
CHANGED
|
@@ -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.
|
package/skills/ac:setup.md
CHANGED
|
@@ -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:
|
|
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).
|