@agent-compose/cli 0.1.2 → 0.1.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 +167 -6
- package/package.json +3 -3
- package/skills/ac:demo.md +7 -7
- package/skills/ac:generate-agent.md +11 -12
- package/skills/ac:generate-workflow.md +8 -9
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
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 readFileSync4 } from "node:fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8
8
|
import { program } from "commander";
|
|
9
9
|
|
|
@@ -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, networkPolicy, placeholders, snapshot, saveSnapshot } = await bundleWorkflow(workflowPath);
|
|
186
|
+
const { source, manifest, networkPolicy, placeholders, snapshot, saveSnapshot, memory, 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 (snapshot)
|
|
@@ -195,13 +195,16 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
195
195
|
const result = await client.register({
|
|
196
196
|
name,
|
|
197
197
|
source,
|
|
198
|
+
manifest,
|
|
199
|
+
workflowPlan,
|
|
198
200
|
factorySlug: opts.factory,
|
|
199
201
|
...opts.version ? { version: opts.version } : {},
|
|
200
202
|
...opts.schedule ? { schedule: opts.schedule } : {},
|
|
201
203
|
...networkPolicy ? { networkPolicy } : {},
|
|
202
204
|
...placeholders ? { placeholders } : {},
|
|
203
205
|
...snapshot ? { snapshot } : {},
|
|
204
|
-
...saveSnapshot !== undefined ? { saveSnapshot } : {}
|
|
206
|
+
...saveSnapshot !== undefined ? { saveSnapshot } : {},
|
|
207
|
+
...memory !== undefined ? { memory } : {}
|
|
205
208
|
});
|
|
206
209
|
const scheduleNote = opts.schedule ? ` — schedule: ${opts.schedule}` : "";
|
|
207
210
|
console.log(`✓ Workflow: ${result.name}@${result.version} (${result.id})${scheduleNote}`);
|
|
@@ -246,6 +249,37 @@ function extractToolSnippet(toolInput) {
|
|
|
246
249
|
const raw = toolInput ? String(toolInput) : "";
|
|
247
250
|
return raw.match(/"(?:command|file_path|url|query|pattern)":"([^"]{1,80})/)?.[1] ?? "";
|
|
248
251
|
}
|
|
252
|
+
function printAgentMessage(data) {
|
|
253
|
+
const message = data.message;
|
|
254
|
+
if (!message)
|
|
255
|
+
return;
|
|
256
|
+
switch (message.type) {
|
|
257
|
+
case "text": {
|
|
258
|
+
const text = String(message.text ?? "").replace(/<status>[\s\S]*?<\/status>/g, "").trim();
|
|
259
|
+
if (text)
|
|
260
|
+
process.stdout.write(` ${pc.dim("text")} ${text.slice(0, 160)}
|
|
261
|
+
`);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
case "thinking": {
|
|
265
|
+
const text = String(message.text ?? "").trim();
|
|
266
|
+
if (text)
|
|
267
|
+
process.stdout.write(` ${pc.dim("thinking")} ${text.slice(0, 160)}
|
|
268
|
+
`);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "tool_use": {
|
|
272
|
+
const detail = extractToolSnippet(message.toolInputPreview);
|
|
273
|
+
process.stdout.write(` ${pc.cyan("→")} ${pc.bold(String(message.toolName ?? "tool"))}${detail ? ` ${pc.dim(detail)}` : ""}
|
|
274
|
+
`);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "tool_result":
|
|
278
|
+
process.stdout.write(` ${message.isError ? pc.red("tool error") : pc.dim("tool result")} ${String(message.output ?? "").slice(0, 160)}
|
|
279
|
+
`);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
249
283
|
function printEvent(event, data, state) {
|
|
250
284
|
switch (event) {
|
|
251
285
|
case "step_started":
|
|
@@ -263,6 +297,7 @@ ${pc.dim(`● ${data.step}`)}
|
|
|
263
297
|
process.stdout.write(` ${pc.red("✗")} ${data.step}: ${pc.dim(String(data.reason ?? ""))}
|
|
264
298
|
`);
|
|
265
299
|
break;
|
|
300
|
+
case "agent.spawned":
|
|
266
301
|
case "agent_spawned": {
|
|
267
302
|
const name = String(data.agentName ?? data.label ?? "agent");
|
|
268
303
|
const id = String(data.agentId ?? "");
|
|
@@ -275,6 +310,7 @@ ${pc.bold(pc.magenta(`▶ ${name}`))}${tag}
|
|
|
275
310
|
`);
|
|
276
311
|
break;
|
|
277
312
|
}
|
|
313
|
+
case "agent.settled":
|
|
278
314
|
case "agent_settled": {
|
|
279
315
|
const ok = data.outcome === "success" || data.outcome === "done";
|
|
280
316
|
const id = String(data.agentId ?? "");
|
|
@@ -292,6 +328,16 @@ ${pc.bold(pc.magenta(`▶ ${name}`))}${tag}
|
|
|
292
328
|
}
|
|
293
329
|
break;
|
|
294
330
|
}
|
|
331
|
+
case "agent.iteration": {
|
|
332
|
+
const name = String(data.label ?? "agent");
|
|
333
|
+
const iteration = data.iteration ? ` ${data.iteration}` : "";
|
|
334
|
+
process.stdout.write(` ${pc.dim(`${name} iteration${iteration}`)}
|
|
335
|
+
`);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case "agent.message":
|
|
339
|
+
printAgentMessage(data);
|
|
340
|
+
break;
|
|
295
341
|
case "agent_event": {
|
|
296
342
|
const { toolName } = data;
|
|
297
343
|
if (toolName) {
|
|
@@ -600,7 +646,7 @@ function requireAdminKey(adminKey) {
|
|
|
600
646
|
process.exit(1);
|
|
601
647
|
}
|
|
602
648
|
var keysCommand = new Command6("keys").description("Manage API keys");
|
|
603
|
-
keysCommand.command("create <name>").description("Create a new API key (requires admin-scoped ac_… key)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--admin-key <key>", "Admin-scoped ac_… key (or AGENT_COMPOSE_ADMIN_KEY env var)", process.env.AGENT_COMPOSE_ADMIN_KEY ?? "").option("--scopes <list>", "Comma-separated scope list: read, invoke, admin. Default (empty) = full team access.").option("--expires-in <duration>", `Expire the key after this duration — e.g. "30d", "2h", "7d12h", "1w", "90m". Default: never.`).option("--factory <slug>", "Restrict the key to a single factory (slug). Default: team-wide.").action(async (name, opts) => {
|
|
649
|
+
keysCommand.command("create <name>").description("Create a new API key (requires admin-scoped ac_… key)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--admin-key <key>", "Admin-scoped ac_… key (or AGENT_COMPOSE_ADMIN_KEY env var)", process.env.AGENT_COMPOSE_ADMIN_KEY ?? "").option("--scopes <list>", "Comma-separated scope list: read, invoke, admin, events:read, events:write. Default (empty) = full team access.").option("--expires-in <duration>", `Expire the key after this duration — e.g. "30d", "2h", "7d12h", "1w", "90m". Default: never.`).option("--factory <slug>", "Restrict the key to a single factory (slug). Default: team-wide.").action(async (name, opts) => {
|
|
604
650
|
requireAdminKey(opts.adminKey);
|
|
605
651
|
const scopes = opts.scopes ? opts.scopes.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
606
652
|
let expiresAt;
|
|
@@ -964,8 +1010,122 @@ var cancelCommand = new Command12("cancel").description("Cancel an in-progress r
|
|
|
964
1010
|
await streamLogs(runId, opts);
|
|
965
1011
|
});
|
|
966
1012
|
|
|
1013
|
+
// src/commands/events.ts
|
|
1014
|
+
import { Command as Command13 } from "commander";
|
|
1015
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1016
|
+
import pc3 from "picocolors";
|
|
1017
|
+
function parseBody(raw) {
|
|
1018
|
+
if (raw === undefined || raw === "")
|
|
1019
|
+
return {};
|
|
1020
|
+
const src = raw.startsWith("@") ? readFileSync3(raw.slice(1), "utf8") : raw;
|
|
1021
|
+
try {
|
|
1022
|
+
return JSON.parse(src);
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
throw new Error(`--body is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function parseAttribute(raw, prev = {}) {
|
|
1028
|
+
const idx = raw.indexOf("=");
|
|
1029
|
+
if (idx < 0)
|
|
1030
|
+
throw new Error(`--attribute must be key=value, got "${raw}"`);
|
|
1031
|
+
const key = raw.slice(0, idx);
|
|
1032
|
+
const val = raw.slice(idx + 1);
|
|
1033
|
+
if (!key)
|
|
1034
|
+
throw new Error(`--attribute key is empty in "${raw}"`);
|
|
1035
|
+
let parsed;
|
|
1036
|
+
try {
|
|
1037
|
+
parsed = JSON.parse(val);
|
|
1038
|
+
} catch {
|
|
1039
|
+
parsed = val;
|
|
1040
|
+
}
|
|
1041
|
+
return { ...prev, [key]: parsed };
|
|
1042
|
+
}
|
|
1043
|
+
function parseConfidence(raw) {
|
|
1044
|
+
const n = Number(raw);
|
|
1045
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
1046
|
+
throw new Error(`--confidence must be a number in [0, 1], got "${raw}"`);
|
|
1047
|
+
}
|
|
1048
|
+
return n;
|
|
1049
|
+
}
|
|
1050
|
+
function parseLimit(raw) {
|
|
1051
|
+
const n = parseInt(raw, 10);
|
|
1052
|
+
if (!Number.isFinite(n) || n < 1)
|
|
1053
|
+
throw new Error(`--limit must be a positive integer, got "${raw}"`);
|
|
1054
|
+
return n;
|
|
1055
|
+
}
|
|
1056
|
+
function fmtBodyPreview(body) {
|
|
1057
|
+
if (body === null || body === undefined)
|
|
1058
|
+
return pc3.dim("—");
|
|
1059
|
+
if (typeof body === "string")
|
|
1060
|
+
return body.length > 60 ? body.slice(0, 57) + "…" : body;
|
|
1061
|
+
try {
|
|
1062
|
+
const s = JSON.stringify(body);
|
|
1063
|
+
return s.length > 60 ? s.slice(0, 57) + "…" : s;
|
|
1064
|
+
} catch {
|
|
1065
|
+
return String(body);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
var sendCommand = new Command13("send").description("Send an event to an existing run (requires events:write or invoke scope)").argument("<run-id>", "Target run id (uuid)").argument("<name>", "Event name (e.g. quality.accepted, deploy.completed)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--body <json>", "Event body — inline JSON or @file (default: {})").option("--summary <s>", "One-line summary (≤2000 chars)").option("--confidence <0..1>", "Confidence score, [0, 1]", parseConfidence).option("-a, --attribute <key=value>", "Attribute (repeatable; value parsed as JSON if possible)", parseAttribute, {}).option("--idempotency-key <k>", "Replay-safe key: identical (run, name, key) returns the original event").option("--no-propagate", "Mark event as non-propagating").option("--timestamp <iso>", "Event timestamp (ISO 8601, defaults to server-now)").option("--json", "Output the raw event JSON instead of the one-line summary").action(async (runId, name, opts) => {
|
|
1069
|
+
let body;
|
|
1070
|
+
try {
|
|
1071
|
+
body = parseBody(opts.body);
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
const client = makeClient(opts);
|
|
1077
|
+
const event = await client.reportEvent(runId, {
|
|
1078
|
+
name,
|
|
1079
|
+
body,
|
|
1080
|
+
...opts.summary ? { summary: opts.summary } : {},
|
|
1081
|
+
...opts.confidence !== undefined ? { confidence: opts.confidence } : {},
|
|
1082
|
+
...opts.timestamp ? { timestamp: opts.timestamp } : {},
|
|
1083
|
+
...Object.keys(opts.attribute).length ? { attributes: opts.attribute } : {},
|
|
1084
|
+
...opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {},
|
|
1085
|
+
...opts.propagate === false ? { propagate: false } : {}
|
|
1086
|
+
}).catch(reportSdkError);
|
|
1087
|
+
if (opts.json) {
|
|
1088
|
+
console.log(JSON.stringify(event, null, 2));
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
console.log(`${pc3.green("✓")} event ${pc3.bold(event.id.slice(0, 8))} ${event.name} ${pc3.dim(`run ${runId.slice(0, 8)}`)}`);
|
|
1092
|
+
});
|
|
1093
|
+
var listCommand2 = new Command13("list").description("List events for a run, or factory-wide with --factory <slug>").argument("[run-id]", "Run id (uuid). If omitted, requires --factory.").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--factory <slug>", "List events across a factory instead of a single run").option("--limit <n>", "Max rows (factory mode only; server clamps)", parseLimit).option("--name <pattern>", "Filter by event name (factory mode only)").option("--json", "Output raw JSON instead of the columnar view").action(async (runId, opts) => {
|
|
1094
|
+
if (!runId && !opts.factory) {
|
|
1095
|
+
console.error("Error: provide either <run-id> or --factory <slug>");
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
if (runId && opts.factory) {
|
|
1099
|
+
console.error("Error: <run-id> and --factory are mutually exclusive");
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
const client = makeClient(opts);
|
|
1103
|
+
const events = runId ? await client.listRunEvents(runId).catch(reportSdkError) : (await client.listFactoryEvents({
|
|
1104
|
+
factorySlug: opts.factory,
|
|
1105
|
+
...opts.limit !== undefined ? { limit: opts.limit } : {},
|
|
1106
|
+
...opts.name ? { name: opts.name } : {}
|
|
1107
|
+
}).catch(reportSdkError)).events;
|
|
1108
|
+
if (opts.json) {
|
|
1109
|
+
console.log(JSON.stringify(events, null, 2));
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (events.length === 0) {
|
|
1113
|
+
const scope = runId ? `run ${runId.slice(0, 8)}` : `factory "${opts.factory}"`;
|
|
1114
|
+
console.log(`No events for ${scope}.`);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const sorted = [...events].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
1118
|
+
for (const ev of sorted) {
|
|
1119
|
+
const ts = ev.timestamp.replace(/\.\d+Z$/, "Z");
|
|
1120
|
+
const detail = ev.summary ?? fmtBodyPreview(ev.body);
|
|
1121
|
+
const runHint = runId ? "" : pc3.dim(` run ${(ev.runId ?? "—").slice(0, 8)}`);
|
|
1122
|
+
console.log(`${pc3.dim(ts)} ${pc3.bold(ev.name.padEnd(28))} ${detail}${runHint}`);
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
var eventsCommand = new Command13("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
|
|
1126
|
+
|
|
967
1127
|
// src/index.ts
|
|
968
|
-
var pkg = JSON.parse(
|
|
1128
|
+
var pkg = JSON.parse(readFileSync4(fileURLToPath2(new URL("../package.json", import.meta.url)), "utf8"));
|
|
969
1129
|
program.name("agentc").description("CLI for agent-compose — register, invoke, and monitor workflows").version(pkg.version);
|
|
970
1130
|
program.addCommand(registerCommand);
|
|
971
1131
|
program.addCommand(invokeCommand);
|
|
@@ -979,4 +1139,5 @@ program.addCommand(usageCommand);
|
|
|
979
1139
|
program.addCommand(snapshotCommand);
|
|
980
1140
|
program.addCommand(factoryCommand);
|
|
981
1141
|
program.addCommand(cancelCommand);
|
|
1142
|
+
program.addCommand(eventsCommand);
|
|
982
1143
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-compose/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Command-line interface for agent-compose — register, invoke, and monitor workflows from your terminal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
|
39
39
|
"test": "bun test src/__tests__",
|
|
40
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
|
|
41
|
+
"build:js": "bun build src/index.ts --outdir dist --target node --format esm --packages external --banner='#!/usr/bin/env bun'",
|
|
42
42
|
"build:chmod": "chmod +x dist/index.js",
|
|
43
43
|
"clean": "rm -rf dist",
|
|
44
44
|
"prepublishOnly": "bun run build"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@agent-compose/sdk": "^0.2.
|
|
47
|
+
"@agent-compose/sdk": "^0.2.5",
|
|
48
48
|
"commander": "^12.0.0",
|
|
49
49
|
"picocolors": "^1.1.1"
|
|
50
50
|
},
|
package/skills/ac:demo.md
CHANGED
|
@@ -34,7 +34,7 @@ for the full version.
|
|
|
34
34
|
> Owns the run end-to-end: orchestrates phases, kicks off agent loops,
|
|
35
35
|
> writes metadata, returns a structured result.
|
|
36
36
|
|
|
37
|
-
> **Agent loop** — `
|
|
37
|
+
> **Agent loop** — `agent({ runtime, prompt, ... })`. Embedded inside
|
|
38
38
|
> the workflow body. Drives one iteration cycle of an LLM agent against
|
|
39
39
|
> the runner sandbox until the model emits `exit_signal: true` (or
|
|
40
40
|
> hits the iteration budget).
|
|
@@ -50,7 +50,7 @@ work. Keep it minimal — one phase, no fancy plumbing:
|
|
|
50
50
|
|
|
51
51
|
```ts
|
|
52
52
|
// hello-agent.ts
|
|
53
|
-
import { defineWorkflow,
|
|
53
|
+
import { defineWorkflow, agent, claudeRuntime } from "@agent-compose/sdk";
|
|
54
54
|
|
|
55
55
|
const PROMPT = `
|
|
56
56
|
You're verifying agent-compose is wired up correctly.
|
|
@@ -62,7 +62,7 @@ exit_signal: true.
|
|
|
62
62
|
|
|
63
63
|
export default defineWorkflow({
|
|
64
64
|
async run(ctx, sandbox) {
|
|
65
|
-
const result = await
|
|
65
|
+
const result = await agent({
|
|
66
66
|
sandbox,
|
|
67
67
|
runtime: claudeRuntime,
|
|
68
68
|
prompt: PROMPT,
|
|
@@ -76,7 +76,7 @@ export default defineWorkflow({
|
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
Walk the user through the file — point at `ctx`, `sandbox`,
|
|
79
|
-
`
|
|
79
|
+
`agent`, the prompt, the tool allowlist, the budget. Confirm before
|
|
80
80
|
writing.
|
|
81
81
|
|
|
82
82
|
### 3. Register
|
|
@@ -96,7 +96,7 @@ agentc invoke hello-agent --follow
|
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
> "The server creates a runner sandbox, drops the bundle in, and starts
|
|
99
|
-
> Node. Inside the sandbox, our workflow runs `
|
|
99
|
+
> Node. Inside the sandbox, our workflow runs `agent` once — Claude
|
|
100
100
|
> spawns, runs the bash command, emits its `<status>`, and exits.
|
|
101
101
|
> Server marks the run complete; the CLI prints the final outcome and
|
|
102
102
|
> elapsed time."
|
|
@@ -123,8 +123,8 @@ artifacts (logs, metadata) the workflow emitted.
|
|
|
123
123
|
> "That was the simplest possible workflow. To go further:"
|
|
124
124
|
>
|
|
125
125
|
> - `/ac:generate-workflow` — describe a multi-phase workflow in plain
|
|
126
|
-
> English; Claude scaffolds the full file with `
|
|
127
|
-
> - `/ac:generate-agent` — scaffold one phase's prompt + `
|
|
126
|
+
> English; Claude scaffolds the full file with `agent` blocks per phase.
|
|
127
|
+
> - `/ac:generate-agent` — scaffold one phase's prompt + `agent` snippet
|
|
128
128
|
> to slot into an existing workflow.
|
|
129
129
|
> - `/ac:generate-runtime` — write a custom runtime for a model the SDK
|
|
130
130
|
> doesn't ship.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ac:generate-agent
|
|
3
|
-
description: Scaffold
|
|
3
|
+
description: Scaffold an `agent` block + prompt for one agent loop inside a workflow.
|
|
4
4
|
allowed-tools: Read Write Edit Bash(bunx *) Bash(bun *)
|
|
5
5
|
effort: high
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Generate Agent
|
|
9
9
|
|
|
10
|
-
Interactively scaffold a single agent loop —
|
|
10
|
+
Interactively scaffold a single agent loop — an `agent({ ... })` call
|
|
11
11
|
plus its `prompt.md` — to drop into an existing workflow.
|
|
12
12
|
|
|
13
13
|
> **Mental model.** There is no standalone "agent" resource. Agents live
|
|
14
|
-
> *inside* workflows via `
|
|
14
|
+
> *inside* workflows via `agent`. Each `agent` call drives one
|
|
15
15
|
> iteration loop with a runtime, prompt, tool allowlist, and budget.
|
|
16
|
-
> Multiple `
|
|
16
|
+
> Multiple `agent` calls compose by sequencing (`await`) or by
|
|
17
17
|
> fanning out (`Promise.all`).
|
|
18
18
|
|
|
19
19
|
## Steps
|
|
@@ -24,12 +24,12 @@ plus its `prompt.md` — to drop into an existing workflow.
|
|
|
24
24
|
- Runtime? (default: `claudeRuntime`. Use `openAIDesktopRuntime` for desktop / computer-use.)
|
|
25
25
|
- Tools? (e.g. `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`, `WebFetch`)
|
|
26
26
|
- Budget? Suggest `{ turnsPerIteration: 30-60, maxIterations: 4-8 }` based on complexity.
|
|
27
|
-
- Typed response? If yes, sketch a zod schema — `
|
|
27
|
+
- Typed response? If yes, sketch a zod schema — `agent({ responseSchema })`
|
|
28
28
|
makes the loop demand a `<response>` block that validates against it.
|
|
29
29
|
- Prompt directory? (default: alongside the workflow file)
|
|
30
30
|
|
|
31
|
-
2. If the user has an existing workflow with
|
|
32
|
-
to match their conventions — prompt
|
|
31
|
+
2. If the user has an existing workflow with an `agent` block, read it
|
|
32
|
+
to match their conventions — prompt structure,
|
|
33
33
|
tool sets, error handling. Otherwise follow the patterns in
|
|
34
34
|
`sdk/src/runtimes/claude.ts` and the SDK's [README](../../sdk/README.md).
|
|
35
35
|
|
|
@@ -40,19 +40,18 @@ plus its `prompt.md` — to drop into an existing workflow.
|
|
|
40
40
|
- Available tools + when to use each.
|
|
41
41
|
- Step-by-step instructions for the phase.
|
|
42
42
|
- The `<status>` protocol is auto-appended; don't include it manually.
|
|
43
|
-
-
|
|
44
|
-
|
|
43
|
+
- Put runtime values directly in the workflow's prompt string; there is no
|
|
44
|
+
separate templating layer.
|
|
45
45
|
|
|
46
46
|
**Snippet to paste into the workflow body:**
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
49
|
import PROMPT from "./<phase>.md" with { type: "text" };
|
|
50
50
|
// …in the workflow's run():
|
|
51
|
-
const result = await
|
|
51
|
+
const result = await agent({
|
|
52
52
|
sandbox,
|
|
53
53
|
runtime: claudeRuntime,
|
|
54
|
-
prompt: PROMPT
|
|
55
|
-
promptVars: { /* … */ },
|
|
54
|
+
prompt: `${PROMPT}\n\nContext: ${JSON.stringify(ctx.input ?? {})}`,
|
|
56
55
|
tools: [/* … */],
|
|
57
56
|
budget: { turnsPerIteration: 40, maxIterations: 6 },
|
|
58
57
|
// responseSchema: MySchema, // when typed handoff is needed
|
|
@@ -12,9 +12,9 @@ Interactively scaffold a new workflow from a plain-English description.
|
|
|
12
12
|
> **Mental model.** A workflow is `async (ctx, sandbox) => T`. The `ctx`
|
|
13
13
|
> exposes `run`, `input?`, `setMetadata`, and `step` (for named timeline
|
|
14
14
|
> phases). The `sandbox` is the runner's own VM — pass it to
|
|
15
|
-
> `
|
|
15
|
+
> `agent({ sandbox, ... })` and to any helper that takes a
|
|
16
16
|
> `SandboxProvider`. Agent loops live *inside* the workflow body via
|
|
17
|
-
> `
|
|
17
|
+
> `agent`. There's no `defineAgent` / `spawnAgent` model anymore.
|
|
18
18
|
|
|
19
19
|
## Steps
|
|
20
20
|
|
|
@@ -29,7 +29,7 @@ Interactively scaffold a new workflow from a plain-English description.
|
|
|
29
29
|
workflows whose end-state other workflows boot from)
|
|
30
30
|
|
|
31
31
|
2. If the user has a reference workflow in their project, read it to
|
|
32
|
-
match their conventions (prompt structure,
|
|
32
|
+
match their conventions (prompt structure,
|
|
33
33
|
error handling). Otherwise follow the patterns in
|
|
34
34
|
[`sdk/README.md`](../../sdk/README.md) and the example in
|
|
35
35
|
[`docs/how-it-works.md`](../../docs/how-it-works.md).
|
|
@@ -37,7 +37,7 @@ Interactively scaffold a new workflow from a plain-English description.
|
|
|
37
37
|
3. Generate a complete, real implementation:
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
|
-
import { defineWorkflow,
|
|
40
|
+
import { defineWorkflow, agent, claudeRuntime } from "@agent-compose/sdk";
|
|
41
41
|
import PROMPT from "./prompt.md" with { type: "text" };
|
|
42
42
|
|
|
43
43
|
export default defineWorkflow({
|
|
@@ -48,13 +48,12 @@ Interactively scaffold a new workflow from a plain-English description.
|
|
|
48
48
|
// pure-server work, fetches, etc.
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
// Phase 2 — agent loop. Each `
|
|
51
|
+
// Phase 2 — agent loop. Each `agent` is one iteration cycle
|
|
52
52
|
// with its own prompt + tool allowlist + budget.
|
|
53
|
-
const result = await
|
|
53
|
+
const result = await agent({
|
|
54
54
|
sandbox,
|
|
55
55
|
runtime: claudeRuntime,
|
|
56
|
-
prompt: PROMPT
|
|
57
|
-
promptVars: { /* {{VAR}} substitutions */ },
|
|
56
|
+
prompt: `${PROMPT}\n\nContext: ${JSON.stringify(data)}`,
|
|
58
57
|
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
|
|
59
58
|
budget: { turnsPerIteration: 40, maxIterations: 6 },
|
|
60
59
|
// responseSchema: MyZodSchema, // for typed handoffs
|
|
@@ -72,7 +71,7 @@ Interactively scaffold a new workflow from a plain-English description.
|
|
|
72
71
|
```
|
|
73
72
|
|
|
74
73
|
- Always declare the return type (or let TS infer + show it on hover).
|
|
75
|
-
- Use `Promise.all` to fan out independent `
|
|
74
|
+
- Use `Promise.all` to fan out independent `agent` calls.
|
|
76
75
|
- Use `ctx.step("phase-name", () => …)` for any named non-agent phase.
|
|
77
76
|
|
|
78
77
|
4. Show the complete file plus a separate `prompt.md` (or per-phase
|