@llblab/pi-actors 0.12.9 → 0.12.10
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 +4 -3
- package/CHANGELOG.md +13 -1
- package/README.md +3 -3
- package/docs/actor-messages.md +5 -4
- package/docs/async-runs.md +7 -7
- package/docs/component-recipes.md +1 -1
- package/docs/recipe-library.md +4 -2
- package/docs/task-first-recipes.md +3 -3
- package/index.ts +5 -1
- package/lib/tools.ts +105 -15
- package/package.json +1 -1
- package/recipes/pipeline-artifact-bundle.json +92 -0
- package/recipes/pipeline-async-run-ops.json +8 -13
- package/recipes/subagent-tools.json +7 -3
- package/recipes/subagents-prompts.json +13 -1
- package/recipes/utility-run-ops-snapshot.json +18 -0
- package/scripts/recipe-utils.mjs +41 -2
package/BACKLOG.md
CHANGED
|
@@ -6,7 +6,7 @@ Continue progressive component/pipeline expansion in small validated slices; rea
|
|
|
6
6
|
|
|
7
7
|
- Plan organic universal communication primitives.
|
|
8
8
|
- Priority: High.
|
|
9
|
-
- Status: The actor-like model is empirically useful: async runs can emit follow-up messages upward, coordinators can send run-local commands downward, multiple parallel runs can progress independently, and recipes no longer need sleep-poll coordination. The design is captured in `docs/actor-messages.md`: `spawn`, `message`, and `inspect` as concentrated verbs; addressed actors; one symmetric message envelope; mailbox `accepts`/`emits`; and adapter mappings from runtime async actions. Initial implementation landed pure actor address/message normalization plus public `spawn`, `message`, and `inspect` tools for `run:<id>` actors; `spawn` accepts state/artifact metadata, `message` routes `run:<id>` → `coordinator` envelopes into the runtime attention path, `message` can invoke `tool:<name>` actors, all packaged async recipes declare mailbox metadata, `inspect view=mailbox` exposes recipe mailbox contracts from run metadata,
|
|
9
|
+
- Status: The actor-like model is empirically useful: async runs can emit follow-up messages upward, coordinators can send run-local commands downward, multiple parallel runs can progress independently, and recipes no longer need sleep-poll coordination. The design is captured in `docs/actor-messages.md`: `spawn`, `message`, and `inspect` as concentrated verbs; addressed actors; one symmetric message envelope; mailbox `accepts`/`emits`; and adapter mappings from runtime async actions. Initial implementation landed pure actor address/message normalization plus public `spawn`, `message`, and `inspect` tools for `run:<id>` actors; `spawn` accepts state/artifact metadata, `message` routes `run:<id>` → `coordinator` and `run:<id>` → `session:<id>` envelopes into the runtime attention path, `message` can invoke `tool:<name>` actors, `inspect target=tool:<name>` exposes registered tool actor contracts, `inspect target=coordinator` exposes current-session run inventory, all packaged async recipes declare mailbox metadata, `inspect view=mailbox` exposes recipe mailbox contracts from run metadata, recipe-authored messages now use envelope-aligned `type` fields with deterministic validated wrapping available through `utility-actor-message`, and run termination now accepts actor-native `control.stop`/`control.cancel`/`control.kill` aliases while preserving legacy `runtime.*` compatibility. Remaining work is to absorb remaining runtime async action surfaces into the actor vocabulary instead of preserving a parallel public API.
|
|
10
10
|
- Scope: Design and implement a small semantic layer around addressed messages and actors while preserving low-level primitives as adapters where useful. Candidate top-level concepts are `spawn` for creating an actor/run from a recipe or template, `message` for sending typed messages to any address, and `inspect` for intentional observation/debugging. Candidate addresses include `run:<id>`, `branch:<run>/<branch>`, `coordinator`, `session:<id>`, `tool:<name>`, and future chat/session endpoints. Candidate message fields include `to`, `from`, `type`, `summary`, `body`, `reply_to`, `correlation_id`, and `metadata`.
|
|
11
11
|
- Contract direction: Unify “send down” and “messages up” as one message model. `to: run:<id>` routes to a run mailbox, `to: coordinator` routes to the coordinator attention path, and branch/tool/session addresses can be layered over the same semantic envelope. Recipes should declare mailbox capability (`accepts`, `emits`) without exposing FIFO/outbox mechanics or delivery policy as their public interface.
|
|
12
12
|
- Design gates: Breaking changes are allowed in this phase, so compress concepts instead of preserving accidental surfaces. Consolidate duplicated lifecycle/message/event APIs into a concentrated protocol with the fewest durable nouns and verbs that still explain the system. Duplex communication should be symmetric where the domain is symmetric: the same message envelope should represent run→coordinator, coordinator→run, run→run, branch→parent, and parent→branch traffic, with routing/transport hidden below it. Keep command templates as the portable synchronous execution graph; keep recipe files as semantic definitions; avoid leaking transports into public args; make polling an explicit diagnostic operation, not an example path; replace runtime action names with the actor API rather than preserving parallel concepts.
|
|
@@ -14,18 +14,19 @@ Continue progressive component/pipeline expansion in small validated slices; rea
|
|
|
14
14
|
|
|
15
15
|
- Progressively increase component parameterization and higher-level recipe composition.
|
|
16
16
|
- Priority: High.
|
|
17
|
+
- Status: `subagent-tools` and `subagents-prompts` now align with the common subagent policy knobs for thinking, tools, model, and output format.
|
|
17
18
|
- Scope: Iteratively strengthen atom/component recipes with public policy knobs such as model pools, stage-specific models, thinking, tool policy, output format, evidence policy, risk policy, source policy, artifact paths, mailbox contract, handoff format, resume/continuity policy, and validation gates; add higher-level component recipes that compose existing atoms into reusable coordinator patterns.
|
|
18
19
|
- Exit: Each iteration adds or refines at least one atom-level parameterization surface and at least one composed recipe/pipeline, with packaged recipe import validation passing and docs/changelog updated.
|
|
19
20
|
|
|
20
21
|
- Add another task-first high-level pipeline candidate from the design map.
|
|
21
22
|
- Priority: Medium.
|
|
22
|
-
- Status: `pipeline-release-readiness`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`,
|
|
23
|
+
- Status: `pipeline-release-readiness`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`, `pipeline-media-library`, and `pipeline-artifact-bundle` landed. `pipeline-media-library` required playlist output-mode parameterization, then reused playlist and artifact-report cells. `pipeline-artifact-bundle` reused artifact-write, artifact-manifest, validation, manifest-write, and actor-message cells.
|
|
23
24
|
- Scope: Reassess `docs/task-first-recipes.md` for the next high-value task cell, then implement only the minimum missing cells needed for that task.
|
|
24
25
|
- Exit: Another task-first pipeline lands with docs, package validation, and a note about which missing atoms/utilities it required.
|
|
25
26
|
|
|
26
27
|
- Grow the standard recipe library with safer structured utility transforms.
|
|
27
28
|
- Priority: Low.
|
|
28
|
-
- Status: `utility-artifact-manifest` landed for machine-readable artifact metadata; `utility-artifact-write` landed for deterministic writes of accepted prepared artifacts; `utility-package-summary` landed for bounded package metadata used by release/repo-health flows; `utility-validate-recipe` landed with a dedicated recipe validator script.
|
|
29
|
+
- Status: `utility-artifact-manifest` landed for machine-readable artifact metadata; `utility-artifact-write` landed for deterministic writes of accepted prepared artifacts; `utility-package-summary` landed for bounded package metadata used by release/repo-health flows; `utility-validate-recipe` landed with a dedicated recipe validator script; `utility-run-ops-snapshot` landed for async-run summaries, event tails, and operator-gated cleanup recommendations.
|
|
29
30
|
- Scope: Continue beyond listing/extraction utilities toward structured transforms for artifact packaging, report normalization, release prep, and machine-readable summaries. Keep helpers generic, parameterized, and justified by repeated recipe needs.
|
|
30
31
|
- Exit: A future utility slice adds another structured transform only when a repeated recipe need appears; otherwise treat the current helper-backed utility surface as sufficient.
|
|
31
32
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.12.10: Actor Ownership and Recipe Operations
|
|
4
|
+
|
|
5
|
+
- `[Actor Messages]` Added actor-native `control.stop`, `control.cancel`, and `control.kill` handling for run termination while retaining `runtime.cancel` and `runtime.kill` as compatibility aliases. Impact: public examples can use the same control-message vocabulary declared by recipe mailboxes instead of preserving runtime action names.
|
|
6
|
+
- `[Actor Messages]` Added `inspect target=tool:<name>` support for registered tool actor status/schema contracts. Impact: tool actors can now be intentionally observed through the same actor vocabulary used to invoke them with `message to=tool:<name>`.
|
|
7
|
+
- `[Recipe Library]` Added `pipeline-artifact-bundle`, a task-first handoff pipeline that composes optional validation, deterministic artifact writing, machine-readable manifest generation, deterministic manifest writing, and an actor-message handoff. Impact: callers who explicitly want filesystem writes can produce paired artifact and manifest paths as one reusable bundle workflow.
|
|
8
|
+
- `[Component Recipes]` Aligned `subagent-tools` and `subagents-prompts` with the common subagent policy knobs for `model`, `thinking`, `tools`, and `output_format`. Impact: prompt launchers and prompt fanout can be tuned through the same public controls as the richer subagent atoms.
|
|
9
|
+
- `[Recipe Library]` Added `utility-run-ops-snapshot` and routed `pipeline-async-run-ops` through it so run summaries, event tails, and stale/terminal recommendations stay in one structured input. Impact: async-run operations reports no longer lose summary context before normalization and can suggest `inspect` or `control.stop` messages without executing them.
|
|
10
|
+
- `[Actor Messages]` Added `inspect target=coordinator` support for current-session run inventory. Impact: the coordinator actor can now be intentionally observed without spelling out the session id.
|
|
11
|
+
- `[Actor Messages]` Added `message to=session:<id>` support for run-owned session-directed follow-ups. Impact: explicit session checkpoints now use the same actor envelope as coordinator follow-ups while preserving run-owner checks.
|
|
12
|
+
- `[Actor Messages]` Hardened `message to=session:<id>` routing to require an owned sender run. Impact: unowned or cross-session runs cannot synthesize session-directed follow-ups.
|
|
13
|
+
- `[Actor Messages]` Applied coordinator-session ownership checks to addressed run, branch, and coordinator message routes when a session context is available. Impact: actor messages now fail closed before controlling or emitting from runs owned by another coordinator session.
|
|
14
|
+
- `[Actor Messages]` Applied the same coordinator-session ownership gate to direct `inspect target=run:<id>` views. Impact: explicit run inspection no longer leaks cross-session run details when the current session is known.
|
|
15
|
+
- `[Actor Messages]` Tightened `inspect target=coordinator` to require a current coordinator session instead of falling back to all runs. Impact: callers without session context must use explicit `session:<id>` or `session:all` inventory.
|
|
4
16
|
|
|
5
17
|
## 0.12.9: Actor Runtime Hotfix
|
|
6
18
|
|
package/README.md
CHANGED
|
@@ -307,7 +307,7 @@ Read recent events or logs only after a follow-up asks for inspection, at a real
|
|
|
307
307
|
{ "target": "run:docs-review", "view": "tail", "lines": "80" }
|
|
308
308
|
```
|
|
309
309
|
|
|
310
|
-
Reusable local recipes live in `~/.pi/agent/recipes/*.json`; recipe tools honor each file's `async` flag. Use `spawn` for explicit detached starts from a file or inline template, and `inspect target=session:<id> view=runs status=running
|
|
310
|
+
Reusable local recipes live in `~/.pi/agent/recipes/*.json`; recipe tools honor each file's `async` flag. Use `spawn` for explicit detached starts from a file or inline template, and `inspect target=coordinator view=runs status=running`, `inspect target=session:<id> view=runs status=running`, or `inspect target=session:all view=runs` for explicit inventory/diagnosis. List output includes `tool` and `recipe` when the launcher recorded that source context.
|
|
311
311
|
|
|
312
312
|
## Recipe Library
|
|
313
313
|
|
|
@@ -376,8 +376,8 @@ See [`docs/recipe-library.md`](./docs/recipe-library.md) for install notes and r
|
|
|
376
376
|
- Obvious high-risk templates such as shells, interpreter eval modes, and broad filesystem mutation surface lightweight warnings without blocking existing tools.
|
|
377
377
|
- `async: true` on a recipe selects detached run lifecycle; omitted or false async runs the recipe foreground through registered tools.
|
|
378
378
|
- Layer boundaries stay explicit: command templates define synchronous execution graphs; template recipes add saved JSON metadata/import resolution and named `artifacts`; async runs add detached lifecycle, state, IPC, and observability.
|
|
379
|
-
- `spawn`, `message`, and `inspect` are high-level actor adapters. `spawn` creates `run:<id>` actors from recipes or inline templates with optional state/artifact metadata, `message` sends one typed envelope to `run:<id>` mailboxes, `branch:<run>/<branch>` mailboxes, `tool:<name>` calls, or
|
|
380
|
-
- `spawn`, `message`, and `inspect` are the public async coordination vocabulary. Low-level async actions map to this actor API: start belongs to `spawn`; send/control belongs to `message`; status/tail/events/list
|
|
379
|
+
- `spawn`, `message`, and `inspect` are high-level actor adapters. `spawn` creates `run:<id>` actors from recipes or inline templates with optional state/artifact metadata, `message` sends one typed envelope to `run:<id>` mailboxes, `branch:<run>/<branch>` mailboxes, `tool:<name>` calls, or coordinator/session attention paths, and `inspect` intentionally reads `run:<id>` status/tail/events/mailbox metadata, coordinator/session run status, or registered `tool:<name>` contracts while the broader actor/message protocol is refined.
|
|
380
|
+
- `spawn`, `message`, and `inspect` are the public async coordination vocabulary. Low-level async actions map to this actor API: start belongs to `spawn`; send/control/stop/kill belongs to `message`; status/tail/events/list belongs to `inspect`. Prefer `control.stop` and `control.kill` for run termination; legacy `runtime.cancel` and `runtime.kill` remain compatibility aliases.
|
|
381
381
|
- Actor management returns compact text by default; pass `verbose: true` to `inspect` when full JSON state is needed.
|
|
382
382
|
- Detached runs inject `{run_id}` and `{state_dir}` into template values for run-local artifacts or recipe-specific control endpoints.
|
|
383
383
|
- Runtime actor messages are stored in `<state_dir>/outbox.jsonl`; coordinator attention is inferred by the runtime, not exposed as recipe or message-envelope input. Follow-ups preserve bounded body previews and metadata for decision messages.
|
package/docs/actor-messages.md
CHANGED
|
@@ -58,7 +58,7 @@ Field rules:
|
|
|
58
58
|
- `type`: required semantic message type.
|
|
59
59
|
- `summary`: short human-facing line for notifications/follow-ups.
|
|
60
60
|
- `body`: string or JSON payload.
|
|
61
|
-
- routing/delivery is inferred from `to`, actor ownership, and coordinator runtime policy; recipes should not expose delivery knobs.
|
|
61
|
+
- routing/delivery is inferred from `to`, actor ownership, and coordinator runtime policy; recipes should not expose delivery knobs. When a coordinator session is known, addressed run/branch/control messages fail closed before controlling or emitting from runs owned by another session.
|
|
62
62
|
- `reply_to`: optional message id for conversational checkpoints.
|
|
63
63
|
- `correlation_id`: optional task/run/workflow id.
|
|
64
64
|
- `metadata`: optional structured routing or domain hints.
|
|
@@ -79,7 +79,7 @@ coordinator -> tool
|
|
|
79
79
|
Transports differ, but the public contract does not:
|
|
80
80
|
|
|
81
81
|
- `to: run:<id>` may route to FIFO, mailbox file, socket, or process stdin.
|
|
82
|
-
- `to: coordinator` routes to outbox/watch/follow-up delivery when `from` names a run actor. Generic async-runner `command.done` events and explicit coordinator-bound messages include the actor envelope fields alongside the runtime event fields.
|
|
82
|
+
- `to: coordinator` routes to outbox/watch/follow-up delivery when `from` names a run actor. `to: session:<id>` uses the same actor-message path only when the sender run is owned by that session, making explicit session-directed checkpoints possible without exposing runtime delivery knobs. Generic async-runner `command.done` events and explicit coordinator/session-bound messages include the actor envelope fields alongside the runtime event fields.
|
|
83
83
|
- `to: branch:<run>/<branch>` routes through the parent run mailbox with the full envelope preserved so the run can dispatch branch-local control.
|
|
84
84
|
- `to: tool:<name>` invokes an executable pi tool by name. Object bodies become tool parameters; primitive bodies are passed as `{ "input": body }`.
|
|
85
85
|
|
|
@@ -126,7 +126,7 @@ Recipes can declare their conversational surface:
|
|
|
126
126
|
}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
The implementation supports `status`, `tail`, `events`, `artifacts`, `files`, and `mailbox` for `run:<id>` actors,
|
|
129
|
+
The implementation supports `status`, `tail`, `events`, `artifacts`, `files`, and `mailbox` for `run:<id>` actors, `status`/`runs` for `coordinator`, `session:<id>`, and `session:all` actors with optional status filtering, and `status`/`schema` for registered `tool:<name>` actors. `inspect target=coordinator` requires a current coordinator session; use `session:<id>` or `session:all` when the session is intentionally explicit. Direct `run:<id>` inspection respects coordinator-session ownership when the current session is known. `inspect` is for decision points and diagnosis only; examples must not teach sleep-then-inspect polling.
|
|
130
130
|
|
|
131
131
|
## Runtime Direction
|
|
132
132
|
|
|
@@ -135,7 +135,8 @@ Runtime operations use the actor/message vocabulary:
|
|
|
135
135
|
```text
|
|
136
136
|
create detached work -> spawn
|
|
137
137
|
run-local control -> message to run:<id>
|
|
138
|
-
|
|
138
|
+
run stop/kill -> message type control.stop/control.kill
|
|
139
|
+
coordinator signal -> message to coordinator/session
|
|
139
140
|
tool execution -> message to tool:<name>
|
|
140
141
|
intentional observe -> inspect
|
|
141
142
|
```
|
package/docs/async-runs.md
CHANGED
|
@@ -127,22 +127,22 @@ The core loop is:
|
|
|
127
127
|
|
|
128
128
|
4. Do not inspect just because time passed. Inspect `status`, `tail`, or `events` only when a follow-up asks for inspection, a real decision depends on it, or a suspected stuck run needs diagnosis.
|
|
129
129
|
|
|
130
|
-
Addressed `message` calls and coordinator follow-ups are the paired control plane: run-to-coordinator actor messages flow upward, while coordinator-to-run actor messages flow downward. Recipe scripts own the message vocabulary (`next`, `pause`, `approve`, `revise`, `continue`, and so on); pi-actors owns the safe run-local transport, ownership checks, and coordinator attention policy.
|
|
130
|
+
Addressed `message` calls and coordinator follow-ups are the paired control plane: run-to-coordinator actor messages flow upward, while coordinator-to-run actor messages flow downward. Recipe scripts own the message vocabulary (`next`, `pause`, `approve`, `revise`, `continue`, and so on); pi-actors owns the safe run-local transport, coordinator-session ownership checks, and coordinator attention policy.
|
|
131
131
|
|
|
132
132
|
## Tool Surface
|
|
133
133
|
|
|
134
134
|
The actor-level surface is:
|
|
135
135
|
|
|
136
136
|
- `spawn`: start a detached `run:<id>` actor from `file`, `recipe`, or inline `template`.
|
|
137
|
-
- `message`: send one typed envelope to `run:<id>`, `branch:<run>/<branch>`, `tool:<name>`, or `
|
|
138
|
-
- `inspect`: intentionally read `run:<id>` status, tail, events, artifacts, files, or mailbox metadata; read `session:<id>` or `session:all` run inventory with optional status filtering.
|
|
137
|
+
- `message`: send one typed envelope to `run:<id>`, `branch:<run>/<branch>`, `tool:<name>`, `coordinator`, or `session:<id>`.
|
|
138
|
+
- `inspect`: intentionally read owned `run:<id>` status, tail, events, artifacts, files, or mailbox metadata; read current `coordinator` run inventory only when a coordinator session is known; read `session:<id>` or `session:all` run inventory with optional status filtering when the session is explicit; read `tool:<name>` status or schema for registered tool actors.
|
|
139
139
|
|
|
140
140
|
Low-level async actions map into the actor surface instead of forming a second public model:
|
|
141
141
|
|
|
142
142
|
- start → `spawn`
|
|
143
143
|
- send/control → `message`
|
|
144
144
|
- status/tail/events/list → `inspect`
|
|
145
|
-
- stop/kill →
|
|
145
|
+
- stop/kill → `message` with `control.stop` or `control.kill`, with synchronous results
|
|
146
146
|
|
|
147
147
|
Compact text is returned by default so async management does not flood agent context; use verbose inspection when the full state object is needed. List output intentionally shares one state root across music, subagents, timers, and other async work; source fields such as `tool` and `recipe` distinguish run purpose when the launcher recorded them. Registered tools are the preferred user-facing surface for reusable recipes.
|
|
148
148
|
|
|
@@ -172,13 +172,13 @@ Native Windows does not support this Unix FIFO contract. Use WSL/Linux/macOS for
|
|
|
172
172
|
|
|
173
173
|
## Coordinator Notifications
|
|
174
174
|
|
|
175
|
-
The launching coordinator should not busy-poll long-running async runs. The extension watches run state directories and delivers terminal `done`/`failed`/unhandled `killed`/`exited` transitions plus script-authored `notify`/`followup` actor messages back to the owning session. This gives the top-level async task a completion signal on the happy path while still letting recipe-local messages bubble up when scripts need finer-grained notifications. Terminal follow-ups include recipe-level named `artifacts` when declared. The generic runner also emits compact `command.done` actor messages for completed leaf commands; recipe authors declare that capability in `mailbox.emits` rather than configuring a separate delivery policy. Failures and in-flight parallel branch completions can bubble as follow-ups, while successful final leaf completions stay diagnostic to avoid flooding long sequential pipelines. Branch-level `command.done` follow-ups omit artifact manifests because the top-level terminal follow-up carries them once. Intentional `
|
|
175
|
+
The launching coordinator should not busy-poll long-running async runs. The extension watches run state directories and delivers terminal `done`/`failed`/unhandled `killed`/`exited` transitions plus script-authored `notify`/`followup` actor messages back to the owning session. This gives the top-level async task a completion signal on the happy path while still letting recipe-local messages bubble up when scripts need finer-grained notifications. Terminal follow-ups include recipe-level named `artifacts` when declared. The generic runner also emits compact `command.done` actor messages for completed leaf commands; recipe authors declare that capability in `mailbox.emits` rather than configuring a separate delivery policy. Failures and in-flight parallel branch completions can bubble as follow-ups, while successful final leaf completions stay diagnostic to avoid flooding long sequential pipelines. Branch-level `command.done` follow-ups omit artifact manifests because the top-level terminal follow-up carries them once. Intentional `control.stop`, `control.kill`, and recipe-local stop commands stay out of follow-up context because the initiating message already returns synchronously. If a follow-up asks for direction, answer with `message` rather than starting a polling loop. Use explicit `inspect` only when a delivered follow-up requests inspection, a real decision depends on state, or a suspected stuck run needs diagnosis — never merely because a timeout elapsed.
|
|
176
176
|
|
|
177
177
|
Ambient status indicators may refresh while work is active, but coordinator attention is event-driven from state-file changes rather than a coordinator agent loop. This lets the coordinator continue other work after `spawn`; the run signals back through `events.jsonl`, `result.json`, and `outbox.jsonl`. The ambient triangle count represents active async work units: each running async run contributes at least one triangle, and a run with multiple active parallel command/subagent branches contributes the reported active branch count. If a coordinator starts one parent run with four active parallel branches, four triangles are shown; if the same coordinator starts five independent single-branch runs, five triangles are shown.
|
|
178
178
|
|
|
179
179
|
## Run Actor Messages
|
|
180
180
|
|
|
181
|
-
A recipe or script may append coordinator-bound actor message records to:
|
|
181
|
+
A recipe or script may append coordinator-bound or session-bound actor message records to:
|
|
182
182
|
|
|
183
183
|
```text
|
|
184
184
|
<state_dir>/outbox.jsonl
|
|
@@ -200,7 +200,7 @@ Shape:
|
|
|
200
200
|
|
|
201
201
|
`level` is `info`, `warning`, or `error`. The public message describes sender, receiver, type, summary, and body; it does not choose notification mechanics. Runtime attention policy infers whether a coordinator-bound message stays available for explicit `inspect`, becomes a UI notification, or re-enters the launching coordinator as compact follow-up context.
|
|
202
202
|
|
|
203
|
-
Use coordinator-bound messages for completion and decision points, not for every progress tick. Packaged multi-agent branch completion is a completion message and should bubble by default. Follow-up path lists use Markdown hierarchy: a section heading, `- Base: ...`, and `- Files: ...`, so repeated run-state prefixes do not flood agent context.
|
|
203
|
+
Use coordinator/session-bound messages for completion and decision points, not for every progress tick. Packaged multi-agent branch completion is a completion message and should bubble by default. Follow-up path lists use Markdown hierarchy: a section heading, `- Base: ...`, and `- Files: ...`, so repeated run-state prefixes do not flood agent context.
|
|
204
204
|
|
|
205
205
|
## Cancellation And Ownership
|
|
206
206
|
|
|
@@ -32,7 +32,7 @@ Keep components narrow. Higher-level recipes should own composition, not hidden
|
|
|
32
32
|
|
|
33
33
|
Start one subagent or branch with a caller-provided prompt and model. Launchers do not judge or merge output.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Examples: `recipes/subagent-prompt.json`, `recipes/subagent-tools.json`, and `recipes/subagents-prompts.json`.
|
|
36
36
|
|
|
37
37
|
### Reviewers
|
|
38
38
|
|
package/docs/recipe-library.md
CHANGED
|
@@ -46,7 +46,7 @@ Core subagent recipes:
|
|
|
46
46
|
- `recipes/subagent-followup.json`: Same-context or degraded continuation.
|
|
47
47
|
- `recipes/subagent-judge.json`: Post-merge/report quality judge.
|
|
48
48
|
|
|
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. 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.
|
|
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. The 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
50
|
|
|
51
51
|
Register one atom:
|
|
52
52
|
|
|
@@ -82,6 +82,7 @@ Pipeline recipes demonstrate second-order composition:
|
|
|
82
82
|
- `recipes/pipeline-media-library.json`: Playlist build → media-library artifact report.
|
|
83
83
|
- `recipes/pipeline-artifact-report.json`: Normalize → artifact-shaped output → actor-message-shaped record. This pipeline prepares a candidate artifact and emits `artifact.prepared`/`artifact.blocked`; the `artifact_path` is a target path, not a guarantee that the file was written.
|
|
84
84
|
- `recipes/pipeline-artifact-write.json`: Normalize → artifact-shaped output → deterministic artifact write → actor-message-shaped record. Use only when the caller explicitly wants filesystem writes; `write_mode` is `create`, `overwrite`, or `append`.
|
|
85
|
+
- `recipes/pipeline-artifact-bundle.json`: Optional validation → deterministic artifact write → machine-readable manifest generation → deterministic manifest write → actor-message-shaped record. Use when the caller explicitly wants a filesystem handoff bundle with both artifact and manifest paths.
|
|
85
86
|
|
|
86
87
|
These are examples of library composition, not a workflow DSL. Pipeline recipes declare mailbox metadata for their high-level completion, artifact, and control message surface. The recipe layer owns imports and saved defaults; command templates own execution shape; async runs own lifecycle.
|
|
87
88
|
|
|
@@ -98,6 +99,7 @@ Utility recipes cover local operator workflows that do not need subagents:
|
|
|
98
99
|
- `recipes/utility-changelog-head.json`: Read the top slice of a changelog for release summary prep.
|
|
99
100
|
- `recipes/utility-playlist-scan.json`: List local media files as playlist-building input.
|
|
100
101
|
- `recipes/utility-run-summary.json`: Use `scripts/recipe-utils.mjs` to summarize async run state files as JSON.
|
|
102
|
+
- `recipes/utility-run-ops-snapshot.json`: Combine async run summaries, event-tail JSONL, and stale/terminal recommendations into one structured operations snapshot.
|
|
101
103
|
- `recipes/utility-playlist-build.json`: Use `scripts/recipe-utils.mjs` to build a filtered playlist listing as newline paths, M3U, or inline `|`-separated source.
|
|
102
104
|
- `recipes/utility-changelog-section.json`: Use `scripts/recipe-utils.mjs` to extract one changelog release section.
|
|
103
105
|
- `recipes/utility-artifact-manifest.json`: Use `scripts/recipe-utils.mjs` to emit a machine-readable JSON manifest for an artifact path.
|
|
@@ -173,4 +175,4 @@ Message body is currently adapted to one newline-delimited command written to `<
|
|
|
173
175
|
- Only play trusted local files or URLs.
|
|
174
176
|
- Volume is clamped to `0..100` by the wrapper.
|
|
175
177
|
- Prefer a stable `run_id` such as `music` when the operator expects to control the run by name.
|
|
176
|
-
- Use `message type=
|
|
178
|
+
- Use `message type=control.kill` only when graceful `control.stop` cancellation fails.
|
|
@@ -108,7 +108,7 @@ Existing seeds:
|
|
|
108
108
|
|
|
109
109
|
Implemented seed:
|
|
110
110
|
|
|
111
|
-
- `pipeline-async-run-ops`: run
|
|
111
|
+
- `pipeline-async-run-ops`: structured run operations snapshot → normalized operations report → artifact report. The snapshot combines run summary, event tail, and recommended inspect/control messages before the LLM normalization step.
|
|
112
112
|
|
|
113
113
|
### Research Brief Cell
|
|
114
114
|
|
|
@@ -227,7 +227,7 @@ Prefer adding a high-level recipe when at least three cells already exist and th
|
|
|
227
227
|
Good next candidates for the standard library after the first task-first wave:
|
|
228
228
|
|
|
229
229
|
1. Package/release metadata enrichment: use `utility-package-summary` with changelog and validation cells to make release-readiness reports more evidence-rich without adding publish automation.
|
|
230
|
-
2. Artifact packaging and manifesting:
|
|
230
|
+
2. Artifact packaging and manifesting: implemented as `pipeline-artifact-bundle`, which composes optional validation, `pipeline-artifact-write`, `utility-artifact-manifest`, deterministic manifest writing, and an actor-message handoff when the caller explicitly requests filesystem writes.
|
|
231
231
|
3. Async run cleanup planning: extend async-run operations with stale-run classification and recommended `message`, `cancel`, or `kill` controls, keeping actual control execution operator-gated.
|
|
232
232
|
|
|
233
|
-
Each candidate should land with the minimum missing cells rather than a broad one-shot framework. Already implemented task-first seeds include `pipeline-release-readiness`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`, and `pipeline-
|
|
233
|
+
Each candidate should land with the minimum missing cells rather than a broad one-shot framework. Already implemented task-first seeds include `pipeline-release-readiness`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`, `pipeline-media-library`, and `pipeline-artifact-bundle`.
|
package/index.ts
CHANGED
|
@@ -194,5 +194,9 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
|
|
|
194
194
|
getTool: (name) => actorToolDefinitions.get(name),
|
|
195
195
|
}),
|
|
196
196
|
);
|
|
197
|
-
pi.registerTool(
|
|
197
|
+
pi.registerTool(
|
|
198
|
+
Tools.createInspectToolDefinition<ExtensionContext>({
|
|
199
|
+
getTool: (name) => actorToolDefinitions.get(name),
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
198
202
|
}
|
package/lib/tools.ts
CHANGED
|
@@ -216,6 +216,15 @@ function compactSessionRuns(session: string, runs: Array<Record<string, unknown>
|
|
|
216
216
|
.join("\n")}`;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
function compactToolActor(name: string, tool: Record<string, unknown>): string {
|
|
220
|
+
const parameters = asRecord(tool.parameters);
|
|
221
|
+
const required = Array.isArray(parameters.required)
|
|
222
|
+
? parameters.required.join(",")
|
|
223
|
+
: "";
|
|
224
|
+
const properties = asRecord(parameters.properties);
|
|
225
|
+
return `\ntool=${name} description=${String(tool.description ?? "").replaceAll(/\s+/g, "_")} args=${Object.keys(properties).join(",")} required=${required}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
219
228
|
function compactActorMessageResult(
|
|
220
229
|
message: ActorMessages.ActorMessage,
|
|
221
230
|
result: Record<string, unknown>,
|
|
@@ -379,17 +388,44 @@ export function createSpawnToolDefinition<
|
|
|
379
388
|
};
|
|
380
389
|
}
|
|
381
390
|
|
|
382
|
-
export
|
|
391
|
+
export interface InspectToolDeps<TContext = unknown> {
|
|
392
|
+
getTool?: (name: string) => any | undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function getContextSessionId(ctx: unknown): string | undefined {
|
|
396
|
+
return (ctx as AsyncRunToolContext | undefined)?.sessionManager?.getSessionId?.();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function requireContextSessionId(ctx: unknown, actor: string): string {
|
|
400
|
+
const sessionId = getContextSessionId(ctx);
|
|
401
|
+
if (!sessionId) {
|
|
402
|
+
throw new Error(`${actor} requires a current coordinator session; use session:<id> or session:all for explicit session inventory.`);
|
|
403
|
+
}
|
|
404
|
+
return sessionId;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function assertRunAccessibleToContext(runId: string, ctx: unknown): Record<string, unknown> {
|
|
408
|
+
const status = AsyncRuns.getRunStatus(runId);
|
|
409
|
+
const sessionId = getContextSessionId(ctx);
|
|
410
|
+
if (sessionId && status.ownerId && status.ownerId !== sessionId) {
|
|
411
|
+
throw new Error(`run:${runId} is owned by session:${status.ownerId}; current session is ${sessionId}.`);
|
|
412
|
+
}
|
|
413
|
+
return status;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function createInspectToolDefinition<TContext = unknown>(
|
|
417
|
+
deps: InspectToolDeps<TContext> = {},
|
|
418
|
+
): any {
|
|
383
419
|
return {
|
|
384
420
|
name: "inspect",
|
|
385
421
|
label: "Inspect",
|
|
386
422
|
description:
|
|
387
|
-
"Intentionally inspect an actor. Supports run:<id> views: status, tail, events, artifacts, files, mailbox; and
|
|
423
|
+
"Intentionally inspect an actor. Supports run:<id> views: status, tail, events, artifacts, files, mailbox; coordinator/session status; and tool:<name> status/schema.",
|
|
388
424
|
parameters: objectSchema(
|
|
389
425
|
{
|
|
390
426
|
lines: stringSchema("Line count for tail/events views. Default 40."),
|
|
391
427
|
status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
|
|
392
|
-
target: stringSchema("Actor address to inspect, e.g. run:<id>, session:<id>,
|
|
428
|
+
target: stringSchema("Actor address to inspect, e.g. run:<id>, coordinator, session:<id>, session:all, or tool:<name>."),
|
|
393
429
|
verbose: booleanSchema("Return full JSON instead of compact text where available."),
|
|
394
430
|
view: stringSchema("Inspection view: status, tail, events, artifacts, files, or mailbox."),
|
|
395
431
|
},
|
|
@@ -400,12 +436,30 @@ export function createInspectToolDefinition(): any {
|
|
|
400
436
|
params: unknown,
|
|
401
437
|
_signal: AbortSignal | undefined,
|
|
402
438
|
_onUpdate: unknown,
|
|
403
|
-
|
|
439
|
+
ctx: TContext,
|
|
404
440
|
) {
|
|
405
441
|
const input = asRecord(params);
|
|
406
442
|
const target = String(input.target ?? "");
|
|
407
443
|
const address = ActorMessages.parseActorAddress(target);
|
|
408
444
|
const view = String(input.view ?? "");
|
|
445
|
+
if (address.kind === "coordinator") {
|
|
446
|
+
if (view !== "status" && view !== "runs") {
|
|
447
|
+
throw new Error("inspect coordinator supports view=status or view=runs.");
|
|
448
|
+
}
|
|
449
|
+
const session = requireContextSessionId(ctx, "inspect coordinator");
|
|
450
|
+
const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
|
|
451
|
+
.map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
|
|
452
|
+
.filter((run) => run.ownerId === session);
|
|
453
|
+
return {
|
|
454
|
+
content: [
|
|
455
|
+
{
|
|
456
|
+
type: "text" as const,
|
|
457
|
+
text: maybeJsonText({ session, runs }, input.verbose === true, compactSessionRuns(session, runs)),
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
details: { session, runs },
|
|
461
|
+
};
|
|
462
|
+
}
|
|
409
463
|
if (address.kind === "session") {
|
|
410
464
|
if (view !== "status" && view !== "runs") {
|
|
411
465
|
throw new Error("inspect session:<id> supports view=status or view=runs.");
|
|
@@ -423,11 +477,33 @@ export function createInspectToolDefinition(): any {
|
|
|
423
477
|
details: { session: address.value, runs },
|
|
424
478
|
};
|
|
425
479
|
}
|
|
480
|
+
if (address.kind === "tool" && address.value) {
|
|
481
|
+
if (view !== "status" && view !== "schema") {
|
|
482
|
+
throw new Error("inspect tool:<name> supports view=status or view=schema.");
|
|
483
|
+
}
|
|
484
|
+
const tool = deps.getTool?.(address.value);
|
|
485
|
+
if (!tool) throw new Error(`tool actor not found: ${address.value}`);
|
|
486
|
+
const details = {
|
|
487
|
+
name: address.value,
|
|
488
|
+
description: tool.description,
|
|
489
|
+
parameters: tool.parameters,
|
|
490
|
+
promptSnippet: tool.promptSnippet,
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
content: [
|
|
494
|
+
{
|
|
495
|
+
type: "text" as const,
|
|
496
|
+
text: maybeJsonText(details, input.verbose === true || view === "schema", compactToolActor(address.value, details)),
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
details,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
426
502
|
const runId = address.kind === "run" ? address.value : undefined;
|
|
427
|
-
if (!runId) throw new Error("inspect target must be run:<id
|
|
503
|
+
if (!runId) throw new Error("inspect target must be run:<id>, coordinator, session:<id>, or tool:<name>.");
|
|
428
504
|
switch (view) {
|
|
429
505
|
case "status": {
|
|
430
|
-
const status =
|
|
506
|
+
const status = assertRunAccessibleToContext(runId, ctx);
|
|
431
507
|
return {
|
|
432
508
|
content: [
|
|
433
509
|
{
|
|
@@ -439,10 +515,12 @@ export function createInspectToolDefinition(): any {
|
|
|
439
515
|
};
|
|
440
516
|
}
|
|
441
517
|
case "tail": {
|
|
518
|
+
assertRunAccessibleToContext(runId, ctx);
|
|
442
519
|
const text = AsyncRuns.tailRun(runId, Number(input.lines || 40));
|
|
443
520
|
return { content: [{ type: "text" as const, text: `\n${text}` }], details: {} };
|
|
444
521
|
}
|
|
445
522
|
case "events": {
|
|
523
|
+
assertRunAccessibleToContext(runId, ctx);
|
|
446
524
|
const events = AsyncRuns.readRunEvents(runId, Number(input.lines || 40));
|
|
447
525
|
return {
|
|
448
526
|
content: [
|
|
@@ -456,7 +534,7 @@ export function createInspectToolDefinition(): any {
|
|
|
456
534
|
}
|
|
457
535
|
case "artifacts":
|
|
458
536
|
case "files": {
|
|
459
|
-
const status =
|
|
537
|
+
const status = assertRunAccessibleToContext(runId, ctx);
|
|
460
538
|
return {
|
|
461
539
|
content: [
|
|
462
540
|
{
|
|
@@ -468,7 +546,7 @@ export function createInspectToolDefinition(): any {
|
|
|
468
546
|
};
|
|
469
547
|
}
|
|
470
548
|
case "mailbox": {
|
|
471
|
-
const status =
|
|
549
|
+
const status = assertRunAccessibleToContext(runId, ctx);
|
|
472
550
|
const mailbox = asRecord(status.mailbox);
|
|
473
551
|
return {
|
|
474
552
|
content: [
|
|
@@ -498,7 +576,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
498
576
|
name: "message",
|
|
499
577
|
label: "Message",
|
|
500
578
|
description:
|
|
501
|
-
"Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, tool:<name> calls, and coordinator-bound run messages.",
|
|
579
|
+
"Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, tool:<name> calls, and coordinator/session-bound run messages.",
|
|
502
580
|
parameters: objectSchema(
|
|
503
581
|
{
|
|
504
582
|
body: unionSchema([
|
|
@@ -529,9 +607,10 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
529
607
|
const address = ActorMessages.parseActorAddress(message.to);
|
|
530
608
|
let result: Record<string, unknown>;
|
|
531
609
|
if (address.kind === "run" && address.value) {
|
|
532
|
-
|
|
610
|
+
assertRunAccessibleToContext(address.value, ctx);
|
|
611
|
+
if (message.type === "control.stop" || message.type === "control.cancel" || message.type === "runtime.cancel") {
|
|
533
612
|
result = AsyncRuns.cancelRun(address.value);
|
|
534
|
-
} else if (message.type === "runtime.kill") {
|
|
613
|
+
} else if (message.type === "control.kill" || message.type === "runtime.kill") {
|
|
535
614
|
result = AsyncRuns.killRun(address.value);
|
|
536
615
|
} else {
|
|
537
616
|
result = AsyncRuns.sendRunMessage(
|
|
@@ -540,6 +619,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
540
619
|
);
|
|
541
620
|
}
|
|
542
621
|
} else if (address.kind === "branch" && address.value) {
|
|
622
|
+
assertRunAccessibleToContext(address.value, ctx);
|
|
543
623
|
result = AsyncRuns.sendRunMessage(
|
|
544
624
|
address.value,
|
|
545
625
|
JSON.stringify(message),
|
|
@@ -562,17 +642,27 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
562
642
|
tool: address.value,
|
|
563
643
|
tool_result: toolResult,
|
|
564
644
|
};
|
|
565
|
-
} else if (address.kind === "coordinator") {
|
|
645
|
+
} else if (address.kind === "coordinator" || address.kind === "session") {
|
|
566
646
|
if (!message.from) {
|
|
567
|
-
throw new Error(
|
|
647
|
+
throw new Error(`message to ${address.kind} requires from=run:<id>.`);
|
|
568
648
|
}
|
|
569
649
|
const sender = ActorMessages.parseActorAddress(message.from);
|
|
570
650
|
if (sender.kind !== "run" || !sender.value) {
|
|
571
|
-
throw new Error(
|
|
651
|
+
throw new Error(`message to ${address.kind} currently requires from=run:<id>.`);
|
|
652
|
+
}
|
|
653
|
+
const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
|
|
654
|
+
if (address.kind === "session") {
|
|
655
|
+
if (!senderStatus.ownerId) {
|
|
656
|
+
throw new Error(`message to session:${address.value} requires sender run owner ${address.value}; got no owner.`);
|
|
657
|
+
}
|
|
658
|
+
if (senderStatus.ownerId !== address.value) {
|
|
659
|
+
throw new Error(`message to session:${address.value} requires sender run owner ${address.value}; got ${senderStatus.ownerId}.`);
|
|
660
|
+
}
|
|
572
661
|
}
|
|
573
662
|
result = AsyncRuns.appendRunOutboxEvent(sender.value, {
|
|
574
663
|
body: message.body,
|
|
575
664
|
correlation_id: message.correlation_id,
|
|
665
|
+
delivery: address.kind === "session" ? "followup" : undefined,
|
|
576
666
|
event: message.type,
|
|
577
667
|
from: message.from,
|
|
578
668
|
metadata: message.metadata,
|
|
@@ -583,7 +673,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
583
673
|
});
|
|
584
674
|
} else {
|
|
585
675
|
throw new Error(
|
|
586
|
-
`message currently supports run:<id>, branch:<run>/<branch>, tool:<name>, and
|
|
676
|
+
`message currently supports run:<id>, branch:<run>/<branch>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
|
|
587
677
|
);
|
|
588
678
|
}
|
|
589
679
|
return {
|
package/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pipeline-artifact-bundle",
|
|
3
|
+
"async": true,
|
|
4
|
+
"imports": {
|
|
5
|
+
"artifactWrite": "pipeline-artifact-write.json",
|
|
6
|
+
"manifest": "utility-artifact-manifest.json",
|
|
7
|
+
"manifestWrite": "utility-artifact-write.json",
|
|
8
|
+
"validation": "utility-validation-wrapper.json",
|
|
9
|
+
"message": "utility-actor-message.json"
|
|
10
|
+
},
|
|
11
|
+
"args": [
|
|
12
|
+
"input:string",
|
|
13
|
+
"artifact_path:path",
|
|
14
|
+
"manifest_path:path",
|
|
15
|
+
"artifact_title:string",
|
|
16
|
+
"artifact_format:string",
|
|
17
|
+
"artifact_status:enum(draft,ready,blocked,accepted)",
|
|
18
|
+
"write_mode:enum(create,overwrite,append)",
|
|
19
|
+
"run_validation:bool",
|
|
20
|
+
"validation_command:string",
|
|
21
|
+
"validation_scope:string",
|
|
22
|
+
"validation_timeout_ms:int",
|
|
23
|
+
"model:string",
|
|
24
|
+
"tools:string"
|
|
25
|
+
],
|
|
26
|
+
"defaults": {
|
|
27
|
+
"artifact_title": "Artifact Bundle",
|
|
28
|
+
"artifact_format": "Markdown sections: Summary, Findings, Evidence, Risks, Next Actions.",
|
|
29
|
+
"artifact_status": "ready",
|
|
30
|
+
"write_mode": "create",
|
|
31
|
+
"run_validation": "false",
|
|
32
|
+
"validation_command": "true",
|
|
33
|
+
"validation_scope": ".",
|
|
34
|
+
"validation_timeout_ms": "300000",
|
|
35
|
+
"model": "openai-codex/gpt-5.5",
|
|
36
|
+
"tools": ""
|
|
37
|
+
},
|
|
38
|
+
"mailbox": {
|
|
39
|
+
"accepts": ["control.stop"],
|
|
40
|
+
"emits": ["artifact.bundle_ready", "artifact.written", "artifact.blocked", "run.done", "run.failed"]
|
|
41
|
+
},
|
|
42
|
+
"artifacts": {
|
|
43
|
+
"artifact": "{artifact_path}",
|
|
44
|
+
"manifest": "{manifest_path}"
|
|
45
|
+
},
|
|
46
|
+
"template": [
|
|
47
|
+
{
|
|
48
|
+
"when": "run_validation",
|
|
49
|
+
"name": "validation",
|
|
50
|
+
"values": {
|
|
51
|
+
"command": "{validation_command}",
|
|
52
|
+
"scope": "{validation_scope}",
|
|
53
|
+
"timeout_ms": "{validation_timeout_ms}"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "artifactWrite",
|
|
58
|
+
"values": {
|
|
59
|
+
"input": "{input}",
|
|
60
|
+
"artifact_path": "{artifact_path}",
|
|
61
|
+
"artifact_format": "{artifact_format}",
|
|
62
|
+
"write_mode": "{write_mode}",
|
|
63
|
+
"model": "{model}",
|
|
64
|
+
"tools": "{tools}"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "manifest",
|
|
69
|
+
"values": {
|
|
70
|
+
"artifact_path": "{artifact_path}",
|
|
71
|
+
"title": "{artifact_title}",
|
|
72
|
+
"status": "{artifact_status}",
|
|
73
|
+
"summary": "Artifact bundle manifest for {artifact_path}."
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "manifestWrite",
|
|
78
|
+
"values": {
|
|
79
|
+
"artifact_path": "{manifest_path}",
|
|
80
|
+
"mode": "{write_mode}"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "message",
|
|
85
|
+
"values": {
|
|
86
|
+
"type": "artifact.bundle_ready",
|
|
87
|
+
"summary": "Artifact bundle ready: {artifact_path} with manifest {manifest_path}.",
|
|
88
|
+
"metadata": "{\"artifact\":\"{artifact_path}\",\"manifest\":\"{manifest_path}\"}"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
"name": "pipeline-async-run-ops",
|
|
3
3
|
"async": true,
|
|
4
4
|
"imports": {
|
|
5
|
-
"
|
|
6
|
-
"events": "utility-jsonl-tail.json",
|
|
5
|
+
"snapshot": "utility-run-ops-snapshot.json",
|
|
7
6
|
"normalizer": "subagent-normalize.json",
|
|
8
7
|
"artifact": "pipeline-artifact-report.json"
|
|
9
8
|
},
|
|
@@ -11,6 +10,7 @@
|
|
|
11
10
|
"state_root:path",
|
|
12
11
|
"event_file:path",
|
|
13
12
|
"lines:int",
|
|
13
|
+
"stale_minutes:int",
|
|
14
14
|
"artifact_path:path",
|
|
15
15
|
"model:string",
|
|
16
16
|
"tools:string"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"state_root": "~/.pi/agent/tmp/pi-actors/runs",
|
|
20
20
|
"event_file": "~/.pi/agent/tmp/pi-actors/runs/music/outbox.jsonl",
|
|
21
21
|
"lines": "80",
|
|
22
|
+
"stale_minutes": "60",
|
|
22
23
|
"artifact_path": "./async-run-ops.md",
|
|
23
24
|
"model": "openai-codex/gpt-5.5",
|
|
24
25
|
"tools": ""
|
|
@@ -29,24 +30,18 @@
|
|
|
29
30
|
},
|
|
30
31
|
"template": [
|
|
31
32
|
{
|
|
32
|
-
"name": "
|
|
33
|
+
"name": "snapshot",
|
|
33
34
|
"values": {
|
|
34
|
-
"state_root": "{state_root}"
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"name": "events",
|
|
39
|
-
"failure": "continue",
|
|
40
|
-
"values": {
|
|
41
|
-
"file": "{event_file}",
|
|
35
|
+
"state_root": "{state_root}",
|
|
36
|
+
"event_file": "{event_file}",
|
|
42
37
|
"lines": "{lines}",
|
|
43
|
-
"
|
|
38
|
+
"stale_minutes": "{stale_minutes}"
|
|
44
39
|
}
|
|
45
40
|
},
|
|
46
41
|
{
|
|
47
42
|
"name": "normalizer",
|
|
48
43
|
"values": {
|
|
49
|
-
"input": "Use async run
|
|
44
|
+
"input": "Use async run operations snapshot JSON from stdin. State root: {state_root}. Event file: {event_file}.",
|
|
50
45
|
"format": "Markdown sections: Active Runs, Terminal Runs, Recent Events, Stale/Risky Runs, Recommended Controls, Follow-up Needed.",
|
|
51
46
|
"model": "{model}",
|
|
52
47
|
"thinking": "off",
|
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
"args": [
|
|
5
5
|
"prompt:string",
|
|
6
6
|
"tools:string",
|
|
7
|
-
"model:string"
|
|
7
|
+
"model:string",
|
|
8
|
+
"thinking:string",
|
|
9
|
+
"output_format:string"
|
|
8
10
|
],
|
|
9
11
|
"defaults": {
|
|
10
|
-
"model": "openai-codex/gpt-5.5"
|
|
12
|
+
"model": "openai-codex/gpt-5.5",
|
|
13
|
+
"thinking": "off",
|
|
14
|
+
"output_format": "Concise Markdown with assumptions and next actions."
|
|
11
15
|
},
|
|
12
16
|
"mailbox": {
|
|
13
17
|
"accepts": ["control.stop"],
|
|
14
18
|
"emits": ["command.done", "run.done", "run.failed"]
|
|
15
19
|
},
|
|
16
|
-
"template": "pi -p --model {model} --tools {tools} {prompt}"
|
|
20
|
+
"template": "pi -p --model {model} --thinking {thinking} --tools {tools} {prompt}. Output format: {output_format}"
|
|
17
21
|
}
|
|
@@ -6,10 +6,18 @@
|
|
|
6
6
|
},
|
|
7
7
|
"args": [
|
|
8
8
|
"prompts:array",
|
|
9
|
+
"model:string",
|
|
10
|
+
"thinking:string",
|
|
11
|
+
"tools:string",
|
|
12
|
+
"output_format:string",
|
|
9
13
|
"report_path:path",
|
|
10
14
|
"summary_path:path"
|
|
11
15
|
],
|
|
12
16
|
"defaults": {
|
|
17
|
+
"model": "openai-codex/gpt-5.5",
|
|
18
|
+
"thinking": "off",
|
|
19
|
+
"tools": "",
|
|
20
|
+
"output_format": "Concise Markdown with assumptions and next actions.",
|
|
13
21
|
"report_path": "{state_dir}/stdout.log",
|
|
14
22
|
"summary_path": "{state_dir}/result.json"
|
|
15
23
|
},
|
|
@@ -26,7 +34,11 @@
|
|
|
26
34
|
"template": {
|
|
27
35
|
"name": "subagent",
|
|
28
36
|
"values": {
|
|
29
|
-
"prompt": "{prompts[index]}"
|
|
37
|
+
"prompt": "{prompts[index]}",
|
|
38
|
+
"model": "{model}",
|
|
39
|
+
"thinking": "{thinking}",
|
|
40
|
+
"tools": "{tools}",
|
|
41
|
+
"output_format": "{output_format}"
|
|
30
42
|
}
|
|
31
43
|
}
|
|
32
44
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "utility-run-ops-snapshot",
|
|
3
|
+
"args": [
|
|
4
|
+
"repo:path",
|
|
5
|
+
"state_root:path",
|
|
6
|
+
"event_file:path",
|
|
7
|
+
"lines:int",
|
|
8
|
+
"stale_minutes:int"
|
|
9
|
+
],
|
|
10
|
+
"defaults": {
|
|
11
|
+
"repo": "~/.pi/agent/extensions/pi-actors",
|
|
12
|
+
"state_root": "~/.pi/agent/tmp/pi-actors/runs",
|
|
13
|
+
"event_file": "~/.pi/agent/tmp/pi-actors/runs/music/outbox.jsonl",
|
|
14
|
+
"lines": "80",
|
|
15
|
+
"stale_minutes": "60"
|
|
16
|
+
},
|
|
17
|
+
"template": "{repo}/scripts/recipe-utils.mjs run-ops-snapshot {state_root} {event_file} {lines} {stale_minutes}"
|
|
18
|
+
}
|
package/scripts/recipe-utils.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, extname, join, relative, resolve } from "node:path";
|
|
|
5
5
|
function usage() {
|
|
6
6
|
console.error(`Usage:
|
|
7
7
|
recipe-utils.mjs run-summary <state-root>
|
|
8
|
+
recipe-utils.mjs run-ops-snapshot <state-root> <event-file> [lines] [stale-minutes]
|
|
8
9
|
recipe-utils.mjs playlist <source-dir> [extensions] [max-depth] [paths|m3u|inline]
|
|
9
10
|
recipe-utils.mjs changelog-section <file> <version>
|
|
10
11
|
recipe-utils.mjs artifact-manifest <artifact-path> <title> <status> [summary]
|
|
@@ -75,7 +76,7 @@ function getRunStatus(run, progress, result) {
|
|
|
75
76
|
return run.status ?? "unknown";
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
function
|
|
79
|
+
function collectRunSummary(rootValue) {
|
|
79
80
|
const root = resolve(
|
|
80
81
|
rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"),
|
|
81
82
|
);
|
|
@@ -105,7 +106,43 @@ function runSummary(rootValue) {
|
|
|
105
106
|
rows.sort((a, b) =>
|
|
106
107
|
`${a.status}:${a.run}`.localeCompare(`${b.status}:${b.run}`),
|
|
107
108
|
);
|
|
108
|
-
|
|
109
|
+
return rows;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function runSummary(rootValue) {
|
|
113
|
+
console.log(JSON.stringify(collectRunSummary(rootValue), null, 2));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function tailJsonl(fileValue, linesValue = "80") {
|
|
117
|
+
const file = resolve(fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
|
|
118
|
+
if (!existsSync(file)) return [];
|
|
119
|
+
const lines = Number.parseInt(linesValue, 10);
|
|
120
|
+
const count = Number.isFinite(lines) && lines > 0 ? lines : 80;
|
|
121
|
+
return readFileSync(file, "utf8").trimEnd().split("\n").filter(Boolean).slice(-count).map((line) => {
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(line);
|
|
124
|
+
} catch {
|
|
125
|
+
return { raw: line };
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function runOpsSnapshot(rootValue, eventFileValue, linesValue = "80", staleMinutesValue = "60") {
|
|
131
|
+
const runs = collectRunSummary(rootValue);
|
|
132
|
+
const staleMs = Number(staleMinutesValue) * 60 * 1000;
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
const recommendations = runs.flatMap((run) => {
|
|
135
|
+
const updatedMs = Date.parse(run.updated || "");
|
|
136
|
+
const stale = Number.isFinite(updatedMs) && Number.isFinite(staleMs) && now - updatedMs > staleMs;
|
|
137
|
+
if (run.status === "running" && stale) {
|
|
138
|
+
return [{ run: run.run, reason: "running-stale", suggested_message: `message to=run:${run.run} type=control.stop body=stop` }];
|
|
139
|
+
}
|
|
140
|
+
if (["failed", "exited", "killed"].includes(run.status)) {
|
|
141
|
+
return [{ run: run.run, reason: `terminal-${run.status}`, suggested_inspect: `inspect target=run:${run.run} view=tail` }];
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
});
|
|
145
|
+
console.log(JSON.stringify({ runs, events: tailJsonl(eventFileValue, linesValue), recommendations }, null, 2));
|
|
109
146
|
}
|
|
110
147
|
|
|
111
148
|
function playlist(
|
|
@@ -255,6 +292,8 @@ if (!command) {
|
|
|
255
292
|
|
|
256
293
|
if (command === "run-summary")
|
|
257
294
|
runSummary(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs");
|
|
295
|
+
else if (command === "run-ops-snapshot")
|
|
296
|
+
runOpsSnapshot(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs", args[1] ?? "~/.pi/agent/tmp/pi-actors/runs/music/outbox.jsonl", args[2], args[3]);
|
|
258
297
|
else if (command === "playlist")
|
|
259
298
|
playlist(args[0] ?? "~/Music", args[1], args[2], args[3]);
|
|
260
299
|
else if (command === "changelog-section")
|