@tt-a1i/openpi 0.5.0 → 0.6.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.
Files changed (80) hide show
  1. package/README.md +30 -20
  2. package/SETUP.md +10 -4
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/bin/openpi.js +25 -15
  5. package/extensions/ai-providers/LICENSE.upstream +23 -0
  6. package/extensions/ai-providers/README.md +65 -0
  7. package/extensions/ai-providers/antigravity/credentials.ts +52 -0
  8. package/extensions/ai-providers/antigravity/discovery.ts +130 -0
  9. package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
  10. package/extensions/ai-providers/antigravity/models.ts +84 -0
  11. package/extensions/ai-providers/antigravity/oauth.ts +700 -0
  12. package/extensions/ai-providers/antigravity/provider.ts +1116 -0
  13. package/extensions/ai-providers/antigravity/routing.ts +340 -0
  14. package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
  15. package/extensions/ai-providers/cursor/constants.ts +5 -0
  16. package/extensions/ai-providers/cursor/credentials.ts +14 -0
  17. package/extensions/ai-providers/cursor/discovery.ts +291 -0
  18. package/extensions/ai-providers/cursor/input-images.ts +105 -0
  19. package/extensions/ai-providers/cursor/models.ts +45 -0
  20. package/extensions/ai-providers/cursor/oauth.ts +263 -0
  21. package/extensions/ai-providers/cursor/proto.ts +1271 -0
  22. package/extensions/ai-providers/cursor/protobuf.ts +1181 -0
  23. package/extensions/ai-providers/cursor/provider.ts +1431 -0
  24. package/extensions/ai-providers/cursor/proxy.ts +213 -0
  25. package/extensions/ai-providers/cursor/tool-bridge.ts +68 -0
  26. package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
  27. package/extensions/ai-providers/index.ts +86 -0
  28. package/extensions/ai-providers/oauth-adapter.ts +81 -0
  29. package/extensions/ai-providers/usage.ts +10 -0
  30. package/extensions/background-terminals/index.ts +8 -1
  31. package/extensions/background-terminals/src/manager.ts +3 -5
  32. package/extensions/background-terminals/src/result-delivery.ts +43 -23
  33. package/extensions/cron/index.ts +68 -27
  34. package/extensions/cron/schedule.ts +5 -1
  35. package/extensions/model-info/cache-diagnostics.ts +220 -0
  36. package/extensions/model-info/index.ts +45 -1
  37. package/extensions/plan-mode/index.ts +75 -4
  38. package/extensions/setup/index.ts +15 -3
  39. package/extensions/shared/child-session.ts +39 -5
  40. package/extensions/shared/completion-inbox.ts +193 -0
  41. package/extensions/shared/setup-config.ts +10 -1
  42. package/extensions/shared/structured-output.ts +154 -0
  43. package/extensions/subagents/index.ts +64 -7
  44. package/extensions/subagents/src/agent-types.ts +5 -17
  45. package/extensions/subagents/src/backends/pi.ts +130 -48
  46. package/extensions/subagents/src/backends/tool-preview.ts +29 -0
  47. package/extensions/subagents/src/domain.ts +16 -1
  48. package/extensions/subagents/src/manager.ts +7 -71
  49. package/extensions/subagents/src/prompt.ts +19 -5
  50. package/extensions/subagents/src/result-artifact.ts +32 -0
  51. package/extensions/subagents/src/result-delivery.ts +33 -14
  52. package/extensions/subagents/src/runtime.ts +10 -3
  53. package/extensions/ui-customization/footer.ts +16 -5
  54. package/extensions/user-input-fold/index.ts +42 -6
  55. package/extensions/web/index.ts +25 -2
  56. package/extensions/workflows/acceptance.ts +43 -19
  57. package/extensions/workflows/completion-projection.ts +3 -1
  58. package/extensions/workflows/dashboard.ts +147 -21
  59. package/extensions/workflows/index.ts +75 -20
  60. package/extensions/workflows/model.ts +5 -1
  61. package/extensions/workflows/progress-projection.ts +7 -1
  62. package/extensions/workflows/prompt.ts +4 -10
  63. package/extensions/workflows/result-delivery.ts +96 -22
  64. package/extensions/workflows/retention.ts +6 -0
  65. package/extensions/workflows/runner.ts +11 -233
  66. package/extensions/workflows/sandbox.ts +4 -0
  67. package/package.json +7 -7
  68. package/skills/subagents/REFERENCE.md +9 -9
  69. package/skills/subagents/SKILL.md +2 -1
  70. package/skills/workflows/REFERENCE.md +5 -3
  71. package/skills/workflows/SKILL.md +1 -1
  72. package/web/adapter/pi-adapter.ts +3 -0
  73. package/web/host/pi-coding-agent-entry.ts +162 -0
  74. package/web/host/web-host.ts +330 -50
  75. package/web/protocol/types.ts +5 -0
  76. package/web/runtime/pi-runtime.ts +240 -25
  77. package/web/runtime/types.ts +32 -1
  78. package/web/ui/app.js +343 -41
  79. package/web/ui/index.html +3 -0
  80. package/web/ui/styles.css +119 -37
