@llblab/pi-actors 0.24.8 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/BACKLOG.md +5 -9
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +1 -1
  4. package/dist/lib/actor-worker.d.ts +3 -0
  5. package/dist/lib/actor-worker.js +54 -6
  6. package/dist/lib/recipe-discovery.js +85 -4
  7. package/dist/lib/tools.js +15 -3
  8. package/dist/recipes/actor-worker.json +10 -6
  9. package/dist/recipes/lens-swarm.json +0 -2
  10. package/dist/recipes/pipeline-architect-coordinator.json +0 -2
  11. package/dist/recipes/pipeline-artifact-bundle.json +0 -2
  12. package/dist/recipes/pipeline-artifact-report.json +0 -2
  13. package/dist/recipes/pipeline-artifact-write.json +0 -2
  14. package/dist/recipes/pipeline-async-run-ops.json +0 -2
  15. package/dist/recipes/pipeline-checkpoint-continuation.json +0 -2
  16. package/dist/recipes/pipeline-development-tasking.json +0 -2
  17. package/dist/recipes/pipeline-docs-maintenance.json +0 -2
  18. package/dist/recipes/pipeline-media-library.json +0 -2
  19. package/dist/recipes/pipeline-quorum-review.json +0 -2
  20. package/dist/recipes/pipeline-release-readiness.json +0 -2
  21. package/dist/recipes/pipeline-release-summary.json +0 -2
  22. package/dist/recipes/pipeline-repo-health.json +0 -2
  23. package/dist/recipes/pipeline-research-synthesis.json +0 -2
  24. package/dist/recipes/pipeline-review-readiness.json +0 -2
  25. package/dist/recipes/pipeline-room-swarm.json +0 -2
  26. package/dist/recipes/subagent-artifact.json +0 -2
  27. package/dist/recipes/subagent-checkpoint.json +0 -2
  28. package/dist/recipes/subagent-conflict-report.json +0 -2
  29. package/dist/recipes/subagent-contradiction-map.json +0 -2
  30. package/dist/recipes/subagent-critic.json +0 -2
  31. package/dist/recipes/subagent-evidence-map.json +0 -2
  32. package/dist/recipes/subagent-followup.json +0 -2
  33. package/dist/recipes/subagent-judge.json +0 -2
  34. package/dist/recipes/subagent-merge.json +0 -2
  35. package/dist/recipes/subagent-message.json +0 -2
  36. package/dist/recipes/subagent-normalize.json +0 -2
  37. package/dist/recipes/subagent-plan.json +0 -2
  38. package/dist/recipes/subagent-prompt.json +0 -2
  39. package/dist/recipes/subagent-quorum.json +0 -2
  40. package/dist/recipes/subagent-review-coordinator.json +0 -2
  41. package/dist/recipes/subagent-review.json +0 -2
  42. package/dist/recipes/subagent-task-card.json +0 -2
  43. package/dist/recipes/subagent-tools.json +0 -2
  44. package/dist/recipes/subagent-verify.json +0 -2
  45. package/dist/recipes/subagents-prompts.json +0 -2
  46. package/dist/skills/actors/SKILL.md +2 -2
  47. package/dist/skills/swarm/SKILL.md +1 -1
  48. package/docs/actor-messages.md +1 -1
  49. package/docs/template-recipes.md +2 -2
  50. package/docs/tool-registry.md +2 -1
  51. package/lib/actor-worker.ts +62 -6
  52. package/lib/recipe-discovery.ts +94 -4
  53. package/lib/tools.ts +20 -3
  54. package/package.json +1 -1
  55. package/recipes/actor-worker.json +10 -6
  56. package/recipes/lens-swarm.json +0 -2
  57. package/recipes/pipeline-architect-coordinator.json +0 -2
  58. package/recipes/pipeline-artifact-bundle.json +0 -2
  59. package/recipes/pipeline-artifact-report.json +0 -2
  60. package/recipes/pipeline-artifact-write.json +0 -2
  61. package/recipes/pipeline-async-run-ops.json +0 -2
  62. package/recipes/pipeline-checkpoint-continuation.json +0 -2
  63. package/recipes/pipeline-development-tasking.json +0 -2
  64. package/recipes/pipeline-docs-maintenance.json +0 -2
  65. package/recipes/pipeline-media-library.json +0 -2
  66. package/recipes/pipeline-quorum-review.json +0 -2
  67. package/recipes/pipeline-release-readiness.json +0 -2
  68. package/recipes/pipeline-release-summary.json +0 -2
  69. package/recipes/pipeline-repo-health.json +0 -2
  70. package/recipes/pipeline-research-synthesis.json +0 -2
  71. package/recipes/pipeline-review-readiness.json +0 -2
  72. package/recipes/pipeline-room-swarm.json +0 -2
  73. package/recipes/subagent-artifact.json +0 -2
  74. package/recipes/subagent-checkpoint.json +0 -2
  75. package/recipes/subagent-conflict-report.json +0 -2
  76. package/recipes/subagent-contradiction-map.json +0 -2
  77. package/recipes/subagent-critic.json +0 -2
  78. package/recipes/subagent-evidence-map.json +0 -2
  79. package/recipes/subagent-followup.json +0 -2
  80. package/recipes/subagent-judge.json +0 -2
  81. package/recipes/subagent-merge.json +0 -2
  82. package/recipes/subagent-message.json +0 -2
  83. package/recipes/subagent-normalize.json +0 -2
  84. package/recipes/subagent-plan.json +0 -2
  85. package/recipes/subagent-prompt.json +0 -2
  86. package/recipes/subagent-quorum.json +0 -2
  87. package/recipes/subagent-review-coordinator.json +0 -2
  88. package/recipes/subagent-review.json +0 -2
  89. package/recipes/subagent-task-card.json +0 -2
  90. package/recipes/subagent-tools.json +0 -2
  91. package/recipes/subagent-verify.json +0 -2
  92. package/recipes/subagents-prompts.json +0 -2
  93. package/skills/actors/SKILL.md +2 -2
  94. package/skills/swarm/SKILL.md +1 -1
