pi-extensible-workflows 1.0.1 → 3.0.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 (47) hide show
  1. package/README.md +9 -2
  2. package/dist/src/agent-execution.d.ts +13 -14
  3. package/dist/src/agent-execution.js +110 -197
  4. package/dist/src/budget.d.ts +38 -0
  5. package/dist/src/budget.js +160 -0
  6. package/dist/src/cli.d.ts +9 -0
  7. package/dist/src/cli.js +536 -6
  8. package/dist/src/doctor.d.ts +4 -4
  9. package/dist/src/doctor.js +9 -29
  10. package/dist/src/execution.d.ts +17 -0
  11. package/dist/src/execution.js +630 -0
  12. package/dist/src/herdr.d.ts +12 -0
  13. package/dist/src/herdr.js +74 -0
  14. package/dist/src/host.d.ts +62 -0
  15. package/dist/src/host.js +2696 -0
  16. package/dist/src/index.d.ts +11 -557
  17. package/dist/src/index.js +8 -3974
  18. package/dist/src/persistence.d.ts +32 -19
  19. package/dist/src/persistence.js +310 -70
  20. package/dist/src/registry.d.ts +31 -0
  21. package/dist/src/registry.js +169 -0
  22. package/dist/src/session-inspector.d.ts +1 -0
  23. package/dist/src/session-inspector.js +4 -1
  24. package/dist/src/types.d.ts +565 -0
  25. package/dist/src/types.js +27 -0
  26. package/dist/src/utils.d.ts +26 -0
  27. package/dist/src/utils.js +128 -0
  28. package/dist/src/validation.d.ts +24 -0
  29. package/dist/src/validation.js +740 -0
  30. package/dist/src/workflow-evals.js +2 -1
  31. package/package.json +4 -3
  32. package/skills/pi-extensible-workflows/SKILL.md +52 -67
  33. package/src/agent-execution.ts +84 -147
  34. package/src/budget.ts +75 -0
  35. package/src/cli.ts +427 -6
  36. package/src/doctor.ts +13 -32
  37. package/src/execution.ts +540 -0
  38. package/src/herdr.ts +73 -0
  39. package/src/host.ts +2265 -0
  40. package/src/index.ts +11 -3453
  41. package/src/persistence.ts +255 -47
  42. package/src/registry.ts +157 -0
  43. package/src/session-inspector.ts +5 -1
  44. package/src/types.ts +113 -0
  45. package/src/utils.ts +108 -0
  46. package/src/validation.ts +616 -0
  47. package/src/workflow-evals.ts +2 -1
@@ -707,6 +707,7 @@ export async function replayWorkflowScript(script, args = null, signal) {
707
707
  active -= 1;
708
708
  }
709
709
  },
710
+ worktree: async () => ({ path: "/worktrees/eval", branch: "eval-branch" }),
710
711
  phase: (name) => { phases.push(name); },
711
712
  log: (message) => { logs.push(message); },
712
713
  }, signal);
@@ -860,7 +861,7 @@ function semanticJudgePrompt(evalCase, calls, cwd, home) {
860
861
  return [];
861
862
  } }));
862
863
  const roleText = [...usedRoles].map((role) => `${role}: ${roles[role]?.description ?? "no description"}`).join("\n") || "none";
863
- const docs = "agent(prompt, options) delegates; parallel(name, tasks) runs independent tasks concurrently; pipeline(name, items, stages) applies ordered stages; prompt(template, data) carries values into prompts. A role owns model/thinking/tools policy.";
864
+ const docs = "agent(prompt, options) delegates; shell(command, options) runs a deterministic host command and returns exitCode/stdout/stderr; parallel(name, tasks) runs independent tasks concurrently; pipeline(name, items, stages) applies ordered stages; prompt(template, data) carries values into prompts. A role owns model/thinking/tools policy.";
864
865
  return `Judge whether the captured workflow design satisfies each criterion. Do not execute it. Return only JSON: {"criteria":[{"id":"criterion id","pass":true,"evidence":"specific script evidence"}]}.\n\nOriginal request:\n${evalCase.prompt}\n\nCriteria:\n${JSON.stringify(evalCase.semanticCriteria ?? [])}\n\nDSL:\n${docs}\n\nRelevant roles:\n${roleText}\n\nCaptured workflow script(s):\n${calls.map((call, index) => `--- ${String(index)} ---\n${call.script ?? "<missing>"}`).join("\n")}`;
865
866
  }
866
867
  async function runSemanticJudge(input, calls, cwd, home, sessionDir, maxCost) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extensible-workflows",
3
- "version": "1.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "Deterministic multi-agent workflow orchestration for Pi",
5
5
  "homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
