@llblab/pi-actors 0.37.1 → 0.38.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/BACKLOG.md +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +6 -5
- package/dist/index.js +23 -3
- package/dist/lib/async-runs.d.ts +5 -0
- package/dist/lib/async-runs.js +87 -8
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/execution.d.ts +4 -0
- package/dist/lib/execution.js +154 -13
- package/dist/lib/model-context.d.ts +56 -0
- package/dist/lib/model-context.js +220 -0
- package/dist/lib/observability.d.ts +2 -0
- package/dist/lib/observability.js +32 -6
- package/dist/lib/preflight-diagnostics.d.ts +27 -0
- package/dist/lib/preflight-diagnostics.js +86 -0
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipes-context.d.ts +7 -0
- package/dist/lib/recipes-context.js +20 -2
- package/dist/lib/recipes-discovery.js +15 -0
- package/dist/lib/recipes-references.d.ts +2 -0
- package/dist/lib/recipes-references.js +10 -0
- package/dist/lib/tools-local.d.ts +2 -1
- package/dist/lib/tools-local.js +6 -4
- package/dist/lib/tools-response.js +22 -1
- package/dist/lib/tools-spawn.d.ts +2 -1
- package/dist/lib/tools-spawn.js +2 -1
- package/dist/recipes/lens-swarm.json +15 -2
- package/dist/recipes/pipeline-release-readiness.json +22 -3
- package/dist/recipes/pipeline-review-readiness.json +22 -3
- package/dist/recipes/subagent-judge.json +2 -1
- package/dist/recipes/subagent-merge.json +5 -2
- package/dist/recipes/subagent-normalize.json +2 -1
- package/dist/recipes/subagent-preflight.json +29 -0
- package/dist/recipes/subagent-review-coordinator.json +79 -7
- package/dist/recipes/subagent-review.json +2 -1
- package/dist/recipes/subagent-verify.json +2 -1
- package/dist/scripts/async-runner.mjs +84 -10
- package/dist/scripts/conformance.mjs +1 -0
- package/dist/skills/actors/SKILL.md +4 -4
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/async-runs.md +3 -1
- package/docs/command-templates.md +4 -1
- package/docs/recipe-library.md +3 -2
- package/docs/template-recipes.md +2 -2
- package/index.ts +24 -3
- package/lib/async-runs.ts +157 -8
- package/lib/command-templates.ts +2 -0
- package/lib/execution.ts +218 -24
- package/lib/model-context.ts +359 -0
- package/lib/observability.ts +35 -6
- package/lib/preflight-diagnostics.ts +132 -0
- package/lib/prompts.ts +2 -2
- package/lib/recipes-context.ts +29 -2
- package/lib/recipes-discovery.ts +15 -0
- package/lib/recipes-references.ts +12 -0
- package/lib/tools-local.ts +22 -8
- package/lib/tools-response.ts +26 -1
- package/lib/tools-spawn.ts +6 -2
- package/package.json +1 -1
- package/recipes/lens-swarm.json +15 -2
- package/recipes/pipeline-release-readiness.json +22 -3
- package/recipes/pipeline-review-readiness.json +22 -3
- package/recipes/subagent-judge.json +2 -1
- package/recipes/subagent-merge.json +5 -2
- package/recipes/subagent-normalize.json +2 -1
- package/recipes/subagent-preflight.json +29 -0
- package/recipes/subagent-review-coordinator.json +79 -7
- package/recipes/subagent-review.json +2 -1
- package/recipes/subagent-verify.json +2 -1
- package/scripts/async-runner.mjs +84 -10
- package/scripts/conformance.mjs +1 -0
- package/skills/actors/SKILL.md +4 -4
- package/skills/swarm/SKILL.md +2 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.38.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
|
@@ -199,9 +199,9 @@ Rules:
|
|
|
199
199
|
7. Declare `mailbox` for actors that accept or emit meaningful messages.
|
|
200
200
|
8. Declare `artifacts` for durable outputs the coordinator should inspect.
|
|
201
201
|
9. File-backed recipe identity comes from the filename basename; legacy top-level `name` fields are ignored by loaders.
|
|
202
|
-
10. File-backed async recipes pass child `pi -p` actors a bounded JSONL recipe context bundle by default: raw entry/import recipe records, derived `name`, import path/alias, and `"you_are_here": true` on the launching recipe node. Set `"actor_context": false` or `"off"` to suppress
|
|
202
|
+
10. File-backed async recipes pass child `pi -p` actors a bounded JSONL recipe context bundle by default: raw entry/import recipe records, derived `name`, import path/alias, and `"you_are_here": true` on the launching recipe node. The runner materializes child prompts under `prompts/command-NNN.md` and invokes Pi with `@file` args so large prompts and recipe context stay inspectable and argv-safe. Set `"actor_context": false` or `"off"` to suppress recipe context for minimal prompts.
|
|
203
203
|
11. Keep packaged recipes generic: no machine-local paths, no private companion identities, no project-specific defaults unless the recipe is explicitly project-specific.
|
|
204
|
-
12. Do not ship concrete model-version defaults in packaged recipes;
|
|
204
|
+
12. Do not ship concrete model-version defaults in packaged recipes. For review-oriented subagent/lens recipes, default model/thinking args through `{current_model}` and `{current_thinking}` so they inherit the selected Pi session policy; keep `model`, `models`, `thinking`, and stage-specific model args explicit so callers can override policy at launch.
|
|
205
205
|
|
|
206
206
|
Priority for same-id recipes:
|
|
207
207
|
|
|
@@ -284,7 +284,7 @@ The user recipe root is the default tool set by location. It accepts canonical J
|
|
|
284
284
|
|
|
285
285
|
Use packaged recipes by name with `spawn file=<name>` for async actors, or register/call them as tools when repeated use deserves a stable shortcut.
|
|
286
286
|
|
|
287
|
-
Packaged review recipes are directly spawnable. Use `spawn file="pipeline-review-readiness" values={...}` for readiness review or `spawn file="subagent-review" values={...}` for one reviewer; pass model/thinking/tool policy through values, then inspect the run. Do not recreate their script commands, call packaged scripts directly, or create wrapper recipes just to launch the maintained recipe.
|
|
287
|
+
Packaged review recipes are directly spawnable. Use `spawn file="pipeline-review-readiness" values={...}` for readiness review or `spawn file="subagent-review" values={...}` for one reviewer; pass model/thinking/tool policy through values, then inspect the run. Review coordinators preflight stage models before fanout; `ACTOR_PREFLIGHT_FAILED` diagnostics identify the failed stage, selected policy, provider error class, prompt file, and override args. Quorum-aware review fanout exposes `subagent_ttl_ms`, `reviewer_concurrency`, `min_successful_reviewers`, and `merge_policy`; partial reviewer evidence is preserved and marked `complete`, `degraded`, or `insufficient_data`. Run status/progress exposes `model_policy` so inherited vs explicit model/thinking choices remain visible. Do not recreate their script commands, call packaged scripts directly, or create wrapper recipes just to launch the maintained recipe.
|
|
288
288
|
|
|
289
289
|
- [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json): room-visible swarm coordination with roles, rounds, optional locker, artifact synthesis, and `subagent_ttl_ms` for hard participant budgets.
|
|
290
290
|
- [`pipeline-repo-health`](../../recipes/pipeline-repo-health.json): git/doc/validation evidence → normalized repository health report.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: swarm
|
|
3
3
|
description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.38.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Swarm
|
|
@@ -162,7 +162,7 @@ Use [`references/development-swarm.md`](./references/development-swarm.md) for c
|
|
|
162
162
|
|
|
163
163
|
Purpose: turn one result into many risk lenses and a decision-grade verdict.
|
|
164
164
|
|
|
165
|
-
Use lens swarm for broad coverage, quorum for confidence on one critical judgement, or both for high-stakes releases. The final report should separate consensus findings, minority findings, merger findings, risks, and recommended next actions.
|
|
165
|
+
Use lens swarm for broad coverage, quorum for confidence on one critical judgement, or both for high-stakes releases. In adapters that expose current session model/thinking policy, default ordinary same-policy review swarms to that current policy and require explicit args only when intentionally varying models or thinking levels. Run a cheap model/tool preflight before launching expensive reviewer fanout; if it fails, use the `ACTOR_PREFLIGHT_FAILED` stage/model/error-class/prompt-file diagnostic to choose explicit override args instead of rerunning blindly. For packaged review swarms, tune `min_successful_reviewers`, `reviewer_concurrency`, `subagent_ttl_ms`, and `merge_policy` instead of manual reruns; preserve partial reports and label the outcome `complete`, `degraded`, or `insufficient_data`. The final report should separate consensus findings, minority findings, merger findings, risks, and recommended next actions.
|
|
166
166
|
|
|
167
167
|
A review swarm synthesis must not fabricate claims. Every final finding should trace to a reviewer note, checked artifact, command output, source, or explicit merger rationale. Devil's Advocate critical findings must be preserved or explicitly disproved with evidence.
|
|
168
168
|
|
package/docs/async-runs.md
CHANGED
|
@@ -87,10 +87,11 @@ Use ordinary files under the extension temp directory so status tools stay simpl
|
|
|
87
87
|
|
|
88
88
|
- `run.json`: pid, optional source metadata (`launch_source`, `tool`, `recipe`, `recipe_file`), command-template config, cwd, coordinator owner id, values, named `artifacts`, mailbox metadata, created time, and state dir.
|
|
89
89
|
- `communication.json`: compact actor communication snapshot with self/root/parent, default-room, member, and contact hints for room-aware scripts and agents.
|
|
90
|
-
- `progress.json`: phase, active command count, completed count, failures, and
|
|
90
|
+
- `progress.json`: phase, active command count, completed count, failures, updated time, and optional `model_policy` provenance for inherited/explicit model and thinking values.
|
|
91
91
|
- `events.jsonl`: append-only implementation lifecycle log.
|
|
92
92
|
- `outbox.jsonl`: implementation storage for actor-message envelopes used by `inspect view=messages`, coordinator notifications, or follow-up context. Coordinator follow-ups preserve bounded `body` previews plus message metadata for decision points.
|
|
93
93
|
- `stdout.log` and `stderr.log`: detached process output.
|
|
94
|
+
- `prompts/command-NNN.md`: state-owned prompt files materialized for child `pi -p` commands so long prompts and appended recipe context travel as `@file` arguments instead of fragile inline argv text.
|
|
94
95
|
- `result.json`: final code, killed flag, output selector, and optional full-output path.
|
|
95
96
|
|
|
96
97
|
For pi-actors, actor run state defaults to:
|
|
@@ -109,6 +110,7 @@ State files use this shape:
|
|
|
109
110
|
~/.pi/agent/tmp/pi-actors/runs/<run>/outbox.jsonl
|
|
110
111
|
~/.pi/agent/tmp/pi-actors/runs/<run>/stdout.log
|
|
111
112
|
~/.pi/agent/tmp/pi-actors/runs/<run>/stderr.log
|
|
113
|
+
~/.pi/agent/tmp/pi-actors/runs/<run>/prompts/command-001.md
|
|
112
114
|
~/.pi/agent/tmp/pi-actors/runs/<run>/result.json
|
|
113
115
|
```
|
|
114
116
|
|
|
@@ -50,6 +50,8 @@ Common object fields:
|
|
|
50
50
|
|
|
51
51
|
- `label`: Optional human label for diagnostics and parallel branch reports.
|
|
52
52
|
- `parallel`: Optional boolean for array templates. Default is `false` for sequence; `true` runs children concurrently.
|
|
53
|
+
- `concurrency`: Optional positive integer cap for a `parallel: true` node. Omit it to launch all children at once.
|
|
54
|
+
- `min_successful`: Optional non-negative integer evidence threshold for a `parallel: true` node. Usable branches are successful branches with non-empty stdout; joins include a `parallel_status` header when this is set.
|
|
53
55
|
- `when`: Optional node guard. A false guard skips the node; strings may be `flag`, `!flag`, or `{flag?yes:no}` style expressions.
|
|
54
56
|
- `args`: Optional placeholder declarations. Untyped names remain valid; compact typed forms such as `file:path`, `request_timeout:int`, `speed:number`, `dry_run:bool`, `prompts:array`, and `mode:enum(check,fix)` are valid when the host supports typed tool schemas. Defaults belong in `defaults` or inline placeholder defaults; hosts may normalize interactive shorthand such as `request_timeout:int=60000` before persistence.
|
|
55
57
|
- `defaults`: Placeholder default values by name.
|
|
@@ -187,6 +189,7 @@ Composition rules:
|
|
|
187
189
|
- Skipped nodes preserve current stdin/stdout flow and do not execute commands
|
|
188
190
|
- Each parallel child receives the same stdin, and child stdout values are joined in stable array order before flowing to the next sequence leaf
|
|
189
191
|
- Parallel branch joins include branch label and status, and tool details include branch metadata plus coverage summary
|
|
192
|
+
- `min_successful` adds a join header with `complete`, `degraded`, or `insufficient_data`; with `failure: "branch"` or `"root"`, an unmet threshold fails at that scope
|
|
190
193
|
- Each leaf still applies its own inline defaults
|
|
191
194
|
|
|
192
195
|
```json
|
|
@@ -398,7 +401,7 @@ string → leaf command
|
|
|
398
401
|
string[] → sequential composition
|
|
399
402
|
{ template } → leaf command object
|
|
400
403
|
{ parallel, template } → sequence or parallel subtree
|
|
401
|
-
{ parallel, when, args, defaults, delay, retry, failure, recover, output, template } → full node
|
|
404
|
+
{ parallel, concurrency, min_successful, when, args, defaults, delay, retry, failure, recover, output, template } → full node
|
|
402
405
|
```
|
|
403
406
|
|
|
404
407
|
Start with a string. Add composition when needed. Add `parallel: true` when independent work can run concurrently. Add `when` when a node is conditional. Add delay when launch pacing matters. Add retry when flaky. Add `failure` when propagation scope matters. Add `recover` when a retried node needs cleanup before another attempt. Same contract, growing capability, no dead weight.
|
package/docs/recipe-library.md
CHANGED
|
@@ -29,6 +29,7 @@ Core subagent recipes:
|
|
|
29
29
|
- `recipes/subagent-prompt.json`: Start one prompt-driven subagent.
|
|
30
30
|
- `recipes/subagent-tools.json`: Start a subagent with an explicit tool allowlist.
|
|
31
31
|
- `recipes/subagents-prompts.json`: Run prompt fanout with one imported subagent component.
|
|
32
|
+
- `recipes/subagent-preflight.json`: Tiny model/thinking/tool-policy smoke check before expensive fanout; failures surface `ACTOR_PREFLIGHT_FAILED` with stage, selected policy, provider error class, prompt file, and override args.
|
|
32
33
|
- `recipes/subagent-review.json`: Evidence-grounded review lens.
|
|
33
34
|
- `recipes/subagent-critic.json`: Assumption and failure-mode critique.
|
|
34
35
|
- `recipes/subagent-plan.json`: Bounded plan slices and validation gates.
|
|
@@ -46,7 +47,7 @@ Core subagent recipes:
|
|
|
46
47
|
- `recipes/subagent-followup.json`: Same-context or degraded continuation.
|
|
47
48
|
- `recipes/subagent-judge.json`: Post-merge/report quality judge.
|
|
48
49
|
|
|
49
|
-
Most atoms expose policy knobs such as `model`, `thinking`, `tools`, `output_format`, `evidence_policy`, `risk_policy`, source policy, continuity policy, handoff format, or model pools. Packaged recipes intentionally do not ship concrete model-version defaults: callers
|
|
50
|
+
Most atoms expose policy knobs such as `model`, `thinking`, `tools`, `output_format`, `evidence_policy`, `risk_policy`, source policy, continuity policy, handoff format, or model pools. Packaged recipes intentionally do not ship concrete model-version defaults: review-oriented subagent and lens-swarm recipes default model/thinking args through `{current_model}` and `{current_thinking}` so they inherit the selected Pi session policy, and callers can still pass explicit values when a run should diverge. Recipe inspection marks these inherited policy defaults as `current_policy`, and run status/progress records whether launch policy was inherited, explicit, mixed, or unresolved. Generic prompt launchers, including `subagent-tools` and `subagents-prompts`, expose the same core model/thinking/tool/output knobs so callers do not need separate recipe families for policy tuning. Interactive async atoms also declare mailbox metadata for their basic control, completion, and domain-result message surface. Higher-level recipes pass these knobs through instead of hard-coding local policy.
|
|
50
51
|
|
|
51
52
|
For one-off packaged subagent reviews, launch the recipe directly with `spawn file="subagent-review" values={...}` or `spawn file="pipeline-review-readiness" values={...}`. Do not copy the underlying `pi -p` command or wrap the recipe unless you are creating a durable operator tool with a narrower interface.
|
|
52
53
|
|
|
@@ -73,7 +74,7 @@ inspect target=run:docs_review view=tail
|
|
|
73
74
|
Pipeline recipes demonstrate second-order composition:
|
|
74
75
|
|
|
75
76
|
- `recipes/coordinator-locker.json`: Long-lived coordinator cell with queue, acquire/renew/release lease locks, journal, actor messages for worker coordination, and platform-adapted control metadata.
|
|
76
|
-
- `recipes/subagent-review-coordinator.json`:
|
|
77
|
+
- `recipes/subagent-review-coordinator.json`: Model/tool preflight with compact provider diagnostics → quorum-aware lens reviewers → verifier → merger → judge → normalizer. Review pipelines expose `subagent_ttl_ms`, `reviewer_concurrency`, `min_successful_reviewers`, and `merge_policy` knobs; reviewer joins preserve partial evidence and mark `complete`, `degraded`, or `insufficient_data`. `npm run conformance` includes a fake-`pi` review-readiness dogfood fixture for this packaged path.
|
|
77
78
|
- `recipes/pipeline-release-readiness.json`: Task-first release cell: changelog section → package summary → packaged skill summary → validation → release review → artifact report.
|
|
78
79
|
- `recipes/pipeline-release-summary.json`: Evidence-only release summary cell: changelog section → package summary → packaged skill summary → validation → release summary / risks / PR body draft artifact. It does not commit, open a PR, merge, tag, publish, or perform external release side effects.
|
|
79
80
|
- `recipes/pipeline-repo-health.json`: Task-first repository-health cell: git status/log → docs index → validation → normalized artifact report.
|
package/docs/template-recipes.md
CHANGED
|
@@ -208,7 +208,7 @@ Top-level command-template flags may sit beside recipe metadata such as `async`:
|
|
|
208
208
|
}
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
Valid command-template flags include `args`, `defaults`, `parallel`, `when`, `label`, `timeout`, `delay`, `output`, `retry`, `failure`, `recover`, and `repeat`.
|
|
211
|
+
Valid command-template flags include `args`, `defaults`, `parallel`, `concurrency`, `min_successful`, `when`, `label`, `timeout`, `delay`, `output`, `retry`, `failure`, `recover`, and `repeat`.
|
|
212
212
|
|
|
213
213
|
Timeout is disabled by default. Set a positive `timeout` when a recipe should fail closed after a bounded runtime; omit it, or set `0`, for intentionally open-ended runs that will be stopped by async cancellation, such as background audio playback.
|
|
214
214
|
|
|
@@ -260,7 +260,7 @@ If a tool recipe contains `async: true`, calling the tool starts a detached acto
|
|
|
260
260
|
|
|
261
261
|
## Values And Public Args
|
|
262
262
|
|
|
263
|
-
Recipe placeholders come from runtime values, recipe `defaults`, inline placeholder defaults, and registered-tool defaults.
|
|
263
|
+
Recipe placeholders come from runtime values, recipe `defaults`, inline placeholder defaults, and registered-tool defaults. Pi tool launches also inject `{current_model}` and `{current_thinking}` when the active session exposes a selected model and thinking level; recipes that require those placeholders fail before fanout when the current value is unavailable unless the caller supplies an explicit override such as `model` or `thinking`. Async runs persist `model_policy` provenance in run status, progress, and terminal results so operators can tell whether model/thinking values were inherited, explicit, mixed, or unresolved.
|
|
264
264
|
|
|
265
265
|
Recipe tools derive public arguments from the referenced or co-located command template when the recipe is available locally. Explicit `args` is still available when the public tool surface should be narrower than the recipe internals.
|
|
266
266
|
|
package/index.ts
CHANGED
|
@@ -102,13 +102,34 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
|
|
|
102
102
|
activeRunContext && scheduleRunEventUpdate(activeRunContext),
|
|
103
103
|
});
|
|
104
104
|
const actorToolDefinitions = new Map<string, Tools.ActorToolDefinition>();
|
|
105
|
+
const withCurrentThinkingContext = <T extends Tools.ActorToolDefinition>(
|
|
106
|
+
definition: T,
|
|
107
|
+
): T => {
|
|
108
|
+
if (typeof definition.execute !== "function") return definition;
|
|
109
|
+
const execute = definition.execute as (...args: unknown[]) => unknown;
|
|
110
|
+
return {
|
|
111
|
+
...definition,
|
|
112
|
+
execute: (...args: unknown[]) => {
|
|
113
|
+
const nextArgs = [...args];
|
|
114
|
+
const ctx = nextArgs[4];
|
|
115
|
+
if (ctx && typeof ctx === "object") {
|
|
116
|
+
nextArgs[4] = {
|
|
117
|
+
...(ctx as Record<string, unknown>),
|
|
118
|
+
getThinkingLevel: () => pi.getThinkingLevel(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return execute(...nextArgs);
|
|
122
|
+
},
|
|
123
|
+
} as T;
|
|
124
|
+
};
|
|
105
125
|
const runtime = Runtime.createAutoToolsRuntime({
|
|
106
126
|
configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
|
|
107
127
|
exec: CommandTemplates.execCommandTemplate,
|
|
108
128
|
getActiveTools: () => pi.getActiveTools(),
|
|
109
129
|
registerTool: (definition) => {
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
const wrapped = withCurrentThinkingContext(definition);
|
|
131
|
+
actorToolDefinitions.set(wrapped.name, wrapped);
|
|
132
|
+
pi.registerTool(wrapped);
|
|
112
133
|
},
|
|
113
134
|
reservedToolNames: Tools.RESERVED_TOOL_NAMES,
|
|
114
135
|
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
@@ -186,6 +207,6 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
|
|
|
186
207
|
getRuntimeTool: (name) => actorToolDefinitions.get(name),
|
|
187
208
|
registryRuntime: runtime,
|
|
188
209
|
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
189
|
-
}),
|
|
210
|
+
}).map(withCurrentThinkingContext),
|
|
190
211
|
);
|
|
191
212
|
}
|
package/lib/async-runs.ts
CHANGED
|
@@ -22,6 +22,12 @@ import type {
|
|
|
22
22
|
CommandTemplateValue,
|
|
23
23
|
} from "./command-templates.ts";
|
|
24
24
|
import { writeJsonAtomic } from "./file-state.ts";
|
|
25
|
+
import {
|
|
26
|
+
CURRENT_MODEL_VALUE_KEY,
|
|
27
|
+
CURRENT_THINKING_VALUE_KEY,
|
|
28
|
+
describeCurrentPolicyProvenance,
|
|
29
|
+
type CurrentPolicyProvenance,
|
|
30
|
+
} from "./model-context.ts";
|
|
25
31
|
import * as Paths from "./paths.ts";
|
|
26
32
|
import * as RecipesReferences from "./recipes-references.ts";
|
|
27
33
|
import * as RecipesUsage from "./recipes-usage.ts";
|
|
@@ -93,6 +99,8 @@ export interface AsyncRunStartParams {
|
|
|
93
99
|
args?: string[];
|
|
94
100
|
defaults?: Record<string, unknown>;
|
|
95
101
|
parallel?: boolean;
|
|
102
|
+
concurrency?: number | string;
|
|
103
|
+
min_successful?: number | string;
|
|
96
104
|
label?: string;
|
|
97
105
|
when?: boolean | string;
|
|
98
106
|
timeout?: number | string;
|
|
@@ -106,6 +114,7 @@ export interface AsyncRunStartParams {
|
|
|
106
114
|
recover?: CommandTemplateValue;
|
|
107
115
|
repeat?: number;
|
|
108
116
|
values?: Record<string, unknown>;
|
|
117
|
+
policy_values?: Record<string, unknown>;
|
|
109
118
|
actor_context?: boolean | string;
|
|
110
119
|
cwd?: string;
|
|
111
120
|
}
|
|
@@ -136,6 +145,7 @@ export interface AsyncRunMeta {
|
|
|
136
145
|
artifacts?: Record<string, RunArtifactDeclaration>;
|
|
137
146
|
control?: AsyncRunControlEndpoint;
|
|
138
147
|
mailbox?: RecipesReferences.TemplateRecipeMailbox;
|
|
148
|
+
model_policy?: CurrentPolicyProvenance;
|
|
139
149
|
recipe_context_records?: RecipesReferences.TemplateRecipeContextRecord[];
|
|
140
150
|
retire_when?: "children_terminal";
|
|
141
151
|
}
|
|
@@ -180,6 +190,8 @@ function resolveRunTemplate(params: AsyncRunStartParams): {
|
|
|
180
190
|
"args",
|
|
181
191
|
"defaults",
|
|
182
192
|
"parallel",
|
|
193
|
+
"concurrency",
|
|
194
|
+
"min_successful",
|
|
183
195
|
"label",
|
|
184
196
|
"when",
|
|
185
197
|
"timeout",
|
|
@@ -270,6 +282,130 @@ function readJson(path: string): Record<string, unknown> | undefined {
|
|
|
270
282
|
).value;
|
|
271
283
|
}
|
|
272
284
|
|
|
285
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
286
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isFalsyValue(value: unknown): boolean {
|
|
290
|
+
if (value === undefined || value === null || value === false) return true;
|
|
291
|
+
const normalized = String(value).trim().toLowerCase();
|
|
292
|
+
return (
|
|
293
|
+
normalized === "" ||
|
|
294
|
+
normalized === "0" ||
|
|
295
|
+
normalized === "false" ||
|
|
296
|
+
normalized === "no"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function stringReferencesPlaceholder(
|
|
301
|
+
value: string,
|
|
302
|
+
placeholder: string,
|
|
303
|
+
): boolean {
|
|
304
|
+
return new RegExp(`\\{\\s*${placeholder}\\s*\\}`).test(value);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function collectUnresolvedCurrentPlaceholderReferences(
|
|
308
|
+
value: unknown,
|
|
309
|
+
values: Record<string, unknown>,
|
|
310
|
+
placeholder: string,
|
|
311
|
+
valueKey: string,
|
|
312
|
+
path = "template",
|
|
313
|
+
refs: string[] = [],
|
|
314
|
+
): string[] {
|
|
315
|
+
if (typeof value === "string") {
|
|
316
|
+
if (
|
|
317
|
+
stringReferencesPlaceholder(value, placeholder) &&
|
|
318
|
+
isFalsyValue(values[valueKey])
|
|
319
|
+
) {
|
|
320
|
+
refs.push(path);
|
|
321
|
+
}
|
|
322
|
+
return refs;
|
|
323
|
+
}
|
|
324
|
+
if (Array.isArray(value)) {
|
|
325
|
+
value.forEach((item, index) =>
|
|
326
|
+
collectUnresolvedCurrentPlaceholderReferences(
|
|
327
|
+
item,
|
|
328
|
+
values,
|
|
329
|
+
placeholder,
|
|
330
|
+
valueKey,
|
|
331
|
+
`${path}[${index}]`,
|
|
332
|
+
refs,
|
|
333
|
+
),
|
|
334
|
+
);
|
|
335
|
+
return refs;
|
|
336
|
+
}
|
|
337
|
+
if (!isRecord(value)) return refs;
|
|
338
|
+
for (const [key, child] of Object.entries(value)) {
|
|
339
|
+
if (key === "defaults" && isRecord(child)) {
|
|
340
|
+
for (const [defaultKey, defaultValue] of Object.entries(child)) {
|
|
341
|
+
if (
|
|
342
|
+
typeof defaultValue === "string" &&
|
|
343
|
+
stringReferencesPlaceholder(defaultValue, placeholder) &&
|
|
344
|
+
isFalsyValue(values[defaultKey]) &&
|
|
345
|
+
isFalsyValue(values[valueKey])
|
|
346
|
+
) {
|
|
347
|
+
refs.push(`${path}.defaults.${defaultKey}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
collectUnresolvedCurrentPlaceholderReferences(
|
|
353
|
+
child,
|
|
354
|
+
values,
|
|
355
|
+
placeholder,
|
|
356
|
+
valueKey,
|
|
357
|
+
`${path}.${key}`,
|
|
358
|
+
refs,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return refs;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function assertCurrentPlaceholderReferencesResolved(
|
|
365
|
+
template: CommandTemplateValue,
|
|
366
|
+
defaults: Record<string, unknown> | undefined,
|
|
367
|
+
values: Record<string, unknown>,
|
|
368
|
+
modelPolicy: CurrentPolicyProvenance,
|
|
369
|
+
): void {
|
|
370
|
+
const checks = [
|
|
371
|
+
{
|
|
372
|
+
label: "model",
|
|
373
|
+
placeholder: "current_model",
|
|
374
|
+
valueKey: CURRENT_MODEL_VALUE_KEY,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
label: "thinking level",
|
|
378
|
+
placeholder: "current_thinking",
|
|
379
|
+
valueKey: CURRENT_THINKING_VALUE_KEY,
|
|
380
|
+
},
|
|
381
|
+
];
|
|
382
|
+
for (const check of checks) {
|
|
383
|
+
const refs = collectUnresolvedCurrentPlaceholderReferences(
|
|
384
|
+
template,
|
|
385
|
+
values,
|
|
386
|
+
check.placeholder,
|
|
387
|
+
check.valueKey,
|
|
388
|
+
);
|
|
389
|
+
if (defaults) {
|
|
390
|
+
collectUnresolvedCurrentPlaceholderReferences(
|
|
391
|
+
{ defaults },
|
|
392
|
+
values,
|
|
393
|
+
check.placeholder,
|
|
394
|
+
check.valueKey,
|
|
395
|
+
"recipe",
|
|
396
|
+
refs,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
if (refs.length === 0) continue;
|
|
400
|
+
throw Object.assign(
|
|
401
|
+
new Error(
|
|
402
|
+
`Template recipe requires the current Pi ${check.label} for inheritance, but no current ${check.label} was available. Pass explicit values or launch from a Pi session with a selected ${check.label}. unresolved=${refs.slice(0, 4).join(",")}`,
|
|
403
|
+
),
|
|
404
|
+
{ model_policy: modelPolicy },
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
273
409
|
function acquireStateStartLock(stateDir: string): () => void {
|
|
274
410
|
return RunsStart.acquireStateStartLock(stateDir);
|
|
275
411
|
}
|
|
@@ -286,6 +422,25 @@ export function startRun(
|
|
|
286
422
|
const resolved = resolveRunTemplate(startParams);
|
|
287
423
|
const run = safeRunId(startParams.run_id);
|
|
288
424
|
const stateDir = resolveStateDir(startParams, run);
|
|
425
|
+
const values = {
|
|
426
|
+
...(startParams.values || {}),
|
|
427
|
+
actor_address: `run:${run}`,
|
|
428
|
+
communication_file: join(stateDir, "communication.json"),
|
|
429
|
+
default_room: `room:${run}`,
|
|
430
|
+
run_id: run,
|
|
431
|
+
state_dir: stateDir,
|
|
432
|
+
};
|
|
433
|
+
const modelPolicy = describeCurrentPolicyProvenance({
|
|
434
|
+
defaults: startParams.defaults,
|
|
435
|
+
template: resolved.template,
|
|
436
|
+
values: startParams.policy_values ?? startParams.values ?? {},
|
|
437
|
+
});
|
|
438
|
+
assertCurrentPlaceholderReferencesResolved(
|
|
439
|
+
resolved.template,
|
|
440
|
+
startParams.defaults,
|
|
441
|
+
values,
|
|
442
|
+
modelPolicy,
|
|
443
|
+
);
|
|
289
444
|
assertNoActiveRunState(stateDir);
|
|
290
445
|
mkdirSync(stateDir, { recursive: true });
|
|
291
446
|
const releaseStartLock = acquireStateStartLock(stateDir);
|
|
@@ -315,14 +470,6 @@ export function startRun(
|
|
|
315
470
|
const outFd = openSync(stdout, "a");
|
|
316
471
|
const errFd = openSync(stderr, "a");
|
|
317
472
|
const argv = asyncRunnerArgv(stateDir);
|
|
318
|
-
const values = {
|
|
319
|
-
...(startParams.values || {}),
|
|
320
|
-
actor_address: `run:${run}`,
|
|
321
|
-
communication_file: join(stateDir, "communication.json"),
|
|
322
|
-
default_room: `room:${run}`,
|
|
323
|
-
run_id: run,
|
|
324
|
-
state_dir: stateDir,
|
|
325
|
-
};
|
|
326
473
|
const outputValues = {
|
|
327
474
|
...(startParams.defaults || {}),
|
|
328
475
|
...values,
|
|
@@ -345,6 +492,7 @@ export function startRun(
|
|
|
345
492
|
...(startParams.tool ? { tool: startParams.tool } : {}),
|
|
346
493
|
template: resolved.template,
|
|
347
494
|
values,
|
|
495
|
+
model_policy: modelPolicy,
|
|
348
496
|
...(artifacts ? { artifacts } : {}),
|
|
349
497
|
...(startParams.control ? { control: startParams.control } : {}),
|
|
350
498
|
...(startParams.mailbox ? { mailbox: startParams.mailbox } : {}),
|
|
@@ -368,6 +516,7 @@ export function startRun(
|
|
|
368
516
|
writeJsonAtomic(join(stateDir, "progress.json"), {
|
|
369
517
|
completed: 0,
|
|
370
518
|
failures: [],
|
|
519
|
+
model_policy: modelPolicy,
|
|
371
520
|
phase: "starting",
|
|
372
521
|
updatedAt: new Date().toISOString(),
|
|
373
522
|
});
|
package/lib/command-templates.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface CommandTemplateObjectConfig {
|
|
|
22
22
|
actorRecipeContext?: CommandTemplateActorRecipeContext;
|
|
23
23
|
label?: string;
|
|
24
24
|
parallel?: boolean;
|
|
25
|
+
concurrency?: number | string;
|
|
26
|
+
min_successful?: number | string;
|
|
25
27
|
when?: boolean | string;
|
|
26
28
|
template?: CommandTemplateValue;
|
|
27
29
|
args?: string[];
|