@@ -16,14 +16,12 @@ import {
16
16
  type AgentSessionEventListener,
17
17
  createAgentSession,
18
18
  DefaultResourceLoader,
19
- defineTool,
20
19
  type ExtensionAPI,
21
20
  type ExtensionContext,
22
21
  SessionManager,
23
22
  SettingsManager,
24
23
  type ToolDefinition,
25
24
  } from "@earendil-works/pi-coding-agent";
26
- import { type TSchema, Type } from "typebox";
27
25
  import { AgentToolRenderLedger } from "../shared/agent-tool-renderer.ts";
28
26
  import {
29
27
  bindChildSessionExtensions,
@@ -34,10 +32,11 @@ import {
34
32
  import { createToolCallTimeoutGuard } from "../shared/tool-call-timeout.ts";
35
33
  import { type AgentUsage, emptyUsage, type TranscriptEntry } from "./model.ts";
36
34
  import {
37
- buildWorkflowAgentPrompt,
35
+ childToolsWithStructuredOutput,
36
+ createStructuredOutputTool,
38
37
  STRUCTURED_OUTPUT_SYSTEM_INSTRUCTION,
39
- STRUCTURED_OUTPUT_TOOL_DESCRIPTION,
40
- } from "./prompt.ts";
38
+ } from "../shared/structured-output.ts";
39
+ import { buildWorkflowAgentPrompt } from "./prompt.ts";
41
40
  import {
42
41
  AgentProgressProjection,
43
42
  type ProgressAssistantMessage,
@@ -51,7 +50,6 @@ import { truncateUtf8 } from "./serialization.ts";
51
50
  import { bindWorkflowToolRenderer } from "./tool-renderer.ts";
52
51
 
53
52
  const AGENT_OUTPUT_MAX_BYTES = 64 * 1024;
54
- export const MODEL_PROGRESS_TIMEOUT_MS = 45_000;
55
53
 
56
54
  export type WorkflowModel = NonNullable<ExtensionContext["model"]>;
57
55
  export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
@@ -112,8 +110,6 @@ export interface RunAgentOptions {
112
110
  replayFilesystemBoundary?: ReplayFilesystemBoundaryOptions;
113
111
  /** Test-only override for the per-tool execution timeout. */
114
112
  toolCallTimeoutMs?: number;
115
- /** Test-only override for the per-provider-turn model-progress timeout. */
116
- modelProgressTimeoutMs?: number;
117
113
  /** Test-only override for the end-to-end abort/shutdown deadline. */
118
114
  shutdownTimeoutMs?: number;
119
115
  /** Test seam for lifecycle races; production always uses createAgentSession. */
@@ -142,9 +138,7 @@ export function workflowChildTools(
142
138
  tools: readonly string[] | undefined,
143
139
  structured: boolean,
144
140
  ) {
145
- return tools
146
- ? [...new Set([...tools, ...(structured ? ["structured_output"] : [])])]
147
- : undefined;
141
+ return childToolsWithStructuredOutput(tools, structured);
148
142
  }
149
143
 
150
144
  interface WorkflowToolSession {
@@ -179,68 +173,6 @@ export function guardWorkflowChildTools(
179
173
  });
180
174
  }
181
175
 
182
- function isJsonSchema(value: unknown): value is TSchema {
183
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
184
- const seen = new WeakSet<object>();
185
- let nodes = 0;
186
- const validate = (current: unknown, depth: number): boolean => {
187
- if (++nodes > 10_000 || depth > 24) return false;
188
- if (
189
- current === null ||
190
- typeof current === "string" ||
191
- typeof current === "boolean"
192
- ) {
193
- return true;
194
- }
195
- if (typeof current === "number") return Number.isFinite(current);
196
- if (Array.isArray(current)) {
197
- return current.every((item) => validate(item, depth + 1));
198
- }
199
- if (typeof current !== "object") return false;
200
- if (seen.has(current)) return false;
201
- seen.add(current);
202
- return Object.keys(current).every((key) => {
203
- if (key === "__proto__" || key === "constructor" || key === "prototype") {
204
- return false;
205
- }
206
- return validate((current as Record<string, unknown>)[key], depth + 1);
207
- });
208
- };
209
- return validate(value, 0);
210
- }
211
-
212
- /** Preserve the caller's full JSON Schema instead of lossy keyword conversion. */
213
- function jsonSchemaToTypebox(schema: unknown): TSchema {
214
- if (!isJsonSchema(schema)) {
215
- throw new Error("structured output schema must be a bounded JSON object");
216
- }
217
- return Type.Unsafe(schema);
218
- }
219
-
220
- /**
221
- * One-shot terminating tool injected when a schema is supplied: the subagent
222
- * calls it as its final action and we capture the validated object.
223
- */
224
- function makeStructuredOutputTool(
225
- schema: unknown,
226
- capture: (value: unknown) => void,
227
- ): ToolDefinition {
228
- return defineTool({
229
- name: "structured_output",
230
- label: "Structured Output",
231
- description: STRUCTURED_OUTPUT_TOOL_DESCRIPTION,
232
- parameters: jsonSchemaToTypebox(schema),
233
- async execute(_toolCallId, params) {
234
- capture(params);
235
- return {
236
- content: [{ type: "text", text: "Recorded structured result." }],
237
- details: params,
238
- terminate: true,
239
- };
240
- },
241
- });
242
- }
243
-
244
176
  type AssistantMessage = ProgressAssistantMessage;
245
177
 
246
178
  export { transcriptFromMessages };
@@ -310,119 +242,6 @@ function errorText(error: unknown): string {
310
242
  );
311
243
  }
312
244
 
313
- function formatTimeout(timeoutMs: number) {
314
- return timeoutMs % 1_000 === 0
315
- ? `${timeoutMs / 1_000} seconds`
316
- : `${timeoutMs} ms`;
317
- }
318
-
319
- export function resolveModelProgressTimeoutMs(
320
- settingsManager: SettingsManager,
321
- override?: number,
322
- ) {
323
- if (override !== undefined) return override;
324
- const configured =
325
- settingsManager.getProjectSettings().httpIdleTimeoutMs ??
326
- settingsManager.getGlobalSettings().httpIdleTimeoutMs;
327
- return typeof configured === "number" && Number.isFinite(configured)
328
- ? Math.max(MODEL_PROGRESS_TIMEOUT_MS, Math.floor(configured))
329
- : MODEL_PROGRESS_TIMEOUT_MS;
330
- }
331
-
332
- /** Abort any provider turn that stops producing model-visible progress. */
333
- export function createModelProgressWatchdog(
334
- onTimeout: (error: Error) => Promise<unknown>,
335
- options: { timeoutMs?: number; model?: string } = {},
336
- ) {
337
- const timeoutMs = options.timeoutMs ?? MODEL_PROGRESS_TIMEOUT_MS;
338
- let timer: ReturnType<typeof setTimeout> | undefined;
339
- let activeTurn = false;
340
- let closed = false;
341
- let rejectTimeout!: (error: Error) => void;
342
- const timeout = new Promise<never>((_resolve, reject) => {
343
- rejectTimeout = reject;
344
- });
345
-
346
- const clear = () => {
347
- if (timer) clearTimeout(timer);
348
- timer = undefined;
349
- };
350
- const schedule = () => {
351
- clear();
352
- if (!activeTurn || closed) return;
353
- // This timer owns the awaited watchdog outcome. Keep it referenced so a
354
- // short-lived Node 22 process cannot exit with the promise still pending.
355
- timer = setTimeout(() => {
356
- timer = undefined;
357
- activeTurn = false;
358
- closed = true;
359
- const model = options.model ? ` for ${options.model}` : "";
360
- const error = new Error(
361
- `Agent provider turn${model} produced no model-visible progress for ${formatTimeout(timeoutMs)}; the provider request may be stalled. Retry the workflow.`,
362
- );
363
- rejectTimeout(error);
364
- try {
365
- void onTimeout(error).catch(() => {});
366
- } catch {
367
- // The timeout result remains authoritative even if abort throws before
368
- // returning its promise; bounded shutdown below gets another chance.
369
- }
370
- }, timeoutMs);
371
- };
372
- const armTurn = () => {
373
- if (closed) return;
374
- activeTurn = true;
375
- schedule();
376
- };
377
- const markProgress = () => {
378
- if (!activeTurn || closed) return;
379
- schedule();
380
- };
381
- const completeTurn = () => {
382
- activeTurn = false;
383
- clear();
384
- };
385
- const cancel = () => {
386
- closed = true;
387
- activeTurn = false;
388
- clear();
389
- };
390
-
391
- return {
392
- armTurn,
393
- markProgress,
394
- completeTurn,
395
- cancel,
396
- async waitFor<T>(operation: Promise<T>) {
397
- try {
398
- return await Promise.race([operation, timeout]);
399
- } finally {
400
- cancel();
401
- }
402
- },
403
- };
404
- }
405
-
406
- function isModelVisibleProgress(event: AgentSessionEvent) {
407
- if (event.type !== "message_update" || event.message.role !== "assistant") {
408
- return false;
409
- }
410
- // Raw transport heartbeats never become AgentSession events. Empty stream,
411
- // text, and thinking starts likewise cannot keep a provider turn alive.
412
- const update = event.assistantMessageEvent;
413
- if (
414
- update.type === "text_delta" ||
415
- update.type === "thinking_delta" ||
416
- update.type === "toolcall_delta"
417
- ) {
418
- return update.delta.length > 0;
419
- }
420
- if (update.type === "text_end" || update.type === "thinking_end") {
421
- return update.content.length > 0;
422
- }
423
- return update.type === "toolcall_start" || update.type === "toolcall_end";
424
- }
425
-
426
245
  export async function runAgent(
427
246
  options: RunAgentOptions,
428
247
  ): Promise<AgentOutcome> {
@@ -432,8 +251,6 @@ export async function runAgent(
432
251
  let session: AgentSession | undefined;
433
252
  let unsubscribeToolGuards: (() => void) | undefined;
434
253
  let aborted = false;
435
- let terminalCause: "abort" | "model-progress-timeout" | undefined;
436
- let modelProgressTimeoutMessage: string | undefined;
437
254
  let abortOperation: Promise<unknown> | undefined;
438
255
  let rejectForAbort: ((error: Error) => void) | undefined;
439
256
  let rejectForProjectionFailure: ((error: Error) => void) | undefined;
@@ -454,7 +271,6 @@ export async function runAgent(
454
271
  const onAbort = () => {
455
272
  if (aborted) return;
456
273
  aborted = true;
457
- terminalCause ??= "abort";
458
274
  if (session) {
459
275
  try {
460
276
  abortOperation ??= session.abort();
@@ -475,7 +291,7 @@ export async function runAgent(
475
291
  customTools =
476
292
  options.schema !== undefined
477
293
  ? [
478
- makeStructuredOutputTool(options.schema, (value) => {
294
+ createStructuredOutputTool(options.schema, (value) => {
479
295
  if (!settled) structured = value;
480
296
  }),
481
297
  ]
@@ -659,10 +475,6 @@ export async function runAgent(
659
475
  });
660
476
  };
661
477
 
662
- let armModelProgress = () => {};
663
- let markModelProgress = () => {};
664
- let completeModelTurn = () => {};
665
- let cancelModelProgressWatchdog = () => {};
666
478
  let compactionReconcileQueued = false;
667
479
  const queueCompactionReconcile = () => {
668
480
  if (compactionReconcileQueued) return;
@@ -690,7 +502,6 @@ export async function runAgent(
690
502
  };
691
503
  const unsubscribe = childSession.subscribe((event) => {
692
504
  if (settled) return;
693
- if (event.type === "turn_start") armModelProgress();
694
505
  if (event.type === "tool_execution_start") {
695
506
  toolRenderer.start(
696
507
  event.toolCallId,
@@ -713,10 +524,6 @@ export async function runAgent(
713
524
  event.isError,
714
525
  );
715
526
  }
716
- if (isModelVisibleProgress(event)) markModelProgress();
717
- if (event.type === "message_end" && event.message.role === "assistant") {
718
- completeModelTurn();
719
- }
720
527
  if (event.type === "message_end") {
721
528
  assistantSettlement = observeAssistantSettlement(
722
529
  assistantSettlement,
@@ -751,32 +558,10 @@ export async function runAgent(
751
558
  captureToolRenderData(childSession.messages);
752
559
  snapshotProjection();
753
560
  if (!aborted) {
754
- const watchdog = createModelProgressWatchdog(
755
- (error) => {
756
- terminalCause ??= "model-progress-timeout";
757
- if (terminalCause === "model-progress-timeout") {
758
- modelProgressTimeoutMessage ??= error.message;
759
- }
760
- abortOperation ??= childSession.abort();
761
- void abortOperation.catch(() => {});
762
- return abortOperation;
763
- },
764
- {
765
- timeoutMs: resolveModelProgressTimeoutMs(
766
- options.settingsManager,
767
- options.modelProgressTimeoutMs,
768
- ),
769
- model: modelId,
770
- },
771
- );
772
- armModelProgress = watchdog.armTurn;
773
- markModelProgress = watchdog.markProgress;
774
- completeModelTurn = watchdog.completeTurn;
775
- cancelModelProgressWatchdog = watchdog.cancel;
561
+ // Pi owns transport liveness and retries. Quiet model output is not
562
+ // evidence of a stalled request (thinking and retry backoff can be silent).
776
563
  await Promise.race([
777
- watchdog.waitFor(
778
- childSession.prompt(buildWorkflowAgentPrompt(options.prompt)),
779
- ),
564
+ childSession.prompt(buildWorkflowAgentPrompt(options.prompt)),
780
565
  abortRace,
781
566
  projectionFailureRace,
782
567
  ]);
@@ -784,7 +569,6 @@ export async function runAgent(
784
569
  } catch (error) {
785
570
  promptErrorMessage ??= errorText(error);
786
571
  } finally {
787
- cancelModelProgressWatchdog();
788
572
  options.signal?.removeEventListener("abort", onAbort);
789
573
  settled = true;
790
574
  unsubscribe();
@@ -823,11 +607,7 @@ export async function runAgent(
823
607
  ? `Cleanup failed: ${cleanupErrors.join("; ")}`
824
608
  : undefined;
825
609
 
826
- if (
827
- terminalCause === "abort" ||
828
- (terminalCause === undefined &&
829
- assistantSettlement?.stopReason === "aborted")
830
- ) {
610
+ if (aborted || assistantSettlement?.stopReason === "aborted") {
831
611
  return {
832
612
  ok: false,
833
613
  output,
@@ -844,9 +624,7 @@ export async function runAgent(
844
624
  }
845
625
 
846
626
  const failureMessage =
847
- (terminalCause === "model-progress-timeout"
848
- ? modelProgressTimeoutMessage
849
- : agentFailureMessage(assistantSettlement, promptErrorMessage)) ??
627
+ agentFailureMessage(assistantSettlement, promptErrorMessage) ??
850
628
  cleanupError;
851
629
  if (failureMessage !== undefined) {
852
630
  return {
@@ -35,6 +35,7 @@ export interface SandboxAgentOptions {
35
35
  provider?: unknown;
36
36
  effort?: unknown;
37
37
  isolation?: unknown;
38
+ working_dir?: unknown;
38
39
  operator?: unknown;
39
40
  inputs?: unknown;
40
41
  }
@@ -110,6 +111,9 @@ function sanitizeAgentOptions(value: unknown): SandboxAgentOptions {
110
111
  ...(value.provider !== undefined ? { provider: value.provider } : {}),
111
112
  ...(value.effort !== undefined ? { effort: value.effort } : {}),
112
113
  ...(value.isolation !== undefined ? { isolation: value.isolation } : {}),
114
+ ...(value.working_dir !== undefined
115
+ ? { working_dir: value.working_dir }
116
+ : {}),
113
117
  ...(value.operator !== undefined ? { operator: value.operator } : {}),
114
118
  ...(value.inputs !== undefined ? { inputs: value.inputs } : {}),
115
119
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tt-a1i/openpi",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "OpenPI — a Pi-native multi-agent workbench with background execution, isolated subagents, replay-safe workflows, goals, tasks, and observable TUI",
5
5
  "license": "MIT",
6
6
  "author": "tt-a1i",
@@ -63,9 +63,9 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@biomejs/biome": "2.5.8",
66
- "@earendil-works/pi-ai": "^0.84.1",
67
- "@earendil-works/pi-coding-agent": "^0.84.1",
68
- "@earendil-works/pi-tui": "^0.84.1",
66
+ "@earendil-works/pi-ai": "^0.85.1",
67
+ "@earendil-works/pi-coding-agent": "^0.85.1",
68
+ "@earendil-works/pi-tui": "^0.85.1",
69
69
  "@effect/tsgo": "^0.24.2",
70
70
  "@effect/vitest": "^4.0.0-beta.99",
71
71
  "@types/node": "^26.1.1",
@@ -75,9 +75,9 @@
75
75
  "vitest": "4.1.10"
76
76
  },
77
77
  "peerDependencies": {
78
- "@earendil-works/pi-ai": "*",
79
- "@earendil-works/pi-coding-agent": "*",
80
- "@earendil-works/pi-tui": "*",
78
+ "@earendil-works/pi-ai": ">=0.85.1",
79
+ "@earendil-works/pi-coding-agent": ">=0.85.1",
80
+ "@earendil-works/pi-tui": ">=0.85.1",
81
81
  "typebox": "*"
82
82
  },
83
83
  "type": "module",
@@ -32,7 +32,7 @@ file:line references. You cannot modify files — do not attempt to.
32
32
  | ------------------ | -------- | ------------------------------------------------------------------------------------ |
33
33
  | `name` | yes | `[a-z0-9-]`, ≤64 chars, and must equal the filename stem. |
34
34
  | `description` | yes | Shown to the parent model when it picks a type. ≤1024 chars. |
35
- | `tools` | no | Tool allowlist. **Omit to inherit the normal tool set.** |
35
+ | `tools` | no | Tool allowlist. **Omit to inherit active parent child-eligible tools.** |
36
36
  | `model` | no | `provider/model-id`, or a bare id resolved against the current provider; ≤256 chars. |
37
37
  | `reasoning_effort` | no | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. |
38
38
  | body | no | Appended to the child's system prompt. ≤16384 chars. |
@@ -50,16 +50,15 @@ file with the same name.
50
50
 
51
51
  | Role | Tools | Relative effort guidance | Purpose |
52
52
  | ------------- | ------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------ |
53
- | `explorer` | `read grep find ls fd rg git_show git_diff git_log` | Moderate | Read-only codebase tracing; increase for harder tasks. |
54
- | `implementer` | `read bash edit write grep find ls fd rg git_show git_diff git_log` | Medium-high | Focused implementation; adjust for scope and risk. |
55
- | `reviewer` | `read grep find ls fd rg git_show git_diff git_log` | High | Read-only correctness, safety, and regression review. |
56
- | `advisor` | `read grep find ls fd rg git_show git_diff git_log` | High | Deep read-only analysis and technical advice. |
53
+ | `explorer` | Active parent child-eligible tools | Moderate | Read-only codebase tracing; increase for harder tasks. |
54
+ | `implementer` | Active parent child-eligible tools | Medium-high | Focused implementation; adjust for scope and risk. |
55
+ | `reviewer` | Active parent child-eligible tools | High | Read-only correctness, safety, and regression review. |
56
+ | `advisor` | Active parent child-eligible tools | High | Deep read-only analysis and technical advice. |
57
57
 
58
58
  These are relative selection hints, not fixed Pi thinking levels. Built-ins set
59
59
  no model or reasoning-effort default. An explicit user requirement takes
60
60
  priority; otherwise the parent model chooses from levels supported by the
61
- resolved child model according to the role and task difficulty. Their tool
62
- allowlists still intersect with plan mode and the child denylist.
61
+ resolved child model according to the role and task difficulty. Custom role allowlists intersect with active parent tools, Plan Mode, and the child denylist. Built-in read-only task guidance is a role instruction, not a filesystem sandbox. Existing custom role files are never widened automatically.
63
62
 
64
63
  ## Discovery
65
64
 
@@ -153,8 +152,9 @@ So `tools: [read, grep, find, ls]` yields a child that genuinely has no
153
152
  `write`, `edit`, or `bash` tool to call — not one that has been asked not to.
154
153
  Parent-only names are removed before the generated roster and spawn result are
155
154
  shown, so a type that lists `subagent_spawn` never advertises it as usable.
156
- A structured Workflow child additionally receives only its terminating
157
- `structured_output` tool; this does not restore any denied repository tool.
155
+ A Workflow child with a schema, or a Direct Subagent spawned with
156
+ `output_schema`, additionally receives only its terminating `structured_output`
157
+ tool; this does not restore any denied repository tool.
158
158
 
159
159
  While `/plan` is armed, `isolation: "worktree"` is rejected before Git is
160
160
  changed. A selected type whose declared tools plan mode would narrow (such as
@@ -10,9 +10,10 @@ The tool definitions are canonical for parameters, limits, model syntax, isolati
10
10
  - Delegate substantial independent work, not a lookup or edit the parent can do directly.
11
11
  - Give the child a standalone prompt with paths, constraints, relevant context, and the expected report; it cannot see the parent conversation or ask the user.
12
12
  - Inherit the parent model by default. When choosing the child's reasoning effort, honor an explicit user requirement first; otherwise use the selected role's relative guidance and the task's difficulty, choosing from levels supported by the resolved child model.
13
- - Prefer a matching agent type when one exists; its tool restriction is enforced. Model precedence is explicit spawn override, selected type-file model, configured built-in role model, then parent model. Reasoning precedence is explicit spawn override, selected type default, then parent effort. Types live in `~/.pi/agent/agents/*.md` and, for trusted projects, `.pi/agents/*.md`; see [Agent types](REFERENCE.md).
13
+ - Prefer a matching agent type when one exists; built-ins inherit active parent tools, while an explicit custom tool list is enforced as a narrowing restriction. Model precedence is explicit spawn override, selected type-file model, configured built-in role model, then parent model. Reasoning precedence is explicit spawn override, selected type default, then parent effort. Types live in `~/.pi/agent/agents/*.md` and, for trusted projects, `.pi/agents/*.md`; see [Agent types](REFERENCE.md).
14
14
  - Isolate concurrent writers in worktrees according to the `subagent_spawn` schema so they cannot overwrite one checkout or git index. While Plan Mode is active, use only read-only exploration types (or no type); worktree isolation and types narrowed by Plan Mode are rejected.
15
15
  - After spawning, continue useful parent work. In an interactive session, if none remains, tell the user the child is still running and end the turn; automatic result delivery will re-invoke the parent when it settles. Do not block merely because the next step depends on the result or because there is nothing else to do. Use `subagent_wait` only when the user explicitly asks to keep the current response open for the result, or when non-interactive automation must return it in the same invocation.
16
+ - Use optional `output_schema` when downstream work needs a machine-validated result rather than prose. The child then receives one terminating `structured_output` tool, and the run fails if it finishes without submitting a matching value. Keep schemas small and task-specific; the validated JSON is delivered to the parent and preserved in a private content-addressed artifact. Omit the option for ordinary text reports.
16
17
 
17
18
  ## Worktree isolation
18
19
 
@@ -12,13 +12,15 @@ The `workflow` script is an async JavaScript function body executed in a restric
12
12
 
13
13
  ## Agent calls
14
14
 
15
- `await agent(prompt, options)` runs one child and always resolves to `{ ok, output, structured?, ref?, acceptance?, error? }`. Check `ok` before reading output. Children receive normal trust-aware resources but cannot recursively orchestrate or ask the user.
15
+ `await agent(prompt, options)` runs one child and always resolves to `{ ok, output, structured?, ref?, acceptance?, acceptanceWarning?, error? }`. Check `ok` before reading output. Children receive normal trust-aware resources but cannot recursively orchestrate or ask the user.
16
16
 
17
- Useful options include `agent_type`, `label`, `phase`, `schema`, `acceptance`, `model`, `provider`, `effort`, `isolation`, `operator`, and `inputs`.
17
+ Useful options include `agent_type`, `label`, `phase`, `schema`, `model`, `provider`, `effort`, `working_dir`, `isolation`, `operator`, and `inputs`. The legacy `acceptance` option remains readable only during the 0.x migration window described below.
18
18
 
19
+ - Set `working_dir` when tools must run in another repository. Relative paths resolve against the parent cwd; prompt text alone does not change it. The directory must exist. Project resource trust is checked for the target independently.
20
+ - Built-in roles inherit currently active parent child-eligible tools, including shell/network when available. Explicit custom tool lists only narrow this surface. Built-ins with inherited tools execute for real on resume; custom bounded read-only calls retain the replay filesystem boundary below.
19
21
  - Prefer a matching `agent_type`. Model precedence is explicit model/provider, type file, configured built-in role, then parent. Effort precedence is explicit effort, type default, then parent.
20
22
  - `schema` validates structured output. Use it whenever later workflow logic branches on fields.
21
- - `acceptance: { criteria: [{ id, description, requiredEvidence?: string[] }] }` requires the same child to return an evidence ledger. An invocation accepts 1–32 criteria; each criterion has a 1–500 character human-readable `description` and an optional `requiredEvidence` array of at most 16 concise string labels (up to 120 characters each). The child must return exact matching labels in `acceptance.criteria[].evidence`. A criterion without `requiredEvidence` is an attestation of its description, not an evidence-backed gate; criteria that need evidence-backed acceptance must declare the required labels. Missing, malformed, or rejected criteria make `ok:false` while preserving output and evidence.
23
+ - `acceptance` is deprecated since OpenPI 0.5 and scheduled for removal in 1.0. Compatibility calls still return the child-authored ledger with `authority: "model-self-attestation"` and a migration warning, but it never determines `ok`. Use ordinary `schema` for findings, then let the parent evaluate them alongside runtime-observed exit codes, test receipts, file fingerprints, and tool results. Old DSL, journals, and artifacts remain readable during 0.x.
22
24
  - `operator: "name"` reuses one in-memory child Session for serialized follow-ups inside the same run. Its model, role/tools, effort, structured mode, and cwd are frozen by the first activation. Operators cannot use per-call worktrees or replay, and do not survive restarts.
23
25
  - `inputs: [ref, ...]` accepts successful opaque refs from the same workflow run only. Each conclusion is bounded to 16 KiB and total injected input to 48 KiB. The total budget is fairly distributed, so a large fan-out cannot starve later results merely because of order; partial projections are labeled. Full successful child results remain in the run's `agent-results/` artifacts. Inputs are marked as untrusted data; the resulting graph is observability, not scheduling authority.
24
26
  - Fair projection preserves the head and tail of every partial result and names its run-relative `agent-results/agent-N.json` audit artifact. That path is provenance for the parent/operator, not a child-readable handle. Fair presence is not proof of full evidence coverage: for large fan-out, group source refs into local Report agents, then pass only their refs to a global Report. The workflow script—not Runtime—must state planned, selected, covered, failed, and deferred counts.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workflows
3
- description: Orchestrates multi-agent work with OpenPI's inline JavaScript Workflow DSL. Use when a task needs multi-phase fan-out, pipelines, barriers, structured handoffs, acceptance evidence, or resumable background orchestration.
3
+ description: Orchestrates multi-agent work with OpenPI's inline JavaScript Workflow DSL. Use when a task needs multi-phase fan-out, pipelines, barriers, structured handoffs, or resumable background orchestration.
4
4
  ---
5
5
 
6
6
  # Workflows
@@ -490,6 +490,9 @@ export class PiWebAdapter {
490
490
  status: this.runtime.isIdle()
491
491
  ? ("idle" as const)
492
492
  : ("running" as const),
493
+ ...(this.runtime.getActiveTurn()
494
+ ? { activeTurn: this.runtime.getActiveTurn() }
495
+ : {}),
493
496
  capabilities: webCapabilitySnapshot(this.runtime.sessionManager),
494
497
  },
495
498
  truncation: {