@llblab/pi-actors 0.42.2 → 0.43.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 (254) hide show
  1. package/AGENTS.md +121 -175
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +113 -276
  4. package/dist/fixtures/protocol/control-endpoint.json +6 -0
  5. package/dist/fixtures/protocol/control-record.json +9 -0
  6. package/dist/fixtures/protocol/recipe-summary.json +4 -12
  7. package/dist/fixtures/protocol/trace-event.json +9 -0
  8. package/dist/lib/async-runs.d.ts +14 -38
  9. package/dist/lib/async-runs.js +158 -108
  10. package/dist/lib/control.d.ts +12 -0
  11. package/dist/lib/control.js +84 -0
  12. package/dist/lib/execution-sessions.d.ts +17 -0
  13. package/dist/lib/execution-sessions.js +85 -0
  14. package/dist/lib/file-state.d.ts +1 -0
  15. package/dist/lib/file-state.js +17 -5
  16. package/dist/lib/inspector-actions.d.ts +2 -2
  17. package/dist/lib/inspector-actions.js +2 -2
  18. package/dist/lib/inspector-command.js +3 -3
  19. package/dist/lib/inspector-overlay.d.ts +52 -70
  20. package/dist/lib/inspector-overlay.js +532 -905
  21. package/dist/lib/inspector.d.ts +3 -71
  22. package/dist/lib/inspector.js +19 -665
  23. package/dist/lib/limits.d.ts +4 -2
  24. package/dist/lib/limits.js +4 -2
  25. package/dist/lib/observability.d.ts +17 -17
  26. package/dist/lib/observability.js +45 -84
  27. package/dist/lib/pi.d.ts +1 -1
  28. package/dist/lib/prompts.d.ts +1 -1
  29. package/dist/lib/prompts.js +2 -2
  30. package/dist/lib/recipe-control.d.ts +7 -0
  31. package/dist/lib/recipe-control.js +39 -0
  32. package/dist/lib/recipes-discovery.js +2 -0
  33. package/dist/lib/recipes-references.d.ts +1 -14
  34. package/dist/lib/recipes-references.js +6 -21
  35. package/dist/lib/review-projection.js +1 -5
  36. package/dist/lib/run-ui-runtime.js +2 -2
  37. package/dist/lib/runs-control-delivery.d.ts +21 -0
  38. package/dist/lib/runs-control-delivery.js +127 -0
  39. package/dist/lib/runs-controls.d.ts +35 -0
  40. package/dist/lib/runs-controls.js +144 -0
  41. package/dist/lib/runs-retention.d.ts +7 -0
  42. package/dist/lib/runs-retention.js +27 -3
  43. package/dist/lib/runs-start.js +4 -2
  44. package/dist/lib/runs-status.js +11 -6
  45. package/dist/lib/runs-trace.d.ts +24 -0
  46. package/dist/lib/runs-trace.js +98 -0
  47. package/dist/lib/runtime-notifier.d.ts +1 -1
  48. package/dist/lib/runtime-notifier.js +1 -1
  49. package/dist/lib/tools-inspect.d.ts +3 -3
  50. package/dist/lib/tools-inspect.js +203 -708
  51. package/dist/lib/tools-local.js +2 -10
  52. package/dist/lib/tools-message.d.ts +7 -7
  53. package/dist/lib/tools-message.js +95 -396
  54. package/dist/lib/tools-response.d.ts +1 -4
  55. package/dist/lib/tools-response.js +5 -39
  56. package/dist/lib/tools-spawn.js +16 -28
  57. package/dist/lib/tools.js +1 -2
  58. package/dist/lib/trace-projection.d.ts +22 -0
  59. package/dist/lib/trace-projection.js +165 -0
  60. package/dist/recipes/draft-review.json +0 -10
  61. package/dist/recipes/lens-swarm.json +0 -14
  62. package/dist/recipes/music-player.json +10 -19
  63. package/dist/recipes/pipeline-architect-coordinator.json +0 -11
  64. package/dist/recipes/pipeline-artifact-bundle.json +1 -22
  65. package/dist/recipes/pipeline-artifact-report.json +1 -18
  66. package/dist/recipes/pipeline-artifact-write.json +1 -18
  67. package/dist/recipes/pipeline-async-run-ops.json +0 -12
  68. package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
  69. package/dist/recipes/pipeline-development-tasking.json +0 -12
  70. package/dist/recipes/pipeline-docs-maintenance.json +0 -12
  71. package/dist/recipes/pipeline-media-library.json +0 -12
  72. package/dist/recipes/pipeline-quorum-review.json +0 -12
  73. package/dist/recipes/pipeline-release-readiness.json +0 -12
  74. package/dist/recipes/pipeline-release-summary.json +0 -12
  75. package/dist/recipes/pipeline-repo-health.json +0 -12
  76. package/dist/recipes/pipeline-research-synthesis.json +0 -11
  77. package/dist/recipes/pipeline-review-readiness.json +0 -12
  78. package/dist/recipes/resource-locker.json +27 -0
  79. package/dist/recipes/subagent-artifact.json +0 -9
  80. package/dist/recipes/subagent-checkpoint.json +0 -10
  81. package/dist/recipes/subagent-conflict-report.json +0 -11
  82. package/dist/recipes/subagent-contradiction-map.json +0 -11
  83. package/dist/recipes/subagent-critic.json +0 -11
  84. package/dist/recipes/subagent-evidence-map.json +0 -11
  85. package/dist/recipes/subagent-followup.json +0 -10
  86. package/dist/recipes/subagent-judge.json +0 -11
  87. package/dist/recipes/subagent-merge.json +0 -11
  88. package/dist/recipes/subagent-normalize.json +0 -11
  89. package/dist/recipes/subagent-plan.json +0 -11
  90. package/dist/recipes/subagent-preflight.json +0 -11
  91. package/dist/recipes/subagent-prompt.json +0 -10
  92. package/dist/recipes/subagent-quorum.json +0 -10
  93. package/dist/recipes/subagent-review-coordinator.json +0 -14
  94. package/dist/recipes/subagent-review.json +0 -11
  95. package/dist/recipes/subagent-task-card.json +0 -11
  96. package/dist/recipes/subagent-tools.json +0 -10
  97. package/dist/recipes/subagent-verify.json +0 -11
  98. package/dist/recipes/subagents-prompts.json +0 -10
  99. package/dist/recipes/tool-review.json +0 -10
  100. package/dist/scripts/async-runner.mjs +25 -25
  101. package/dist/scripts/conformance.mjs +4 -2
  102. package/dist/scripts/locker.mjs +200 -66
  103. package/dist/scripts/music-player.mjs +159 -150
  104. package/dist/scripts/recipe-utils.mjs +6 -96
  105. package/dist/scripts/release-gates.mjs +60 -0
  106. package/dist/scripts/validate-recipe.mjs +3 -53
  107. package/dist/skills/actors/SKILL.md +53 -266
  108. package/dist/skills/swarm/SKILL.md +11 -33
  109. package/docs/0.43-baseline.md +44 -0
  110. package/docs/README.md +3 -3
  111. package/docs/actor-inspector.md +26 -64
  112. package/docs/actors-deep-reference.md +92 -50
  113. package/docs/async-runs.md +81 -328
  114. package/docs/command-templates.md +2 -2
  115. package/docs/component-recipes.md +30 -133
  116. package/docs/recipe-library.md +57 -182
  117. package/docs/task-first-recipes.md +10 -12
  118. package/docs/template-recipes.md +76 -289
  119. package/docs/tool-registry.md +41 -161
  120. package/fixtures/protocol/control-endpoint.json +6 -0
  121. package/fixtures/protocol/control-record.json +9 -0
  122. package/fixtures/protocol/recipe-summary.json +4 -12
  123. package/fixtures/protocol/trace-event.json +9 -0
  124. package/lib/async-runs.ts +202 -201
  125. package/lib/control.ts +102 -0
  126. package/lib/execution-sessions.ts +111 -0
  127. package/lib/file-state.ts +17 -4
  128. package/lib/inspector-actions.ts +2 -2
  129. package/lib/inspector-command.ts +3 -3
  130. package/lib/inspector-overlay.ts +577 -1121
  131. package/lib/inspector.ts +46 -979
  132. package/lib/limits.ts +4 -2
  133. package/lib/observability.ts +63 -104
  134. package/lib/pi.ts +1 -1
  135. package/lib/prompts.ts +2 -2
  136. package/lib/recipe-control.ts +45 -0
  137. package/lib/recipes-discovery.ts +2 -0
  138. package/lib/recipes-references.ts +9 -45
  139. package/lib/review-projection.ts +1 -5
  140. package/lib/run-ui-runtime.ts +2 -2
  141. package/lib/runs-control-delivery.ts +181 -0
  142. package/lib/runs-controls.ts +204 -0
  143. package/lib/runs-retention.ts +38 -3
  144. package/lib/runs-start.ts +4 -2
  145. package/lib/runs-status.ts +11 -6
  146. package/lib/runs-trace.ts +132 -0
  147. package/lib/runtime-notifier.ts +1 -1
  148. package/lib/tools-inspect.ts +240 -901
  149. package/lib/tools-local.ts +2 -12
  150. package/lib/tools-message.ts +112 -519
  151. package/lib/tools-response.ts +5 -52
  152. package/lib/tools-spawn.ts +16 -32
  153. package/lib/tools.ts +1 -2
  154. package/lib/trace-projection.ts +221 -0
  155. package/package.json +2 -1
  156. package/recipes/draft-review.json +0 -10
  157. package/recipes/lens-swarm.json +0 -14
  158. package/recipes/music-player.json +10 -19
  159. package/recipes/pipeline-architect-coordinator.json +0 -11
  160. package/recipes/pipeline-artifact-bundle.json +1 -22
  161. package/recipes/pipeline-artifact-report.json +1 -18
  162. package/recipes/pipeline-artifact-write.json +1 -18
  163. package/recipes/pipeline-async-run-ops.json +0 -12
  164. package/recipes/pipeline-checkpoint-continuation.json +0 -14
  165. package/recipes/pipeline-development-tasking.json +0 -12
  166. package/recipes/pipeline-docs-maintenance.json +0 -12
  167. package/recipes/pipeline-media-library.json +0 -12
  168. package/recipes/pipeline-quorum-review.json +0 -12
  169. package/recipes/pipeline-release-readiness.json +0 -12
  170. package/recipes/pipeline-release-summary.json +0 -12
  171. package/recipes/pipeline-repo-health.json +0 -12
  172. package/recipes/pipeline-research-synthesis.json +0 -11
  173. package/recipes/pipeline-review-readiness.json +0 -12
  174. package/recipes/resource-locker.json +27 -0
  175. package/recipes/subagent-artifact.json +0 -9
  176. package/recipes/subagent-checkpoint.json +0 -10
  177. package/recipes/subagent-conflict-report.json +0 -11
  178. package/recipes/subagent-contradiction-map.json +0 -11
  179. package/recipes/subagent-critic.json +0 -11
  180. package/recipes/subagent-evidence-map.json +0 -11
  181. package/recipes/subagent-followup.json +0 -10
  182. package/recipes/subagent-judge.json +0 -11
  183. package/recipes/subagent-merge.json +0 -11
  184. package/recipes/subagent-normalize.json +0 -11
  185. package/recipes/subagent-plan.json +0 -11
  186. package/recipes/subagent-preflight.json +0 -11
  187. package/recipes/subagent-prompt.json +0 -10
  188. package/recipes/subagent-quorum.json +0 -10
  189. package/recipes/subagent-review-coordinator.json +0 -14
  190. package/recipes/subagent-review.json +0 -11
  191. package/recipes/subagent-task-card.json +0 -11
  192. package/recipes/subagent-tools.json +0 -10
  193. package/recipes/subagent-verify.json +0 -11
  194. package/recipes/subagents-prompts.json +0 -10
  195. package/recipes/tool-review.json +0 -10
  196. package/scripts/async-runner.mjs +25 -25
  197. package/scripts/conformance.mjs +4 -2
  198. package/scripts/locker.mjs +200 -66
  199. package/scripts/music-player.mjs +159 -150
  200. package/scripts/recipe-utils.mjs +6 -96
  201. package/scripts/release-gates.mjs +60 -0
  202. package/scripts/validate-recipe.mjs +3 -53
  203. package/skills/actors/SKILL.md +53 -266
  204. package/skills/swarm/SKILL.md +11 -33
  205. package/dist/fixtures/protocol/actor-message-branch.json +0 -13
  206. package/dist/fixtures/protocol/mailbox-contract.json +0 -15
  207. package/dist/fixtures/protocol/room-message.json +0 -11
  208. package/dist/fixtures/protocol/room-roster.json +0 -11
  209. package/dist/fixtures/protocol/run-inbox-message.json +0 -9
  210. package/dist/fixtures/protocol/run-outbox-event.json +0 -9
  211. package/dist/lib/mailbox-loop.d.ts +0 -41
  212. package/dist/lib/mailbox-loop.js +0 -60
  213. package/dist/lib/messages.d.ts +0 -25
  214. package/dist/lib/messages.js +0 -122
  215. package/dist/lib/rooms.d.ts +0 -104
  216. package/dist/lib/rooms.js +0 -647
  217. package/dist/lib/runs-mailbox.d.ts +0 -25
  218. package/dist/lib/runs-mailbox.js +0 -146
  219. package/dist/lib/runs-messages.d.ts +0 -15
  220. package/dist/lib/runs-messages.js +0 -179
  221. package/dist/lib/runs-outbox.d.ts +0 -41
  222. package/dist/lib/runs-outbox.js +0 -87
  223. package/dist/lib/tools-mailbox.d.ts +0 -8
  224. package/dist/lib/tools-mailbox.js +0 -48
  225. package/dist/recipes/actor-worker.json +0 -39
  226. package/dist/recipes/coordinator-locker.json +0 -45
  227. package/dist/recipes/locker.json +0 -45
  228. package/dist/recipes/pipeline-room-swarm.json +0 -50
  229. package/dist/recipes/subagent-message.json +0 -32
  230. package/dist/recipes/utility-actor-message.json +0 -23
  231. package/dist/scripts/actor-worker.mjs +0 -214
  232. package/dist/scripts/coordinator.mjs +0 -799
  233. package/docs/actor-messages.md +0 -225
  234. package/fixtures/protocol/actor-message-branch.json +0 -13
  235. package/fixtures/protocol/mailbox-contract.json +0 -15
  236. package/fixtures/protocol/room-message.json +0 -11
  237. package/fixtures/protocol/room-roster.json +0 -11
  238. package/fixtures/protocol/run-inbox-message.json +0 -9
  239. package/fixtures/protocol/run-outbox-event.json +0 -9
  240. package/lib/mailbox-loop.ts +0 -144
  241. package/lib/messages.ts +0 -151
  242. package/lib/rooms.ts +0 -939
  243. package/lib/runs-mailbox.ts +0 -208
  244. package/lib/runs-messages.ts +0 -252
  245. package/lib/runs-outbox.ts +0 -144
  246. package/lib/tools-mailbox.ts +0 -56
  247. package/recipes/actor-worker.json +0 -39
  248. package/recipes/coordinator-locker.json +0 -45
  249. package/recipes/locker.json +0 -45
  250. package/recipes/pipeline-room-swarm.json +0 -50
  251. package/recipes/subagent-message.json +0 -32
  252. package/recipes/utility-actor-message.json +0 -23
  253. package/scripts/actor-worker.mjs +0 -214
  254. package/scripts/coordinator.mjs +0 -799
