@tt-a1i/openpi 0.1.0 → 0.2.0
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/LICENSE +21 -0
- package/README.md +295 -389
- package/SETUP.md +24 -22
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/assets/readme-hero-mobile.svg +2 -2
- package/assets/readme-hero.svg +10 -10
- package/extensions/ask-user/handoff.ts +5 -1
- package/extensions/ask-user/index.ts +44 -0
- package/extensions/background-terminals/index.ts +118 -29
- package/extensions/background-terminals/src/domain.ts +5 -1
- package/extensions/background-terminals/src/manager.ts +2 -1
- package/extensions/background-terminals/src/prompt.ts +35 -0
- package/extensions/background-terminals/src/result-delivery.ts +76 -3
- package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
- package/extensions/capabilities/index.ts +198 -0
- package/extensions/context-pivot/index.ts +21 -0
- package/extensions/cron/index.ts +42 -15
- package/extensions/execution-convergence/active-evidence.ts +129 -0
- package/extensions/execution-convergence/index.ts +442 -0
- package/extensions/execution-convergence/workspace-provenance.ts +338 -0
- package/extensions/file-search/index.ts +8 -1
- package/extensions/file-search/src/binaries.ts +2 -1
- package/extensions/git-info/src/runtime.ts +1 -1
- package/extensions/goal/controller.ts +2 -1
- package/extensions/goal/index.ts +20 -1
- package/extensions/plan-mode/index.ts +12 -0
- package/extensions/setup/index.ts +241 -45
- package/extensions/setup/intercom-fs-helper.cjs +130 -0
- package/extensions/setup/intercom.ts +603 -0
- package/extensions/shared/child-session.ts +42 -5
- package/extensions/shared/setup-config.ts +27 -1
- package/extensions/shared/setup-episode-state.ts +7 -0
- package/extensions/shared/tool-surface.ts +435 -0
- package/extensions/subagents/index.ts +16 -1
- package/extensions/subagents/src/manager.ts +13 -11
- package/extensions/subagents/src/prompt.ts +1 -1
- package/extensions/tasks/index.ts +39 -12
- package/extensions/ui-customization/footer.ts +6 -1
- package/extensions/workflows/artifacts.ts +6 -1
- package/extensions/workflows/dashboard.ts +138 -27
- package/extensions/workflows/graph-projection.ts +240 -0
- package/extensions/workflows/handoff.ts +194 -0
- package/extensions/workflows/index.ts +258 -56
- package/extensions/workflows/invocation-ledger.ts +368 -0
- package/extensions/workflows/model.ts +57 -1
- package/extensions/workflows/operator.ts +131 -0
- package/extensions/workflows/prompt.ts +10 -38
- package/extensions/workflows/replay-safety.ts +9 -8
- package/extensions/workflows/runner.ts +10 -2
- package/extensions/workflows/sandbox.ts +5 -0
- package/package.json +15 -15
- package/skills/subagents/SKILL.md +6 -0
- package/skills/workflows/EXAMPLES.md +58 -0
- package/skills/workflows/REFERENCE.md +44 -0
- package/skills/workflows/SKILL.md +39 -0
|
@@ -379,14 +379,15 @@ export function beginProcessReplayWorkspaceLease(replaySafe: boolean) {
|
|
|
379
379
|
return processReplayWorkspaceGuard.begin(replaySafe);
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
interface ReplayResourceLoader
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
382
|
+
interface ReplayResourceLoader
|
|
383
|
+
extends Pick<
|
|
384
|
+
DefaultResourceLoader,
|
|
385
|
+
| "getAgentsFiles"
|
|
386
|
+
| "getAppendSystemPrompt"
|
|
387
|
+
| "getExtensions"
|
|
388
|
+
| "getSkills"
|
|
389
|
+
| "getSystemPrompt"
|
|
390
|
+
> {}
|
|
390
391
|
|
|
391
392
|
function digest(value: string | Buffer) {
|
|
392
393
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -97,6 +97,8 @@ export interface RunAgentOptions {
|
|
|
97
97
|
cwd: string;
|
|
98
98
|
loader: DefaultResourceLoader;
|
|
99
99
|
settingsManager: SettingsManager;
|
|
100
|
+
/** Optional per-run manager reused by one logical workflow operator. */
|
|
101
|
+
sessionManager?: SessionManager;
|
|
100
102
|
modelRegistry: ExtensionContext["modelRegistry"];
|
|
101
103
|
/** Agent Type allowlist; childToolPolicy can only narrow capabilities. */
|
|
102
104
|
tools?: readonly string[];
|
|
@@ -465,6 +467,8 @@ export function createFirstResponseWatchdog(
|
|
|
465
467
|
const timeoutMs = options.timeoutMs ?? FIRST_RESPONSE_TIMEOUT_MS;
|
|
466
468
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
467
469
|
const timeout = new Promise<never>((_resolve, reject) => {
|
|
470
|
+
// This timer owns the awaited watchdog outcome. Keep it referenced so a
|
|
471
|
+
// short-lived Node 22 process cannot exit with the promise still pending.
|
|
468
472
|
timer = setTimeout(() => {
|
|
469
473
|
timer = undefined;
|
|
470
474
|
const model = options.model ? ` for ${options.model}` : "";
|
|
@@ -475,7 +479,6 @@ export function createFirstResponseWatchdog(
|
|
|
475
479
|
);
|
|
476
480
|
void onTimeout().catch(() => {});
|
|
477
481
|
}, timeoutMs);
|
|
478
|
-
timer.unref?.();
|
|
479
482
|
});
|
|
480
483
|
|
|
481
484
|
const cancel = () => {
|
|
@@ -485,6 +488,7 @@ export function createFirstResponseWatchdog(
|
|
|
485
488
|
|
|
486
489
|
return {
|
|
487
490
|
markResponse: cancel,
|
|
491
|
+
cancel,
|
|
488
492
|
async waitFor<T>(operation: Promise<T>) {
|
|
489
493
|
try {
|
|
490
494
|
return await Promise.race([operation, timeout]);
|
|
@@ -533,7 +537,8 @@ export async function runAgent(
|
|
|
533
537
|
: {}),
|
|
534
538
|
resourceLoader: options.loader,
|
|
535
539
|
settingsManager: options.settingsManager,
|
|
536
|
-
sessionManager:
|
|
540
|
+
sessionManager:
|
|
541
|
+
options.sessionManager ?? SessionManager.inMemory(options.cwd),
|
|
537
542
|
...(customTools ? { customTools } : {}),
|
|
538
543
|
...childToolPolicy(childTools),
|
|
539
544
|
}));
|
|
@@ -619,6 +624,7 @@ export async function runAgent(
|
|
|
619
624
|
};
|
|
620
625
|
|
|
621
626
|
let markFirstResponse = () => {};
|
|
627
|
+
let cancelFirstResponseWatchdog = () => {};
|
|
622
628
|
const unsubscribe = childSession.subscribe((event) => {
|
|
623
629
|
if (settled) return;
|
|
624
630
|
if (isAssistantResponseEvent(event)) markFirstResponse();
|
|
@@ -693,6 +699,7 @@ export async function runAgent(
|
|
|
693
699
|
},
|
|
694
700
|
);
|
|
695
701
|
markFirstResponse = watchdog.markResponse;
|
|
702
|
+
cancelFirstResponseWatchdog = watchdog.cancel;
|
|
696
703
|
await Promise.race([
|
|
697
704
|
watchdog.waitFor(
|
|
698
705
|
childSession.prompt(buildWorkflowAgentPrompt(options.prompt)),
|
|
@@ -703,6 +710,7 @@ export async function runAgent(
|
|
|
703
710
|
} catch (error) {
|
|
704
711
|
promptErrorMessage = errorText(error);
|
|
705
712
|
} finally {
|
|
713
|
+
cancelFirstResponseWatchdog();
|
|
706
714
|
options.signal?.removeEventListener("abort", onAbort);
|
|
707
715
|
settled = true;
|
|
708
716
|
unsubscribe();
|
|
@@ -30,12 +30,15 @@ export interface SandboxAgentOptions {
|
|
|
30
30
|
provider?: unknown;
|
|
31
31
|
effort?: unknown;
|
|
32
32
|
isolation?: unknown;
|
|
33
|
+
operator?: unknown;
|
|
34
|
+
inputs?: unknown;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface SandboxAgentResult {
|
|
36
38
|
ok: boolean;
|
|
37
39
|
output: string;
|
|
38
40
|
structured?: unknown;
|
|
41
|
+
ref?: string;
|
|
39
42
|
error?: string;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -102,6 +105,8 @@ function sanitizeAgentOptions(value: unknown): SandboxAgentOptions {
|
|
|
102
105
|
...(value.provider !== undefined ? { provider: value.provider } : {}),
|
|
103
106
|
...(value.effort !== undefined ? { effort: value.effort } : {}),
|
|
104
107
|
...(value.isolation !== undefined ? { isolation: value.isolation } : {}),
|
|
108
|
+
...(value.operator !== undefined ? { operator: value.operator } : {}),
|
|
109
|
+
...(value.inputs !== undefined ? { inputs: value.inputs } : {}),
|
|
105
110
|
};
|
|
106
111
|
}
|
|
107
112
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tt-a1i/openpi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenPI — a Pi-native multi-agent workbench with background execution, isolated subagents, replay-safe workflows, goals, tasks, and observable TUI",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "MIT",
|
|
6
6
|
"author": "tt-a1i",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"pi-package",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
],
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/tt-a1i/
|
|
17
|
+
"url": "git+https://github.com/tt-a1i/openpi.git"
|
|
18
18
|
},
|
|
19
|
-
"homepage": "https://github.com/tt-a1i/
|
|
19
|
+
"homepage": "https://github.com/tt-a1i/openpi#readme",
|
|
20
20
|
"bugs": {
|
|
21
|
-
"url": "https://github.com/tt-a1i/
|
|
21
|
+
"url": "https://github.com/tt-a1i/openpi/issues"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"extensions",
|
|
@@ -41,13 +41,10 @@
|
|
|
41
41
|
"extensions": [
|
|
42
42
|
"./extensions"
|
|
43
43
|
],
|
|
44
|
-
"skills": [
|
|
45
|
-
"./skills"
|
|
46
|
-
],
|
|
47
44
|
"themes": [
|
|
48
45
|
"./themes"
|
|
49
46
|
],
|
|
50
|
-
"image": "https://raw.githubusercontent.com/tt-a1i/
|
|
47
|
+
"image": "https://raw.githubusercontent.com/tt-a1i/openpi/main/assets/openpi-package.png"
|
|
51
48
|
},
|
|
52
49
|
"dependencies": {
|
|
53
50
|
"@effect/platform-node": "^4.0.0-beta.99",
|
|
@@ -55,13 +52,13 @@
|
|
|
55
52
|
"effect": "^4.0.0-beta.99"
|
|
56
53
|
},
|
|
57
54
|
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "2.5.8",
|
|
58
56
|
"@earendil-works/pi-ai": "^0.84.1",
|
|
59
57
|
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
60
58
|
"@earendil-works/pi-tui": "^0.84.1",
|
|
61
59
|
"@effect/tsgo": "^0.24.2",
|
|
62
60
|
"@effect/vitest": "^4.0.0-beta.99",
|
|
63
61
|
"@types/node": "^26.1.1",
|
|
64
|
-
"prettier": "^3.9.5",
|
|
65
62
|
"typebox": "^1.3.6",
|
|
66
63
|
"typescript": "^7.0.2",
|
|
67
64
|
"vitest": "4.1.10"
|
|
@@ -77,11 +74,14 @@
|
|
|
77
74
|
"node": ">=22.19.0"
|
|
78
75
|
},
|
|
79
76
|
"scripts": {
|
|
80
|
-
"prepublishOnly": "
|
|
81
|
-
"check": "
|
|
77
|
+
"prepublishOnly": "bun run check && bun run test",
|
|
78
|
+
"check": "bun run format:check && bun run lint && bun run typecheck",
|
|
82
79
|
"prepare": "node scripts/prepare-effect-tsgo.mjs",
|
|
83
|
-
"format": "
|
|
84
|
-
"format:check": "
|
|
80
|
+
"format": "biome format --write .",
|
|
81
|
+
"format:check": "biome format .",
|
|
82
|
+
"lint": "biome lint . --error-on-warnings",
|
|
83
|
+
"typecheck": "tsc --noEmit",
|
|
85
84
|
"test": "node --test --experimental-strip-types extensions/*/*.test.ts && vitest run extensions/file-search/index.spec.ts"
|
|
86
|
-
}
|
|
85
|
+
},
|
|
86
|
+
"packageManager": "bun@1.3.14"
|
|
87
87
|
}
|
|
@@ -13,3 +13,9 @@ The tool definitions are canonical for parameters, limits, model syntax, isolati
|
|
|
13
13
|
- Prefer a matching agent type when one exists; its tool restriction is enforced. An explicit spawn model or reasoning effort wins, otherwise use the type default then inherit the parent. Types live in `~/.pi/agent/agents/*.md` and, for trusted projects, `.pi/agents/*.md`; see `extensions/subagents/docs/agent-types.md`.
|
|
14
14
|
- Isolate concurrent writers in worktrees according to the `subagent_spawn` schema so they cannot overwrite one checkout or git index. While Plan Mode is active, use only read-only exploration types (or no type); worktree isolation and types narrowed by Plan Mode are rejected.
|
|
15
15
|
- After spawning, continue useful parent work. Let automatic result delivery drive the next turn; block only when the immediate next step truly depends on that result.
|
|
16
|
+
|
|
17
|
+
## Worktree isolation
|
|
18
|
+
|
|
19
|
+
Concurrent writers without isolation share the same checkout and Git index, so edits and `git add` operations can overwrite each other. Set `isolation: "worktree"` and tell every writing child to commit.
|
|
20
|
+
|
|
21
|
+
The worktree is branched from `HEAD`, requires a Git repository, and starts clean without gitignored files such as build output or `.env`. A successful committed child leaves a branch for the parent to merge. An empty branch can be deleted; dirty, untracked, ignored, detached, timed-out, or uninspectable work is preserved instead of being destroyed. Direct subagents retain their checkout for later `subagent_send` review and only reclaim it on Session retirement after a bounded empty-work proof.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Workflow examples
|
|
2
|
+
|
|
3
|
+
## Independent scan and verification pipeline
|
|
4
|
+
|
|
5
|
+
Each file advances to verification as soon as its own scan completes:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
export const meta = {
|
|
9
|
+
name: "reliability-review",
|
|
10
|
+
description: "Review modules for reliability risks, then report",
|
|
11
|
+
phases: [{ title: "Scan" }, { title: "Verify" }, { title: "Report" }],
|
|
12
|
+
}
|
|
13
|
+
const FINDINGS = {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
issues: { type: "array", items: { type: "string" } },
|
|
17
|
+
ok: { type: "boolean" },
|
|
18
|
+
},
|
|
19
|
+
required: ["issues", "ok"],
|
|
20
|
+
}
|
|
21
|
+
phase("Scan")
|
|
22
|
+
const checked = await pipeline(
|
|
23
|
+
args.files,
|
|
24
|
+
(file) => agent(`Trace ${file} for reliability risks with file:line evidence.`, {
|
|
25
|
+
agent_type: "explorer", label: `scan:${file}`, phase: "Scan", schema: FINDINGS,
|
|
26
|
+
}),
|
|
27
|
+
(scan, file) => scan.ok
|
|
28
|
+
? agent(`Verify the candidate issues in ${file}.`, {
|
|
29
|
+
agent_type: "reviewer", label: `verify:${file}`, phase: "Verify", inputs: [scan.ref],
|
|
30
|
+
})
|
|
31
|
+
: null,
|
|
32
|
+
)
|
|
33
|
+
const verified = checked.filter((result) => result && result.ok)
|
|
34
|
+
const dropped = checked.length - verified.length
|
|
35
|
+
if (dropped) log(`${dropped}/${checked.length} file(s) dropped before verification`)
|
|
36
|
+
phase("Report")
|
|
37
|
+
const report = await agent("Synthesize recommendations from the verified findings.", {
|
|
38
|
+
agent_type: "advisor", label: "report", phase: "Report",
|
|
39
|
+
inputs: verified.map((result) => result.ref),
|
|
40
|
+
})
|
|
41
|
+
log(`done — ${verified.length} verified, ${usage().total} tokens`)
|
|
42
|
+
return { verified: verified.length, dropped, report: report.ok ? report.output : report.error }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## When a barrier is correct
|
|
46
|
+
|
|
47
|
+
Use `parallel()` when one synthesis prompt must compare every independent result:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const findings = await parallel(files.map((file) => () =>
|
|
51
|
+
agent(`Inspect ${file}.`, { agent_type: "explorer", label: file })
|
|
52
|
+
))
|
|
53
|
+
const usable = findings.filter((result) => result && result.ok)
|
|
54
|
+
return agent("Deduplicate and rank all findings.", {
|
|
55
|
+
agent_type: "advisor",
|
|
56
|
+
inputs: usable.map((result) => result.ref),
|
|
57
|
+
})
|
|
58
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Workflow DSL reference
|
|
2
|
+
|
|
3
|
+
The `workflow` script is an async JavaScript function body executed in a restricted, killable sandbox. It has no imports, eval, timers, filesystem, network, or process APIs. Normal JavaScript control flow, array methods, `await`, and template strings are available. Return a JSON-serializable value.
|
|
4
|
+
|
|
5
|
+
## Metadata and narration
|
|
6
|
+
|
|
7
|
+
- `export const meta = { name?, description?, phases: [{ title, detail? }] }` declares progress metadata. Declare phases up front.
|
|
8
|
+
- `phase(title)` selects a declared phase.
|
|
9
|
+
- `log(message)` emits one terminal-safe progress line. The latest 100 lines are retained and dropped-line counts are reported.
|
|
10
|
+
- `usage()` returns cumulative `{ input, output, cacheRead, cacheWrite, total, cost, agents }`. It refreshes after agents settle. Compaction can make it a lower bound; it is a reading, not a limit.
|
|
11
|
+
- `args` is the parsed `args` tool parameter, or the original string when it is not valid JSON.
|
|
12
|
+
|
|
13
|
+
## Agent calls
|
|
14
|
+
|
|
15
|
+
`await agent(prompt, options)` runs one child and always resolves to `{ ok, output, structured?, ref?, acceptance?, error? }`. Check `ok` before reading output. Children receive normal trust-aware resources but cannot recursively orchestrate or ask the user.
|
|
16
|
+
|
|
17
|
+
Useful options include `agent_type`, `label`, `phase`, `schema`, `acceptance`, `model`, `provider`, `effort`, `isolation`, `operator`, and `inputs`.
|
|
18
|
+
|
|
19
|
+
- Prefer a matching `agent_type`. Model precedence is explicit model/provider, type file, configured built-in role, then parent. Effort precedence is explicit effort, type default, then parent.
|
|
20
|
+
- `schema` validates structured output. Use it whenever later workflow logic branches on fields.
|
|
21
|
+
- `acceptance: { criteria: [{ id, description, requiredEvidence? }] }` requires the same child to return an evidence ledger. Missing, malformed, or rejected criteria make `ok:false` while preserving output and evidence.
|
|
22
|
+
- `operator: "name"` reuses one in-memory child Session for serialized follow-ups inside the same run. Its model, role/tools, effort, structured mode, and cwd are frozen by the first activation. Operators cannot use per-call worktrees or replay, and do not survive restarts.
|
|
23
|
+
- `inputs: [ref, ...]` accepts successful opaque refs from the same workflow run only. Each conclusion is bounded to 16 KiB and total injected input to 48 KiB. Inputs are marked as untrusted data; the resulting graph is observability, not scheduling authority.
|
|
24
|
+
- `isolation: "worktree"` gives a writing child its own branch and checkout. Concurrent writers without isolation share one checkout and Git index and can overwrite each other. Tell isolated writers to commit. Empty worktrees are reclaimed; commits keep the branch; dirty work may keep the directory.
|
|
25
|
+
|
|
26
|
+
## Fan-out
|
|
27
|
+
|
|
28
|
+
`await pipeline(items, stage1, stage2, ...)` advances each item independently. A stage receives `(previousResult, originalItem, index)`. A throwing stage drops that item to null and skips its remaining stages. Results preserve input order.
|
|
29
|
+
|
|
30
|
+
`await parallel([() => agent(...), ...], { concurrency? })` is a barrier: later code starts after every thunk settles. A throwing thunk becomes null without discarding siblings. Use it when the next step needs the whole set for comparison, deduplication, merging, or an early aggregate decision.
|
|
31
|
+
|
|
32
|
+
Prefer `pipeline()` for ordinary multi-stage fan-out. Mapping, filtering, or flattening between stages is not by itself a reason for a barrier.
|
|
33
|
+
|
|
34
|
+
## Limits and failures
|
|
35
|
+
|
|
36
|
+
Workflow concurrency defaults to the configured package value and has a hard maximum of 64. Agent calls default to the configured package limit and have a hard maximum of 1024. There is no whole-run deadline. A child must produce its first assistant event within 45 seconds; individual child tool calls time out independently after 3 minutes and return an error result the child can recover from.
|
|
37
|
+
|
|
38
|
+
Each call persists intent, admission, and execution state. Interrupted nonterminal calls become `uncertain`, never guessed failed. Artifacts contain results, bounded transcripts, and a read-only graph projection for explicit result refs.
|
|
39
|
+
|
|
40
|
+
## Background and replay
|
|
41
|
+
|
|
42
|
+
`background: true` returns a run id immediately. The Session later receives a completion message; `workflow_status` inspects and `workflow_stop` cancels. Lifecycle tools become visible after a background run starts.
|
|
43
|
+
|
|
44
|
+
`resume_from_run_id` accepts a previous run id or unique suffix. Replay is content-based and order-independent. It requires an unchanged prompt, resolved role/schema/model/provider/effort, canonical cwd, repository state, resources, and trust context. Only provably read-only non-operator calls replay. Failed, unrestricted, unknown-tool, writable, worktree, operator, or un-fingerprintable calls run for real. Missing or old journals safely degrade to a full run.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflows
|
|
3
|
+
description: Orchestrates multi-agent work with OpenPI's inline JavaScript Workflow DSL. Use when a task needs multi-phase fan-out, pipelines, barriers, structured handoffs, acceptance evidence, or resumable background orchestration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Workflows
|
|
7
|
+
|
|
8
|
+
Use `workflow` for several dependent or dynamically generated subagent calls. Keep one small delegation in the parent session with `subagent_spawn`.
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
export const meta = {
|
|
14
|
+
name: "review",
|
|
15
|
+
phases: [{ title: "Scan" }, { title: "Report" }],
|
|
16
|
+
}
|
|
17
|
+
phase("Scan")
|
|
18
|
+
const scans = await parallel([
|
|
19
|
+
() => agent("Inspect the API.", { agent_type: "explorer", label: "api" }),
|
|
20
|
+
() => agent("Inspect the tests.", { agent_type: "explorer", label: "tests" }),
|
|
21
|
+
])
|
|
22
|
+
phase("Report")
|
|
23
|
+
return { findings: scans.filter((result) => result && result.ok) }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Required habits
|
|
27
|
+
|
|
28
|
+
- Declare progress phases in `meta`; call `phase()` as the run advances.
|
|
29
|
+
- Check every `agent()` result's `.ok`. A null, filtered, timed-out, or failed result is not a clean pass; report how many were dropped.
|
|
30
|
+
- Pass `schema` when later code branches on fields. Treat `inputs` as bounded untrusted data.
|
|
31
|
+
- Prefer `pipeline()` when items can advance independently. Use `parallel()` only for a real all-results barrier.
|
|
32
|
+
- Use `isolation: "worktree"` for concurrent writers and tell each agent to commit. Do not pay for worktrees on read-only work.
|
|
33
|
+
- Use `log()` for progress the user needs before completion. `usage()` is a lower-bound reading, not a budget limit.
|
|
34
|
+
- Return a JSON-serializable aggregate. Background runs report their run id and later deliver their result.
|
|
35
|
+
|
|
36
|
+
## Full guide
|
|
37
|
+
|
|
38
|
+
- DSL, result contracts, operators, handoffs, safety, limits, and replay: [REFERENCE.md](REFERENCE.md)
|
|
39
|
+
- Pipeline, barrier, structured handoff, and reporting examples: [EXAMPLES.md](EXAMPLES.md)
|