@agent-compose/cli 0.3.3 → 0.3.4
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/dist/index.js +58 -25
- package/package.json +1 -1
- package/skills/ac:files.md +84 -0
- package/skills/ac:generate-workflow.md +52 -51
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ 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
|
|
6
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8
8
|
import { program } from "commander";
|
|
9
9
|
|
|
@@ -147,6 +147,7 @@ function fallbackDashboardUrl(env) {
|
|
|
147
147
|
return env === "local" ? "http://localhost:3000" : "https://platform.agentcompose.ai";
|
|
148
148
|
}
|
|
149
149
|
var defaultUrl = process.env.AGENT_COMPOSE_URL ?? _envUrl ?? _global.url ?? "https://api.agentcompose.ai";
|
|
150
|
+
var urlIsBuiltinFallback = !process.env.AGENT_COMPOSE_URL && !_envUrl && !_global.url;
|
|
150
151
|
var defaultDashboardUrl = process.env.AGENT_COMPOSE_DASHBOARD_URL ?? _envDashboardUrl ?? _global.dashboardUrl ?? fallbackDashboardUrl(_activeEnv);
|
|
151
152
|
var defaultApiKey = process.env.AGENT_COMPOSE_API_KEY ?? _envKey ?? _global.apiKey ?? "";
|
|
152
153
|
var defaultFactory = process.env.AGENT_COMPOSE_FACTORY ?? _envFactory ?? "default";
|
|
@@ -155,6 +156,10 @@ var projectSettings = _project;
|
|
|
155
156
|
var localSettings = _local;
|
|
156
157
|
var activeEnv = _activeEnv;
|
|
157
158
|
function makeClient({ url, apiKey }) {
|
|
159
|
+
if (urlIsBuiltinFallback && url === "https://api.agentcompose.ai") {
|
|
160
|
+
console.error(`[agentc] No server configured here — targeting https://api.agentcompose.ai (built-in default).
|
|
161
|
+
` + " If you meant another environment: pass --url, set AGENT_COMPOSE_URL, or run from a repo with .agentc settings.");
|
|
162
|
+
}
|
|
158
163
|
if (!apiKey) {
|
|
159
164
|
console.error(`API key required. Provide via:
|
|
160
165
|
` + ` • Project: .agentc/settings.local.json
|
|
@@ -184,7 +189,9 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
184
189
|
console.error(`Error: workflow not found at ${workflowPath}`);
|
|
185
190
|
process.exit(1);
|
|
186
191
|
}
|
|
187
|
-
const { source, manifest, description, networkPolicy, placeholders, snapshots,
|
|
192
|
+
const { source, manifest, description, networkPolicy, placeholders, snapshots, inputSchema, outputSchema, workflowPlan, connectors, connectorOperation, invokePolicy } = await bundleWorkflow(workflowPath);
|
|
193
|
+
if (connectors)
|
|
194
|
+
console.log(`[register] Connectors required: ${Object.keys(connectors).join(", ")} — tokens are injected at the network layer at dispatch`);
|
|
188
195
|
if (networkPolicy)
|
|
189
196
|
console.log(`[register] Network policy detected — credentials will be brokered via Vercel firewall`);
|
|
190
197
|
if (snapshots?.bootFrom)
|
|
@@ -205,18 +212,14 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
205
212
|
...networkPolicy ? { networkPolicy } : {},
|
|
206
213
|
...placeholders ? { placeholders } : {},
|
|
207
214
|
...snapshots !== undefined ? { snapshots } : {},
|
|
208
|
-
...memory !== undefined ? { memory } : {},
|
|
209
|
-
...postRunHooks !== undefined ? { postRunHooks } : {},
|
|
210
215
|
...inputSchema !== undefined ? { inputSchema } : {},
|
|
211
|
-
...outputSchema !== undefined ? { outputSchema } : {}
|
|
216
|
+
...outputSchema !== undefined ? { outputSchema } : {},
|
|
217
|
+
...connectors !== undefined ? { connectors } : {},
|
|
218
|
+
...connectorOperation !== undefined ? { connectorOperation } : {},
|
|
219
|
+
...invokePolicy !== undefined ? { invokePolicy } : {}
|
|
212
220
|
});
|
|
213
221
|
const scheduleNote = opts.schedule ? ` — schedule: ${opts.schedule}` : "";
|
|
214
222
|
console.log(`✓ Workflow: ${result.name}@${result.version} (${result.id})${scheduleNote}`);
|
|
215
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
216
|
-
for (const w of result.warnings) {
|
|
217
|
-
console.warn(`! warning: ${w}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
223
|
if (!opts.build)
|
|
221
224
|
return;
|
|
222
225
|
console.log(`[build] Invoking "${name}" with snapshots:{ saveLatest: true } to capture a snapshot…`);
|
|
@@ -242,7 +245,7 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
242
245
|
process.exit(1);
|
|
243
246
|
});
|
|
244
247
|
function formatBootFrom(b) {
|
|
245
|
-
return `snapshot ${b.snapshotId}`;
|
|
248
|
+
return b === "reuse" ? "reuse (this workflow's own latest snapshot)" : `snapshot ${b.snapshotId}`;
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
// src/commands/invoke.ts
|
|
@@ -1213,11 +1216,38 @@ var listCommand2 = new Command13("list").description("List events for a run, or
|
|
|
1213
1216
|
});
|
|
1214
1217
|
var eventsCommand = new Command13("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
|
|
1215
1218
|
|
|
1216
|
-
// src/commands/
|
|
1219
|
+
// src/commands/files.ts
|
|
1220
|
+
import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
1217
1221
|
import { Command as Command14 } from "commander";
|
|
1218
|
-
var sharedOpts2 = (cmd) => cmd.option("--factory <slug>", `Factory
|
|
1219
|
-
var
|
|
1220
|
-
sharedOpts2(
|
|
1222
|
+
var sharedOpts2 = (cmd) => cmd.option("--factory <slug>", `Factory whose drive to target (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey);
|
|
1223
|
+
var filesCommand = new Command14("files").description("Read/write documents on a factory's drive");
|
|
1224
|
+
sharedOpts2(filesCommand.command("put <local-file> <drive-path>").description("Upload a local file to the factory drive (creates or overwrites)").option("--content-type <mime>", "Content type", "text/plain; charset=utf-8")).action(async (localFile, drivePath, opts) => {
|
|
1225
|
+
const client = makeClient(opts);
|
|
1226
|
+
const result = await client.putFactoryFile(drivePath, readFileSync4(localFile), {
|
|
1227
|
+
factorySlug: opts.factory,
|
|
1228
|
+
contentType: opts.contentType
|
|
1229
|
+
});
|
|
1230
|
+
console.log(`✓ ${result.created ? "created" : "updated"} ${result.path} (${result.sizeBytes} bytes)`);
|
|
1231
|
+
});
|
|
1232
|
+
sharedOpts2(filesCommand.command("get <drive-path>").description("Print a drive file's content (or save with -o)").option("-o, --out <local-file>", "Write to a local file instead of stdout").option("--revision <n>", "Read a specific revision id")).action(async (drivePath, opts) => {
|
|
1233
|
+
const client = makeClient(opts);
|
|
1234
|
+
const content = await client.getFactoryFile(drivePath, {
|
|
1235
|
+
factorySlug: opts.factory,
|
|
1236
|
+
...opts.revision !== undefined ? { revision: Number(opts.revision) } : {}
|
|
1237
|
+
});
|
|
1238
|
+
if (opts.out) {
|
|
1239
|
+
writeFileSync(opts.out, content);
|
|
1240
|
+
console.log(`✓ ${drivePath} → ${opts.out}`);
|
|
1241
|
+
} else {
|
|
1242
|
+
process.stdout.write(content);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// src/commands/schedule.ts
|
|
1247
|
+
import { Command as Command15 } from "commander";
|
|
1248
|
+
var sharedOpts3 = (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);
|
|
1249
|
+
var scheduleCommand = new Command15("schedule").description("Manage cron schedules attached to registered workflows");
|
|
1250
|
+
sharedOpts3(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) => {
|
|
1221
1251
|
const client = makeClient(opts);
|
|
1222
1252
|
const created = await client.createSchedule({
|
|
1223
1253
|
name,
|
|
@@ -1227,7 +1257,7 @@ sharedOpts2(scheduleCommand.command("create <name>").description("Create a new s
|
|
|
1227
1257
|
});
|
|
1228
1258
|
console.log(`✓ Schedule "${created.name}" → workflow "${created.workflow}" @ "${created.cron}" (${created.id})`);
|
|
1229
1259
|
});
|
|
1230
|
-
|
|
1260
|
+
sharedOpts3(scheduleCommand.command("list").description("List schedules in a factory")).action(async (opts) => {
|
|
1231
1261
|
const client = makeClient(opts);
|
|
1232
1262
|
const rows = await client.listSchedules(opts.factory);
|
|
1233
1263
|
if (rows.length === 0) {
|
|
@@ -1240,21 +1270,21 @@ sharedOpts2(scheduleCommand.command("list").description("List schedules in a fac
|
|
|
1240
1270
|
console.log(` ${s.name.padEnd(24)} ${s.workflowName.padEnd(20)} ${s.cron.padEnd(16)} next: ${next} ${s.id}`);
|
|
1241
1271
|
}
|
|
1242
1272
|
});
|
|
1243
|
-
|
|
1273
|
+
sharedOpts3(scheduleCommand.command("delete <id>").description("Delete a schedule by id")).action(async (id, opts) => {
|
|
1244
1274
|
const client = makeClient(opts);
|
|
1245
1275
|
await client.deleteSchedule(id, opts.factory);
|
|
1246
1276
|
console.log(`✓ Schedule ${id} deleted from factory "${opts.factory}"`);
|
|
1247
1277
|
});
|
|
1248
1278
|
|
|
1249
1279
|
// src/commands/upgrade.ts
|
|
1250
|
-
import { Command as
|
|
1280
|
+
import { Command as Command16 } from "commander";
|
|
1251
1281
|
import { spawnSync } from "node:child_process";
|
|
1252
|
-
import { readFileSync as
|
|
1282
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1253
1283
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1254
1284
|
import pc4 from "picocolors";
|
|
1255
1285
|
var PACKAGE_NAME = "@agent-compose/cli";
|
|
1256
|
-
var upgradeCommand = new
|
|
1257
|
-
const pkg = JSON.parse(
|
|
1286
|
+
var upgradeCommand = new Command16("upgrade").description("Update the agentc CLI to the latest published version").option("--check", "Check for updates without installing", false).action(async (opts) => {
|
|
1287
|
+
const pkg = JSON.parse(readFileSync5(fileURLToPath2(new URL("../../package.json", import.meta.url)), "utf8"));
|
|
1258
1288
|
const current = pkg.version;
|
|
1259
1289
|
const controller = new AbortController;
|
|
1260
1290
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
@@ -1296,7 +1326,7 @@ var upgradeCommand = new Command15("upgrade").description("Update the agentc CLI
|
|
|
1296
1326
|
|
|
1297
1327
|
// src/update-check.ts
|
|
1298
1328
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1299
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1329
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1300
1330
|
import { homedir as homedir3 } from "node:os";
|
|
1301
1331
|
import { join as join3 } from "node:path";
|
|
1302
1332
|
import { createInterface } from "node:readline/promises";
|
|
@@ -1336,7 +1366,7 @@ function isNewer(latest, current) {
|
|
|
1336
1366
|
}
|
|
1337
1367
|
function readCache() {
|
|
1338
1368
|
try {
|
|
1339
|
-
const raw =
|
|
1369
|
+
const raw = readFileSync6(cachePath(), "utf8");
|
|
1340
1370
|
const parsed = JSON.parse(raw);
|
|
1341
1371
|
if (typeof parsed.checkedAt !== "number")
|
|
1342
1372
|
return null;
|
|
@@ -1352,7 +1382,7 @@ function writeCache(entry) {
|
|
|
1352
1382
|
try {
|
|
1353
1383
|
const path = cachePath();
|
|
1354
1384
|
mkdirSync2(join3(path, ".."), { recursive: true });
|
|
1355
|
-
|
|
1385
|
+
writeFileSync2(path, JSON.stringify(entry, null, 2) + `
|
|
1356
1386
|
`, "utf8");
|
|
1357
1387
|
} catch {}
|
|
1358
1388
|
}
|
|
@@ -1425,7 +1455,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
1425
1455
|
}
|
|
1426
1456
|
|
|
1427
1457
|
// src/index.ts
|
|
1428
|
-
var pkg = JSON.parse(
|
|
1458
|
+
var pkg = JSON.parse(readFileSync7(fileURLToPath3(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1429
1459
|
program.name("agentc").description("Coordinate agent production lines.").version(pkg.version);
|
|
1430
1460
|
program.hook("preAction", async () => {
|
|
1431
1461
|
try {
|
|
@@ -1439,6 +1469,7 @@ program.addCommand(cancelCommand);
|
|
|
1439
1469
|
program.addCommand(scheduleCommand);
|
|
1440
1470
|
program.addCommand(logsCommand);
|
|
1441
1471
|
program.addCommand(eventsCommand);
|
|
1472
|
+
program.addCommand(filesCommand);
|
|
1442
1473
|
program.addCommand(factoryCommand);
|
|
1443
1474
|
program.addCommand(snapshotCommand);
|
|
1444
1475
|
program.addCommand(secretsCommand);
|
|
@@ -1452,6 +1483,7 @@ Topics:
|
|
|
1452
1483
|
Workflows register, invoke, list, cancel
|
|
1453
1484
|
Scheduling schedule
|
|
1454
1485
|
Run inspection logs, events
|
|
1486
|
+
Factory drive files
|
|
1455
1487
|
Resources factory, snapshot, secrets
|
|
1456
1488
|
Account keys, auth, usage
|
|
1457
1489
|
Setup init, upgrade
|
|
@@ -1461,6 +1493,7 @@ Examples:
|
|
|
1461
1493
|
$ agentc invoke pipeline --follow
|
|
1462
1494
|
$ agentc schedule create nightly --workflow pipeline --cron '0 9 * * *'
|
|
1463
1495
|
$ agentc logs <run-id>
|
|
1496
|
+
$ agentc files put report.md runs/abc123/report.md
|
|
1464
1497
|
|
|
1465
1498
|
Docs:
|
|
1466
1499
|
https://github.com/Layr-Labs/agent-compose
|
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ac:files
|
|
3
|
+
description: Read and write documents on a factory's drive from the CLI.
|
|
4
|
+
allowed-tools: Bash(agentc *)
|
|
5
|
+
effort: low
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Files
|
|
9
|
+
|
|
10
|
+
Put files onto a factory's shared drive and read them back — all through
|
|
11
|
+
`agentc`, no raw HTTP. The drive is where agent-written documents, briefs,
|
|
12
|
+
and run artifacts live; the dashboard's **Files** tab renders them, and
|
|
13
|
+
writes are indexed (revisions, search, attribution). Two subcommands:
|
|
14
|
+
`agentc files put` and `agentc files get`.
|
|
15
|
+
|
|
16
|
+
The primary consumer is an **agent working inside a run sandbox**: publishing
|
|
17
|
+
work to the dashboard while the run is going. Inside a sandbox the env already
|
|
18
|
+
carries `AGENT_COMPOSE_URL` / `AGENT_COMPOSE_API_KEY` / `AGENT_COMPOSE_FACTORY`,
|
|
19
|
+
and the CLI attaches the run-callback token automatically so the write is
|
|
20
|
+
**attributed to the run** (it shows on the file and in the run's Artifacts
|
|
21
|
+
card). Works identically from a dev machine with saved credentials.
|
|
22
|
+
|
|
23
|
+
> Requires an API key with `invoke` (writes) / `read` (reads). See `/ac:setup`.
|
|
24
|
+
|
|
25
|
+
## When to use
|
|
26
|
+
|
|
27
|
+
- **Publish run output** — an agent saves a report/brief so a human sees it
|
|
28
|
+
in the dashboard mid-run.
|
|
29
|
+
- **Share between runs** — write a file other runs of the same factory read.
|
|
30
|
+
- **Pull a file locally** — fetch a drive document to inspect or edit.
|
|
31
|
+
|
|
32
|
+
## Upload a file
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Create or overwrite a drive file from a local file.
|
|
36
|
+
# Convention: agents write under runs/<short-run-id>/...
|
|
37
|
+
agentc files put report.md runs/abc123/report.md
|
|
38
|
+
|
|
39
|
+
# Override the content type (default text/plain; charset=utf-8):
|
|
40
|
+
agentc files put index.html sites/demo/index.html --content-type "text/html; charset=utf-8"
|
|
41
|
+
|
|
42
|
+
# Target a non-default factory:
|
|
43
|
+
agentc files put notes.md briefs/notes.md --factory my-factory
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Download / read a file
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Print a drive file to stdout:
|
|
50
|
+
agentc files get briefs/site-7b7954fc.md
|
|
51
|
+
|
|
52
|
+
# Save to a local file:
|
|
53
|
+
agentc files get runs/abc123/report.md -o ./report.md
|
|
54
|
+
|
|
55
|
+
# Read a specific historical revision:
|
|
56
|
+
agentc files get briefs/notes.md --revision 12
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Browsing the drive
|
|
60
|
+
|
|
61
|
+
There is no `agentc files list` — browse via the dashboard's **Files** tab,
|
|
62
|
+
or (inside a sandbox whose factory has a provisioned Archil disk)
|
|
63
|
+
`ls /factory` on the POSIX mount.
|
|
64
|
+
|
|
65
|
+
## Authoring from inside a workflow
|
|
66
|
+
|
|
67
|
+
The CLI is for agents + ad-hoc use. Workflow code uses the SDK, which attaches
|
|
68
|
+
the run token the same way:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { AgentComposeClient } from "@agent-compose/sdk";
|
|
72
|
+
const client = new AgentComposeClient({ apiKey, baseUrl });
|
|
73
|
+
await client.putFactoryFile("runs/abc123/report.md", contents, {
|
|
74
|
+
contentType: "text/markdown; charset=utf-8",
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## The drive vs. /factory mount
|
|
79
|
+
|
|
80
|
+
If the factory has a provisioned Archil disk, the drive is also POSIX-mounted
|
|
81
|
+
at `/factory` inside sandboxes — fine for reading and for scratch shared
|
|
82
|
+
between sibling runs. But a direct `/factory` write is **not indexed** (no
|
|
83
|
+
revision/attribution), so to make a file appear in the dashboard Files tab use
|
|
84
|
+
`agentc files put` (or the SDK), which goes through the indexed files API.
|
|
@@ -60,7 +60,7 @@ about "step-form vs run-form" — that's an internal call.
|
|
|
60
60
|
- step-form vs run-form
|
|
61
61
|
- sandbox environments / dependency installation
|
|
62
62
|
- snapshots: `saveLatest`, `retainSteps`, `bootFrom`
|
|
63
|
-
- `
|
|
63
|
+
- `processors`
|
|
64
64
|
|
|
65
65
|
Decide those internally based on what they described (see the
|
|
66
66
|
"Internal decisions" section below).
|
|
@@ -69,28 +69,44 @@ Decide those internally based on what they described (see the
|
|
|
69
69
|
|
|
70
70
|
After answering the questions above, decide the shape WITHOUT asking:
|
|
71
71
|
|
|
72
|
-
### Pick the workflow shape
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
### Pick the workflow shape — DEFAULT TO STEP-FORM
|
|
73
|
+
|
|
74
|
+
**Default to step-form, even for agent-driven workflows.** Each meaningful
|
|
75
|
+
phase — including an agent pass — becomes a `defineStep(...)`, and
|
|
76
|
+
`snapshots: { saveLatest: true }` makes the engine capture a snapshot AFTER
|
|
77
|
+
EACH step (latest-only). That durability is the whole point: if a later step's
|
|
78
|
+
sandbox dies, or a step needs retrying, the engine resumes from the last
|
|
79
|
+
completed step's snapshot instead of re-running the entire (often expensive,
|
|
80
|
+
multi-agent) pipeline from the top.
|
|
81
|
+
|
|
82
|
+
A multi-phase agent pipeline written **run-form is ONE engine step** — a single
|
|
83
|
+
snapshot at the very end — so ANY failure throws away all the work and restarts
|
|
84
|
+
at phase one. That's the brittleness step-form avoids.
|
|
85
|
+
|
|
86
|
+
- **Step-form** (`defineWorkflow({ id, input, output, snapshots: { saveLatest: true } }).step(s1).step(s2).build()`
|
|
87
|
+
with `defineStep(...)` objects) — the default. Each phase (fetch, an agent
|
|
88
|
+
pass, a transform, a deploy) is a step. **Steps DO receive a `sandbox`** and
|
|
89
|
+
run `agent({ sandbox: ctx.sandbox, … })`, `ctx.sandbox.commands.run(…)`, etc.
|
|
90
|
+
— they are NOT limited to pure data transforms. Each step's output threads
|
|
91
|
+
into the next step's input. The dashboard renders each as a typed,
|
|
92
|
+
separately-timed, separately-snapshotted phase. Examples: content → imagery →
|
|
93
|
+
polish → verify → deploy; fetch → score → rank → publish.
|
|
94
|
+
|
|
95
|
+
- **Run-form** (one `async (ctx, sandbox)` body) — LEGACY; being phased out in
|
|
96
|
+
favor of step-form everywhere. Only acceptable when the work is a SINGLE
|
|
97
|
+
indivisible agent loop with no meaningful phase boundaries. You give up
|
|
98
|
+
per-step snapshots (one capture at the very end). Use `ctx.step("phase", () => …)`
|
|
99
|
+
inside for timeline observability — it is NOT a snapshot boundary.
|
|
100
|
+
⚠️ Replay semantics: a paused run-form workflow RESUMES BY RE-EXECUTING the
|
|
101
|
+
whole function from the top — only `ctx.requestDecision` results are
|
|
102
|
+
memoized. Every pre-pause side effect re-fires on resume, so writes must be
|
|
103
|
+
create-only/non-clobbering (an unconditional re-write destroyed a human's
|
|
104
|
+
pause-time edits in production testing). If the workflow pauses at all,
|
|
105
|
+
author it step-form: completed steps structurally never replay.
|
|
106
|
+
|
|
107
|
+
If the work has more than one phase — especially multiple agent passes, or any
|
|
108
|
+
step whose work you'd hate to lose on a failure — it's step-form. Each such
|
|
109
|
+
phase is its own step.
|
|
94
110
|
|
|
95
111
|
### Should it have a sandbox environment?
|
|
96
112
|
|
|
@@ -196,6 +212,7 @@ export default defineWorkflow({
|
|
|
196
212
|
description: "Pulls open PRs from a GitHub repo, scores each one by review urgency, surfaces the top five.",
|
|
197
213
|
input: InputSchema,
|
|
198
214
|
output: OutputSchema,
|
|
215
|
+
snapshots: { saveLatest: true }, // snapshot after each step → resume from the last one, not the top
|
|
199
216
|
})
|
|
200
217
|
.step(fetchStep)
|
|
201
218
|
.step(scoreStep)
|
|
@@ -206,10 +223,18 @@ export default defineWorkflow({
|
|
|
206
223
|
Notes:
|
|
207
224
|
- Each step's `output` schema must satisfy the next step's `input`
|
|
208
225
|
schema (the SDK enforces this at `defineWorkflow().step(...)` time
|
|
209
|
-
via TS inference)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
via TS inference), and the FINAL step's `output` must be the same
|
|
227
|
+
zod instance as the workflow's declared `output`. Reshape inside the
|
|
228
|
+
upstream step's `run` body, not at the boundary.
|
|
229
|
+
- **Step bodies DO receive a `sandbox`** — `ctx.sandbox` is present
|
|
230
|
+
whenever the run executes in a sandbox-backed workspace. Run
|
|
231
|
+
`agent({ sandbox: ctx.sandbox, … })`, `ctx.sandbox.commands.run(…)`,
|
|
232
|
+
`ctx.sandbox.files.write(…)` inside any step. (The example above uses
|
|
233
|
+
pure transforms, but an agent pass is a perfectly good step — and the
|
|
234
|
+
preferred shape, because each step is independently snapshotted.)
|
|
235
|
+
- `snapshots: { saveLatest: true }` captures the sandbox after EACH
|
|
236
|
+
step (latest-only); add `retainSteps: true` only if you need every
|
|
237
|
+
step's snapshot kept for fork/replay (linear storage cost).
|
|
213
238
|
|
|
214
239
|
### Template B — run-form (agent-driven body)
|
|
215
240
|
|
|
@@ -281,13 +306,6 @@ export default defineWorkflow({
|
|
|
281
306
|
// saveLatest: true,
|
|
282
307
|
// retainSteps: false,
|
|
283
308
|
// },
|
|
284
|
-
//
|
|
285
|
-
// memory — opt-in. Requires `workflow-memory` to be registered in
|
|
286
|
-
// this factory.
|
|
287
|
-
// memory: true,
|
|
288
|
-
//
|
|
289
|
-
// postRunHooks — workflows that run after this one completes:
|
|
290
|
-
// postRunHooks: ["audit-trail", "notify-slack"],
|
|
291
309
|
});
|
|
292
310
|
```
|
|
293
311
|
|
|
@@ -347,20 +365,3 @@ export default defineWorkflow({
|
|
|
347
365
|
"To run it on the schedule: `agentc schedule create pr-triage-daily
|
|
348
366
|
--workflow pr-triage --cron '<expr>'` — the cron pattern we
|
|
349
367
|
worked out earlier.")
|
|
350
|
-
|
|
351
|
-
## When the user asks for memory extraction
|
|
352
|
-
|
|
353
|
-
The built-in memory extractor (`workflow-memory`) is a separate
|
|
354
|
-
workflow that must be registered in the same factory. Walk them
|
|
355
|
-
through it in user terms:
|
|
356
|
-
|
|
357
|
-
1. "I'll add the `workflow-memory` recipe to your project."
|
|
358
|
-
Copy from `.agentc/templates/workflow-memory.ts`.
|
|
359
|
-
2. "Run `agentc register ./workflow-memory.ts` once to install it."
|
|
360
|
-
3. "Now any workflow with `memory: true` will trigger it after each
|
|
361
|
-
successful run."
|
|
362
|
-
|
|
363
|
-
For custom post-run workflows (analytics, audit, notifications)
|
|
364
|
-
without the built-in extractor, use `postRunHooks: [...]` instead.
|
|
365
|
-
Each post-hook runs in declaration order with the source run's full
|
|
366
|
-
context.
|