@tt-a1i/openpi 0.1.1 → 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.
Files changed (43) hide show
  1. package/README.md +37 -22
  2. package/SETUP.md +8 -6
  3. package/extensions/ask-user/handoff.ts +5 -1
  4. package/extensions/ask-user/index.ts +44 -0
  5. package/extensions/background-terminals/index.ts +118 -29
  6. package/extensions/background-terminals/src/domain.ts +5 -1
  7. package/extensions/background-terminals/src/manager.ts +2 -1
  8. package/extensions/background-terminals/src/prompt.ts +35 -0
  9. package/extensions/background-terminals/src/result-delivery.ts +76 -3
  10. package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
  11. package/extensions/capabilities/index.ts +198 -0
  12. package/extensions/context-pivot/index.ts +21 -0
  13. package/extensions/cron/index.ts +42 -15
  14. package/extensions/execution-convergence/active-evidence.ts +129 -0
  15. package/extensions/execution-convergence/index.ts +442 -0
  16. package/extensions/execution-convergence/workspace-provenance.ts +338 -0
  17. package/extensions/file-search/index.ts +8 -1
  18. package/extensions/file-search/src/binaries.ts +2 -1
  19. package/extensions/git-info/src/runtime.ts +1 -1
  20. package/extensions/goal/controller.ts +2 -1
  21. package/extensions/goal/index.ts +20 -1
  22. package/extensions/plan-mode/index.ts +12 -0
  23. package/extensions/setup/index.ts +93 -7
  24. package/extensions/shared/child-session.ts +40 -4
  25. package/extensions/shared/setup-config.ts +22 -0
  26. package/extensions/shared/setup-episode-state.ts +7 -0
  27. package/extensions/shared/tool-surface.ts +435 -0
  28. package/extensions/subagents/index.ts +15 -0
  29. package/extensions/subagents/src/manager.ts +13 -11
  30. package/extensions/subagents/src/prompt.ts +1 -1
  31. package/extensions/tasks/index.ts +39 -12
  32. package/extensions/ui-customization/footer.ts +6 -1
  33. package/extensions/workflows/graph-projection.ts +6 -4
  34. package/extensions/workflows/index.ts +16 -1
  35. package/extensions/workflows/invocation-ledger.ts +8 -2
  36. package/extensions/workflows/model.ts +5 -1
  37. package/extensions/workflows/prompt.ts +10 -40
  38. package/extensions/workflows/replay-safety.ts +9 -8
  39. package/package.json +10 -10
  40. package/skills/subagents/SKILL.md +6 -0
  41. package/skills/workflows/EXAMPLES.md +58 -0
  42. package/skills/workflows/REFERENCE.md +44 -0
  43. package/skills/workflows/SKILL.md +39 -0
@@ -46,6 +46,10 @@ import { Type, type Static } from "typebox";
46
46
  import { formatActivityStatus } from "../shared/activity-status.ts";
47
47
  import { waitBounded } from "../shared/child-session.ts";
48
48
  import { loadSetupConfig } from "../shared/setup-config.ts";