package/BACKLOG.md CHANGED
@@ -165,7 +165,7 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
165
165
  ### M-08 Recipe Doctor Remediation UX
166
166
 
167
167
  - Priority: High.
168
- - Status: Open.
168
+ - Status: Done.
169
169
  - Goal: Turn recipe doctor output into an operator action surface, not just a diagnostic listing.
170
170
  - Why now: Recipe registry warnings are intentionally actionable; the next value is helping operators decide whether to fix, disable, delete, or inspect a recipe without hiding the warning.
171
171
  - Direction:
@@ -180,7 +180,7 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
180
180
  ### M-09 Actor Worker v2
181
181
 
182
182
  - Priority: High.
183
- - Status: Open.
183
+ - Status: Done.
184
184
  - Goal: Promote `actor-worker` from a minimal demo into the canonical standard-worker reference pattern.
185
185
  - Why now: Mailbox-loop semantics are now stable enough to show artifact production, compact status, and stale-claim recovery without adding a scheduler or broker.
186
186
  - Direction:
@@ -196,7 +196,7 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
196
196
  ### M-10 Dist Package Contract Hardening
197
197
 
198
198
  - Priority: Medium.
199
- - Status: Open.
199
+ - Status: Done.
200
200
  - Goal: Make the dist-first package contract difficult to regress after the 0.24 packaging shift.
201
201
  - Why now: `dist/` is now the default JS-only runtime surface and carries mirrored scripts, recipes, fixtures, and skills.
202
202
  - Direction:
@@ -211,7 +211,7 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
211
211
  ### M-11 Actor Termination Semantics
212
212
 
213
213
  - Priority: Medium.
214
- - Status: Open; core mailbox-loop hotfix landed in 0.24.8.
214
+ - Status: Done.
215
215
  - Goal: Make `control.kill` the canonical parent-to-actor termination action while keeping `control.stop` and `control.cancel` as actor-domain messages whose meaning depends on the actor protocol.
216
216
  - Why now: Mailbox workers need a clearer lifecycle boundary before v2 patterns harden. Treating `stop`, `cancel`, and `kill` as equivalent stop messages blurs actor termination with domain-specific task or playback control.
217
217
  - Remaining direction:
