@bastani/atomic 0.8.29-alpha.3 → 0.8.29

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 (62) hide show
  1. package/CHANGELOG.md +9 -6
  2. package/dist/builtin/cursor/CHANGELOG.md +2 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +3 -1
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/mcp/CHANGELOG.md +3 -1
  7. package/dist/builtin/mcp/package.json +1 -1
  8. package/dist/builtin/subagents/CHANGELOG.md +5 -4
  9. package/dist/builtin/subagents/README.md +4 -4
  10. package/dist/builtin/subagents/package.json +1 -1
  11. package/dist/builtin/subagents/src/extension/index.ts +14 -0
  12. package/dist/builtin/subagents/src/extension/schemas.ts +1 -1
  13. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +1 -6
  14. package/dist/builtin/subagents/src/runs/foreground/execution.ts +1 -6
  15. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +0 -1
  16. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +0 -1
  17. package/dist/builtin/subagents/src/runs/shared/structured-output.ts +16 -285
  18. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  19. package/dist/builtin/subagents/src/shared/types.ts +0 -1
  20. package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +3 -18
  21. package/dist/builtin/web-access/CHANGELOG.md +3 -1
  22. package/dist/builtin/web-access/package.json +1 -1
  23. package/dist/builtin/workflows/CHANGELOG.md +6 -5
  24. package/dist/builtin/workflows/README.md +1 -1
  25. package/dist/builtin/workflows/builtin/goal.ts +8 -52
  26. package/dist/builtin/workflows/builtin/open-claude-design.ts +15 -38
  27. package/dist/builtin/workflows/builtin/ralph.ts +11 -50
  28. package/dist/builtin/workflows/package.json +1 -1
  29. package/dist/builtin/workflows/src/extension/index.ts +17 -0
  30. package/dist/builtin/workflows/src/extension/workflow-schema.ts +2 -29
  31. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +1 -5
  32. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +1 -1
  33. package/dist/builtin/workflows/src/shared/types.ts +1 -1
  34. package/dist/core/sdk.d.ts +3 -3
  35. package/dist/core/sdk.d.ts.map +1 -1
  36. package/dist/core/sdk.js +2 -2
  37. package/dist/core/sdk.js.map +1 -1
  38. package/dist/core/system-prompt.d.ts.map +1 -1
  39. package/dist/core/system-prompt.js +0 -36
  40. package/dist/core/system-prompt.js.map +1 -1
  41. package/dist/core/tools/index.d.ts +1 -1
  42. package/dist/core/tools/index.d.ts.map +1 -1
  43. package/dist/core/tools/index.js +1 -1
  44. package/dist/core/tools/index.js.map +1 -1
  45. package/dist/core/tools/structured-output.d.ts +7 -18
  46. package/dist/core/tools/structured-output.d.ts.map +1 -1
  47. package/dist/core/tools/structured-output.js +9 -89
  48. package/dist/core/tools/structured-output.js.map +1 -1
  49. package/dist/core/tools/todos.d.ts +1 -0
  50. package/dist/core/tools/todos.d.ts.map +1 -1
  51. package/dist/core/tools/todos.js +4 -0
  52. package/dist/core/tools/todos.js.map +1 -1
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1 -1
  56. package/dist/index.js.map +1 -1
  57. package/docs/extensions.md +1 -1
  58. package/docs/sdk.md +1 -1
  59. package/docs/subagents.md +4 -6
  60. package/docs/usage.md +1 -1
  61. package/docs/workflows.md +5 -5
  62. package/package.json +2 -2
@@ -11,13 +11,6 @@ import type { JsonSchemaObject } from "../../shared/types.ts";
11
11
 
12
12
  export { SUBAGENT_INTERCOM_SESSION_NAME_ENV } from "./pi-args.ts";
13
13
 
14
- const STRUCTURED_OUTPUT_INSTRUCTIONS = [
15
- "This subagent step has a strict structured output contract.",
16
- "Your final action must be to call the `structured_output` tool with JSON matching the provided schema.",
17
- "Pass the schema fields directly as the tool arguments; do not wrap them in `{ value: ... }` unless the schema explicitly defines a top-level `value` field.",
18
- "Do not rely on prose-only completion; if you do not call `structured_output`, the parent will fail this step.",
19
- ].join("\n");
20
-
21
14
  export const CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS = [
22
15
  "You are a child subagent, not the parent orchestrator.",
23
16
  "The parent session owns delegation, orchestration, review fanout, and follow-up worker launches.",
@@ -107,8 +100,7 @@ export function rewriteSubagentPrompt(
107
100
  rewritten = stripSubagentOrchestrationSkill(rewritten);
108
101
  rewritten = stripChildBoundaryInstructions(rewritten);
109
102
  const boundary = options.fanoutChild ? CHILD_FANOUT_BOUNDARY_INSTRUCTIONS : CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS;
110
- const structured = process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] ? `\n\n${STRUCTURED_OUTPUT_INSTRUCTIONS}` : "";
111
- return `${boundary}${structured}\n\n${rewritten}`;
103
+ return `${boundary}\n\n${rewritten}`;
112
104
  }
113
105
 