package/lib/limits.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * Shared output and preview size limits.
3
- * Zones: output governance, actor-message previews, inspect defaults
3
+ * Zones: output governance, Trace previews, inspect defaults
4
4
  */
5
5
 
6
6
  export const DEFAULT_INSPECT_LINES = 40;
7
7
  export const TOOL_OUTPUT_MAX_BYTES = 50 * 1024;
8
8
  export const TOOL_OUTPUT_MAX_LINES = 2_000;
9
9
  export const COMPACT_PREVIEW_CHARS = 160;
10
+ export const CONTROL_INPUT_MAX_BYTES = 64 * 1024;
10
11
  export const INSPECTOR_BODY_PREVIEW_CHARS = 320;
11
- export const ROOM_MESSAGE_PREVIEW_CHARS = 320;
12
12
  export const DOCTOR_ACTION_PREVIEW_CHARS = 72;
13
13
  export const SESSION_EVIDENCE_MAX_TURNS = 100;
14
14
  export const SESSION_EVIDENCE_TEXT_CHARS = 4_000;
15
15
  export const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
16
+ export const TRACE_EVENT_MAX_BYTES = 64 * 1024;
17
+ export const TRACE_EVENT_MAX_READ = 200;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Async run observability helpers
3
3
  * Zones: async runtime, ambient UI, diagnostics