@@ -240,9 +240,5 @@ These are valid ideas but not current focus. Reintroduce only with concrete evid
240
240
  ## Suggested Milestone Order
241
241
 
242
242
  ```text
243
- 0.25 — Operator remediation and worker maturity:
244
- M-08, M-09
245
-
246
- 0.26 — Package contract hardening and lifecycle semantics:
247
- M-10, M-11
243
+ Next milestone: choose from deferred items only after concrete actor workflow evidence appears.
248
244
  ```
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.26.0: Actor Worker v2 and Package Contract Hardening
6
+
7
+ - `[Actor Worker]` Promoted the packaged `actor-worker` into a v2 reference pattern with compact `worker-status.json`, optional per-task result artifacts under `worker-artifacts/`, stale-claim surfacing through status, and artifact metadata on `task.result` room messages.
8
+ - `[Recipes]` Audited packaged async recipe mailbox contracts so `control.stop` and `control.cancel` remain declared only for actor-domain handlers such as locker/coordinator/music control, while generic async recipes expose `control.kill` for runtime termination.
9
+ - `[Packaging]` Hardened dist package regressions with mirrored asset checks for `dist/scripts`, `dist/recipes`, `dist/fixtures`, and `dist/skills`, plus negative checks for stale dist files and source-only runtime imports in built script shims.
10
+ - `[Skills]` Updated the actors skill recipe navigator to document the v2 worker pattern and its `control.kill` termination contract.
11
+
12
+ ## 0.25.0: Recipe Doctor Remediation UX
13
+
14
+ - `[Recipe Doctor]` Added prioritized advisory remediations to recipe doctor output, including a compact top action, structured verbose remediation entries, blocked lower-priority candidates for invalid/disabled overrides, and ordered coverage for invalid, disabled, shadowed, and risky shell-boundary recipes.
15
+ - `[Docs]` Documented the recipe doctor remediation surface in the tool registry guide.
16
+
5
17
  ## 0.24.8: Mailbox Loop Kill Contract Hotfix
6
18
 
7
19
  - `[Mailbox Loop]` Aligned generic mailbox-loop termination with the actor-message kill contract: only `control.kill` stops generic loop drains; `control.stop` and `control.cancel` remain actor-domain messages unless a recipe handles them explicitly.
package/README.md CHANGED
@@ -89,7 +89,7 @@ cat > ~/.pi/agent/recipes/docs_review.json <<'JSON'
89
89
  "async": true,
90
90
  "args": ["scope:path", "model:string"],
91
91
  "mailbox": {
92
- "accepts": ["control.stop", "control.continue"],
92
+ "accepts": ["control.kill", "control.continue"],
93
93
  "emits": ["review.completed", "run.failed"]
94
94
  },
95
95
  "template": "pi -p --model {model} --no-tools \"Review {scope} for unclear actor-runtime onboarding. Return concise findings.\""
@@ -3,10 +3,13 @@
3
3
  * Zones: worker demo, mailbox loop recipe runtime
4
4
  */
5
5
  interface ActorWorkerArgs {
6
+ artifact_dir?: string;
6
7
  branch?: string;
7
8
  poll_ms?: string | number;
8
9
  run?: string;
10
+ stale_claim_ms?: string | number;
9
11
  state_dir?: string;
12
+ write_artifacts?: string | boolean;
10
13
  }
11
14
  export declare function parseActorWorkerArgs(argv: string[]): ActorWorkerArgs;
12
15
  export declare function runActorWorker(argv?: string[]): Promise<void>;
@@ -2,9 +2,9 @@
2
2
  * Canonical mailbox-backed actor worker entrypoint logic.
3
3
  * Zones: worker demo, mailbox loop recipe runtime
4
4
  */
5
- import { appendFileSync } from "node:fs";
5
+ import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
- import { appendRoomMessage, ensureRoomMember } from "./actor-rooms.js";
7
+ import { appendRoomMessage, ensureRoomMember, readBranchInboxMessages } from "./actor-rooms.js";
8
8
  import { handleMailboxLoopOnce, isMailboxLoopStopMessage } from "./mailbox-loop.js";