114
106
  function isParentOnlySubagentMessage(message: unknown): boolean {
@@ -803,7 +803,6 @@ export interface RunSyncOptions {
803
803
  schema: JsonSchemaObject;
804
804
  schemaPath: string;
805
805
  outputPath: string;
806
- metadataPath: string;
807
806
  };
808
807
  acceptance?: AcceptanceInput;
809
808
  acceptanceContext?: {
@@ -1,36 +1,22 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { ChainConfig } from "../agents/agents.ts";
4
- import { assertJsonSchemaDescriptor, assertStructuredOutputParameterSchema } from "../runs/shared/structured-output.ts";
5
4
  import { isDynamicParallelStep, isParallelStep, type ChainStep } from "../shared/settings.ts";
6
5
  import type { JsonSchemaObject } from "../shared/types.ts";
7
6
 
8
7
  function loadSavedOutputSchema(
9
8
  chain: ChainConfig,
10
- stepAgent: string,
9
+ _stepAgent: string,
11
10
  outputSchema: unknown,
12
- options: { schemaRole: "tool-parameters" | "collection" } = { schemaRole: "tool-parameters" },
13
11
  ): JsonSchemaObject | undefined {
14
12
  if (outputSchema === undefined) return undefined;
15
- const labelForSchema = (schemaPath?: string): string => schemaPath
16
- ? `outputSchema for chain '${chain.name}' step '${stepAgent}' (${schemaPath})`
17
- : `outputSchema for chain '${chain.name}' step '${stepAgent}'`;
18
- const validateSavedSchema = (schema: unknown, label: string): JsonSchemaObject => {
19
- if (options.schemaRole === "collection") {
20
- assertJsonSchemaDescriptor(schema, label);
21
- } else {
22
- assertStructuredOutputParameterSchema(schema, label);
23
- }
24
- return schema;
25
- };
26
13
  if (typeof outputSchema === "string") {
27
14
  const schemaPath = path.isAbsolute(outputSchema)
28
15
  ? outputSchema
29
16
  : path.join(path.dirname(chain.filePath), outputSchema);
30
- const parsed = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as unknown;
31
- return validateSavedSchema(parsed, labelForSchema(schemaPath));
17
+ return JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as JsonSchemaObject;
32
18
  }
33
- return validateSavedSchema(outputSchema, labelForSchema());
19
+ return outputSchema as JsonSchemaObject;
34
20
  }
35
21
 
36
22
  export function mapSavedChainSteps(chain: ChainConfig, worktree = false): ChainStep[] {
@@ -50,7 +36,6 @@ export function mapSavedChainSteps(chain: ChainConfig, worktree = false): ChainS
50
36
  chain,
51
37
  `${step.collect.as} collection`,
52
38
  step.collect.outputSchema,
53
- { schemaRole: "collection" },
54
39
  );
55
40
  return {
56
41
  ...step,
@@ -4,9 +4,11 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.8.29] - 2026-06-15
8
+
7
9
  ### Changed
8
10
 
9
- - Published a synchronized Atomic 0.8.29-alpha.1 prerelease with the upstream pi TUI dependency aligned to `^0.79.3`; no functional changes were made in the web-access extension.
11
+ - Published a synchronized Atomic 0.8.29 stable release with the upstream pi TUI dependency aligned to `^0.79.3`; no functional changes were made in the web-access extension.
10
12
 
11
13
  ## [0.8.28] - 2026-06-11
12
14
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.8.29-alpha.3",
3
+ "version": "0.8.29",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.29] - 2026-06-15
10
+
9
11
  ### Added
10
12
 
11
- - Added opt-in schema-backed workflow item results: `ctx.stage(..., { schema })`, `ctx.task(..., { schema })`, `ctx.chain` items, and `ctx.parallel` items now receive a schema-specific `structured_output` tool only for that item, require the final tool call, return the parsed value from `ctx.stage().prompt(...)`, and expose parsed task values as `result.structured` while preserving formatted JSON handoff text ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
13
+ - Added opt-in schema-backed workflow item results: `ctx.stage(..., { schema })`, `ctx.task(..., { schema })`, `ctx.chain` items, and `ctx.parallel` items now receive a schema-specific `structured_output` tool only for that item, return the captured value from `ctx.stage().prompt(...)`, and expose parsed task values as `result.structured` while preserving formatted JSON handoff text ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
12
14
 
13
15
  ### Changed
14
16
 
@@ -17,13 +19,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
17
19
  - Changed the builtin `ralph` prompt-engineering stage to disable all tools while relying on the `/skill:prompt-engineer` skill prompt, keeping that first-pass rewrite focused and tool-free.
18
20
  - Changed builtin `goal` worker/reviewer prompts and `ralph` orchestrator/reviewer prompts to request end-to-end verification when practical, using browser-skilled subagents for web/frontend flows that may depend on backend/API behavior and tmux-skilled subagents for TUI or terminal-app scenarios.
19
21
  - Aligned the workflows extension with upstream pi TUI `^0.79.3` so workflow graph, custom UI, and prompt-broker integrations inherit the latest shared TUI compatibility fixes.
20
- - Documented the opt-in `structured_output` workflow path and clarified that ordinary workflow stages do not receive `structured_output` from the default tool registry; schema-enabled items auto-add the runtime tool to explicit `tools` allowlists ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
21
- - Clarified that workflow `structured_output` gate schemas must be top-level object tool-argument schemas, with arrays and primitives wrapped in object fields before being returned through the terminating tool, and documented the one-`prompt()` limit for schema-backed `StageContext` result contracts ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
22
- - Documented that terminating workflow-stage `structured_output` JSON stays inline even when large, while artifact-sized handoffs should still be saved to files when downstream stages do not need the full payload in context ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
22
+ - Documented the opt-in `structured_output` workflow path and clarified that ordinary workflow stages do not receive `structured_output` from the default tool registry; schema-enabled items auto-add the runtime tool to explicit `tools` allowlists without adding extra workflow prompt text about the tool ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
23
+ - Removed top-level-object restrictions from workflow `structured_output` gate schemas; Atomic now passes any plain JSON Schema object directly to the tool and documents the one-`prompt()` limit for schema-backed `StageContext` result contracts ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
23
24
 
24
25
  ### Fixed
25
26
 
26
- - Fixed direct workflow tool validation so schema-enabled `task`, `tasks`, `chain`, and `parallel` items reject array or primitive structured-output schemas at argument-validation time while accepting the same object-root contracts as runtime validation, including object-only `allOf` schemas ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
27
+ - Fixed direct workflow tool validation so schema-enabled `task`, `tasks`, `chain`, and `parallel` items accept plain JSON Schema objects without additional object-root constraints ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
27
28
  - Fixed schema-backed workflow stages to fail with a clear stage-level error when `prompt()` is called more than once on the same `StageContext`, rather than surfacing the lower-level structured-output single-use guard ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
28
29
  - Fixed schema-backed workflow model fallback so an attempt that already captured a valid terminating `structured_output` result is treated as successful instead of retrying against fallback models and tripping the single-use result guard ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
29
30
  - Fixed the workflow graph overlay remaining interactive when the parent/main-chat agent opens `ask_user_question`: the graph keeps focus, the parent question stays pending behind it with a clear “Main chat needs input — exit graph to answer.” status hint, hiding/exiting the graph focuses the pending question, and host custom-UI state changes no longer hide, restore, remount, or repaint the overlay ([#1353](https://github.com/bastani-inc/atomic/issues/1353)).
@@ -280,7 +280,7 @@ const decision = await ctx.stage("review-gate", { schema: Decision }).prompt(
280
280
  // decision.approved is typed from the schema.
281
281
  ```
282
282
 
283
- Atomic registers the canonical `structured_output` tool only for schema-enabled items, automatically adds it to explicit `tools` allowlists, and fails the item if the model completes without the final tool call. The schema is used directly as the tool argument contract, so wrap arrays or primitives in an object field such as `{ items: [...] }` or `{ value: ... }`. A schema-backed `StageContext` supports one `prompt()` call because the final-answer tool is an exact-once result contract; create another `ctx.stage(..., { schema })` for another structured prompt. `ctx.task`/`ctx.chain`/`ctx.parallel` results expose the parsed value as `result.structured` and keep `result.text` as formatted JSON for handoffs.
283
+ Atomic registers the canonical `structured_output` tool only for schema-enabled items and automatically adds it to explicit `tools` allowlists. The schema is used directly as the tool argument contract. A schema-backed `StageContext` supports one `prompt()` call because the final-answer tool is a single result contract; create another `ctx.stage(..., { schema })` for another structured prompt. `ctx.task`/`ctx.chain`/`ctx.parallel` results expose the captured value as `result.structured` and keep `result.text` as formatted JSON for handoffs.
284
284
 
285
285
  `subagent` is available as a default workflow-stage tool with the same default two-hop nesting budget as main chat: a stage can launch a subagent, and that child can launch one nested subagent before the guard blocks further delegation. `tools` allowlists apply to bundled extension tools as well as built-ins; if a stage sets `tools`, list every tool it should see. Workflow stages can explicitly list `subagent`, `web_search`, `fetch_content`, `intercom`, and other loaded extension tools, while `excludedTools` and `noTools: "all"` still win. Bundled `@bastani/subagents` agent definitions are available to the `subagent` tool in workflow stages, including workflows launched from a subagent child process.
286
286
 
@@ -314,26 +314,8 @@ function normalizeBranchInput(
314
314
  return looksLikeSafeGitRef ? trimmed : fallback;
315
315
  }
316
316
 
317
- function parseReviewDecision(text: string): ReviewDecision | undefined {
318
- try {
319
- const parsed = JSON.parse(text) as Partial<ReviewDecision>;
320
- if (
321
- parsed.overall_correctness !== "patch is correct" &&
322
- parsed.overall_correctness !== "patch is incorrect"
323
- ) {
324
- return undefined;
325
- }
326
- if (!Array.isArray(parsed.findings)) return undefined;
327
- if (typeof parsed.stop_review_loop !== "boolean") return undefined;
328
- if (typeof parsed.overall_explanation !== "string") return undefined;
329
- if (typeof parsed.overall_confidence_score !== "number") return undefined;
330
- if (typeof parsed.goal_oracle_satisfied !== "boolean") return undefined;
331
- if (typeof parsed.receipt_assessment !== "string") return undefined;
332
- if (typeof parsed.verification_remaining !== "string") return undefined;
333
- return parsed as ReviewDecision;
334
- } catch {
335
- return undefined;
336
- }
317
+ function reviewDecisionFromResult(result: WorkflowTaskResult): ReviewDecision | undefined {
318
+ return result.structured as ReviewDecision | undefined;
337
319
  }
338
320
 
339
321
  function reviewApproved(decision: ReviewDecision): boolean {
@@ -872,37 +854,9 @@ function renderReviewerPrompt(args: {
872
854
  [
873
855
  "output_format",
874
856
  [
875
- "Use the schema-backed structured_output tool after your investigation and validation attempts.",
876
- "The tool terminates the turn and provides the structured data; do not emit a separate final assistant response after calling it.",
877
- "The review gate decides completion only from the JSON object captured by structured_output; invalid JSON, missing fields, reviewer_error, or stop_review_loop=false are treated as not approved for safety.",
878
857
  "Set stop_review_loop=true only when there are no P0/P1/P2 findings, overall_correctness is patch is correct, goal_oracle_satisfied is true, no objective-relevant verification remains, and reviewer_error is null/omitted.",
879
858
  "P3 nice-to-have findings are non-blocking when the rest of the approval contract is satisfied; do not use P3 for work required by the objective or verification oracle.",
880
- "If you hit a reviewer/tool/validation error, still return the object with stop_review_loop=false and reviewer_error populated instead of pretending the patch is approved.",
881
- [
882
- "The structured_output schema is authoritative; do not copy a hand-written JSON blob into the final response. Here is an example output:",
883
- "{",
884
- ' "findings": [',
885
- " {",
886
- ' "title": "<≤ 80 chars, imperative, starts with [P0]/[P1]/[P2]/[P3]>",',
887
- ' "body": "<one paragraph of valid Markdown explaining why this is a problem; cite files/lines/functions>",',
888
- ' "confidence_score": <float 0.0-1.0>,',
889
- ' "priority": <int 0-3 or null>,',
890
- ' "code_location": {',
891
- ' "absolute_file_path": "<absolute file path>",',
892
- ' "line_range": {"start": <int>, "end": <int>}',
893
- " }",
894
- " }",
895
- " ],",
896
- ' "overall_correctness": "patch is correct" | "patch is incorrect",',
897
- ' "overall_explanation": "<1-3 sentence explanation justifying the verdict>",',
898
- ' "overall_confidence_score": <float 0.0-1.0>,',
899
- ' "goal_oracle_satisfied": <boolean>,',
900
- ' "receipt_assessment": "<how receipts/current evidence map to the verification oracle>",',
901
- ' "verification_remaining": "<oracle-relevant verification still missing, or none>",',
902
- ' "stop_review_loop": <boolean>,',
903
- ' "reviewer_error": null | {"kind": "validation_unavailable" | "dependency_unavailable" | "tool_failure" | "reviewer_failure", "message": "<what failed>", "attempted_recovery": "<what you tried>"}',
904
- "}",
905
- ].join("\n"),
859
+ "If you hit a reviewer/tool/validation error, set stop_review_loop=false and populate reviewer_error instead of pretending the patch is approved.",
906
860
  ].join("\n"),
907
861
  ],
908
862
  ]);
@@ -1169,20 +1123,22 @@ export default defineWorkflow("goal")
1169
1123
  });
1170
1124
  } catch (err) {
1171
1125
  const message = err instanceof Error ? err.message : String(err);
1126
+ const structured = reviewerErrorDecision(message);
1172
1127
  reviewResults = [
1173
1128
  {
1174
1129
  name: `reviewer-error-${turn}`,
1175
1130
  stageName: `reviewer-error-${turn}`,
1176
- text: JSON.stringify(reviewerErrorDecision(message), null, 2),
1131
+ text: JSON.stringify(structured, null, 2),
1132
+ structured,
1177
1133
  },
1178
1134
  ];
1179
1135
  }
1180
1136
 
1181
1137
  latestReviews = await Promise.all(reviewResults.map(async (result) => {
1182
1138
  const reviewerName = result.name ?? result.stageName;
1183
- const parsed = parseReviewDecision(result.text) ??
1139
+ const parsed = reviewDecisionFromResult(result) ??
1184
1140
  reviewerErrorDecision(
1185
- `Reviewer ${reviewerName} returned invalid structured JSON.`,
1141
+ `Reviewer ${reviewerName} returned no structured decision.`,
1186
1142
  );
1187
1143
  const reviewArtifactPath = await writeReviewArtifact(
1188
1144
  artifactDir,
@@ -144,41 +144,20 @@ const exportGateDecisionSchema = Type.Object(
144
144
  { additionalProperties: false },
145
145
  );
146
146
 
147
- function parseRefinementDecision(text: string): RefinementDecision {
148
- const parsed = JSON.parse(text) as Partial<RefinementDecision>;
149
- if (typeof parsed.ready_for_export !== "boolean") {
150
- throw new Error("open-claude-design refinement decision missing ready_for_export.");
147
+ function refinementDecisionFromResult(result: WorkflowTaskResult): RefinementDecision {
148
+ const decision = result.structured as RefinementDecision | undefined;
149
+ if (!decision) {
150
+ throw new Error("open-claude-design refinement decision missing structured result.");
151
151
  }
152
- return {
153
- ready_for_export: parsed.ready_for_export,
154
- rationale: typeof parsed.rationale === "string" ? parsed.rationale : "",
155
- required_changes: Array.isArray(parsed.required_changes)
156
- ? parsed.required_changes.filter((item): item is string => typeof item === "string")
157
- : [],
158
- };
152
+ return decision;
159
153
  }
160
154
 
161
- function parseExportGateDecision(text: string): ExportGateDecision {
162
- const parsed = JSON.parse(text) as Partial<ExportGateDecision>;
163
- if (typeof parsed.has_blocking_findings !== "boolean") {
164
- throw new Error("open-claude-design export gate decision missing has_blocking_findings.");
155
+ function exportGateDecisionFromResult(result: WorkflowTaskResult): ExportGateDecision {
156
+ const decision = result.structured as ExportGateDecision | undefined;
157
+ if (!decision) {
158
+ throw new Error("open-claude-design export gate decision missing structured result.");
165
159
  }
166
- return {
167
- has_blocking_findings: parsed.has_blocking_findings,
168
- rationale: typeof parsed.rationale === "string" ? parsed.rationale : "",
169
- blocking_findings: Array.isArray(parsed.blocking_findings)
170
- ? parsed.blocking_findings.filter(
171
- (item): item is ExportGateFinding =>
172
- typeof item === "object" &&
173
- item !== null &&
174
- "finding" in item &&
175
- "evidence" in item &&
176
- "why_blocking" in item &&
177
- "must_fix_action" in item &&
178
- "severity" in item,
179
- )
180
- : [],
181
- };
160
+ return decision;
182
161
  }
183
162
 
184
163
  function joinResults(results: readonly WorkflowTaskResult[]): string {
@@ -805,7 +784,7 @@ export default defineWorkflow("open-claude-design")
805
784
  [
806
785
  "1. If a previous `preview-display-*` step captured annotated user feedback or notes, honor them as the primary signal.",
807
786
  "2. Otherwise, you may inspect the HTML file at preview_path directly (read it from disk) and run an impeccable `critique` against it.",
808
- "3. Decide whether the current design is ready for export using the schema-backed structured_output tool.",
787
+ "3. Decide whether the current design is ready for export.",
809
788
  "4. If refinement is still needed, put specific changes in required_changes ordered by user value and implementation risk.",
810
789
  "5. Never request changes that contradict DESIGN.md unless you explicitly identify and explain the conflict.",
811
790
  ].join("\n"),
@@ -813,7 +792,6 @@ export default defineWorkflow("open-claude-design")
813
792
  [
814
793
  "output_format",
815
794
  [
816
- "Call structured_output after your inspection.",
817
795
  "Set ready_for_export=true only when the current preview needs no further refinement before export.",
818
796
  "Set ready_for_export=false and populate required_changes when another polish iteration is needed.",
819
797
  ].join("\n"),
@@ -823,7 +801,7 @@ export default defineWorkflow("open-claude-design")
823
801
  ...refinementDecisionConfig,
824
802
  });
825
803
 
826
- const feedbackDecision = parseRefinementDecision(feedback.text);
804
+ const feedbackDecision = refinementDecisionFromResult(feedback);
827
805
  if (feedbackDecision.ready_for_export) {
828
806
  approvedForExport = true;
829
807
  break;
@@ -1013,14 +991,13 @@ export default defineWorkflow("open-claude-design")
1013
991
  "1. Read the HTML at preview_path and score it across all five audit dimensions.",
1014
992
  "2. Scan for banned anti-patterns, accessibility blockers, severe visual regressions, missing critical states, and handoff gaps.",
1015
993
  "3. Only mark findings as blocking when they would materially harm implementation or user experience (impeccable P0 severity).",
1016
- "4. Decide whether export is blocked using the schema-backed structured_output tool.",
994
+ "4. Decide whether export is blocked.",
1017
995
  "5. Every blocking finding must include selector-level evidence and a must-fix action.",
1018
996
  ].join("\n"),
1019
997
  ],
1020
998
  [
1021
- "output_format",
999
+ "decision_rules",
1022
1000
  [
1023
- "Call structured_output after the audit.",
1024
1001
  "Set has_blocking_findings=true only when one or more P0 findings block export.",
1025
1002
  "Populate blocking_findings with every blocking P0 issue; leave it empty when export is safe.",
1026
1003
  ].join("\n"),
@@ -1030,7 +1007,7 @@ export default defineWorkflow("open-claude-design")
1030
1007
  ...exportGateDecisionConfig,
1031
1008
  });
1032
1009
 
1033
- const exportGateDecision = parseExportGateDecision(preExport.text);
1010
+ const exportGateDecision = exportGateDecisionFromResult(preExport);
1034
1011
  if (exportGateDecision.has_blocking_findings) {
1035
1012
  const forcedFix = await ctx.task("forced-fix", {
1036
1013
  prompt: taggedPrompt([
@@ -189,23 +189,8 @@ async function createImplementationNotesFile(prompt: string): Promise<string> {
189
189
  return notesPath;
190
190
  }
191
191
 
192
- function parseReviewDecision(text: string): ReviewDecision | undefined {
193
- try {
194
- const parsed = JSON.parse(text) as Partial<ReviewDecision>;
195
- if (
196
- parsed.overall_correctness !== "patch is correct" &&
197
- parsed.overall_correctness !== "patch is incorrect"
198
- ) {
199
- return undefined;
200
- }
201
- if (!Array.isArray(parsed.findings)) return undefined;
202
- if (typeof parsed.stop_review_loop !== "boolean") return undefined;
203
- if (typeof parsed.overall_explanation !== "string") return undefined;
204
- if (typeof parsed.overall_confidence_score !== "number") return undefined;
205
- return parsed as ReviewDecision;
206
- } catch {
207
- return undefined;
208
- }
192
+ function reviewDecisionFromResult(result: WorkflowTaskResult): ReviewDecision | undefined {
193
+ return result.structured as ReviewDecision | undefined;
209
194
  }
210
195
 
211
196
  function reviewDecisionApproved(decision: ReviewDecision): boolean {
@@ -237,10 +222,12 @@ function reviewerErrorDecision(error: string): ReviewDecision {
237
222
  function reviewerErrorResult(
238
223
  error: string,
239
224
  ): WorkflowTaskResult {
225
+ const structured = reviewerErrorDecision(error);
240
226
  return {
241
227
  name: "reviewer-error",
242
228
  stageName: "reviewer-error",
243
- text: JSON.stringify(reviewerErrorDecision(error), null, 2),
229
+ text: JSON.stringify(structured, null, 2),
230
+ structured,
244
231
  };
245
232
  }
246
233
 
@@ -766,7 +753,7 @@ async function runRalphWorkflow(
766
753
  ].join("\n"),
767
754
  ],
768
755
  [
769
- "required_actions_before_tool_call",
756
+ "action_items",
770
757
  [
771
758
  "1. Identify the changed files or diff under review.",
772
759
  "2. Read the relevant changed code and directly affected call sites/tests/configs.",
@@ -782,36 +769,10 @@ async function runRalphWorkflow(
782
769
  ].join("\n"),
783
770
  ],
784
771
  [
785
- "structured_output_contract",
772
+ "decision_rules",
786
773
  [
787
- "Use the schema-backed structured_output tool after your investigation and validation attempts.",
788
- "The tool terminates the turn and provides the structured data; do not emit a separate final assistant response after calling it.",
789
- "The review loop decides whether to stop only from the JSON object captured by structured_output; invalid JSON, missing fields, reviewer_error, or stop_review_loop=false are treated as not approved for safety.",
790
774
  "Set stop_review_loop=true only when findings is empty, overall_correctness is patch is correct, and reviewer_error is null/omitted.",
791
- "If you hit a reviewer/tool/validation error, still return the object with stop_review_loop=false and reviewer_error populated instead of pretending the patch is approved.",
792
- "The structured_output schema is authoritative; do not copy a hand-written JSON blob into the final response. Here is an example output:",
793
- "{",
794
- ' "findings": [',
795
- " {",
796
- ' "title": "<≤ 80 chars, imperative, starts with [P0]/[P1]/[P2]/[P3]>",',
797
- ' "body": "<one paragraph of valid Markdown explaining why this is a problem; cite files/lines/functions>",',
798
- ' "confidence_score": <float 0.0-1.0>,',
799
- ' "priority": <int 0-3 or null>,',
800
- ' "code_location": {',
801
- ' "absolute_file_path": "<absolute file path>",',
802
- ' "line_range": {"start": <int>, "end": <int>}',
803
- " }",
804
- " }",
805
- " ],",
806
- ' "overall_correctness": "patch is correct" | "patch is incorrect",',
807
- ' "overall_explanation": "<1-3 sentence explanation justifying the verdict>",',
808
- ' "overall_confidence_score": <float 0.0-1.0>,',
809
- ' "goal_oracle_satisfied": <boolean>,',
810
- ' "receipt_assessment": "<how receipts/current evidence map to the verification oracle>",',
811
- ' "verification_remaining": "<oracle-relevant verification still missing, or none>",',
812
- ' "stop_review_loop": <boolean>,',
813
- ' "reviewer_error": null | {"kind": "validation_unavailable" | "dependency_unavailable" | "tool_failure" | "reviewer_failure", "message": "<what failed>", "attempted_recovery": "<what you tried>"}',
814
- "}",
775
+ "If you hit a reviewer/tool/validation error, set stop_review_loop=false and populate reviewer_error instead of pretending the patch is approved.",
815
776
  ].join("\n"),
816
777
  ],
817
778
  ]);
@@ -853,8 +814,8 @@ async function runRalphWorkflow(
853
814
 
854
815
  const reviewEntries = await Promise.all(reviews.map(async (review) => {
855
816
  const reviewer = review.name ?? review.stageName;
856
- const decision = parseReviewDecision(review.text) ??
857
- reviewerErrorDecision(`Reviewer ${reviewer} returned invalid structured JSON.`);
817
+ const decision = reviewDecisionFromResult(review) ??
818
+ reviewerErrorDecision(`Reviewer ${reviewer} returned no structured decision.`);
858
819
  const artifactPath = join(
859
820
  artifactDir,
860
821
  `review-${iteration}-${artifactSafeName(reviewer)}.json`,
@@ -886,7 +847,7 @@ async function runRalphWorkflow(
886
847
  ],
887
848
  [
888
849
  "objective",
889
- `Review the changes since the base branch \`${comparisonBaseBranch}\` and create a provider-appropriate pull request, merge request, or code-review handoff if possible and credentials are available. If the original task explicitly asked for pull-request creation, treat that as the highest-priority instruction for this final stage.`,
850
+ `Review the changes since the base branch \`${comparisonBaseBranch}\` and create a provider-appropriate pull request, merge request, or code-review handoff if possible and credentials are available. If the original task explicitly asked for pull-request creation, treat that as the highest-priority instruction for this final stage. Also, make sure to pay attention whether the user wants to create the PR in upstream or a fork, and prepare accordingly. If PR creation is not possible (lack of permissions, etc.), report why instead of pretending success.`,
890
851
  ],
891
852
  workflowCwdContext,
892
853
  [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.8.29-alpha.3",
3
+ "version": "0.8.29",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -131,6 +131,21 @@ export const WORKFLOW_TOOL_DESCRIPTION =
131
131
  "quote the exact path without rewriting separators (Windows backslashes are valid), " +
132
132
  "then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview.";
133
133
 
134
+ export const DEFAULT_PROMPT_GUIDANCE: string[] = [
135
+ `**Workflows**: Use the \`workflow\` tool for existing named workflows and for repeatable, inspectable, resumable, or multi-stage processes; use direct \`task\`, \`tasks\`, or \`chain\` workflow calls for one-off tracked work when that is useful.
136
+ - For unfamiliar named workflows, discover with \`action: "list"\`, inspect with \`action: "get"\` or \`action: "inputs"\`, and run with \`action: "run"\`, \`workflow\`, and validated \`inputs\`; do not invent workflow names or input keys.
137
+ - When designing or editing workflows, read docs/workflows.md and reference its Workflow Starter Patterns: Classify-and-act, Fan-out-and-synthesize, Adversarial verification, Generate-and-filter, Tournament, and Loop until done. Choose or combine these patterns before inventing a custom stage graph, and reflect the selected pattern in the spec and Mermaid diagram when using the create-spec skill.
138
+ - Once you run a workflow with the workflow tool, end your current turn and wait for the next user input or lifecycle notice.
139
+ - You will automatically be alerted of key lifecycle events like start, finish, failure; do not micro-manage the run with sleep/status polling loops or read its logs/stages unless the user asks you to or you need information for the next step.
140
+ - If the user needs information from the workflow run, use targeted \`status\`/\`stages\`/\`stage\` checks instead of trying to read everything.
141
+ - Offer to help the user on another task instead of anxiously polling or help the user run another workflow if they need.
142
+ - Use run-control and messaging actions (\`send\`, \`pause\`, \`resume\`, \`interrupt\`, \`kill\`) only when needed to answer prompts, steer a stage, resume or interrupt paused work, or respond to user requests/control signals.
143
+ - For transcripts, avoid reading whole session transcripts at once. Use \`stages\` or \`stage\` to get \`sessionFile\`/\`transcriptPath\`, quote the exact path without rewriting separators (preserve Windows backslashes), search it with \`rg\`/\`grep\`, and read small relevant ranges; use \`transcript\` with explicit \`tail\` or \`limit\` only for quick recent-context checks.
144
+ - If a user asks to create or edit a workflow, use the create-spec skill when available and ask detailed clarifying questions until you understand its purpose, inputs, stages, handoffs, validation, success criteria, and selected starter pattern. Then read the workflow docs/examples and implement the workflow from the created spec directly as a TypeScript definition. After you implement the workflow, reload it to access it and run it with test inputs to validate it works as intended before presenting it to the user.
145
+ - Tip: when designing workflows, implement it in a way that you pass information from stage to stage by writing it to a file or artifact (either deterministic or model-driven), pass the path with \`reads\`, and explicitly prompt the downstream agent with wording like \`Read the file at <path>...\`; do not inject large \`previous\` payloads or session history into the next prompt unless explicitly requested to.
146
+ - If you run \`ralph\` or \`goal\` workflow, define an objective that includes tight scope, concrete and verifiable done criteria, and validation steps; then monitor progress as above instead of doing parallel implementation yourself.`,
147
+ ];
148
+
134
149
  // ---------------------------------------------------------------------------
135
150
  // Minimal ExtensionAPI structural types
136
151
  // No `any`; all optional fields use explicit union with undefined.
@@ -269,6 +284,7 @@ export interface PiToolOpts<TArgs, TDetails> {
269
284
  label: string;
270
285
  description: string;
271
286
  parameters: unknown; // TypeBox TSchema — pi consumes it opaquely
287
+ promptGuidelines?: string[];
272
288
  renderShell?: "default" | "self";
273
289
  /**
274
290
  * Pi calls execute positionally: `(toolCallId, params, signal, onUpdate, ctx)`.
@@ -2744,6 +2760,7 @@ function factory(pi: ExtensionAPI): void {
2744
2760
  label: "workflow",
2745
2761
  description: WORKFLOW_TOOL_DESCRIPTION,
2746
2762
  parameters: workflowParameters,
2763
+ promptGuidelines: DEFAULT_PROMPT_GUIDANCE,
2747
2764
  renderShell: "self",
2748
2765
  execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
2749
2766
  // Overlay is opt-in via F2 / ctrl+h; do not auto-open from a
@@ -37,37 +37,10 @@ const McpOptionsSchema = Type.Object({
37
37
  deny: Type.Optional(Type.Array(Type.String())),
38
38
  });
39
39
 
40
- const JsonSchemaObjectTypeValue = {
41
- anyOf: [
42
- { const: "object" },
43
- { type: "array", minItems: 1, maxItems: 1, items: { const: "object" } },
44
- ],
45
- };
46
-
47
- const JsonSchemaExplicitObjectDescriptor = {
40
+ const JsonSchemaObject = Type.Unsafe<Record<string, unknown>>({
48
41
  type: "object",
49
- required: ["type"],
50
- properties: { type: JsonSchemaObjectTypeValue },
51
42
  additionalProperties: true,
52
- };
53
-
54
- const JsonSchemaObject = Type.Unsafe<Record<string, unknown>>({
55
- description: "Top-level object JSON Schema used as structured_output tool arguments for this workflow item.",
56
- anyOf: [
57
- JsonSchemaExplicitObjectDescriptor,
58
- {
59
- type: "object",
60
- required: ["allOf"],
61
- properties: {
62
- allOf: {
63
- type: "array",
64
- minItems: 1,
65
- items: JsonSchemaExplicitObjectDescriptor,
66
- },
67
- },
68
- additionalProperties: true,
69
- },
70
- ],
43
+ description: "Plain JSON Schema used as final-answer tool arguments for this workflow item.",
71
44
  });
72
45
 
73
46
  const BashCommandRuleSchema = Type.Union([
@@ -537,10 +537,6 @@ function splitPromptOptions(options: StagePromptOptions | undefined): {
537
537
 
538
538
  const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
539
539
 
540
- function structuredOutputPrompt(text: string): string {
541
- return `${text}\n\nFinal output contract:\n- Your final action MUST be a structured_output tool call.\n- Pass the schema fields directly as tool arguments; do not wrap them in { value: ... } unless the schema explicitly defines a top-level value field.\n- Do not emit a prose final answer instead of structured_output.\n- If you need to inspect files or run commands first, do so, then call structured_output exactly once.`;
542
- }
543
-
544
540
  function stringifyStructuredOutputValue(value: unknown): string {
545
541
  try {
546
542
  return JSON.stringify(value, null, 2);
@@ -959,7 +955,7 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
959
955
  adapterMessages = assistantMessage(lastAssistantText);
960
956
  return lastAssistantText;
961
957
  }
962
- await promptWithFallback(structuredOutputCapture ? structuredOutputPrompt(text) : text, sdkOptions);
958
+ await promptWithFallback(text, sdkOptions);
963
959
  if (structuredOutputCapture) {
964
960
  if (!structuredOutputCapture.called) {
965
961
  throw new Error("atomic-workflows: stage configured with schema must finish by calling structured_output.");
@@ -113,7 +113,7 @@ export interface WorkflowFastModeSettingsManager {
113
113
  getCodexFastModeSettings(): WorkflowFastModeSettings;
114
114
  }
115
115
  export interface StageOptions<TSchemaDef extends TSchema | undefined = TSchema | undefined> extends WorkflowModelFallbackFields {
116
- /** Optional structured final-answer schema. When set, the stage receives a schema-specific `structured_output` tool and must finish by calling it. */
116
+ /** Optional structured final-answer schema. When set, the stage receives a schema-specific final-answer tool. */
117
117
  readonly schema?: TSchemaDef;
118
118
  readonly model?: WorkflowModelValue;
119
119
  readonly mcp?: StageMcpOptions;
@@ -156,7 +156,7 @@ export interface StageMcpOptions extends AuthoringContract.StageMcpOptions {
156
156
  export interface StageOptions<TSchemaDef extends TSchema | undefined = TSchema | undefined>
157
157
  extends Omit<CreateAgentSessionOptions, "model" | keyof AuthoringContract.StageOptions>,
158
158
  Omit<Mutable<AuthoringContract.StageOptions<TSchemaDef>>, "sessionManager" | "settingsManager"> {
159
- /** Optional structured final-answer schema. When set, the stage receives a schema-specific `structured_output` tool and must finish by calling it. */
159
+ /** Optional structured final-answer schema. When set, the stage receives a schema-specific final-answer tool. */
160
160
  schema?: TSchemaDef;
161
161
  /** Model id or pi SDK model object used as the primary stage model. */
162
162
  model?: WorkflowModelValue;
@@ -7,7 +7,7 @@ import { ModelRegistry } from "./model-registry.ts";
7
7
  import type { ResourceLoader } from "./resource-loader.ts";
8
8
  import { SessionManager } from "./session-manager.ts";
9
9
  import { SettingsManager } from "./settings-manager.ts";
10
- import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, STRUCTURED_OUTPUT_TOOL_NAME, createStructuredOutputCapture, createStructuredOutputTool, getStructuredOutputMetadataPath, createWriteTool, withFileMutationQueue, type BashCommandPolicy } from "./tools/index.ts";
10
+ import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, STRUCTURED_OUTPUT_TOOL_NAME, createStructuredOutputCapture, createStructuredOutputTool, createWriteTool, withFileMutationQueue, type BashCommandPolicy } from "./tools/index.ts";
11
11
  export interface CreateAgentSessionOptions {
12
12
  /** Working directory for project-local discovery. Default: process.cwd() */
13
13
  cwd?: string;
@@ -79,8 +79,8 @@ export * from "./agent-session-runtime.ts";
79
79
  export type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionFactory, SlashCommandInfo, SlashCommandSource, ToolDefinition, } from "./extensions/index.ts";
80
80
  export type { PromptTemplate } from "./prompt-templates.ts";
81
81
  export type { Skill } from "./skills.ts";
82
- export type { BashCommandParseError, BashCommandParseResult, BashCommandPolicy, BashCommandPolicyDecision, BashCommandPolicyMatchMode, BashCommandPolicyRejection, BashCommandRule, BashCommandSegment, BashCommandSegmentSource, JsonObject, JsonPrimitive, JsonValue, StructuredOutputCapture, StructuredOutputCaptureMetadata, StructuredOutputFileCapture, StructuredOutputToolOptions, Tool, } from "./tools/index.ts";
83
- export { withFileMutationQueue, STRUCTURED_OUTPUT_TOOL_NAME, createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, createStructuredOutputCapture, createStructuredOutputTool, getStructuredOutputMetadataPath, };
82
+ export type { BashCommandParseError, BashCommandParseResult, BashCommandPolicy, BashCommandPolicyDecision, BashCommandPolicyMatchMode, BashCommandPolicyRejection, BashCommandRule, BashCommandSegment, BashCommandSegmentSource, JsonObject, JsonPrimitive, JsonValue, StructuredOutputCapture, StructuredOutputFileCapture, StructuredOutputToolOptions, Tool, } from "./tools/index.ts";
83
+ export { withFileMutationQueue, STRUCTURED_OUTPUT_TOOL_NAME, createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, createStructuredOutputCapture, createStructuredOutputTool, };
84
84
  /**
85
85
  * Create an AgentSession with the specified options.
86
86
  *