49
+ import {
50
+ OPENPI_TOOL_SURFACE,
51
+ patchOwnedTools,
52
+ } from "../shared/tool-surface.ts";
49
53
  import {
50
54
  loadAgentTypes,
51
55
  resolveAgentModel,
@@ -421,6 +425,14 @@ export default function workflows(pi: ExtensionAPI) {
421
425
  [...activeRuns].map(([runId, run]) => [runId, run.details] as const),
422
426
  );
423
427
  const settledRuns = new Map<string, WorkflowDetails>();
428
+ const hideLifecycleTools = () =>
429
+ patchOwnedTools(pi, "workflows", {
430
+ disable: OPENPI_TOOL_SURFACE.workflows.deferred,
431
+ });
432
+ const showLifecycleTools = () =>
433
+ patchOwnedTools(pi, "workflows", {
434
+ enable: OPENPI_TOOL_SURFACE.workflows.deferred,
435
+ });
424
436
  const stripState = new WorkflowStripState();
425
437
  const widgetKey = "workflow-navigation";
426
438
 
@@ -577,6 +589,7 @@ export default function workflows(pi: ExtensionAPI) {
577
589
  };
578
590
 
579
591
  pi.on("session_start", (_event, ctx) => {
592
+ hideLifecycleTools();
580
593
  if (ctx.hasUI) lastContext = ctx;
581
594
  agentTypes = loadAgentTypes({
582
595
  agentDir: getAgentDir(),
@@ -1153,7 +1166,8 @@ export default function workflows(pi: ExtensionAPI) {
1153
1166
  });
1154
1167
  const replayLease = beginProcessReplayWorkspaceLease(replaySafe);
1155
1168
  let replayResources:
1156
- Awaited<ReturnType<typeof getResources>> | undefined;
1169
+ | Awaited<ReturnType<typeof getResources>>
1170
+ | undefined;
1157
1171
  let replayIdentity: ReturnType<typeof createReplayIdentity> | undefined;
1158
1172
  if (replaySafe) {
1159
1173
  try {
@@ -1643,6 +1657,7 @@ export default function workflows(pi: ExtensionAPI) {
1643
1657
  // Session may be shutting down.
1644
1658
  }
1645
1659
  });
1660
+ showLifecycleTools();
1646
1661
  return {
1647
1662
  content: [
1648
1663
  {
@@ -5,9 +5,15 @@ export interface InvocationIdentity {
5
5
 
6
6
  export type InvocationIntentState = "requested";
7
7
  export type InvocationAdmissionState =
8
- "pending" | "claimed" | "replayed" | "rejected";
8
+ | "pending"
9
+ | "claimed"
10
+ | "replayed"
11
+ | "rejected";
9
12
  export type InvocationExecutionState =
10
- "pending" | "running" | "settled" | "uncertain";
13
+ | "pending"
14
+ | "running"
15
+ | "settled"
16
+ | "uncertain";
11
17
  export type InvocationOutcome = "success" | "error" | "uncertain";
12
18
 
13
19
  /**
@@ -51,7 +51,11 @@ export type AgentState = "running" | "done" | "error";
51
51
  export type WorkflowStatus = "running" | "completed" | "failed" | "aborted";
52
52
 
53
53
  export type TranscriptRole =
54
- "user" | "assistant" | "thinking" | "tool" | "toolResult";
54
+ | "user"
55
+ | "assistant"
56
+ | "thinking"
57
+ | "tool"
58
+ | "toolResult";
55
59
 
56
60
  export interface TranscriptEntry {
57
61
  role: TranscriptRole;
@@ -15,7 +15,7 @@ export const WORKFLOW_PARAMETER_DESCRIPTIONS = {
15
15
  background:
16
16
  "Run in the background: the tool returns a run id immediately and you receive a follow-up message when the workflow finishes. Defaults to false (blocking with live progress).",
17
17
  resumeFromRunId:
18
- 'Optional run id of a previous workflow (e.g. "wf_1a2b3c4d5e6f", or a unique suffix) to replay cached read-only agent results. A call replays only when its prompt, resolved agent type/schema/model/provider/effort, canonical cwd, repository state, loaded resources, and trust context match. Unrestricted/no-type agents, writable or unknown tool lists, worktree-isolated agents, failed calls, and calls whose context cannot be fingerprinted always run for real. Matching remains content-based and order-independent. Old or unknown journals simply run everything fresh.',
18
+ "Optional prior run id or unique suffix for safe read-only replay. See the workflows Skill for matching rules.",
19
19
  };
20
20
 
21
21
  /** Describes stopping a running background workflow, mirroring subagent_cancel/bg_kill. */
@@ -41,55 +41,25 @@ export const WORKFLOW_STATUS_PARAMETER_DESCRIPTIONS = {
41
41
  export const WORKFLOW_LIFECYCLE_PROMPT_SNIPPET =
42
42
  "Inspect (workflow_status) or cancel (workflow_stop) a background workflow by run id";
43
43
 
44
- /** Defines the workflow DSL, constraints, reliability guidance, and model-authored task examples. */
44
+ /** Compact resident contract; the workflows Skill carries the complete guide. */
45
45
  export const WORKFLOW_TOOL_DESCRIPTION = [
46
46
  "Use the workflow tool when the user explicitly requests a workflow run or when the task clearly requires multi-phase dynamic orchestration.",
47
- "Run a multi-agent workflow from a JavaScript orchestration script you write inline. Use this when a task benefits from fanning work out across several isolated subagents in ordered phases (research fan-out, per-file review, verify-then-synthesize pipelines).",
48
- "The script runs as an async function body with these primitives:",
49
- " export const meta = { name, description, phases: [{ title, detail? }] } — metadata for the progress UI. Declare all phases up front.",
50
- " phase(title) mark the current phase at runtime (use titles from meta.phases).",
51
- " log(message) — emit one progress line to the user and to your own final report. This is the run's narrator: use it for anything the reader needs while the run is still going, or that the return value would not capture — round counts, how many agents were dropped, why a branch was skipped. Unlike phase(), it does not touch the phase list. Lines are one row each (newlines are flattened); the most recent 100 are kept and any earlier ones are reported as dropped.",
52
- "• usage() — read this run's cumulative token spend so far: { input, output, cacheRead, cacheWrite, total, cost, agents }. The reading refreshes as each agent settles, so evaluating it right after an `await` reflects that agent. `total` never decreases, but it is a LOWER BOUND rather than an exact figure: a child session that compacts drops the tokens of the messages it discarded. Use it to report or adapt cost — e.g. log a running total, or stop a discovery loop once the spend stops paying for itself — and expect a long run to have spent somewhat more than it says. It is a reading, not a limit: nothing is enforced for you.",
53
- "• await agent(prompt, { agent_type?, label?, phase?, schema?, acceptance?, model?, provider?, effort?, isolation?, operator?, inputs? }) — run ONE subagent and wait for it. `agent_type` applies the same named preset and enforced capabilities as subagent_spawn: specialized system prompt, tool allowlist, model assignment, and default effort. Prefer a matching type when one exists. Model precedence is explicit model/provider > type-file model > configured built-in role model > parent model; effort precedence is explicit effort > type default > parent effort. Omit `agent_type` for a general-purpose child. Always resolves to { ok, output, structured?, ref?, acceptance?, error? }. Check `ok` before using the result. A successful call's opaque result ref can be passed through `inputs: [previous.ref]` to hydrate bounded same-run handoffs as untrusted data and record explicit lineage. When you pass a JSON `schema`, `structured` holds the validated object on success. Optional `acceptance: { criteria: [{ id, description, requiredEvidence? }] }` is explicit and adds no extra agent: the same structured result must include an evidence ledger; missing, malformed, or rejected criteria make `ok:false` while preserving output and ledger. Children receive normal built-ins and trust-appropriate extensions, settings, skills, and AGENTS.md context, but cannot recursively orchestrate or ask the user.",
54
- "• operator: 'name' reuses one in-memory child Session for serialized follow-up activations inside the same workflow run. Its model, role/tool surface, effort, structured mode, and cwd are frozen by the first activation. Operator calls cannot use per-call worktrees or result replay, and operator continuity is not a cross-restart guarantee.",
55
- "• inputs: [resultRef, ...] accepts only opaque refs issued by successful calls in this same workflow run. The host injects at most 16 KiB per conclusion and 48 KiB total, marks it as data rather than instructions, and derives a read-only graph from those explicit refs. The graph is observability, never scheduling authority.",
56
- "• isolation: 'worktree' runs that one agent in its own git worktree on its own branch, instead of the shared working directory. Use it for any fan-out where agents WRITE — without it, concurrent agents share one checkout and one git index, so their edits and `git add`s silently overwrite each other. Tell such an agent to COMMIT its work: on completion the worktree directory is reclaimed and its branch is kept for you to merge (an empty branch is deleted; uncommitted changes keep the directory instead). The branch name comes back in the run artifacts. Costs a fresh checkout, needs a git repo, and starts without gitignored files, so leave it off for read-only agents.",
57
- "• await parallel([() => agent(...), () => agent(...)], { concurrency? }) — run zero-argument agent thunks concurrently and return results in order. This is a BARRIER: nothing after it starts until every thunk settles. A thunk that throws settles to null (filter it out) rather than failing the whole batch, so one bad item never discards the others' results. The package default is 8 concurrent agents per workflow and can be changed with /openpi-setup (hard maximum 64).",
58
- "• await pipeline(items, stage1, stage2, ...) — run each item through every stage independently, with NO barrier between stages: item A can be in stage 3 while item B is still in stage 1. Results come back in input order. Each stage receives (previousResult, originalItem, index), so a later stage can label its work without threading context through the earlier stage's return value. A stage that throws drops that item to null and skips its remaining stages, leaving siblings untouched.",
59
- "PREFER pipeline() for multi-stage work. parallel() forces every item to wait for the slowest one in each stage, so wall-clock becomes the sum of per-stage worst cases (max stage1 + max stage2) instead of the slowest single chain. The gap is widest when different items are slow in different stages; when one item is slowest everywhere it is the critical path either way. Reach for a barrier only when a stage genuinely needs cross-item context from ALL of the previous one: deduping or merging the full result set, exiting early when the total count is zero, or a prompt that compares one finding against the others. Needing to flatten/map/filter in between is NOT a reason — do that inside a pipeline stage.",
60
-
61
- "• args — the parsed value of the `args` tool parameter (or undefined).",
62
- "Workflow JavaScript runs in a restricted, killable child with no imports, eval, timers, filesystem, network, or process APIs. The package default permits 128 agent calls per run and can be changed with /openpi-setup (hard maximum 1024); there is no overall deadline. Each agent must receive its first assistant response event within 45 seconds so silent provider requests fail clearly; after that, agent() has no wall-clock deadline. Each individual child tool call times out independently after 3 minutes, becomes an error tool result, and leaves the agent loop free to recover. Use map/filter/if/await/template strings to orchestrate, and `return` a JSON-serializable aggregate.",
63
- "Pass a `schema` to agent() whenever a later step branches on the result, so you get typed fields instead of prose. Each call persists independent intent, admission, and execution state; interrupted nonterminal calls are reported as uncertain rather than guessed failed. Artifacts include a bounded graph projection for explicit result-ref dependencies. To re-run an edited workflow cheaply, pass `resume_from_run_id` with the previous run id: only provably read-only non-operator calls whose content and project/resource context are unchanged can replay; writable, unrestricted, unknown-tool, operator, and worktree calls always run for real.",
64
- "Example — each file is verified as soon as ITS OWN scan lands, instead of waiting for every scan:",
65
- "export const meta = { name: 'reliability-review', description: 'Review modules for reliability risks, then report', phases: [{ title: 'Scan' }, { title: 'Verify' }, { title: 'Report' }] }",
66
- "const FINDINGS = { type: 'object', properties: { issues: { type: 'array', items: { type: 'string' } }, ok: { type: 'boolean' } }, required: ['issues', 'ok'] }",
67
- "phase('Scan')",
68
- "const checked = await pipeline(args.files,",
69
- " (f) => agent(`Trace ${f} for candidate reliability risks with file:line evidence.`, { agent_type: 'explorer', label: `scan:${f}`, phase: 'Scan', schema: FINDINGS }),",
70
- " (scan, f) => scan.ok ? agent(`Review the candidate issues in ${f}.`, { agent_type: 'reviewer', label: `verify:${f}`, phase: 'Verify', inputs: [scan.ref] }) : null)",
71
- "const verified = checked.filter((r) => r && r.ok)",
72
- "const dropped = checked.length - verified.length // agents that failed/dropped: surface, never silently swallow",
73
- "if (dropped) log(`${dropped}/${checked.length} file(s) dropped before verification`)",
74
- "phase('Report')",
75
- "const report = await agent('Synthesize tradeoffs and recommendations from the verified findings.', { agent_type: 'advisor', label: 'report', phase: 'Report', inputs: verified.map((r) => r.ref) })",
76
- "log(`done — ${verified.length} verified, ${usage().total} tokens`)",
77
- "return { verified: verified.length, dropped, report: report.ok ? report.output : report.error }",
47
+ "Write an async JavaScript body using optional meta, phase(), log(), usage(), agent(), pipeline(), parallel(), args, and a JSON-serializable return.",
48
+ "agent() returns { ok, output, structured?, ref?, error? }; always check `.ok`, use a schema for branching, and surface failed or null results.",
49
+ "Prefer pipeline() for independent multi-stage items. Use parallel() only for a real barrier where the next step needs every prior result.",
50
+ "For concurrent writers use isolation: 'worktree' and tell each agent to commit. Read-only work should normally stay in the shared checkout.",
51
+ "Read the workflows Skill before a nontrivial script; it covers the restricted sandbox, full DSL, acceptance, result refs, replay, background lifecycle, limits, and examples.",
78
52
  ].join("\n");
79
53
 
80
54
  /** Adds workflow orchestration primitives and background execution to the model's tool prompt. */
81
55
  export const WORKFLOW_PROMPT_SNIPPET =
82
- "Orchestrate isolated subagents from an inline JS script: phase()/agent()/pipeline()/parallel() with structured outputs, log() progress, usage() token readings, and optional background execution";
56
+ "Orchestrate subagents from an inline JS script; read the workflows Skill for the complete DSL";
83
57
 
84
58
  /** Guides the model on appropriate workflow fan-out and mandatory agent result checks. */
85
59
  export const WORKFLOW_PROMPT_GUIDELINES = [
86
60
  "Use workflow when a task needs several subagents with phase dependencies or dynamic fan-out; keep single small delegations in the main session.",
87
- "For each workflow agent() call, select a matching agent_type when one exists (explorer, implementer, reviewer, advisor, or a loaded custom type) so its configured model, prompt, effort, and enforced tools apply; do not hardcode that role's model. Omit agent_type only for genuinely general-purpose work.",
88
- "Default to pipeline() for multi-stage fan-out so each item advances as soon as its own previous stage lands; use parallel() only when a stage truly needs every prior result at once.",
89
- "In workflow scripts, agent() never throws — check `.ok` before using `.output`/`.structured`; but parallel() and pipeline() settle a throwing thunk or stage to `null`, so guard those with `r && r.ok`.",
90
- "A filtered-out or null result is a failed agent, not a clean pass: surface how many dropped (e.g. return a count) so a crashed or timed-out agent never reads as success.",
91
- "log() anything the reader would want before the run ends — round counts, dropped agents, why a branch was skipped. A long run that narrates nothing is indistinguishable from a stalled one, and the return value only arrives at the end.",
92
- "When several agents will edit files concurrently, give each one isolation: 'worktree' and tell it to commit; otherwise they share one checkout and one git index and overwrite each other. Read-only agents do not need it.",
61
+ "select a matching agent_type when available; use its configured model and do not hardcode that role's model.",
62
+ "Read the workflows Skill before a nontrivial script; check every agent result, surface dropped work, and use worktree isolation for concurrent writers.",
93
63
  ];
94
64
 
95
65
  /** Marks and forwards a workflow script's agent() task as an isolated child-model prompt. */
@@ -379,14 +379,15 @@ export function beginProcessReplayWorkspaceLease(replaySafe: boolean) {
379
379
  return processReplayWorkspaceGuard.begin(replaySafe);
380
380
  }
381
381
 
382
- interface ReplayResourceLoader extends Pick<
383
- DefaultResourceLoader,
384
- | "getAgentsFiles"
385
- | "getAppendSystemPrompt"
386
- | "getExtensions"
387
- | "getSkills"
388
- | "getSystemPrompt"
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tt-a1i/openpi",
3
- "version": "0.1.1",
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
5
  "license": "MIT",
6
6
  "author": "tt-a1i",
@@ -41,9 +41,6 @@
41
41
  "extensions": [
42
42
  "./extensions"
43
43
  ],
44
- "skills": [
45
- "./skills"
46
- ],
47
44
  "themes": [
48
45
  "./themes"
49
46
  ],
@@ -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": "npm run format:check && npm run check && npm test",
81
- "check": "tsc --noEmit",
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": "prettier --write \"extensions/**/*.{ts,cjs}\" \"scripts/**/*.{js,mjs,cjs,ts}\" \".github/**/*.{yml,yaml}\" \"*.json\"",
84
- "format:check": "prettier --check \"extensions/**/*.{ts,cjs}\" \"scripts/**/*.{js,mjs,cjs,ts}\" \".github/**/*.{yml,yaml}\" \"*.json\"",
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)