9
9
  export function parseActorWorkerArgs(argv) {
10
10
  const args = {};
@@ -29,21 +29,54 @@ function sleep(ms) {
29
29
  function text(value) {
30
30
  return typeof value === "string" ? value : JSON.stringify(value);
31
31
  }
32
+ function enabled(value) {
33
+ return value !== false && value !== "false" && value !== "0";
34
+ }
35
+ function safeName(value) {
36
+ return String(value ?? "unknown").replaceAll(/[^A-Za-z0-9_.-]+/g, "_");
37
+ }
32
38
  export async function runActorWorker(argv = process.argv.slice(2)) {
33
39
  const args = parseActorWorkerArgs(argv);
34
40
  const stateDir = String(args.state_dir ?? "");
35
41
  const run = String(args.run ?? "");
36
42
  const branch = String(args.branch ?? "worker");
37
43
  const pollMs = Math.max(50, Number(args.poll_ms ?? 1000));
44
+ const staleClaimMs = Math.max(0, Number(args.stale_claim_ms ?? 0));
45
+ const artifactDir = String(args.artifact_dir ?? join(stateDir, "worker-artifacts"));
46
+ const writeArtifacts = enabled(args.write_artifacts ?? true);
38
47
  if (!stateDir || !run) {
39
48
  throw new Error("usage: actor-worker.mjs --state-dir <dir> --run <id> [--branch worker] [--poll-ms 1000]");
40
49
  }
41
50
  const branchAddress = `branch:${run}/${branch}`;
42
51
  const roomAddress = `room:${run}`;
43
52
  const journalPath = join(stateDir, "worker-events.jsonl");
53
+ const statusPath = join(stateDir, "worker-status.json");
54
+ if (writeArtifacts)
55
+ mkdirSync(artifactDir, { recursive: true });
44
56
  function journal(event, data = {}) {
45
57
  appendFileSync(journalPath, `${JSON.stringify({ event, ts: new Date().toISOString(), ...data })}\n`);
46
58
  }
59
+ function staleClaims() {
60
+ if (staleClaimMs <= 0)
61
+ return 0;
62
+ const cutoff = Date.now() - staleClaimMs;
63
+ return readBranchInboxMessages(stateDir, run, branchAddress).filter((record) => {
64
+ if (record.status !== "claimed" || !record.claimed_at)
65
+ return false;
66
+ const claimedAt = Date.parse(record.claimed_at);
67
+ return Number.isFinite(claimedAt) && claimedAt < cutoff;
68
+ }).length;
69
+ }
70
+ function status(state, data = {}) {
71
+ writeFileSync(statusPath, `${JSON.stringify({ artifact_dir: artifactDir, branch, run, stale_claims: staleClaims(), state, ts: new Date().toISOString(), ...data }, null, 2)}\n`);
72
+ }
73
+ function writeResultArtifact(id, result) {
74
+ if (!writeArtifacts)
75
+ return undefined;
76
+ const path = join(artifactDir, `${safeName(id)}.txt`);
77
+ writeFileSync(path, `${result}\n`);
78
+ return path;
79
+ }
47
80
  function room(type, summary, body = {}) {
48
81
  appendRoomMessage(stateDir, "main", {
49
82
  body,
@@ -55,7 +88,9 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
55
88
  }
56
89
  ensureRoomMember(stateDir, run, "main", branchAddress, { display: branch, role: "worker", status: "present" }, `${branch} joined as mailbox worker`);
57
90
  room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
58
- journal("worker.started", { branch, run });
91
+ journal("worker.started", { artifact_dir: artifactDir, branch, run, stale_claim_ms: staleClaimMs });
92
+ status("awaiting_assignment", { handled: 0, stale_claim_ms: staleClaimMs });
93
+ let handled = 0;
59
94
  let stopping = false;
60
95
  while (!stopping) {
61
96
  const result = await handleMailboxLoopOnce({ address: branchAddress, kind: "branch", run, stateDir }, (message) => {
@@ -63,6 +98,7 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
63
98
  stopping = true;
64
99
  room("actor.leave", `${branch} stopping`, { branch, reason: message.type });
65
100
  journal("worker.stopping", { id: message.id, type: message.type });
101
+ status("stopping", { handled, reason: message.type });
66
102
  return;
67
103
  }
68
104
  room("task.claim", `${branch} claimed ${message.type}`, {
@@ -70,17 +106,29 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
70
106
  id: message.id,
71
107
  type: message.type,
72
108
  });
109
+ const result = text(message.body);
110
+ const artifact = writeResultArtifact(message.id, result);
111
+ handled += 1;
73
112
  room("task.result", `${branch} handled ${message.type}`, {
113
+ artifact,
74
114
  branch,
75
115
  id: message.id,
76
- result: text(message.body),
116
+ result,
77
117
  type: message.type,
78
118
  });
79
119
  room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
80
- journal("task.handled", { id: message.id, type: message.type });
120
+ journal("task.handled", { artifact, id: message.id, type: message.type });
121
+ status("awaiting_assignment", {
122
+ handled,
123
+ last_artifact: artifact,
124
+ last_id: message.id,
125
+ last_type: message.type,
126
+ stale_claim_ms: staleClaimMs,
127
+ });
81
128
  }, { owner: branchAddress });
82
129
  if (!result.handled)
83
130
  await sleep(pollMs);
84
131
  }
85
- journal("worker.done", { branch, run });
132
+ journal("worker.done", { branch, handled, run });
133
+ status("done", { handled });
86
134
  }
@@ -273,6 +273,10 @@ function diagnosticSeverity(message) {
273
273
  return "info";
274
274
  }
275
275
  function diagnosticSuggestedAction(message) {
276
+ if (/world-writable|group-writable/i.test(message))
277
+ return "tighten recipe root permissions";
278
+ if (/invokes bash/i.test(message))
279
+ return "audit the trusted shell boundary and keep only if intentional";
276
280
  if (/must define template/i.test(message))
277
281
  return "add a template field or remove the recipe";
278
282
  if (/JSON|Expected|parse/i.test(message))
@@ -285,10 +289,6 @@ function diagnosticSuggestedAction(message) {
285
289
  return "split large prompt or data into separate files";
286
290
  if (/repeat must/i.test(message))
287
291
  return "use a positive repeat count or an array-typed repeat source";
288
- if (/world-writable|group-writable/i.test(message))
289
- return "tighten recipe root permissions";
290
- if (/invokes bash/i.test(message))
291
- return "audit the trusted shell boundary or move details to recipe doctor";
292
292
  if (/shadows/i.test(message))
293
293
  return "confirm the active override or rename one recipe";
294
294
  if (/disabled/i.test(message))
@@ -323,6 +323,84 @@ function diagnosticDetails(result) {
323
323
  String(a.message).localeCompare(String(b.message)));
324
324
  });
325
325
  }
326
+ function remediationForEntry(entry, activePath) {
327
+ const riskyDiagnostics = entry.diagnostics.filter((message) => diagnosticSeverity(message) === "warning");
328
+ const blockedCandidate = entry.shadows[0];
329
+ if (entry.invalid) {
330
+ return {
331
+ id: entry.id,
332
+ kind: blockedCandidate ? "blocking_invalid" : "invalid",
333
+ severity: "error",
334
+ path: entry.path,
335
+ ...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
336
+ reason: blockedCandidate
337
+ ? "invalid higher-priority recipe blocks a lower-priority candidate"
338
+ : "recipe is invalid and cannot be exposed as a tool",
339
+ action: "fix recipe syntax/config, or disable/delete/archive it to restore fallback",
340
+ };
341
+ }
342
+ if (entry.disabled && entry.active) {
343
+ return {
344
+ id: entry.id,
345
+ kind: blockedCandidate ? "blocking_disabled" : "disabled",
346
+ severity: "warning",
347
+ path: entry.path,
348
+ ...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
349
+ reason: blockedCandidate
350
+ ? "disabled higher-priority recipe intentionally blocks a lower-priority candidate"
351
+ : "recipe is disabled and not exposed as a tool",
352
+ action: "keep disabled intentionally, re-enable, or delete/archive the file",
353
+ };
354
+ }
355
+ if (riskyDiagnostics.length > 0) {
356
+ return {
357
+ id: entry.id,
358
+ kind: "risky_shell_boundary",
359
+ severity: "warning",
360
+ path: entry.path,
361
+ reason: riskyDiagnostics[0],
362
+ action: "audit trusted command boundary; keep only if the recipe is local and intentional",
363
+ };
364
+ }
365
+ if (entry.shadowed) {
366
+ return {
367
+ id: entry.id,
368
+ kind: "shadowed",
369
+ severity: "info",
370
+ path: entry.path,
371
+ ...(activePath ? { active_path: activePath } : {}),
372
+ reason: activePath
373
+ ? `shadowed by ${activePath}`
374
+ : "shadowed by a higher-priority recipe",
375
+ action: "keep as fallback/component, merge, rename, delete, or archive",
376
+ };
377
+ }
378
+ return undefined;
379
+ }
380
+ function remediationRank(item) {
381
+ const kind = String(item.kind ?? "");
382
+ if (kind === "blocking_invalid")
383
+ return 0;
384
+ if (kind === "invalid")
385
+ return 1;
386
+ if (kind === "blocking_disabled")
387
+ return 2;
388
+ if (kind === "risky_shell_boundary")
389
+ return 3;
390
+ if (kind === "disabled")
391
+ return 4;
392
+ if (kind === "shadowed")
393
+ return 5;
394
+ return 6;
395
+ }
396
+ function discoveryRemediations(result) {
397
+ return result.entries
398
+ .map((entry) => remediationForEntry(entry, result.active.get(entry.id)?.path))
399
+ .filter((entry) => Boolean(entry))
400
+ .sort((a, b) => remediationRank(a) - remediationRank(b) ||
401
+ String(a.id).localeCompare(String(b.id)) ||
402
+ String(a.path).localeCompare(String(b.path)));
403
+ }
326
404
  function recommendationForEntry(entry, activePath) {
327
405
  const recommendation = cleanupRecommendation(entry);
328
406
  if (!recommendation)
@@ -341,6 +419,7 @@ export function summarizeDiscovery(result) {
341
419
  .filter((entry) => Boolean(entry))
342
420
  .sort((a, b) => String(a.id).localeCompare(String(b.id)) ||
343
421
  String(a.path).localeCompare(String(b.path)));
422
+ const remediations = discoveryRemediations(result);
344
423
  return {
345
424
  active: [...result.active.values()]
346
425
  .map((entry) => ({
@@ -378,6 +457,8 @@ export function summarizeDiscovery(result) {
378
457
  .map((entry) => ({ id: entry.id, path: entry.path }))
379
458
  .sort((a, b) => a.id.localeCompare(b.id)),
380
459
  recommendations,
460
+ remediations,
461
+ top_action: remediations[0],
381
462
  diagnostics: result.diagnostics,
382
463
  diagnostic_details: diagnosticDetails(result),
383
464
  integrity_manifest: createRecipeIntegrityManifest(result),
package/dist/lib/tools.js CHANGED
@@ -387,6 +387,9 @@ function compactRecipeDoctor(summary) {
387
387
  const details = Array.isArray(summary.diagnostic_details)
388
388
  ? summary.diagnostic_details
389
389
  : [];
390
+ const remediations = Array.isArray(summary.remediations)
391
+ ? summary.remediations
392
+ : [];
390
393
  const recommendations = Array.isArray(summary.recommendations)
391
394
  ? summary.recommendations
392
395
  : [];
@@ -396,11 +399,20 @@ function compactRecipeDoctor(summary) {
396
399
  if (severity === "error" || severity === "warning" || severity === "info")
397
400
  counts[severity] += 1;
398
401
  }
402
+ const topAction = asRecord(summary.top_action);
399
403
  const lines = [
400
- `recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} recommendations=${recommendations.length}`,
404
+ `recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} actions=${remediations.length} recommendations=${recommendations.length}`,
401
405
  ];
402
- for (const detail of details.slice(0, 8)) {
403
- lines.push(`${String(detail.severity ?? "info")} id=${String(detail.id ?? "root")} action=${compactPreview(detail.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS) ?? "inspect"}`);
406
+ if (Object.keys(topAction).length > 0) {
407
+ const action = compactPreview(topAction.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
408
+ lines.push(`top severity=${String(topAction.severity ?? "info")} kind=${String(topAction.kind ?? "inspect")} id=${String(topAction.id ?? "root")} action=${action ?? "inspect"}`);
409
+ }
410
+ for (const item of remediations.slice(0, 8)) {
411
+ const action = compactPreview(item.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
412
+ const blocked = item.blocked_candidate
413
+ ? ` blocked=${compactPreview(item.blocked_candidate, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
414
+ : "";
415
+ lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`);
404
416
  }
405
417
  return `\n${lines.join("\n")}`;
406
418
  }
@@ -4,18 +4,20 @@
4
4
  "args": [
5
5
  "repo:path",
6
6
  "branch:string",
7
- "poll_ms:int"
7
+ "poll_ms:int",
8
+ "stale_claim_ms:int",
9
+ "write_artifacts:bool"
8
10
  ],
9
11
  "defaults": {
10
12
  "repo": "~/.pi/agent/extensions/pi-actors",
11
13
  "branch": "worker",
12
- "poll_ms": "1000"
14
+ "poll_ms": "1000",
15
+ "stale_claim_ms": "0",
16
+ "write_artifacts": "true"
13
17
  },
14
18
  "mailbox": {
15
19
  "accepts": [
16
20
  "task.assign",
17
- "control.stop",
18
- "control.cancel",
19
21
  "control.kill"
20
22
  ],
21
23
  "emits": [
@@ -29,7 +31,9 @@
29
31
  ]
30
32
  },
31
33
  "artifacts": {
32
- "journal": "{state_dir}/worker-events.jsonl"
34
+ "journal": "{state_dir}/worker-events.jsonl",
35
+ "status": "{state_dir}/worker-status.json",
36
+ "results": "{state_dir}/worker-artifacts"
33
37
  },
34
- "template": "{repo}/scripts/actor-worker.mjs --state-dir {state_dir} --run {run_id} --branch {branch} --poll-ms {poll_ms}"
38
+ "template": "{repo}/scripts/actor-worker.mjs --state-dir {state_dir} --run {run_id} --branch {branch} --poll-ms {poll_ms} --stale-claim-ms {stale_claim_ms} --write-artifacts {write_artifacts}"
35
39
  }
@@ -30,8 +30,6 @@
30
30
  },
31
31
  "mailbox": {
32
32
  "accepts": [
33
- "control.stop",
34
- "control.cancel",
35
33
  "control.kill"
36
34
  ],
37
35
  "emits": [
@@ -26,8 +26,6 @@
26
26
  },
27
27
  "mailbox": {
28
28
  "accepts": [
29
- "control.stop",
30
- "control.cancel",
31
29
  "control.kill"
32
30
  ],
33
31
  "emits": [
@@ -35,8 +35,6 @@
35
35
  },
36
36
  "mailbox": {
37
37
  "accepts": [
38
- "control.stop",
39
- "control.cancel",
40
38
  "control.kill"
41
39
  ],
42
40
  "emits": [
@@ -18,8 +18,6 @@
18
18
  },
19
19
  "mailbox": {
20
20
  "accepts": [
21
- "control.stop",
22
- "control.cancel",
23
21
  "control.kill"
24
22
  ],
25
23
  "emits": [
@@ -21,8 +21,6 @@
21
21
  },
22
22
  "mailbox": {
23
23
  "accepts": [
24
- "control.stop",
25
- "control.cancel",
26
24
  "control.kill"
27
25
  ],
28
26
  "emits": [
@@ -24,8 +24,6 @@
24
24
  },
25
25
  "mailbox": {
26
26
  "accepts": [
27
- "control.stop",
28
- "control.cancel",
29
27
  "control.kill"
30
28
  ],
31
29
  "emits": [
@@ -22,8 +22,6 @@
22
22
  "accepts": [
23
23
  "control.reply",
24
24
  "control.revise",
25
- "control.stop",
26
- "control.cancel",
27
25
  "control.kill"
28
26
  ],
29
27
  "emits": [
@@ -24,8 +24,6 @@
24
24
  },
25
25
  "mailbox": {
26
26
  "accepts": [
27
- "control.stop",
28
- "control.cancel",
29
27
  "control.kill"
30
28
  ],
31
29
  "emits": [
@@ -23,8 +23,6 @@
23
23
  },
24
24
  "mailbox": {
25
25
  "accepts": [
26
- "control.stop",
27
- "control.cancel",
28
26
  "control.kill"
29
27
  ],
30
28
  "emits": [
@@ -23,8 +23,6 @@
23
23
  },
24
24
  "mailbox": {
25
25
  "accepts": [
26
- "control.stop",
27
- "control.cancel",
28
26
  "control.kill"
29
27
  ],
30
28
  "emits": [
@@ -20,8 +20,6 @@
20
20
  },
21
21
  "mailbox": {
22
22
  "accepts": [
23
- "control.stop",
24
- "control.cancel",
25
23
  "control.kill"
26
24
  ],
27
25
  "emits": [
@@ -40,8 +40,6 @@
40
40
  },
41
41
  "mailbox": {
42
42
  "accepts": [
43
- "control.stop",
44
- "control.cancel",
45
43
  "control.kill"
46
44
  ],
47
45
  "emits": [
@@ -29,8 +29,6 @@
29
29
  },
30
30
  "mailbox": {
31
31
  "accepts": [
32
- "control.stop",
33
- "control.cancel",
34
32
  "control.kill"
35
33
  ],
36
34
  "emits": [
@@ -25,8 +25,6 @@
25
25
  },
26
26
  "mailbox": {
27
27
  "accepts": [
28
- "control.stop",
29
- "control.cancel",
30
28
  "control.kill"
31
29
  ],
32
30
  "emits": [
@@ -22,8 +22,6 @@
22
22
  },
23
23
  "mailbox": {
24
24
  "accepts": [
25
- "control.stop",
26
- "control.cancel",
27
25
  "control.kill"
28
26
  ],
29
27
  "emits": [
@@ -25,8 +25,6 @@
25
25
  },
26
26
  "mailbox": {
27
27
  "accepts": [
28
- "control.stop",
29
- "control.cancel",
30
28
  "control.kill"
31
29
  ],
32
30
  "emits": [
@@ -34,8 +34,6 @@
34
34
  },
35
35
  "mailbox": {
36
36
  "accepts": [
37
- "control.stop",
38
- "control.cancel",
39
37
  "control.kill"
40
38
  ],
41
39
  "emits": [
@@ -19,8 +19,6 @@
19
19
  },
20
20
  "mailbox": {
21
21
  "accepts": [
22
- "control.stop",
23
- "control.cancel",
24
22
  "control.kill"
25
23
  ],
26
24
  "emits": [
@@ -21,8 +21,6 @@
21
21
  "accepts": [
22
22
  "control.continue",
23
23
  "control.revise",
24
- "control.stop",
25
- "control.cancel",
26
24
  "control.kill"
27
25
  ],
28
26
  "emits": [
@@ -17,8 +17,6 @@
17
17
  },
18
18
  "mailbox": {
19
19
  "accepts": [
20
- "control.stop",
21
- "control.cancel",
22
20
  "control.kill"
23
21
  ],
24
22
  "emits": [
@@ -18,8 +18,6 @@
18
18
  },
19
19
  "mailbox": {
20
20
  "accepts": [
21
- "control.stop",
22
- "control.cancel",
23
21
  "control.kill"
24
22
  ],
25
23
  "emits": [
@@ -20,8 +20,6 @@
20
20
  },
21
21
  "mailbox": {
22
22
  "accepts": [
23
- "control.stop",
24
- "control.cancel",
25
23
  "control.kill"
26
24
  ],
27
25
  "emits": [
@@ -18,8 +18,6 @@
18
18
  },
19
19
  "mailbox": {
20
20
  "accepts": [
21
- "control.stop",
22
- "control.cancel",
23
21
  "control.kill"
24
22
  ],
25
23
  "emits": [
@@ -21,8 +21,6 @@
21
21
  "accepts": [
22
22
  "control.reply",
23
23
  "control.revise",
24
- "control.stop",
25
- "control.cancel",
26
24
  "control.kill"
27
25
  ],
28
26
  "emits": [
@@ -18,8 +18,6 @@
18
18
  },
19
19
  "mailbox": {
20
20
  "accepts": [
21
- "control.stop",
22
- "control.cancel",
23
21
  "control.kill"
24
22
  ],
25
23
  "emits": [