@llblab/pi-actors 0.51.0 → 0.52.1
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/AGENTS.md +6 -3
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +3 -3
- package/dist/index.js +3 -0
- package/dist/lib/async-runs.d.ts +2 -2
- package/dist/lib/async-runs.js +42 -19
- package/dist/lib/extension-runtime.d.ts +1 -0
- package/dist/lib/extension-runtime.js +6 -1
- package/dist/lib/limits.d.ts +9 -0
- package/dist/lib/limits.js +9 -0
- package/dist/lib/observability.d.ts +10 -11
- package/dist/lib/observability.js +79 -56
- package/dist/lib/pi.d.ts +31 -0
- package/dist/lib/pi.js +180 -0
- package/dist/lib/run-delivery.d.ts +115 -0
- package/dist/lib/run-delivery.js +623 -0
- package/dist/lib/run-ui-runtime.d.ts +3 -0
- package/dist/lib/run-ui-runtime.js +341 -13
- package/dist/lib/runs-trace.d.ts +1 -1
- package/dist/lib/runs-trace.js +5 -3
- package/dist/lib/session-evidence.d.ts +16 -0
- package/dist/lib/session-evidence.js +143 -0
- package/dist/lib/temp.js +1 -1
- package/dist/lib/tools-inspect.js +3 -1
- package/dist/skills/actors/SKILL.md +17 -7
- package/dist/skills/actors/references/runs.md +1 -1
- package/dist/skills/music-player/SKILL.md +3 -3
- package/dist/skills/music-player/genapps/music-player.mjs +6 -4
- package/dist/skills/music-player/scripts/playback.mjs +85 -18
- package/dist/skills/swarm/SKILL.md +2 -6
- package/dist/skills/swarm/references/development-swarm.md +2 -31
- package/docs/README.md +1 -0
- package/docs/async-runs.md +4 -3
- package/docs/coordinator-delivery.md +207 -0
- package/docs/recipe-library.md +1 -1
- package/index.ts +3 -0
- package/lib/async-runs.ts +42 -21
- package/lib/extension-runtime.ts +6 -1
- package/lib/limits.ts +9 -0
- package/lib/observability.ts +96 -78
- package/lib/pi.ts +210 -0
- package/lib/run-delivery.ts +800 -0
- package/lib/run-ui-runtime.ts +370 -18
- package/lib/runs-trace.ts +6 -4
- package/lib/session-evidence.ts +153 -0
- package/lib/temp.ts +1 -1
- package/lib/tools-inspect.ts +4 -1
- package/package.json +1 -1
- package/skills/actors/SKILL.md +17 -7
- package/skills/actors/references/runs.md +1 -1
- package/skills/music-player/SKILL.md +3 -3
- package/skills/music-player/genapps/music-player.mjs +6 -4
- package/skills/music-player/scripts/playback.mjs +85 -18
- package/skills/swarm/SKILL.md +2 -6
- package/skills/swarm/references/development-swarm.md +2 -31
- package/dist/skills/music-player/scripts/playback-client.mjs +0 -143
- package/skills/music-player/scripts/playback-client.mjs +0 -143
|
@@ -76,16 +76,26 @@ Then:
|
|
|
76
76
|
|
|
77
77
|
Use direct delegation for the same maintained capability under a persistent name or narrower defaults. Use named imports only when one Recipe graph contains reusable child nodes. See [persistent tools](./references/persistent-tools.md) and [Recipes](./references/recipes.md).
|
|
78
78
|
|
|
79
|
-
##
|
|
79
|
+
## Capability map
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Recipes remain with the Skill that owns their behavior. Load that Skill for selection and caller inputs; this map is routing, not a copied Recipe contract.
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
| Owner | Recipe families |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `actors` | `command-validate`, `recipe-validate`, `jsonl-tail`, `run-summary`, `run-state-files`, `run-ops-snapshot`, `resource-locker`, `resource-locker-snapshot` |
|
|
86
|
+
| `swarm` | Lens and quorum reviews, research synthesis, architecture, development tasking, readiness, and supporting `subagent-*` components |
|
|
87
|
+
| `artifacts` | Report, write, manifest, bundle, and supporting file-write |
|
|
88
|
+
| `project-work` | Repository health, docs maintenance, release readiness/summary, Run reports, and supporting deterministic snapshots |
|
|
89
|
+
| `music-player` | The controlled `playback` singleton |
|
|
90
|
+
| `recipe-memory` | Internal automatic reviewers; use its Skill only for diagnosis/recovery |
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
Use the `actors/*` helpers only for their narrow requested result or intentional composition. Prefer public `inspect` for ordinary runtime diagnosis and the capability's primary workflow for a complete outcome.
|
|
87
93
|
|
|
88
|
-
Delegation
|
|
94
|
+
## Delegation boundary
|
|
95
|
+
|
|
96
|
+
The current Pi instance owns user authority and the final result; pi-actors creates explicit local Runs, while companion transports provide presence rather than hidden instance creation. Keep short single-boundary work inline when delegation adds no value. For several participants, reasoning allocation, quorum, or integration methodology, read `swarm`.
|
|
97
|
+
|
|
98
|
+
Treat `attention: "steer"` as an actor-authored urgent semantic checkpoint at Pi's next safe boundary, never as a status-derived completion signal; the later root terminal still arrives through its ordinary completion batch.
|
|
89
99
|
|
|
90
100
|
## Run workflow
|
|
91
101
|
|
|
@@ -97,7 +107,7 @@ Run = Recipe + Trace + Control
|
|
|
97
107
|
```
|
|
98
108
|
|
|
99
109
|
1. Spawn with the exact logical Recipe identity and caller-owned values.
|
|
100
|
-
2. Retain the returned `run:<id>` and normally wait for
|
|
110
|
+
2. Retain the returned `run:<id>` and normally wait for its settled completion batch instead of polling.
|
|
101
111
|
3. Inspect `view=trace` when retained observations or attention matter.
|
|
102
112
|
4. Inspect `view=control` before diagnosing service readiness, stale work, or saturation.
|
|
103
113
|
5. Send `message` only for an action declared and consumed by that controlled Recipe.
|
|
@@ -14,7 +14,7 @@ A rare Skill Recipe may declare `singleton: true`. Do not pass `as`: the runtime
|
|
|
14
14
|
|
|
15
15
|
## Observe
|
|
16
16
|
|
|
17
|
-
Normally wait for
|
|
17
|
+
Normally wait for the settled completion batch. Inspect only when requested, when meaningful attention arrives, or when the Run is overdue or blocked:
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
20
|
inspect target=run:<id> view=recipe
|
|
@@ -13,7 +13,7 @@ When a Telegram-originated turn or explicit Telegram-control question makes repe
|
|
|
13
13
|
|
|
14
14
|
## Playback
|
|
15
15
|
|
|
16
|
-
`music-player/playback` is a singleton async controlled service with the canonical address `run:music-player`. The Recipe is the sole lifecycle owner: it starts the service, supervises it, and stops playback when the Run closes. The service owns queue, backend, checkpoint, and playback state. `playback
|
|
16
|
+
`music-player/playback` is a singleton async controlled service with the canonical address `run:music-player`. The Recipe is the sole lifecycle owner: it starts the service, supervises it, and stops playback when the Run closes. The service owns queue, backend, checkpoint, and playback state. `scripts/playback.mjs` owns both foreground playback and the bounded control CLI. Its `control <state-dir> <action> [percent]` entrypoint observes or controls an existing owner without starting, adopting, or supervising it. Explicit foreground `serve` supports a caller-owned standalone host without importing the Actor runtime; Actor and standalone ownership of one state directory are mutually exclusive.
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
19
|
spawn recipe=music-player/playback values={"source":"~/Music","player":"auto"}
|
|
@@ -35,14 +35,14 @@ Use only declared actions: `play`, `pause`, `resume`, `toggle`, `next`, `previou
|
|
|
35
35
|
- `status` is read-only and exposes bounded machine-readable player state, including the current absolute volume and a duration-derived progress percentage projected at read time.
|
|
36
36
|
- `stop` ends the live process without silently deleting the saved queue.
|
|
37
37
|
|
|
38
|
-
External local views use
|
|
38
|
+
External local views use `playback.mjs control <state-dir> <action> [percent]`. For Actor-owned playback, the script validates Run availability, queues canonical Control, and waits for that exact record to become handled or failed. For standalone playback, it sends a bounded generation-fenced command to the service endpoint without creating Run, Control, or Trace state. Views themselves never read or edit Run files, signal processes, construct Control records, or import pi-actors internals. `status` is read-only and reports `actor_available` separately from playback state.
|
|
39
39
|
|
|
40
40
|
## Maintained Telegram View
|
|
41
41
|
|
|
42
42
|
> [!NOTE]
|
|
43
43
|
> This Skill includes a ready Music Player Generative App at `genapps/music-player.mjs`. When `telegram_bind` is available and Telegram interaction is relevant, use it to copy and install the app as `music-player`; hot replacement keeps the same app name with `replace: true`.
|
|
44
44
|
|
|
45
|
-
Bind with absolute `control
|
|
45
|
+
Bind with absolute `control` (the `scripts/playback.mjs` path), `stateDir`, and `node` arguments. This maintained adapter targets Actor-owned playback and uses the unified `control` entrypoint above for status and mutation; it displays controls only when `actor_available` is true. Exact terminal Control evidence remains visible in the Run inspector and failures reach Telegram. The adapter neither imports extension internals nor starts playback. Its stopped-state Start button returns to Pi so the composition root can spawn `music-player/playback`, while active controls remain deterministic Generative App actions that bypass the model. `pi-telegram` owns only the generic Generative App runtime.
|
|
46
46
|
|
|
47
47
|
## Sources And Backends
|
|
48
48
|
|
|
@@ -70,7 +70,7 @@ function normalizePlayback(status) {
|
|
|
70
70
|
async function readPlayback(adapter, run) {
|
|
71
71
|
const result = await run({
|
|
72
72
|
command: adapter.node,
|
|
73
|
-
args: [adapter.control, "
|
|
73
|
+
args: [adapter.control, "control", adapter.stateDir, "status"],
|
|
74
74
|
cwd: dirname(adapter.control),
|
|
75
75
|
timeoutMs: 5_000,
|
|
76
76
|
});
|
|
@@ -188,8 +188,9 @@ async function applyVolume(percent, context) {
|
|
|
188
188
|
command: context.state.adapter.node,
|
|
189
189
|
args: [
|
|
190
190
|
context.state.adapter.control,
|
|
191
|
-
"
|
|
191
|
+
"control",
|
|
192
192
|
context.state.adapter.stateDir,
|
|
193
|
+
"volume",
|
|
193
194
|
String(percent),
|
|
194
195
|
],
|
|
195
196
|
cwd: dirname(context.state.adapter.control),
|
|
@@ -224,8 +225,9 @@ async function applySeek(percent, context) {
|
|
|
224
225
|
command: context.state.adapter.node,
|
|
225
226
|
args: [
|
|
226
227
|
context.state.adapter.control,
|
|
227
|
-
"
|
|
228
|
+
"control",
|
|
228
229
|
context.state.adapter.stateDir,
|
|
230
|
+
"seek",
|
|
229
231
|
String(percent),
|
|
230
232
|
],
|
|
231
233
|
cwd: dirname(context.state.adapter.control),
|
|
@@ -253,7 +255,7 @@ async function apply(action, context) {
|
|
|
253
255
|
const before = await readPlayback(context.state.adapter, context.run);
|
|
254
256
|
const result = await context.run({
|
|
255
257
|
command: context.state.adapter.node,
|
|
256
|
-
args: [context.state.adapter.control,
|
|
258
|
+
args: [context.state.adapter.control, "control", context.state.adapter.stateDir, action],
|
|
257
259
|
cwd: dirname(context.state.adapter.control),
|
|
258
260
|
timeoutMs: 5_000,
|
|
259
261
|
});
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
watch,
|
|
28
28
|
writeFileSync,
|
|
29
29
|
} from "node:fs";
|
|
30
|
-
import { createServer } from "node:net";
|
|
30
|
+
import { createConnection, createServer } from "node:net";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import {
|
|
33
33
|
basename,
|
|
@@ -66,7 +66,7 @@ let updateRunControlStatusInStateDir;
|
|
|
66
66
|
let isAlive;
|
|
67
67
|
let verifyRunProcessIdentity;
|
|
68
68
|
let appendRunTraceEvent = () => {};
|
|
69
|
-
|
|
69
|
+
async function loadActorAdapter() {
|
|
70
70
|
({
|
|
71
71
|
appendRunControlInStateDir,
|
|
72
72
|
claimRunControlByIdInStateDir,
|
|
@@ -110,8 +110,9 @@ function usage() {
|
|
|
110
110
|
playback.mjs control <state-dir> <play|pause|toggle|next|previous|seek|volume|stop|status> [percent]
|
|
111
111
|
|
|
112
112
|
Runs a foreground music player so pi-actors can own it as a controlled Run.
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
Actor-owned controls use canonical records in <state-dir>/controls.jsonl.
|
|
114
|
+
Standalone controls use the generation-fenced playback service endpoint.
|
|
115
|
+
Prefer message target=run:<run> action=<command> for Actors; external adapters use control <state-dir> <action>.
|
|
115
116
|
Supported players: auto, mpv, afplay, ffplay, cvlc, play, wmp.
|
|
116
117
|
`);
|
|
117
118
|
}
|
|
@@ -1134,6 +1135,7 @@ function readAndClearCommand(ctx) {
|
|
|
1134
1135
|
}
|
|
1135
1136
|
|
|
1136
1137
|
async function playMain(args) {
|
|
1138
|
+
if (actorAdapterEnabled) await loadActorAdapter();
|
|
1137
1139
|
const [
|
|
1138
1140
|
sourceArg,
|
|
1139
1141
|
loopArg = "true",
|
|
@@ -1333,30 +1335,74 @@ function projectCurrentProgress(status, nowMs = Date.now()) {
|
|
|
1333
1335
|
};
|
|
1334
1336
|
}
|
|
1335
1337
|
|
|
1336
|
-
function actorControlAvailability(stateDir) {
|
|
1338
|
+
async function actorControlAvailability(stateDir) {
|
|
1337
1339
|
const run = readJsonFile(join(stateDir, "run.json"), {});
|
|
1338
1340
|
const result = readJsonFile(join(stateDir, "result.json"), {});
|
|
1339
1341
|
const endpoint = readJsonFile(join(stateDir, "control-endpoint.json"), {});
|
|
1340
1342
|
const playerStatus = readJsonFile(join(stateDir, "player.json"), {});
|
|
1343
|
+
const runInstanceId = typeof run.run_instance_id === "string"
|
|
1344
|
+
? run.run_instance_id
|
|
1345
|
+
: undefined;
|
|
1346
|
+
// Inactive status must remain readable without an installed Actor runtime,
|
|
1347
|
+
// including standalone state beside metadata from a rejected Actor launch.
|
|
1348
|
+
if (!runInstanceId || typeof result.completedAt === "string" ||
|
|
1349
|
+
endpoint.run_instance_id !== runInstanceId ||
|
|
1350
|
+
!["playing", "paused"].includes(playerStatus.state)) {
|
|
1351
|
+
return { available: false, runInstanceId };
|
|
1352
|
+
}
|
|
1353
|
+
if (!verifyRunProcessIdentity) {
|
|
1354
|
+
await loadActorAdapter();
|
|
1355
|
+
// Re-read authority after the asynchronous import before admitting control.
|
|
1356
|
+
return actorControlAvailability(stateDir);
|
|
1357
|
+
}
|
|
1341
1358
|
const pid = Number(run.pid || 0);
|
|
1342
1359
|
const hasProcessIdentity = pid > 0 || run.process_identity !== undefined;
|
|
1343
1360
|
const processIdentity = pid > 0
|
|
1344
1361
|
? verifyRunProcessIdentity(pid, run.process_identity)
|
|
1345
1362
|
: { valid: false };
|
|
1346
|
-
const available =
|
|
1347
|
-
typeof run.run_instance_id === "string" &&
|
|
1348
|
-
typeof result.completedAt !== "string" &&
|
|
1349
|
-
(!hasProcessIdentity || (pid > 0 && isAlive(pid) && processIdentity.valid === true)) &&
|
|
1350
|
-
endpoint.run_instance_id === run.run_instance_id &&
|
|
1351
|
-
["playing", "paused"].includes(playerStatus.state);
|
|
1352
1363
|
return {
|
|
1353
|
-
available
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
: undefined,
|
|
1364
|
+
available: !hasProcessIdentity ||
|
|
1365
|
+
(pid > 0 && isAlive(pid) && processIdentity.valid === true),
|
|
1366
|
+
runInstanceId,
|
|
1357
1367
|
};
|
|
1358
1368
|
}
|
|
1359
1369
|
|
|
1370
|
+
async function sendPlaybackCommand(endpoint, action, input) {
|
|
1371
|
+
const payload = `${JSON.stringify({
|
|
1372
|
+
action,
|
|
1373
|
+
...(input !== undefined ? { input } : {}),
|
|
1374
|
+
service_instance_id: endpoint.service_instance_id,
|
|
1375
|
+
})}\n`;
|
|
1376
|
+
const response = await new Promise((resolveResponse, rejectResponse) => {
|
|
1377
|
+
const socket = createConnection(endpoint.path);
|
|
1378
|
+
let content = "";
|
|
1379
|
+
const timeout = setTimeout(() => {
|
|
1380
|
+
socket.destroy(new Error("playback service command timed out"));
|
|
1381
|
+
}, 5_000);
|
|
1382
|
+
timeout.unref?.();
|
|
1383
|
+
socket.setEncoding("utf8");
|
|
1384
|
+
socket.on("connect", () => socket.write(payload));
|
|
1385
|
+
socket.on("data", (chunk) => {
|
|
1386
|
+
content += chunk;
|
|
1387
|
+
if (Buffer.byteLength(content, "utf8") > 4096) {
|
|
1388
|
+
socket.destroy(new Error("playback service response is too large"));
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
socket.on("close", () => clearTimeout(timeout));
|
|
1392
|
+
socket.on("error", rejectResponse);
|
|
1393
|
+
socket.on("end", () => {
|
|
1394
|
+
try {
|
|
1395
|
+
resolveResponse(JSON.parse(content.trim()));
|
|
1396
|
+
} catch {
|
|
1397
|
+
rejectResponse(new Error("playback service returned invalid JSON"));
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
if (response?.ok !== true) {
|
|
1402
|
+
throw new Error(response?.error || "playback service rejected the command");
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1360
1406
|
async function controlMain(args) {
|
|
1361
1407
|
const stateDir = expandPath(args[0] || "");
|
|
1362
1408
|
const command = args[1] || "status";
|
|
@@ -1365,13 +1411,20 @@ async function controlMain(args) {
|
|
|
1365
1411
|
usage();
|
|
1366
1412
|
process.exit(2);
|
|
1367
1413
|
}
|
|
1368
|
-
|
|
1414
|
+
if (!CONTROL_COMMANDS.has(command)) fail(`unsupported command: ${command}`, 2);
|
|
1415
|
+
const endpoint = readJsonFile(join(stateDir, "playback-endpoint.json"), {});
|
|
1416
|
+
// A standalone service retains authority even if an unsuccessful Actor launch
|
|
1417
|
+
// left Run metadata beside its endpoint. Clients never start or adopt it.
|
|
1418
|
+
const actorOwned = endpoint.owner_mode !== "standalone" &&
|
|
1419
|
+
existsSync(join(stateDir, "run.json"));
|
|
1369
1420
|
if (command === "status") {
|
|
1370
1421
|
const statusFile = join(stateDir, "player.json");
|
|
1371
1422
|
const status = exists(statusFile)
|
|
1372
1423
|
? readJsonFile(statusFile, { state: "unknown" })
|
|
1373
1424
|
: { state: "unknown" };
|
|
1374
|
-
const actor =
|
|
1425
|
+
const actor = actorOwned
|
|
1426
|
+
? await actorControlAvailability(stateDir)
|
|
1427
|
+
: { available: false };
|
|
1375
1428
|
process.stdout.write(`${JSON.stringify({
|
|
1376
1429
|
...projectCurrentProgress(status),
|
|
1377
1430
|
actor_available: actor.available,
|
|
@@ -1381,7 +1434,7 @@ async function controlMain(args) {
|
|
|
1381
1434
|
})}\n`);
|
|
1382
1435
|
return;
|
|
1383
1436
|
}
|
|
1384
|
-
if (!actorControlAvailability(stateDir).available) {
|
|
1437
|
+
if (actorOwned && !(await actorControlAvailability(stateDir)).available) {
|
|
1385
1438
|
fail(`Run playback is not active: ${stateDir}`, 3);
|
|
1386
1439
|
}
|
|
1387
1440
|
let input;
|
|
@@ -1396,6 +1449,20 @@ async function controlMain(args) {
|
|
|
1396
1449
|
fail(error instanceof Error ? error.message : String(error), 2);
|
|
1397
1450
|
}
|
|
1398
1451
|
}
|
|
1452
|
+
if (!actorOwned) {
|
|
1453
|
+
if (endpoint.owner_mode !== "standalone" ||
|
|
1454
|
+
typeof endpoint.path !== "string" || !endpoint.path ||
|
|
1455
|
+
typeof endpoint.service_instance_id !== "string" || !endpoint.service_instance_id) {
|
|
1456
|
+
fail(`standalone playback service is not active: ${stateDir}`, 3);
|
|
1457
|
+
}
|
|
1458
|
+
try {
|
|
1459
|
+
await sendPlaybackCommand(endpoint, command === "resume" ? "play" : command, input);
|
|
1460
|
+
console.log(`music-player: command=${command} handled state_dir=${stateDir}`);
|
|
1461
|
+
return;
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
fail(error instanceof Error ? error.message : String(error), 3);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1399
1466
|
const queued = appendControl(
|
|
1400
1467
|
{ controlsFile: join(stateDir, "controls.jsonl"), stateDir },
|
|
1401
1468
|
command,
|
|
@@ -9,13 +9,9 @@ Use multi-actor execution only when at least two scopes or evidence lenses are m
|
|
|
9
9
|
|
|
10
10
|
Read `actors` first for generic Recipe, spawn, Run, Trace, Control, artifact, and lifecycle operation. This Skill owns only multi-actor methodology: decomposition, scope ownership, independence, synthesis, integration, and completion proof.
|
|
11
11
|
|
|
12
|
-
## Coordinator
|
|
12
|
+
## Coordinator and participants
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
This resembles gateway orchestration in dependency direction but not in ownership: the coordinator is itself an agent instance with inspectable Runs, not an infrastructure service that implicitly creates sessions. Preserve that distinction in prompts, docs, recovery, and target routing.
|
|
17
|
-
|
|
18
|
-
Once work is delegated, keep the coordinator available for decisions and integration instead of duplicating participant implementation. Wait for terminal follow-up by default; use meaningful attention or evidence-based timers for overdue work rather than a tight inspection loop.
|
|
14
|
+
The coordinator owns decomposition, shared contracts, integration order, and final validation. Participants own bounded tasks or evidence lenses. Keep the coordinator available for decisions instead of duplicating participant implementation; use `actors` for launch, observation, and lifecycle mechanics.
|
|
19
15
|
|
|
20
16
|
## Reasoning allocation
|
|
21
17
|
|
|
@@ -14,38 +14,9 @@ Use a development swarm only when all are true:
|
|
|
14
14
|
|
|
15
15
|
Do not parallelize implementation when tasks need the same central files, semantic ordering dominates wall-clock time, or the likely conflicts would invalidate the decomposition. Use planning or review first.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Roles and reasoning
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
The coordinator should:
|
|
22
|
-
|
|
23
|
-
- Translate high-level intent into bounded task cards and dependency edges.
|
|
24
|
-
- Keep user authority, shared contracts, integration order, and final validation local.
|
|
25
|
-
- Remain available for checkpoints, permissions, conflicts, and changing evidence.
|
|
26
|
-
- Consume terminal handoffs, durable artifacts, and Trace attention instead of mirroring participant work.
|
|
27
|
-
- Check overdue work on an evidence-based timer; never replace event-driven completion with a rapid polling loop.
|
|
28
|
-
|
|
29
|
-
A participant should:
|
|
30
|
-
|
|
31
|
-
- Own one concrete execution or evidence boundary.
|
|
32
|
-
- Avoid global orchestration and undeclared participant creation.
|
|
33
|
-
- Return a bounded handoff that lets the coordinator decide without replaying the entire task.
|
|
34
|
-
|
|
35
|
-
Use one ordinary Run under `actors` when only one worker is delegated. Activate this development-swarm protocol when two or more participants, parallel ownership, or explicit integration edges exist. Keep trivial single-boundary work inline when delegation overhead has no compensating value.
|
|
36
|
-
|
|
37
|
-
## Reasoning profiles
|
|
38
|
-
|
|
39
|
-
| Role | Default | Raise or fan out when |
|
|
40
|
-
| --- | --- | --- |
|
|
41
|
-
| Bounded implementation author | Reasoning off | The card explicitly owns unresolved diagnosis or design judgement |
|
|
42
|
-
| Reviewer | Medium reasoning, clean context | Stakes require independent lenses or repeated judges |
|
|
43
|
-
| Synthesizer / integrator | Medium reasoning | Evidence conflicts, shared contracts move, or merge order is semantic |
|
|
44
|
-
| Coordinator | Sufficient for decomposition and decisions | Scope, authority, or architecture remains unresolved |
|
|
45
|
-
|
|
46
|
-
Prefer independent review after implementation over asking one author thread to implement, retain all local assumptions, and then certify itself. When risk justifies the cost, use multiple independent reviewers: different lenses increase breadth, while repeated judges increase confidence. Preserve minority high-impact findings and merge only evidence-backed conclusions.
|
|
47
|
-
|
|
48
|
-
More reviewers are not automatically better. Do not fan out when they would inspect unstable code, share contaminated context, repeat one unsupported claim, or exceed the value of the decision. Never change an already-running participant solely to enforce a newer profile; add a fresh review boundary if evidence remains open.
|
|
19
|
+
Apply [Swarm's coordinator and reasoning contract](../SKILL.md#reasoning-allocation). Task cards record those profiles and the owned execution boundary; participants return evidence without undeclared orchestration. Do not fan out review over unstable code or contaminated context. The sections below specify development-only task cards, ownership transfers, conflict reports, and integration.
|
|
49
20
|
|
|
50
21
|
## Decompose by ownership
|
|
51
22
|
|
package/docs/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Living index of all documentation in the `/docs` directory.
|
|
|
7
7
|
- [command-templates.md](./command-templates.md) — Portable synchronous command execution standard
|
|
8
8
|
- [template-recipes.md](./template-recipes.md) — Saved JSON/Markdown recipe standard, imports, and reusable command-template graph composition
|
|
9
9
|
- [async-runs.md](./async-runs.md) — Run lifecycle, state, Control, Trace, cancellation, and terminal reconciliation
|
|
10
|
+
- [coordinator-delivery.md](./coordinator-delivery.md) — Accepted next-minor design for durable terminal batching and explicit urgent Pi steering
|
|
10
11
|
- [actor-inspector.md](./actor-inspector.md) — Owner-filtered actor-instance navigation through Recipe, Trace, and Control
|
|
11
12
|
- [inspection.md](./inspection.md) — Complete `inspect` target/view matrix, authorization boundaries, and diagnostic routes
|
|
12
13
|
- [tool-registry.md](./tool-registry.md) — Local `pi-actors` registry storage and `register_tool` adaptation
|
package/docs/async-runs.md
CHANGED
|
@@ -54,11 +54,12 @@ Trace records strict bounded events:
|
|
|
54
54
|
```json
|
|
55
55
|
{"id":"…","ts":"…","kind":"command.done","summary":"Command completed","data":{"code":0},"level":"info"}
|
|
56
56
|
{"id":"…","ts":"…","kind":"checkpoint.ready","summary":"Review needs a decision","level":"info","attention":"followup"}
|
|
57
|
+
{"id":"…","ts":"…","kind":"checkpoint.blocked","summary":"Approval required before migration","level":"warning","attention":"steer"}
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
Required fields: `id`, `ts`, `kind`. Optional fields: `summary`, `data`, `level`, `attention`. Trace rejects addressed-envelope fields and malformed or oversized data. It retains a recent suffix within 2,048 events and 4 MiB. When either bound would be exceeded, the canonical lock atomically keeps a newest suffix near the lower targets, the new event, and one cumulative warning-only `runtime.trace_compacted` marker. The marker means older history was discarded; it reports cumulative drop evidence and never requests attention.
|
|
60
61
|
|
|
61
|
-
Runtime lifecycle, runner progress, command completion, cancellation, kill, parent teardown, and controlled-service observations use Trace. Generic command lifecycle is Trace-only: runner-owned `command.done` records preserve level, captures, session provenance, and execution evidence but never request or project attention
|
|
62
|
+
Runtime lifecycle, runner progress, command completion, cancellation, kill, parent teardown, and controlled-service observations use Trace. Generic command lifecycle is Trace-only: runner-owned `command.done` records preserve level, captures, session provenance, and execution evidence but never request or project attention, even if malformed legacy evidence carries `steer`. No Recipe field configures command-completion delivery. Semantic checkpoints opt in explicitly: `attention: "notify"` is visible status, `attention: "followup"` supplies ordinary checkpoint context, and `attention: "steer"` requests urgent delivery at Pi's next safe assistant/tool boundary. Steer is never inferred from exit status; its exact Run generation and event id enter the bounded owner journal before Pi delivery, recover through owned session evidence, and require exact model-bound context acknowledgment. Presentation appends a generation-fenced non-attention `delivery.steer_presented` Trace marker so historical retained steer events cannot replay after bounded owner receipts rotate. The eventual root terminal remains independently eligible for its completion batch. Bounded reads preserve complete UTF-8 lines and disclose omitted legacy prefixes. `inspect view=trace` reports retained-history completeness and projects events with Controls, owned Pi turns, logs, results, artifacts, and diagnostics newest-first. Equal timestamps use same-source physical order, then fixed source rank and stable id without claiming cross-source causality. Terminal state, `result.json`, `execution.json`, and artifacts remain authoritative even when old Trace has compacted.
|
|
62
63
|
|
|
63
64
|
## Control
|
|
64
65
|
|
|
@@ -109,9 +110,9 @@ Statuses include `running`, `done`, `failed`, `exited`, `cancelled`, and `killed
|
|
|
109
110
|
|
|
110
111
|
Ambient observation detects root terminal transitions and explicit retained Trace attention. Terminal transitions reconcile before semantic attention. Canonical attention is an in-memory wake hint, not a durable queue: observers prime retained ids at startup, deliver each later retained unseen id once, and bound memory to the current retained set across compaction. Persist durable recovery state or an artifact before emitting attention; compaction may discard older hints and its marker makes that history loss explicit. Terminal follow-up delivery persists handled/failure evidence so reloads retry unhandled transitions without duplicating completed notifications.
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
Ordinary finite Runs project root terminal results through one completion scheduler. Eligible terminals remain authoritative in Run state while Pi is active; after `agent_settled`, session recovery, or an idle debounce, the scheduler snapshots at most 256 exact generations into one owner-fenced immutable batch. One batch causes one automatic agent turn, exposes at most 64 bounded model-facing rows, and marks member terminals handled only after the exact batch id and content appear in model-bound Pi context. Pending send failures retain bounded retry evidence. On restart, queued recovery inspects only a bounded active Pi session parent chain: exact message evidence waits for presentation without resend, proven absence returns the same batch to pending, and incomplete or conflicting evidence stays queued with a diagnostic. Duplicate exact context envelopes collapse before presentation.
|
|
113
114
|
|
|
114
|
-
Large semantic results stay outside compact
|
|
115
|
+
Sequence, parallel, repeat, and imported branches are internal execution topology and never own branch-level turns. Each separately launched Run owns its own generation and terminal lifecycle; compatible singleton reuse is not a new launch. Explicit semantic attention may intentionally add a checkpoint turn; Runs marked silent and synchronously acknowledged stop outcomes suppress automatic projection. Large semantic results stay outside compact completion rows and remain available in structured details, execution captures, or artifacts.
|
|
115
116
|
|
|
116
117
|
## Cancellation and Kill
|
|
117
118
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Coordinator Delivery Scheduler
|
|
2
|
+
|
|
3
|
+
Status: Accepted next-minor design. Implementation is in progress; public delivery behavior remains unchanged until the complete acceptance boundary passes.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Separate durable Run completion truth from the scheduling of model turns:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
one Run generation -> one root terminal record
|
|
11
|
+
one bounded completion epoch -> one coordinator turn
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Ordinary root terminals accumulate while Pi is active and reach the coordinator in one bounded batch after Pi settles. Only an explicitly actor-authored urgent semantic checkpoint may steer an active agent loop.
|
|
15
|
+
|
|
16
|
+
## Non-Goals
|
|
17
|
+
|
|
18
|
+
- Do not interrupt generation mid-token.
|
|
19
|
+
- Do not stream generic progress or Command lifecycle into model context.
|
|
20
|
+
- Do not infer urgency from exit codes, failure status, artifacts, branch position, or active subagent counts.
|
|
21
|
+
- Do not change Run, Recipe, Trace, Control, artifact, generation, ownership, or Inspect authority.
|
|
22
|
+
- Do not add transport-specific behavior or restore Recipe-level Command delivery grammar.
|
|
23
|
+
|
|
24
|
+
## Delivery Classes
|
|
25
|
+
|
|
26
|
+
- Generic Trace and runner-owned `command.done` remain Trace-only.
|
|
27
|
+
- `attention: "notify"` remains visible UI status without a model turn.
|
|
28
|
+
- `attention: "followup"` retains its existing explicit semantic follow-up behavior.
|
|
29
|
+
- New `attention: "steer"` requests urgent semantic delivery at Pi's next safe assistant/tool boundary.
|
|
30
|
+
- Root terminal transitions enter durable completion batching instead of sending one follow-up per Run.
|
|
31
|
+
|
|
32
|
+
`command.done` remains non-projectable even if malformed or legacy Trace attaches any attention value.
|
|
33
|
+
|
|
34
|
+
## Ownership
|
|
35
|
+
|
|
36
|
+
- Run terminal state remains completion truth; the absence of `terminal-handled.json` makes that generation eligible for projection.
|
|
37
|
+
- `runs-trace.ts` owns admission of the new `steer` attention value.
|
|
38
|
+
- `observability.ts` discovers terminal candidates and explicit semantic attention without deciding Pi delivery timing.
|
|
39
|
+
- A new `run-delivery.ts` domain owns the owner-scoped delivery journal, batch construction, bounds, phases, formatting, recovery, and acknowledgments.
|
|
40
|
+
- `run-ui-runtime.ts` owns idle detection, debounce, reconciliation, flushing, and stale-context containment.
|
|
41
|
+
- `extension-runtime.ts` orders completion flushes before automatic Recipe review.
|
|
42
|
+
- `index.ts` remains a thin registration root and adds only the required Pi lifecycle adapter.
|
|
43
|
+
- `pi.ts` exposes narrow ports for batched follow-up and urgent steer delivery.
|
|
44
|
+
|
|
45
|
+
The local TypeScript dependency graph must remain acyclic. No public tool, target, view, Recipe field, or transport contract is added by default.
|
|
46
|
+
|
|
47
|
+
## Durable Delivery State
|
|
48
|
+
|
|
49
|
+
Store one journal per exact coordinator owner under an internal path derived from a safe owner hash:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
<extension-temp>/delivery/<owner-hash>/projection.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The journal records the exact internal owner and contains at most one active completion batch plus a bounded set of unpresented urgent steer envelopes.
|
|
56
|
+
|
|
57
|
+
Completion batch shape:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"batch_id": "uuid",
|
|
62
|
+
"owner_id": "exact internal owner",
|
|
63
|
+
"phase": "pending",
|
|
64
|
+
"members": [
|
|
65
|
+
{
|
|
66
|
+
"run": "review-a",
|
|
67
|
+
"run_instance_id": "generation-id",
|
|
68
|
+
"status": "done",
|
|
69
|
+
"state_dir": "internal path"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"created_at": "timestamp"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Phases are monotonic:
|
|
77
|
+
|
|
78
|
+
1. `pending`: The exact member snapshot is durable but no Pi message has been accepted.
|
|
79
|
+
2. `queued`: Pi accepted a custom message carrying the exact batch or steer ID.
|
|
80
|
+
3. `presented`: A Pi `context` event observed that ID in messages being supplied to an LLM call.
|
|
81
|
+
|
|
82
|
+
Every journal mutation uses the canonical token-owned lock, expected-phase fencing, owner and generation validation, and atomic replacement. Repeated transitions are idempotent. Corrupt, oversized, foreign-owner, or stale-generation state fails closed with bounded diagnostics.
|
|
83
|
+
|
|
84
|
+
A queued envelope is not treated as presented merely because `sendMessage()` returned. Only presentation marks completion members through their existing terminal-handled authority. If a member was synchronously archived or pruned after queueing, the bounded delivery snapshot remains sufficient and the missing state write becomes a diagnostic rather than invalidating the batch.
|
|
85
|
+
|
|
86
|
+
## Completion Collection
|
|
87
|
+
|
|
88
|
+
Reconciliation admits unhandled root terminal generations with status `done`, `failed`, `killed`, or `exited`.
|
|
89
|
+
|
|
90
|
+
It excludes:
|
|
91
|
+
|
|
92
|
+
- Runs with silent notification policy;
|
|
93
|
+
- synchronous stop or cancel outcomes already acknowledged by their caller;
|
|
94
|
+
- handled terminal generations;
|
|
95
|
+
- internal composition branches;
|
|
96
|
+
- every Command lifecycle event.
|
|
97
|
+
|
|
98
|
+
Candidates sort by terminal timestamp, then stable Run identity, then `run_instance_id`. Replacement generations with the same logical Run id remain distinct internal members.
|
|
99
|
+
|
|
100
|
+
While `ctx.isIdle()` is false, candidates remain durable in their Run state and no terminal follow-up is sent. A flush snapshots eligible candidates into one immutable batch. While that batch remains unpresented, newer terminals stay unhandled for the next bounded completion epoch.
|
|
101
|
+
|
|
102
|
+
## Batch Flush
|
|
103
|
+
|
|
104
|
+
Flush one batch when:
|
|
105
|
+
|
|
106
|
+
1. `agent_settled` fires for the still-active context and `ctx.isIdle()` remains true;
|
|
107
|
+
2. terminals arrive while Pi is already idle and survive one short debounce window;
|
|
108
|
+
3. session restoration discovers unhandled terminal generations or recoverable queued delivery state.
|
|
109
|
+
|
|
110
|
+
The model-facing custom message uses `customType: "pi-actors-run-batch"`, `deliverAs: "followUp"`, and `triggerTurn: true`. It includes:
|
|
111
|
+
|
|
112
|
+
- batch ID and completion window;
|
|
113
|
+
- counts by terminal status;
|
|
114
|
+
- stable Run, status, compact semantic summary, and bounded artifact rows;
|
|
115
|
+
- explicit overflow evidence and the canonical runtime Inspect route.
|
|
116
|
+
|
|
117
|
+
The journal may retain at most 256 members and 1 MiB. Model-facing content lists at most 64 exact rows within the centralized model-output bound. Additional members remain represented by exact status counts and supported Inspect guidance. More than 256 unhandled generations form a later batch rather than being discarded.
|
|
118
|
+
|
|
119
|
+
Completion member details remain redacted through existing terminal projection rules: no raw model policy, secrets, private Recipe paths, or machine-local source paths enter the message.
|
|
120
|
+
|
|
121
|
+
## Presentation Acknowledgment And Recovery
|
|
122
|
+
|
|
123
|
+
Register a `context` lifecycle adapter that scans model-bound messages for exact pi-actors batch and steer IDs. On a matching active-owner envelope it atomically:
|
|
124
|
+
|
|
125
|
+
1. moves the envelope to `presented`;
|
|
126
|
+
2. marks every still-present member generation terminal-handled;
|
|
127
|
+
3. records a non-attention `delivery.steer_presented` marker in the exact Run generation for a presented steer;
|
|
128
|
+
4. retains a bounded owner receipt sufficient for near-term deduplication and diagnostics.
|
|
129
|
+
|
|
130
|
+
The generation-fenced Trace marker prevents a retained historical steer from replaying after bounded owner receipts rotate: suffix compaction cannot retain the older steer while discarding its newer presentation marker. Missing, archived, pruned, or replaced Run state needs no marker because it can no longer replay that original generation.
|
|
131
|
+
|
|
132
|
+
Recovery rules:
|
|
133
|
+
|
|
134
|
+
- Send failure: keep `pending`, record failure evidence, and retry.
|
|
135
|
+
- Crash after queueing: inspect existing owned Pi session evidence for the exact custom message ID.
|
|
136
|
+
- Queued message exists: do not resend; wait for `context` presentation.
|
|
137
|
+
- Queued message is absent: return the envelope to `pending`.
|
|
138
|
+
- Presented envelope: never resend.
|
|
139
|
+
- Session or context replacement: close timers and callbacks; never deliver through stale context.
|
|
140
|
+
- Owner mismatch: do not inspect, acknowledge, or deliver the envelope.
|
|
141
|
+
|
|
142
|
+
Session evidence inspection must reuse the existing bounded owned-session readers rather than adding raw unbounded session parsing.
|
|
143
|
+
|
|
144
|
+
## Explicit Urgent Steer
|
|
145
|
+
|
|
146
|
+
Extend canonical Trace attention with `"steer"`:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"kind": "checkpoint.blocked",
|
|
151
|
+
"summary": "Approval required before destructive migration",
|
|
152
|
+
"attention": "steer"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A steer event is:
|
|
157
|
+
|
|
158
|
+
- explicit and actor-authored;
|
|
159
|
+
- admitted to the durable owner delivery journal before Pi delivery;
|
|
160
|
+
- sent through `deliverAs: "steer"` with `triggerTurn: true`;
|
|
161
|
+
- presented only when its exact event ID appears in model-bound `context`;
|
|
162
|
+
- retried after delivery failure without duplicate presentation;
|
|
163
|
+
- independent from the eventual root-terminal batch.
|
|
164
|
+
|
|
165
|
+
Pi steering is a safe-boundary continuation, not token-level interruption: while streaming, Pi delivers it after the current assistant turn finishes its tool calls and before the next LLM call. If Pi is idle, it triggers a new turn immediately.
|
|
166
|
+
|
|
167
|
+
Urgent steer capacity is bounded to 64 unpresented envelopes within the same 1 MiB owner journal. Capacity pressure remains visible and retryable; it never degrades into generic follow-up or drops an admitted envelope silently.
|
|
168
|
+
|
|
169
|
+
## Settled Lifecycle Ordering
|
|
170
|
+
|
|
171
|
+
`onAgentSettled` must use this order:
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
active-context and exact-owner check
|
|
175
|
+
-> completion flush
|
|
176
|
+
-> if a batch was sent, defer automatic Recipe review
|
|
177
|
+
-> batch-triggered model run settles
|
|
178
|
+
-> schedule automatic review only when no batch remains
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Another extension may start work during `agent_settled`; recheck `ctx.isIdle()` immediately before sending. A completion/settled race places each generation in either the current immutable batch or the next batch, never both.
|
|
182
|
+
|
|
183
|
+
## Validation Contract
|
|
184
|
+
|
|
185
|
+
Implementation is complete only when source and packed-extension tests prove:
|
|
186
|
+
|
|
187
|
+
1. Multiple Runs finishing during one agent run cause no immediate terminal turns and one settled batch.
|
|
188
|
+
2. Idle completions inside the debounce window form one batch.
|
|
189
|
+
3. Completion/settled races project every generation exactly once.
|
|
190
|
+
4. Send failure and restart before queueing retry without a handled marker.
|
|
191
|
+
5. Restart after queueing but before presentation neither loses nor duplicates the batch.
|
|
192
|
+
6. Exact `context` presentation acknowledges members atomically and idempotently.
|
|
193
|
+
7. Replacement generations sharing a Run id remain distinct.
|
|
194
|
+
8. Silent, stopped, cancelled, handled, and foreign-owner Runs remain excluded.
|
|
195
|
+
9. Legacy or malformed `command.done` attention, including `steer`, remains non-projectable.
|
|
196
|
+
10. Explicit steer reaches the next safe Pi boundary once and root terminal still batches later.
|
|
197
|
+
11. Overflow, corruption, journal backpressure, archive/prune races, and stale contexts fail safely.
|
|
198
|
+
12. Completion flushing precedes automatic Recipe review.
|
|
199
|
+
13. Pi 0.84.4 remains the exact minimum source and packed lifecycle baseline.
|
|
200
|
+
|
|
201
|
+
Focused observability and delivery tests precede TypeScript/build/import checks. The acceptance checkpoint then runs full product validation, dependency audit, package dry-run, and ABCd context validation.
|
|
202
|
+
|
|
203
|
+
## Rollout
|
|
204
|
+
|
|
205
|
+
This is one minor release because durable batching, presentation acknowledgment, lifecycle ordering, and explicit steer share one model-delivery invariant. Do not ship partial batching that marks terminals handled at `sendMessage()` acceptance, and do not ship steer before its durable deduplication path exists.
|
|
206
|
+
|
|
207
|
+
Update README and Run documentation only when implementation establishes the new public behavior. Move the accepted outcome from BACKLOG to CHANGELOG only after complete validation.
|
package/docs/recipe-library.md
CHANGED
|
@@ -36,7 +36,7 @@ Artifact pipelines terminate in files/manifests and result evidence; they do not
|
|
|
36
36
|
|
|
37
37
|
### Music playback and controlled services
|
|
38
38
|
|
|
39
|
-
- `music-player/playback` — singleton playback service that resolves files, directories, URLs, explicit lists, and playlist files into one persistent queue; it exposes declared playback Controls including arbitrary absolute `volume` percentages, generation-fenced endpoint readiness, structured status, a player-owned continuity checkpoint, and playback Trace. Player selection is `player:enum(auto,mpv,afplay,ffplay,cvlc,play,wmp)=auto`.
|
|
39
|
+
- `music-player/playback` — singleton playback service that resolves files, directories, URLs, explicit lists, and playlist files into one persistent queue; it exposes declared playback Controls including arbitrary absolute `volume` percentages, generation-fenced endpoint readiness, structured status, a player-owned continuity checkpoint, and playback Trace. Player selection is `player:enum(auto,mpv,afplay,ffplay,cvlc,play,wmp)=auto`. The single `skills/music-player/scripts/playback.mjs` executable also supports standalone foreground `serve` and `control <state-dir> <action> [percent]`; control observes the existing owner, using canonical Control records for Actors and generation-fenced RPC for standalone playback. The maintained Generative App uses that same control entrypoint for Actor-owned playback.
|
|
40
40
|
- `actors/resource-locker` — optional queue/lease-lock service with explicit owner/resource input, lock Trace, and a 512-record/1 MiB atomically retained journal.
|
|
41
41
|
|
|
42
42
|
These Recipes declare actor-local Control. Ordinary one-shot Recipes omit it. Helper-backed Skill Recipes self-locate through runtime-owned `{skill_dir}`; callers do not pass package installation roots.
|