6
6
  "repository": {
@@ -31,8 +31,9 @@
31
31
  "build": "rm -rf dist && tsc -p tsconfig.json && chmod +x dist/src/cli.js",
32
32
  "inspect": "node dist/src/cli.js inspect",
33
33
  "lint": "eslint .",
34
- "test": "npm run build && node --test 'dist/test/*.test.js'",
35
- "acceptance": "npm run build && node --test dist/test/runtime-acceptance.test.js",
34
+ "test": "npm run build && TEST_FILES='dist/test/*.test.js' npm run test:run",
35
+ "test:run": "tmp=$(mktemp -d); trap 'rm -rf \"$tmp\"' EXIT; TMPDIR=\"$tmp\" node --test $TEST_FILES",
36
+ "acceptance": "npm run build && TEST_FILES='dist/test/runtime-acceptance.test.js' npm run test:run",
36
37
  "docs:check": "node scripts/check-docs.mjs",
37
38
  "check": "npm run lint && npm test && npm run docs:check",
38
39
  "evals": "npm run build && node dist/src/workflow-evals.js",
@@ -4,87 +4,81 @@ description: Use when the task is complex enough to require multiple subagents o
4
4
  ---
5
5
 
6
6
  # pi-extensible-workflows
7
+ Use `workflow` only for genuinely multi-agent orchestration; one agent uses ordinary tools or `Agent` directly. Give phases distinct responsibilities and keep result flow explicit.
7
8
 
8
- Use `workflow` exclusively for genuinely multi-agent orchestration. For one agent, use ordinary tools or `Agent` directly. Do not wrap a single agent in a workflow; define distinct responsibilities and keep the result flow explicit.
9
-
10
- ## Pattern
11
-
9
+ ## Example
12
10
  ```js
13
- const reportSchema = {
14
- type: "object",
15
- properties: {
16
- summary: { type: "string" },
17
- findings: { type: "array", items: { type: "string" } },
18
- },
19
- required: ["summary", "findings"],
20
- additionalProperties: false,
21
- };
11
+ const reportSchema = { type: "object", properties: { summary: { type: "string" }, findings: { type: "array", items: { type: "string" } } }, required: ["summary", "findings"], additionalProperties: false };
12
+
22
13
 
23
14
  const reports = await parallel("research", {
24
15
  first: () => agent("Research the first target.", { role: "scout", outputSchema: reportSchema }),
25
16
  second: () => agent("Research the second target.", { role: "scout", outputSchema: reportSchema }),
26
17
  });
27
18
 
28
- // Add a downstream agent only when synthesis or independent review is a real phase.
29
19
  return agent(
30
20
  prompt("Review these reports:\n\n{reports}", { reports }),
31
21
  { role: "reviewer", outputSchema: reportSchema },
32
22
  );
33
23
  ```
34
24
 
35
- To pass structured input from the main agent, include `args`:
25
+ Inline launches require a non-empty `name`. Registered function launches must omit `name`; they use `workflow` as the run name:
36
26
  ```json
37
27
  { "workflow": "workflowName", "args": { "issue": 42 } }
38
28
  ```
39
- Inside the workflow, read `args.issue`; omitted `args` is `null`.
40
- Use `workflow_stop` with the exact run ID to stop an active background run from the current Pi session.
41
- If `workflow_catalog` is available, call it once before creating the first workflow for a task. Use the returned global functions, variables, registered workflows, and configured model aliases as needed for the rest of that task. Alias targets are catalog metadata, not an availability probe. Do not try to reinvent already exposed functions.
29
+ Inside the workflow, read `args.issue` (`args` is `null` when omitted). `workflow_stop` requires the exact run ID; foreground results retain their value and completed `runId`, while background launches return `runId` immediately. A terminal `parentRunId` reuses matching named `withWorktree` scopes but does not replay journal results. For an explicitly failed run, use the exact `runId` from failure diagnostics with `workflow_retry({ runId })`: diagnostics list replayable and incomplete paths, artifacts, and valid named worktrees; the tool creates a linked child, replays completed agent, shell, function, and checkpoint operations, and executes incomplete work. Retry versus per-agent `retries` and `workflow_resume` is always explicit; external side effects before failure are not guaranteed exactly once.
30
+ Inspect tool `workflow_catalog` result at least once before creating the first workflow for a task.
42
31
 
43
- Pass downstream only needed results. Workflow JavaScript has no imports, filesystem, network, process, or timers; delegate such work to agents with the required tools.
32
+ Workflow JavaScript has no imports, filesystem, network, process, or timers. Delegate that work to agents. `shell(command, options)` is the trusted host RPC for deterministic gates: it inherits the workflow or active-worktree cwd, merges string `env` overrides, and returns `{ exitCode, stdout, stderr }`; nonzero exits are results, but launch failures and timeouts fail with `SHELL_FAILED`.
44
33
 
45
- ## `agent()` options
34
+ Example use of `shell`:
46
35
 
47
- ```typescript
48
- interface AgentOptions {
49
- label?: string; // optional non-empty display name
50
- model?: string; // configured alias or provider/model[:thinking]
51
- thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
52
- role?: string; // one of the available workflow roles
53
- tools?: string[]; // [] = no tools; omitted uses role or launch tools
54
- outputSchema?: JsonSchema;
55
- retries?: number; // non-negative; use for safe, repeatable work
56
- timeoutMs?: number | null; // positive milliseconds; null means unlimited
36
+ ```js
37
+ // ... other code
38
+ const testRes = await shell("yarn test", { env: { CI: "1" } });
39
+ if (testRes.exitCode === 0) {
40
+ // success path
41
+ return {...}
57
42
  }
58
43
  ```
59
44
 
60
- Extensions may define additional JSON-compatible agent option keys such as `advisor: true`. Core-owned keys still use the validation and role constraints above; extension options are passed to setup hooks and native setup but are not inherited by child agents.
45
+ Most of the times using `shell()` to perform mutations is an antipattern. Use it mainly for verifications or idempotent actions.
61
46
 
62
- Agent calls are unnamed. Direct `agent(...)` calls receive hidden source call-site identity; JavaScript aliases for workflow calls are unsupported. Calls from one source call site must not race outside `parallel` or `pipeline`, whose structural keys keep replay deterministic.
47
+ ## `agent()` options
48
+ ```typescript
49
+ export interface AgentOptions {
50
+ label?: string; model?: string; role?: string; tools?: string[];
51
+ thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
52
+ outputSchema?: JsonSchema;
53
+ retries?: number;
54
+ timeoutMs?: number | null;
55
+ [key: string]: JsonValue;
56
+ }
57
+ ```
63
58
 
64
- ## Shared worktree scope
59
+ Extensions may add JSON-compatible agent options such as `advisor: true`; core keys retain validation and role constraints. Extension options go to setup hooks/native setup and are not inherited by child agents.
65
60
 
66
- Use `withWorktree(callback)` or `withWorktree(name, callback)` when top-level agents should collaborate in one worktree:
61
+ Agent calls are unnamed. Direct calls receive hidden source call-site identity; aliases are unsupported, and calls from one source site must not race outside `parallel` or `pipeline`, whose structural keys make replay deterministic.
67
62
 
63
+ ## Passing agent results
64
+ Use independent `agent(prompt, options)` calls and pass each completed result explicitly to the next prompt. This keeps workflow execution deterministic and makes replay state local to each call:
68
65
  ```js
69
- const results = await withWorktree("implementation", async () => parallel("implementation", {
70
- api: () => agent("Implement the API"),
71
- tests: () => agent("Add integration tests"),
72
- }));
66
+ const findings = await agent("Inspect the implementation.");
67
+ const fix = await agent(prompt("Propose the smallest fix from these findings:\n\n{findings}", { findings }));
68
+ return { findings, fix };
73
69
  ```
74
70
 
75
- The callback result is returned unchanged and the worktree is created only when the first enclosed agent launches. Concurrent agents share mutable files, so give them non-conflicting work or coordinate explicitly.
76
-
77
- `parallel()` tasks may call any workflow function, not only `agent()`:
78
-
71
+ ## Worktrees
72
+ Use `withWorktree(name, callback)` for top-level agents that collaborate in one explicitly named worktree scope:
79
73
  ```js
80
- const results = await parallel("checks", {
81
- security: () => reviewRepository({ focus: "security" }),
82
- release: () => reviewRepository({ focus: "release readiness" }),
74
+ const result = await withWorktree("issue", async ({ path, branch }) => {
75
+ const report = await agent("Implement the issue");
76
+ return { path, branch, report };
83
77
  });
84
78
  ```
79
+ Entering the scope materializes its worktree before the callback. The callback receives a frozen reference containing only the real string `path` and `branch`; callbacks may ignore the argument, and their bare return value is preserved. Concurrent agents share mutable files, so assign non-conflicting work or coordinate explicitly.
85
80
 
86
- Use separate named scopes when each parallel branch needs its own worktree:
87
-
81
+ Branches may call any workflow function, not only `agent()`. Use separate named scopes when parallel branches need isolated worktrees:
88
82
  ```js
89
83
  const results = await parallel("implementation", {
90
84
  api: () => withWorktree("api", () => agent("Implement the API")),
@@ -92,26 +86,17 @@ const results = await parallel("implementation", {
92
86
  });
93
87
  ```
94
88
 
95
- Registered extension functions receive `withWorktree` in their context, so they may create a shared scope internally. They can compose other registered functions without importing their source:
96
- ```ts
97
- const report = await context.invoke("reviewRepository", { focus: "security" });
98
- ```
99
- Their public inputs and outputs must remain JSON; callbacks cannot cross the extension-function boundary.
89
+ Registered extension functions receive `withWorktree` in context and can compose other registered functions with `context.invoke("reviewRepository", { focus: "security" })`. Their public inputs and outputs remain JSON; callbacks cannot cross the extension boundary.
100
90
 
101
91
  ## Rules
102
-
103
- - Do not create a workflow for one agent. Phases must have distinct work.
104
- - Use `log(messageString)` in the script to surface brief status messages to the operator.
105
- - A role owns its execution policy. When `role` is present, do not also set `model`, `thinking`, or `tools`; only task-specific options such as `outputSchema`, retries, timeout, or a `withWorktree` scope may accompany it.
106
- - Use `parallel()` for independent tasks with different flows. Use `pipeline()` when each keyed item passes through the same ordered stages; do not duplicate identical stage chains inside `parallel()` branches.
107
- - Call shapes are `parallel(operationName, tasksRecord)` and `pipeline(operationName, itemsRecord, stagesRecord)`; object keys are stable task, item, and stage names.
108
- - Preserve item metadata in workflow code between pipeline stages instead of requiring agents to echo it through `outputSchema`.
109
- - Repeated work uses a JavaScript loop; each direct `agent(...)` call receives deterministic call-site and occurrence identity.
92
+ - Use `log(messageString)` for brief operator status.
93
+ - A role owns execution policy: with `role`, do not set `model`, `thinking`, or `tools`; only task options such as `outputSchema`, retries, timeout, or a `withWorktree` scope may accompany it.
94
+ - Use `parallel()` for independent tasks with different flows and `pipeline()` when every keyed item follows the same ordered stages; do not duplicate identical chains in `parallel()`. Signatures are `parallel(operationName, tasksRecord)` and `pipeline(operationName, itemsRecord, stagesRecord)`; keys are stable task, item, and stage names.
95
+ - Preserve item metadata in workflow code between pipeline stages instead of making agents echo it through `outputSchema`.
96
+ - Use a JavaScript loop for repeated work; each direct `agent(...)` call gets deterministic call-site and occurrence identity.
110
97
  - Runs default to background; set tool-call `foreground: true` when asked to wait.
111
- - Add `budget` only when the run needs aggregate limits. The only valid dimension names are exactly `tokens`, `costUsd`, `durationMs`, and `agentLaunches` (never `cost`, `duration`, `launches`, or other shorthand). Each dimension is `{ soft?: number, hard?: number }`; `soft` must be less than `hard`.
112
- - A `budget_exhausted` run is resumable through `workflow_resume`. Omitted patch values stay unchanged, explicit `null` removes a limit, and any relaxation requires an exact human-approved proposal through `workflow_respond`.
113
- - `parallel()` and `pipeline()` return keyed bare values. Await results before use.
114
- - Interpolate results with `prompt("...{value}", { value })`; placeholders in plain strings stay literal.
115
- - Use `outputSchema` only when another phase must compare, aggregate, or validate the result. Never add it to a final agent whose prose is returned directly. Keep only fields the consumer needs, and avoid repeating the same evidence in multiple schemas.
116
- - With `outputSchema`, agents must call `workflow_result`; one repair prompt is built in. Omit `retries` unless an additional retry is justified and the work is idempotent.
117
- - Do not add "persona" specs to the prompt for agents. Just define the task.
98
+ - Add `budget` only for aggregate limits. Do not invent limits, omit if user do not ask explicitly. Valid dimensions are exactly `tokens`, `costUsd`, `durationMs`, and `agentLaunches`; each is `{ soft?: number, hard?: number }` with `soft < hard`.
99
+ - `budget_exhausted` runs resume through `workflow_resume`: omitted patch values stay unchanged, `null` removes a limit, and tightening resumes directly. Relaxation stores the exact proposal and returns `{ state: "awaiting_approval", proposalId }`; `workflow_respond` must answer that ID. Rejection leaves the run exhausted; approval applies the budget and cold-resumes it. `workflow_retry` is only for persisted `failed` runs and inherits cumulative usage; replay itself consumes no budget.
100
+ - `parallel()` and `pipeline()` return keyed bare values; await them before use. Interpolate results with `prompt("...{value}", { value })`; placeholders in plain strings remain literal.
101
+ - Use `outputSchema` only when another phase compares, aggregates, or validates a result, never for final prose. Keep only consumer-needed fields and avoid repeated evidence. Agents with it must call `workflow_result`; one repair prompt is built in. Omit `retries` unless an extra retry is justified and work is idempotent.
102
+ - Do not add persona specifications to agent prompts; define the task directly.