4
- * Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
4
+ * Owns ambient summaries, terminal events, and Trace-attention delivery for detached Runs
5
5
  */
6
6
 
7
7
  import {
@@ -34,8 +34,8 @@ export type RunObservedStatus =
34
34
  | "exited"
35
35
  | "cancelled"
36
36
  | "killed";
37
- export type RunOutboxDelivery = "log" | "notify" | "followup";
38
- export type RunOutboxLevel = "info" | "warning" | "error";
37
+ export type RunTraceAttention = "log" | "notify" | "followup";
38
+ export type RunTraceLevel = "info" | "warning" | "error";
39
39
 
40
40
  export interface RunObservation {
41
41
  activeSubagents?: number;
@@ -72,14 +72,14 @@ export interface RunSummary {
72
72
  }
73
73
 
74
74
  export interface RunUiObservationState {
75
+ attentionEventIds: Map<string, Set<string>>;
75
76
  eventLines: Map<string, number>;
76
77
  frame: number;
77
78
  observed: Map<string, RunObservedStatus>;
78
- outboxEventIds: Map<string, Set<string>>;
79
79
  }
80
80
 
81
81
  export interface RunUiSnapshot {
82
- outboxEvents: RunOutboxEvent[];
82
+ attentionEvents: RunAttentionEvent[];
83
83
  status: string | undefined;
84
84
  summary: RunSummary;
85
85
  transitions: RunTransition[];
@@ -90,35 +90,35 @@ export interface RunUiNotificationSink {
90
90
  sendFollowUp(message: {
91
91
  customType: string;
92
92
  content: string;
93
- display: true;
93
+ display: false;
94
94
  details: unknown;
95
95
  }): void;
96
96
  }
97
97
 
98
98
  export function createRunUiObservationState(): RunUiObservationState {
99
99
  return {
100
+ attentionEventIds: new Map<string, Set<string>>(),
100
101
  eventLines: new Map<string, number>(),
101
102
  frame: 0,
102
103
  observed: new Map<string, RunObservedStatus>(),
103
- outboxEventIds: new Map<string, Set<string>>(),
104
104
  };
105
105
  }
106
106
 
107
107
  export function readRunUiSnapshot(
108
108
  state: RunUiObservationState,
109
109
  ownerId: string,
110
- options: { includeOutbox?: boolean; stateRoot?: string } = {},
110
+ options: { includeAttention?: boolean; stateRoot?: string } = {},
111
111
  ): RunUiSnapshot {
112
112
  const summary = summarizeRuns(options.stateRoot, ownerId);
113
113
  const status = renderRunStatus(summary, state.frame++);
114
114
  return {
115
- outboxEvents:
116
- options.includeOutbox === false
115
+ attentionEvents:
116
+ options.includeAttention === false
117
117
  ? []
118
- : detectRunOutboxEvents(
118
+ : detectRunAttentionEvents(
119
119
  state.eventLines,
120
120
  summary,
121
- state.outboxEventIds,
121
+ state.attentionEventIds,
122
122
  ),
123
123
  status,
124
124
  summary,
@@ -137,7 +137,7 @@ export function pruneRunUiObservationState(
137
137
  snapshot.transitions.map(
138
138
  (transition) => transition.stateDir ?? transition.run,
139
139
  ),
140
- state.outboxEventIds,
140
+ state.attentionEventIds,
141
141
  );
142
142
  }
143
143
 
@@ -158,7 +158,7 @@ export function deliverRunTransitionNotifications(
158
158
  sink.sendFollowUp({
159
159
  customType: "pi-actors-run",
160
160
  content: text,
161
- display: true,
161
+ display: false,
162
162
  details: transition,
163
163
  });
164
164
  if (transition.stateDir) {
@@ -194,7 +194,7 @@ export function reconcileRunTerminalNotifications(input: {
194
194
  stateRoot?: string;
195
195
  }): RunUiSnapshot {
196
196
  const snapshot = readRunUiSnapshot(input.state, input.ownerId, {
197
- includeOutbox: false,
197
+ includeAttention: false,
198
198
  stateRoot: input.stateRoot,
199
199
  });
200
200
  deliverRunTransitionNotifications(
@@ -206,19 +206,19 @@ export function reconcileRunTerminalNotifications(input: {
206
206
  return snapshot;
207
207
  }
208
208
 
209
- export function deliverRunOutboxNotifications(
210
- events: RunOutboxEvent[],
209
+ export function deliverRunAttentionNotifications(
210
+ events: RunAttentionEvent[],
211
211
  sink: RunUiNotificationSink,
212
212
  ): void {
213
213
  for (const event of events) {
214
- if (!shouldNotifyRunOutboxEvent(event)) continue;
215
- const text = formatRunOutboxMessage(event);
216
- sink.notify(text, getRunOutboxNotificationType(event));
217
- if (!shouldSendRunOutboxFollowUp(event)) continue;
214
+ if (!shouldNotifyRunAttentionEvent(event)) continue;
215
+ const text = formatRunAttentionMessage(event);
216
+ sink.notify(text, getRunAttentionNotificationType(event));
217
+ if (!shouldSendRunAttentionFollowUp(event)) continue;
218
218
  sink.sendFollowUp({
219
- customType: "pi-actors-run-message",
219
+ customType: "pi-actors-run-trace",
220
220
  content: text,
221
- display: true,
221
+ display: false,
222
222
  details: event,
223
223
  });
224
224
  }
@@ -457,13 +457,13 @@ export interface RunTerminalSemanticResult {
457
457
  type: string;
458
458
  }
459
459
 
460
- export interface RunOutboxEvent {
460
+ export interface RunAttentionEvent {
461
461
  body?: unknown;
462
462
  data?: unknown;
463
- delivery: RunOutboxDelivery;
464
- event: string;
463
+ attention: RunTraceAttention;
465
464
  id: string;
466
- level: RunOutboxLevel;
465
+ kind: string;
466
+ level: RunTraceLevel;
467
467
  metadata?: Record<string, unknown>;
468
468
  run: string;
469
469
  stateDir: string;
@@ -575,48 +575,7 @@ function terminalSemanticResult(
575
575
  : typeof correlation.tool_call_id === "string"
576
576
  ? correlation.tool_call_id
577
577
  : undefined;
578
- const mailbox = status.mailbox &&
579
- typeof status.mailbox === "object" &&
580
- !Array.isArray(status.mailbox)
581
- ? status.mailbox as Record<string, unknown>
582
- : {};
583
- const emits = Array.isArray(mailbox.emits)
584
- ? mailbox.emits.filter((item): item is string => typeof item === "string")
585
- : [];
586
- const outbox = readJsonlFileResilient<Record<string, unknown>>(
587
- join(stateDir, "outbox.jsonl"),
588
- ).records;
589
- const explicit = outbox.findLast((record) => {
590
- const type = String(record.type ?? record.event ?? "");
591
- return emits.includes(type) && !["command.done", "run.done", "run.failed"].includes(type);
592
- });
593
- if (explicit) {
594
- const type = String(explicit.type ?? explicit.event);
595
- return {
596
- ...(explicit.body === undefined
597
- ? {} : { body: formatSemanticBody(explicit.body) }),
598
- ...(typeof explicit.correlation_id === "string"
599
- ? { correlationId: explicit.correlation_id } : correlationId ? { correlationId } : {}),
600
- metadata: {
601
- ...(explicit.metadata &&
602
- typeof explicit.metadata === "object" &&
603
- !Array.isArray(explicit.metadata)
604
- ? explicit.metadata as Record<string, unknown> : {}),
605
- ...(status.transport_context &&
606
- typeof status.transport_context === "object" &&
607
- !Array.isArray(status.transport_context)
608
- ? {
609
- transport_context: status.transport_context as Record<string, unknown>,
610
- } : {}),
611
- run: String(status.run ?? ""),
612
- status: observedStatus,
613
- },
614
- summary: String(explicit.summary ?? type),
615
- synthesized: false,
616
- type,
617
- };
618
- }
619
- const reviewCompleted = observedStatus === "done" && emits.includes("review.completed");
578
+ const reviewCompleted = false;
620
579
  const result = status.result &&
621
580
  typeof status.result === "object" &&
622
581
  !Array.isArray(status.result)
@@ -648,15 +607,6 @@ function terminalSemanticResult(
648
607
  };
649
608
  }
650
609
 
651
- function formatSemanticBody(body: unknown): string {
652
- const rendered = typeof body === "string" ? body : JSON.stringify(body);
653
- const text = typeof rendered === "string" ? rendered : String(body);
654
- const compact = text.trim();
655
- return compact.length > TERMINAL_RESULT_CHARS
656
- ? `${compact.slice(0, TERMINAL_RESULT_CHARS - 1)}…`
657
- : compact;
658
- }
659
-
660
610
  function observeRun(stateDir: string): RunObservation | undefined {
661
611
  try {
662
612
  const status = AsyncRuns.getRunStatus(stateDir);
@@ -1110,24 +1060,26 @@ export function detectRunTransitions(
1110
1060
  return transitions;
1111
1061
  }
1112
1062
 
1113
- function normalizeOutboxDelivery(value: unknown): RunOutboxDelivery {
1063
+ function normalizeTraceAttention(value: unknown): RunTraceAttention {
1114
1064
  return value === "notify" || value === "followup" ? value : "log";
1115
1065
  }
1116
1066
 
1117
- function normalizeOutboxLevel(value: unknown): RunOutboxLevel {
1067
+ function normalizeTraceLevel(value: unknown): RunTraceLevel {
1118
1068
  return value === "warning" || value === "error" ? value : "info";
1119
1069
  }
1120
1070
 
1121
- function parseOutboxRecord(
1071
+ function parseAttentionRecord(
1122
1072
  raw: Record<string, unknown>,
1123
1073
  run: RunObservation,
1124
1074
  index: number,
1125
- ): RunOutboxEvent | undefined {
1075
+ ): RunAttentionEvent | undefined {
1126
1076
  if (!run.stateDir) return undefined;
1127
1077
  const event =
1128
- typeof raw.event === "string" && raw.event.trim()
1129
- ? raw.event.trim()
1130
- : "run.event";
1078
+ typeof raw.kind === "string" && raw.kind.trim()
1079
+ ? raw.kind.trim()
1080
+ : typeof raw.event === "string" && raw.event.trim()
1081
+ ? raw.event.trim()
1082
+ : "run.event";
1131
1083
  const summary =
1132
1084
  typeof raw.summary === "string" && raw.summary.trim()
1133
1085
  ? raw.summary.trim()
@@ -1143,10 +1095,13 @@ function parseOutboxRecord(
1143
1095
  return {
1144
1096
  ...(raw.body !== undefined ? { body: raw.body } : {}),
1145
1097
  ...(raw.data !== undefined ? { data: raw.data } : {}),
1146
- delivery: normalizeOutboxDelivery(raw.delivery),
1147
- event,
1098
+ attention:
1099
+ raw.attention === "notify" || raw.attention === "followup"
1100
+ ? raw.attention
1101
+ : normalizeTraceAttention(raw.delivery),
1148
1102
  id,
1149
- level: normalizeOutboxLevel(raw.level),
1103
+ kind: event,
1104
+ level: normalizeTraceLevel(raw.level),
1150
1105
  ...(raw.metadata &&
1151
1106
  typeof raw.metadata === "object" &&
1152
1107
  !Array.isArray(raw.metadata)
@@ -1159,8 +1114,12 @@ function parseOutboxRecord(
1159
1114
  };
1160
1115
  }
1161
1116
 
1162
- function readOutboxRecords(run: RunObservation): Record<string, unknown>[] {
1117
+ function readTraceAttentionRecords(run: RunObservation): Record<string, unknown>[] {
1163
1118
  if (!run.stateDir) return [];
1119
+ const tracePath = join(run.stateDir, "trace.jsonl");
1120
+ if (existsSync(tracePath)) {
1121
+ return readJsonlFileResilient<Record<string, unknown>>(tracePath).records;
1122
+ }
1164
1123
  return readJsonlFileResilient<Record<string, unknown>>(
1165
1124
  join(run.stateDir, "outbox.jsonl"),
1166
1125
  ).records;
@@ -1199,15 +1158,15 @@ export function pruneRunObservationState(
1199
1158
  }
1200
1159
  }
1201
1160
 
1202
- export function detectRunOutboxEvents(
1161
+ export function detectRunAttentionEvents(
1203
1162
  previousLineCounts: Map<string, number>,
1204
1163
  summary: RunSummary,
1205
1164
  seenEventIds: Map<string, Set<string>> = new Map(),
1206
- ): RunOutboxEvent[] {
1207
- const events: RunOutboxEvent[] = [];
1165
+ ): RunAttentionEvent[] {
1166
+ const events: RunAttentionEvent[] = [];
1208
1167
  for (const run of summary.runs) {
1209
1168
  const key = run.stateDir ?? run.run;
1210
- const records = readOutboxRecords(run);
1169
+ const records = readTraceAttentionRecords(run);
1211
1170
  const previousCount = previousLineCounts.get(key) ?? 0;
1212
1171
  if (run.notificationPolicy === "silent") {
1213
1172
  previousLineCounts.set(key, records.length);
@@ -1217,7 +1176,7 @@ export function detectRunOutboxEvents(
1217
1176
  const start = Math.min(previousCount, records.length);
1218
1177
  const seen = seenEventIds.get(key) ?? new Set<string>();
1219
1178
  for (let index = start; index < records.length; index += 1) {
1220
- const event = parseOutboxRecord(records[index], run, index);
1179
+ const event = parseAttentionRecord(records[index], run, index);
1221
1180
  if (!event || seen.has(event.id)) continue;
1222
1181
  events.push(event);
1223
1182
  seen.add(event.id);
@@ -1228,18 +1187,18 @@ export function detectRunOutboxEvents(
1228
1187
  return events;
1229
1188
  }
1230
1189
 
1231
- export function getRunOutboxNotificationType(
1232
- event: RunOutboxEvent,
1190
+ export function getRunAttentionNotificationType(
1191
+ event: RunAttentionEvent,
1233
1192
  ): RunTransitionNotificationType {
1234
1193
  return event.level;
1235
1194
  }
1236
1195
 
1237
- export function shouldNotifyRunOutboxEvent(event: RunOutboxEvent): boolean {
1238
- return event.delivery === "notify" || event.delivery === "followup";
1196
+ export function shouldNotifyRunAttentionEvent(event: RunAttentionEvent): boolean {
1197
+ return event.attention === "notify" || event.attention === "followup";
1239
1198
  }
1240
1199
 
1241
- export function shouldSendRunOutboxFollowUp(event: RunOutboxEvent): boolean {
1242
- return event.delivery === "followup";
1200
+ export function shouldSendRunAttentionFollowUp(event: RunAttentionEvent): boolean {
1201
+ return event.attention === "followup";
1243
1202
  }
1244
1203
 
1245
1204
  function commonDirectory(paths: string[]): string | undefined {
@@ -1297,7 +1256,7 @@ function formatNamedArtifacts(artifacts: unknown): string {
1297
1256
  );
1298
1257
  }
1299
1258
 
1300
- function getOutboxField(event: RunOutboxEvent, key: string): unknown {
1259
+ function getAttentionDataField(event: RunAttentionEvent, key: string): unknown {
1301
1260
  return event.data &&
1302
1261
  typeof event.data === "object" &&
1303
1262
  !Array.isArray(event.data)
@@ -1313,10 +1272,10 @@ function formatBodyPreview(body: unknown): string {
1313
1272
  return `\nBody: ${compact.length > 500 ? `${compact.slice(0, 500)}…` : compact}`;
1314
1273
  }
1315
1274
 
1316
- export function formatRunOutboxMessage(event: RunOutboxEvent): string {
1317
- if (event.event === "command.done")
1275
+ export function formatRunAttentionMessage(event: RunAttentionEvent): string {
1276
+ if (event.kind === "command.done")
1318
1277
  return `Run ${event.run}: ${event.summary}`;
1319
- return `Run ${event.run}: ${event.summary}${formatBodyPreview(event.body)}${formatNamedArtifacts(getOutboxField(event, "artifacts"))}${formatRunFileList(getOutboxField(event, "run_files"))}`;
1278
+ return `Run ${event.run}: ${event.summary}${formatBodyPreview(event.body)}${formatNamedArtifacts(getAttentionDataField(event, "artifacts"))}${formatRunFileList(getAttentionDataField(event, "run_files"))}`;
1320
1279
  }
1321
1280
 
1322
1281
  export function getRunTransitionNotificationType(
package/lib/pi.ts CHANGED
@@ -16,7 +16,7 @@ export interface PiNotificationSink {
16
16
  sendFollowUp(message: {
17
17
  customType: string;
18
18
  content: string;
19
- display: true;
19
+ display: false;
20
20
  details: unknown;
21
21
  }): void;
22
22
  }
package/lib/prompts.ts CHANGED
@@ -25,10 +25,10 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
25
25
  - Command templates stay sync and shell-free: string leaves split into executable + argv, so operators such as && are literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.
26
26
  - Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
27
27
  - ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
28
- - Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
28
+ - Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.
29
29
  - Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
30
30
  - Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
31
- - Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.
31
+ - Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid internal transport vocabulary in public guidance.
32
32
  - Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.
33
33
  - Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
34
34
  - Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Recipe Control contract.
3
+ * Zones: actor-local action declarations, normalization, reserved-action fencing
4
+ * Owns pure Recipe control validation; Recipe loading and Run capture stay in recipe/run domains.
5
+ */
6
+
7
+ const ACTION_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)*$/;
8
+ const RUNTIME_RUN_ACTIONS = new Set(["archive", "kill", "prune"]);
9
+
10
+ export function normalizeRecipeControl(value: unknown): string[] | undefined {
11
+ if (value === undefined) return undefined;
12
+ if (!Array.isArray(value)) {
13
+ throw new Error("recipe.control must be an array of action strings");
14
+ }
15
+ const actions: string[] = [];
16
+ const seen = new Set<string>();
17
+ for (const raw of value) {
18
+ if (typeof raw !== "string" || !raw.trim()) {
19
+ throw new Error("recipe.control actions must be non-empty strings");
20
+ }
21
+ const action = raw.trim();
22
+ if (!ACTION_PATTERN.test(action)) {
23
+ throw new Error(`invalid recipe.control action: ${action}`);
24
+ }
25
+ if (RUNTIME_RUN_ACTIONS.has(action)) {
26
+ throw new Error(
27
+ `recipe.control action is runtime-reserved and must not be declared: ${action}`,
28
+ );
29
+ }
30
+ if (seen.has(action)) {
31
+ throw new Error(`duplicate recipe.control action: ${action}`);
32
+ }
33
+ seen.add(action);
34
+ actions.push(action);
35
+ }
36
+ return actions;
37
+ }
38
+
39
+ export function assertRecipeHasNoMailbox(recipe: Record<string, unknown>): void {
40
+ if (Object.hasOwn(recipe, "mailbox")) {
41
+ throw new Error(
42
+ "recipe.mailbox was removed; replace it with control: [\"action\"] for actor-local inputs and emit Trace events for outputs",
43
+ );
44
+ }
45
+ }
@@ -503,6 +503,8 @@ function diagnosticSuggestedAction(message: string): string {
503
503
  if (/cyclic/i.test(message)) return "break the import cycle";
504
504
  if (/exceeds.*size/i.test(message))
505
505
  return "split large prompt or data into separate files";
506
+ if (/recipe\.mailbox was removed/i.test(message))
507
+ return "replace mailbox.accepts with control actions and route outputs to Trace events";
506
508
  if (/repeat must/i.test(message))
507
509
  return "use a positive repeat count or an array-typed repeat source";
508
510
  if (/shadows/i.test(message))
@@ -14,6 +14,7 @@ import type {
14
14
  } from "./command-templates.ts";
15
15
  import * as CommandTemplates from "./command-templates.ts";
16
16
  import * as Paths from "./paths.ts";
17
+ import * as RecipeControl from "./recipe-control.ts";
17
18
 
18
19
  const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
19
20
  const MAX_RECIPE_IMPORT_DEPTH = 32;
@@ -26,23 +27,6 @@ export interface TemplateRecipeImportBinding {
26
27
 
27
28
  export type TemplateRecipeImport = string | TemplateRecipeImportBinding;
28
29
 
29
- export type TemplateRecipeMailboxEntry =
30
- | string
31
- | {
32
- type: string;
33
- body_schema?: unknown;
34
- ack?: boolean | string;
35
- idempotency?: string;
36
- requires_response?: boolean;
37
- level?: string;
38
- summary?: string;
39
- };
40
-
41
- export interface TemplateRecipeMailbox {
42
- accepts?: TemplateRecipeMailboxEntry[];
43
- emits?: TemplateRecipeMailboxEntry[];
44
- }
45
-
46
30
  export interface TemplateRecipeDefinition {
47
31
  name?: string;
48
32
  description?: string;
@@ -61,7 +45,7 @@ export interface TemplateRecipeDefinition {
61
45
  accept_output?: "review_evidence";
62
46
  output?: string;
63
47
  artifacts?: Record<string, string>;
64
- mailbox?: TemplateRecipeMailbox;
48
+ control?: string[];
65
49
  retire_when?: "children_terminal";
66
50
  retry?: number | string;
67
51
  failure?: CommandTemplates.CommandTemplateFailureScope;
@@ -912,6 +896,7 @@ export function readResolvedRecipeConfig(
912
896
  }
913
897
  const raw = readRawRecipeConfig(path);
914
898
  if (!raw || !Object.hasOwn(raw, "template")) return undefined;
899
+ RecipeControl.assertRecipeHasNoMailbox(raw);
915
900
  const imports: Record<string, ImportedRecipe> = {};
916
901
  for (const [alias, binding] of Object.entries(getRecipeImports(raw))) {
917
902
  const importPath = resolveRecipeImportPath(
@@ -975,9 +960,11 @@ export function readResolvedRecipeConfig(
975
960
  const artifactSource = isRecord(substituted.artifacts)
976
961
  ? substituted.artifacts
977
962
  : delegated?.artifacts;
978
- const mailboxSource = isRecord(substituted.mailbox)
979
- ? substituted.mailbox
980
- : delegated?.mailbox;
963
+ const control = RecipeControl.normalizeRecipeControl(
964
+ Object.hasOwn(substituted, "control")
965
+ ? substituted.control
966
+ : delegated?.control,
967
+ );
981
968
  return {
982
969
  name: recipeName,
983
970
  ...(typeof substituted.description === "string" &&
@@ -1052,30 +1039,7 @@ export function readResolvedRecipeConfig(
1052
1039
  ),
1053
1040
  }
1054
1041
  : {}),
1055
- ...(isRecord(mailboxSource)
1056
- ? {
1057
- mailbox: {
1058
- ...(Array.isArray(mailboxSource.accepts)
1059
- ? {
1060
- accepts: mailboxSource.accepts.filter(
1061
- (value): value is TemplateRecipeMailboxEntry =>
1062
- typeof value === "string" ||
1063
- (isRecord(value) && typeof value.type === "string"),
1064
- ),
1065
- }
1066
- : {}),
1067
- ...(Array.isArray(mailboxSource.emits)
1068
- ? {
1069
- emits: mailboxSource.emits.filter(
1070
- (value): value is TemplateRecipeMailboxEntry =>
1071
- typeof value === "string" ||
1072
- (isRecord(value) && typeof value.type === "string"),
1073
- ),
1074
- }
1075
- : {}),
1076
- },
1077
- }
1078
- : {}),
1042
+ ...(control !== undefined ? { control } : {}),
1079
1043
  ...(substituted.retire_when === "children_terminal" ||
1080
1044
  delegated?.retire_when === "children_terminal"
1081
1045
  ? { retire_when: "children_terminal" as const }
@@ -87,17 +87,13 @@ function templateShape(value: unknown): Record<string, unknown> {
87
87
 
88
88
  function contractShape(recipe: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
89
89
  if (!recipe) return undefined;
90
- const mailbox = recipe.mailbox && typeof recipe.mailbox === "object" && !Array.isArray(recipe.mailbox)
91
- ? recipe.mailbox as Record<string, unknown>
92
- : {};
93
90
  return {
94
91
  argument_count: countArray(recipe.args),
95
92
  artifact_count: countRecord(recipe.artifacts),
96
93
  async: recipe.async === true,
97
94
  default_count: countRecord(recipe.defaults),
98
95
  import_count: countRecord(recipe.imports),
99
- mailbox_accepts_count: countArray(mailbox.accepts),
100
- mailbox_emits_count: countArray(mailbox.emits),
96
+ control_action_count: countArray(recipe.control),
101
97
  template_shape: templateShape(recipe.template),
102
98
  };
103
99
  }
@@ -38,7 +38,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
38
38
  attempted: retirementAttempts,
39
39
  cancelRun: (candidate) => AsyncRuns.cancelRun(candidate.stateDir),
40
40
  notify: (message, level) => ctx.ui.notify(message, level),
41
- sendStop: (candidate) => AsyncRuns.sendRunMessage(candidate.stateDir, "stop"),
41
+ sendStop: async (candidate) => AsyncRuns.cancelRun(candidate.stateDir),
42
42
  });
43
43
  };
44
44
  const update = (
@@ -62,7 +62,7 @@ export function createRunUiRuntime(deps: RunUiRuntimeDeps): RunUiRuntime {
62
62
  );
63
63
  Observability.pruneRunUiObservationState(observation, snapshot);
64
64
  if (!terminalOnly) {
65
- Observability.deliverRunOutboxNotifications(snapshot.outboxEvents, sink);
65
+ Observability.deliverRunAttentionNotifications(snapshot.attentionEvents, sink);
66
66
  }
67
67
  };
68
68
  const reportDiagnostics = (ctx: Pi.ExtensionContext): void => {