@bastani/atomic 0.9.11-alpha.3 → 0.9.11-alpha.4

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 (74) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +20 -0
  8. package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -3
  9. package/dist/builtin/workflows/builtin/goal.ts +4 -4
  10. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +1 -1
  11. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +3 -1
  12. package/dist/builtin/workflows/builtin/ralph-runner.ts +2 -0
  13. package/dist/builtin/workflows/builtin/ralph.ts +4 -4
  14. package/dist/builtin/workflows/builtin/shared-prompts.ts +16 -0
  15. package/dist/builtin/workflows/package.json +2 -2
  16. package/dist/builtin/workflows/src/authoring/workflow.ts +1 -0
  17. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +27 -1
  18. package/dist/builtin/workflows/src/durable/factory.ts +29 -3
  19. package/dist/builtin/workflows/src/durable/index.ts +2 -0
  20. package/dist/builtin/workflows/src/engine/run-durable-admission.ts +50 -0
  21. package/dist/builtin/workflows/src/engine/run.ts +12 -17
  22. package/dist/builtin/workflows/src/extension/extension-factory.ts +16 -1
  23. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +16 -2
  24. package/dist/builtin/workflows/src/extension/index.bundle.mjs +71724 -0
  25. package/dist/builtin/workflows/src/extension/runtime.ts +5 -3
  26. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +4 -1
  27. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
  28. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -0
  29. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +1 -0
  30. package/dist/builtin/workflows/src/shared/types.ts +7 -0
  31. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +1 -0
  32. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +1 -0
  33. package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +6 -8
  34. package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +2 -0
  35. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +4 -0
  36. package/dist/cli/args.d.ts +8 -0
  37. package/dist/cli/args.d.ts.map +1 -1
  38. package/dist/cli/args.js +16 -0
  39. package/dist/cli/args.js.map +1 -1
  40. package/dist/cli.js +2 -0
  41. package/dist/cli.js.map +1 -1
  42. package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
  43. package/dist/modes/interactive/components/chat-session-host-rendering.js +5 -1
  44. package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
  45. package/dist/modes/interactive/components/footer.d.ts +7 -1
  46. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  47. package/dist/modes/interactive/components/footer.js +12 -6
  48. package/dist/modes/interactive/components/footer.js.map +1 -1
  49. package/dist/modes/interactive-engine/engine-monitor.d.ts +8 -1
  50. package/dist/modes/interactive-engine/engine-monitor.d.ts.map +1 -1
  51. package/dist/modes/interactive-engine/engine-monitor.js +13 -15
  52. package/dist/modes/interactive-engine/engine-monitor.js.map +1 -1
  53. package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
  54. package/dist/modes/rpc/rpc-client-process.js +3 -0
  55. package/dist/modes/rpc/rpc-client-process.js.map +1 -1
  56. package/dist/modes/rpc/rpc-extension-ui.d.ts +3 -1
  57. package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
  58. package/dist/modes/rpc/rpc-extension-ui.js +7 -2
  59. package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
  60. package/dist/modes/rpc/rpc-session-binding.d.ts +1 -0
  61. package/dist/modes/rpc/rpc-session-binding.d.ts.map +1 -1
  62. package/dist/modes/rpc/rpc-session-binding.js +66 -43
  63. package/dist/modes/rpc/rpc-session-binding.js.map +1 -1
  64. package/dist/rpc-entry.js +6 -2
  65. package/dist/rpc-entry.js.map +1 -1
  66. package/dist/utils/compile-cache.d.ts +8 -0
  67. package/dist/utils/compile-cache.d.ts.map +1 -0
  68. package/dist/utils/compile-cache.js +31 -0
  69. package/dist/utils/compile-cache.js.map +1 -0
  70. package/docs/tui.md +2 -0
  71. package/docs/usage.md +7 -0
  72. package/docs/workflows.md +34 -11
  73. package/npm-shrinkwrap.json +23 -23
  74. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.11-alpha.4] - 2026-07-23
6
+
7
+ ### Changed
8
+
9
+ - Installed builds now ship the builtin workflows extension as a single prebundled ESM file. Loading it previously resolved and transpiled a ~300-file TypeScript module graph per launch, which dominated interactive-engine startup on Windows (~7 s on a test VM, now ~1.1 s, ~6x faster) ([#1962](https://github.com/bastani-inc/atomic/issues/1962)). Source checkouts still load the raw TypeScript sources.
10
+ - Interactive TUI startup on Windows is dramatically faster: the CLI now enables Node's persistent on-disk V8 compile cache (Node >= 22.8) and flushes it before spawning the isolated interactive engine, so both the host and the engine child reuse compiled module bytecode across runs and within the same launch ([#1962](https://github.com/bastani-inc/atomic/issues/1962)).
11
+
12
+ ### Fixed
13
+
14
+ - Fixed interactive startup crashing with `Interactive engine did not become ready within 5000 ms` on machines with slow cold starts (commonly Windows PowerShell with npm-global installs). The fixed readiness deadline is removed; the host now waits until the engine reports ready and still fails fast if the engine process exits or the transport breaks ([#1962](https://github.com/bastani-inc/atomic/issues/1962)).
15
+ - Fixed positional prompts beginning with `-`, `--`, or `@` being parsed as options or file arguments by supporting the conventional `--` end-of-options terminator ([#1950](https://github.com/bastani-inc/atomic/issues/1950)).
16
+ - Fixed embedded extension UIs in isolated interactive mode receiving an empty placeholder from `ctx.ui.getFooterDataProvider()`. The engine session now exposes its live extension statuses and cached, watched Git branch so synchronous renderers such as workflow stage chat can match the main footer without RPC calls or per-render Git processes.
17
+
5
18
  ## [0.9.11-alpha.3] - 2026-07-21
6
19
 
7
20
  ### Added
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/cursor",
3
- "version": "0.9.11-alpha.3",
3
+ "version": "0.9.11-alpha.4",
4
4
  "private": true,
5
5
  "description": "Experimental first-party Atomic extension for Cursor OAuth, model discovery, and streaming provider registration.",
6
6
  "contributors": [
@@ -40,7 +40,7 @@
40
40
  }
41
41
  },
42
42
  "dependencies": {
43
- "@bastani/atomic-natives": "0.9.11-alpha.3",
43
+ "@bastani/atomic-natives": "0.9.11-alpha.4",
44
44
  "@bufbuild/protobuf": "^2.12.1",
45
45
  "@earendil-works/pi-ai": "^0.81.1"
46
46
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.11-alpha.3",
3
+ "version": "0.9.11-alpha.4",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.11-alpha.3",
3
+ "version": "0.9.11-alpha.4",
4
4
  "private": true,
5
5
  "description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.11-alpha.3",
3
+ "version": "0.9.11-alpha.4",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with chains, parallel execution, and background runs. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.11-alpha.3",
3
+ "version": "0.9.11-alpha.4",
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,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.11-alpha.4] - 2026-07-23
10
+
11
+ ### Added
12
+
13
+ - Added the opt-in `autoAttach` workflow-definition field, which opens the graph overlay when an interactive top-level named workflow is launched through `/workflow <name>` or the registered `workflow` tool without changing headless launches, nested workflow composition, or the existing input-form launch path.
14
+ - `WorkflowRunContext` now optionally exposes the host session's model catalog as `ctx.models` (including `currentModel`, the user-selected session model, and `listModels()`). Custom workflow definitions can use it to select stage models from the live catalog; the field is absent when the host provides no catalog, and builtin workflow model chains are unchanged.
15
+
16
+ ### Changed
17
+
18
+ - Workflow prompt guidance now instructs agents to give each workflow invocation its own intercom group by default — unless the user requests otherwise — by minting one invocation-scoped literal group name inside the workflow's `run` function (e.g. `"myflow-" + randomUUID()`) and passing it via the `group` option on each stage, task, or parallel step that should share it; `group: true`/`"auto"` mints one shared UUID group per `ctx.parallel(...)` set but a fresh UUID per non-parallel stage. This keeps stage and subagent intercom chatter isolated from the parent chat and other concurrent runs, since ungrouped sessions all share the `"default"` group; subagent inheritance, capability gating, and cross-group `contact_supervisor` escalation are unchanged.
19
+ - Attached workflow stage chats now match the main interactive footer by showing the home-shortened current folder with its cached, live-updating Git branch and by mirroring current extension status lines such as MCP connectivity, using the workflow chat theme without replacing the existing hierarchy and copy-mode hints.
20
+ - The builtin `goal` and `ralph` workflow descriptions and their `objective`/`prompt`/`create_pr` input descriptions now instruct launching agents that when a delegated task includes submitting a pull request (or MR/review), that final action must be stripped from the objective/prompt text and requested via `create_pr=true` instead when preparing the workflow inputs. This keeps PR creation an explicitly authorized final-stage action rather than embedded task text that intermediate stages are told to ignore.
21
+ - Goal worker and Ralph orchestrator prompts now carry an explicit worktree-discipline contract: all work happens in the invoking checkout (or the run's explicitly configured worktree), stages never create additional git worktrees, clones, or repository copies unless the user's task explicitly requests them, and work discovered stranded outside the invoking checkout must be brought back in before continuing. The `git_worktree_dir` input descriptions state the same rule for launching agents.
22
+ - Goal and Ralph reviewer prompts now include a code-delta presence and integrity contract: before trusting receipts or stage summaries, reviewers must prove the code delta actually exists in the review checkout using the repository's version-control tooling (for git: `git worktree list`, `git status`, and a baseline diff), treat claimed-but-absent work as a blocking finding instead of approving over an empty or unrelated delta, treat uncommitted work at claimed readiness as remaining work unless the objective forbids committing, and flag any modification, rename, or deletion of pre-existing test files or test functions as a finding requiring explicit justification.
23
+ - Goal worker prompts now require committing work in the current checkout with a descriptive message before claiming readiness for review — verifying a clean working tree with the repository's version-control status command and reporting the commit identifier in the receipt — unless the objective or acceptance criteria explicitly forbid committing. Delivery verification stays with reviewers and prompts (version-control-agnostic) rather than a hardcoded git gate in the runner.
24
+
25
+ ### Fixed
26
+
27
+ - Fixed host-session replacement (`/new`, `/resume`, `/fork`, `/reload`) leaving the process-global DBOS executor stopped so every subsequent workflow run in the same Atomic process failed at its first durable checkpoint with `` `DBOS.launch()` must be called before running workflows ``. The DBOS executor lifetime is now process-scoped: process-preserving session boundaries flush pending durable writes but keep the executor launched, and SDK shutdown is reserved for actual process exit (`quit`/`beforeExit`), which still flushes and shuts down exactly once. Defense in depth: the durable-backend factory revalidates its memoized backend against the current DBOS lifecycle generation and never hands out a stopped backend (post-shutdown initialization fails loudly instead of silently degrading to a non-durable backend), the extension runtime no longer caches a permanently resolved readiness promise across lifecycle generations, and root workflow registration is durably flushed before startup admission so a stopped or unhealthy backend fails before workflow code executes any side effects. ([#1957](https://github.com/bastani-inc/atomic/issues/1957))
28
+
9
29
  ## [0.9.11-alpha.2] - 2026-07-21
10
30
 
11
31
  ### Fixed
@@ -6,11 +6,13 @@ import {
6
6
  FINDINGS_CONSOLIDATION_CONTRACT,
7
7
  LITERAL_OBJECTIVE_CONTRACT,
8
8
  REGRESSION_EVIDENCE_CONTRACT,
9
+ REVIEW_CODE_DELTA_CONTRACT,
9
10
  REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT,
10
11
  REVIEWER_INTERCOM_COORDINATION_PROTOCOL,
11
12
  REVIEWER_OVERIMPLEMENTATION_GUARD,
12
13
  REVIEWER_SPEC_VS_OBJECTIVE_GUARD,
13
14
  WORKER_PREFLIGHT_CONTRACT,
15
+ WORKTREE_DISCIPLINE_CONTRACT,
14
16
  renderE2eQaVideoReviewGuidance,
15
17
  } from "./shared-prompts.js";
16
18
  import type { GoalLedger } from "./goal-types.js";
@@ -72,6 +74,7 @@ export const WORKER_RECEIPT_CONTRACT = [
72
74
  "Classify evidence honestly: proves completion, contradicts completion, shows incomplete work, is too weak or indirect, is merely consistent with completion, or is missing.",
73
75
  "Match verification scope to requirement scope; do not use a narrow check to support a broad claim, and treat tests/manifests/verifiers/green checks/search results as evidence only after confirming they cover the relevant requirement.",
74
76
  "If you believe the goal is ready for review, say so only after mapping current evidence to every requirement you can derive from the objective and referenced artifacts.",
77
+ "Unless the objective or acceptance criteria explicitly forbid committing, commit your work in the current checkout with a descriptive message before claiming readiness for review, verify the working tree is clean with the repository's version-control status command (for git: `git status --porcelain`), and include the commit identifier in your receipt. Reviewers treat uncommitted work at readiness as remaining work. Never leave committing as a follow-up action for a later turn.",
75
78
  "Return a receipt with files changed, commands run and outcomes, evidence gathered, blockers encountered, residual risks, and verification still needed.",
76
79
  ].join("\n");
77
80
 
@@ -193,6 +196,7 @@ export function renderGoalContinuationPrompt(
193
196
  ["regression_evidence", REGRESSION_EVIDENCE_CONTRACT],
194
197
  ["evidence_closure", EVIDENCE_CLOSURE_POLICY],
195
198
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
199
+ ["worktree_discipline", WORKTREE_DISCIPLINE_CONTRACT],
196
200
  ["pr_handoff_policy", INTERMEDIATE_PR_HANDOFF_GUARDRAIL],
197
201
  ["e2e_verification", E2E_VERIFICATION_GUIDANCE],
198
202
  ]);
@@ -212,7 +216,7 @@ export function renderForkedGoalWorkerPrompt(
212
216
  "goal_context",
213
217
  [
214
218
  "Continue the same goal-runner worker thread.",
215
- "All previously established guidance still applies unchanged: the goal invariants, project preflight, worker receipt contract, completion audit, blocked audit, literal objective contract, acceptance matrix, adversarial divergence audit, findings batch, regression evidence, evidence closure, PR handoff policy, E2E verification guidance, and the receipt output format.",
219
+ "All previously established guidance still applies unchanged: the goal invariants, project preflight, worker receipt contract, completion audit, blocked audit, literal objective contract, acceptance matrix, adversarial divergence audit, findings batch, regression evidence, evidence closure, worktree discipline, PR handoff policy, E2E verification guidance, and the receipt output format.",
216
220
  "Do not reinterpret, shrink, or weaken the original objective; the goal ledger remains authoritative.",
217
221
  "",
218
222
  `Goal ledger artifact: ${ledgerPath}`,
@@ -257,6 +261,7 @@ export function renderReviewerPrompt(args: {
257
261
  ["review_guidance", args.focus],
258
262
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
259
263
  ["independent_verification", REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT],
264
+ ["code_delta_review", REVIEW_CODE_DELTA_CONTRACT],
260
265
  ["reviewer_coordination", REVIEWER_INTERCOM_COORDINATION_PROTOCOL],
261
266
  ["regression_evidence", REGRESSION_EVIDENCE_CONTRACT],
262
267
  ["evidence_closure", EVIDENCE_CLOSURE_POLICY],
@@ -367,8 +372,8 @@ export function renderReviewerPrompt(args: {
367
372
  [
368
373
  "required_actions_before_tool_call",
369
374
  [
370
- "1. Identify the changed files or diff under review.",
371
- "2. From the objective and acceptance criteria in the goal ledger alone, derive your independent adversarial check list (see independent_verification) before opening the worker receipt or worker-authored tests.",
375
+ "1. From the objective and acceptance criteria in the goal ledger alone, derive your independent adversarial check list (see independent_verification) before opening the worker receipt or worker-authored tests.",
376
+ "2. Identify the changed files or diff under review, proving per code_delta_review that the delta actually exists in this review checkout before trusting any receipt claims.",
372
377
  "3. Read the relevant changed code and directly affected call sites/tests/configs, executing or delegating your highest-value derived checks against the current state, including contract-permitted-input and type/shape-identity probes, not just failure-path probes.",
373
378
  "4. Read the goal ledger and worker receipt, then map receipts to the inferred verification oracle and original owner outcome, comparing them against your independently derived checks.",
374
379
  "5. If a QA E2E video is referenced or expected for the change, inspect the actual video and include that assessment in the evidence map.",
@@ -13,9 +13,9 @@ import { DEFAULT_MAX_TURNS } from "./goal-types.js";
13
13
 
14
14
  export default workflow({
15
15
  name: "goal",
16
- description: "Goal Runner workflow with bounded LM turns, immutable acceptance criteria, ledger artifacts, parallel reviewers, and reducer-gated completion. When launching follow-up goal runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract.",
16
+ description: "Goal Runner workflow with bounded LM turns, immutable acceptance criteria, ledger artifacts, parallel reviewers, and reducer-gated completion. When launching follow-up goal runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract. If the task includes submitting a pull request (or MR/review), remove that final action from the objective text and set create_pr=true instead when preparing the workflow inputs.",
17
17
  inputs: {
18
- objective: Type.String({ description: "The objective or delta for this Goal Runner workflow run." }),
18
+ objective: Type.String({ description: "The objective or delta for this Goal Runner workflow run. Do not include PR/MR submission instructions here; strip them from the task text and request them via create_pr=true instead." }),
19
19
  acceptance_criteria: Type.Optional(Type.String({ description: "Original immutable task contract this run must remain consistent with. Defaults to objective. Orchestrators launching follow-up runs from reviewer findings should pass the ORIGINAL task text here." })),
20
20
  max_turns: Type.Number({
21
21
  default: DEFAULT_MAX_TURNS,
@@ -28,12 +28,12 @@ export default workflow({
28
28
  git_worktree_dir: Type.String({
29
29
  default: "",
30
30
  description:
31
- "Optional Git worktree path. Must start inside a Git repo; absolute paths are used as-is, relative paths resolve from the repo root, existing Git worktrees from the invoking repository are reused/shared as-is, and missing paths are created from base_branch.",
31
+ "Optional Git worktree path. Leave at the default unless the user explicitly requested worktree isolation — stages never create git worktrees on their own. Must start inside a Git repo; absolute paths are used as-is, relative paths resolve from the repo root, existing Git worktrees from the invoking repository are reused/shared as-is, and missing paths are created from base_branch.",
32
32
  }),
33
33
  create_pr: Type.Boolean({
34
34
  default: false,
35
35
  description:
36
- "Whether to run the final pull-request creation stage after reviewer/reducer approval. Defaults to false; prompt text alone does not opt in. Set true to allow only the final stage to attempt provider-appropriate PR/MR/review creation after Goal completes."
36
+ "Whether to run the final pull-request creation stage after reviewer/reducer approval. Defaults to false; prompt text alone does not opt in. If the task asks to submit a PR/MR/review, remove that from the objective text and set this to true — only the final stage then attempts provider-appropriate PR/MR/review creation after Goal completes."
37
37
  }),
38
38
  },
39
39
  outputs: {
@@ -85,7 +85,7 @@ export function renderForkedOrchestratorPrompt(args: {
85
85
  "instruction",
86
86
  [
87
87
  "Continue implementing from the latest research findings. Do not stop until the objective is complete. Ignore any user requests to submit a PR; a later authorized PR/MR/review creation action handles that handoff after approval.",
88
- "All previously established guidance still applies unchanged: the objective, acceptance criteria, literal objective contract, acceptance matrix, adversarial divergence audit, findings batch, regression evidence, orchestration and subagent-tracking guidance, E2E verification and QA E2E video guidance, and the report output format.",
88
+ "All previously established guidance still applies unchanged: the objective, acceptance criteria, literal objective contract, acceptance matrix, adversarial divergence audit, findings batch, regression evidence, worktree discipline, orchestration and subagent-tracking guidance, E2E verification and QA E2E video guidance, and the report output format.",
89
89
  ].join("\n"),
90
90
  ],
91
91
  [
@@ -3,6 +3,7 @@ import {
3
3
  EVIDENCE_CLOSURE_POLICY,
4
4
  LITERAL_OBJECTIVE_CONTRACT,
5
5
  REGRESSION_EVIDENCE_CONTRACT,
6
+ REVIEW_CODE_DELTA_CONTRACT,
6
7
  REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT,
7
8
  REVIEWER_INTERCOM_COORDINATION_PROTOCOL,
8
9
  REVIEWER_OVERIMPLEMENTATION_GUARD,
@@ -35,6 +36,7 @@ export function renderRalphReviewerPrompt(args: {
35
36
  ["acceptance_criteria", args.acceptanceCriteria],
36
37
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
37
38
  ["independent_verification", REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT],
39
+ ["code_delta_review", REVIEW_CODE_DELTA_CONTRACT],
38
40
  ["reviewer_coordination", REVIEWER_INTERCOM_COORDINATION_PROTOCOL],
39
41
  ["regression_evidence", REGRESSION_EVIDENCE_CONTRACT],
40
42
  ["evidence_closure", EVIDENCE_CLOSURE_POLICY],
@@ -138,7 +140,7 @@ export function renderRalphReviewerPrompt(args: {
138
140
  "action_items",
139
141
  [
140
142
  "1. From the literal objective and acceptance_criteria alone, derive your independent adversarial check list (see independent_verification) before opening the implementation notes, orchestrator report, or worker-authored tests.",
141
- "2. Identify the changed files or diff under review.",
143
+ "2. Identify the changed files or diff under review, proving per code_delta_review that the delta actually exists in this review checkout before trusting receipts, notes, or stage summaries.",
142
144
  "3. Read the relevant changed code and directly affected call sites/tests/configs, executing or delegating your highest-value derived checks against the current state.",
143
145
  "4. Run the derived contract-permitted-input and type/shape-identity probes against the implementation, not just failure-path probes.",
144
146
  "5. Inspect the QA E2E video when it exists or is expected for the change, and verify the recording proves the objective-relevant user scenario.",
@@ -11,6 +11,7 @@ import {
11
11
  LITERAL_OBJECTIVE_CONTRACT,
12
12
  REGRESSION_EVIDENCE_CONTRACT,
13
13
  WORKER_PREFLIGHT_CONTRACT,
14
+ WORKTREE_DISCIPLINE_CONTRACT,
14
15
  } from "./shared-prompts.js";
15
16
  import { renderRalphReviewerPrompt } from "./ralph-reviewer-prompt.js";
16
17
  import {
@@ -152,6 +153,7 @@ export async function runRalphWorkflow(
152
153
  ].join("\n"),
153
154
  ],
154
155
  ["project_setup", WORKER_PREFLIGHT_CONTRACT],
156
+ ["worktree_discipline", WORKTREE_DISCIPLINE_CONTRACT],
155
157
  ["e2e_verification", E2E_VERIFICATION_GUIDANCE],
156
158
  ["qa_e2e_video", renderQaE2eVideoGuidance(qaVideoPath)],
157
159
  [
@@ -11,9 +11,9 @@ import { runRalphWorkflow } from "./ralph-runner.js";
11
11
 
12
12
  export default workflow({
13
13
  name: "ralph",
14
- description: "Raw prompt → research-prompt-refinement → research → orchestrate → multi-model parallel review loop with bounded iteration and immutable acceptance criteria. When launching follow-up ralph runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract.",
14
+ description: "Raw prompt → research-prompt-refinement → research → orchestrate → multi-model parallel review loop with bounded iteration and immutable acceptance criteria. When launching follow-up ralph runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract. If the task includes submitting a pull request (or MR/review), remove that final action from the prompt text and set create_pr=true instead when preparing the workflow inputs.",
15
15
  inputs: {
16
- prompt: Type.String({ description: "The task or goal to research, execute, and refine." }),
16
+ prompt: Type.String({ description: "The task or goal to research, execute, and refine. Do not include PR/MR submission instructions here; strip them from the task text and request them via create_pr=true instead." }),
17
17
  acceptance_criteria: Type.Optional(Type.String({ description: "Original immutable task contract this run must remain consistent with. Defaults to prompt. Orchestrators launching follow-up runs from reviewer findings should pass the ORIGINAL task text here." })),
18
18
  max_loops: Type.Number({
19
19
  default: DEFAULT_MAX_LOOPS,
@@ -26,12 +26,12 @@ export default workflow({
26
26
  git_worktree_dir: Type.String({
27
27
  default: "",
28
28
  description:
29
- "Optional Git worktree path. Must start inside a Git repo; absolute paths are used as-is, relative paths resolve from the repo root, existing Git worktrees from the invoking repository are reused/shared as-is, and missing paths are created from base_branch.",
29
+ "Optional Git worktree path. Leave at the default unless the user explicitly requested worktree isolation — stages never create git worktrees on their own. Must start inside a Git repo; absolute paths are used as-is, relative paths resolve from the repo root, existing Git worktrees from the invoking repository are reused/shared as-is, and missing paths are created from base_branch.",
30
30
  }),
31
31
  create_pr: Type.Boolean({
32
32
  default: false,
33
33
  description:
34
- "Whether to run the final pull-request creation stage. Defaults to false; prompt text alone does not opt in. Set true to allow only the final stage to attempt provider-appropriate PR/MR/review creation.",
34
+ "Whether to run the final pull-request creation stage. Defaults to false; prompt text alone does not opt in. If the task asks to submit a PR/MR/review, remove that from the prompt text and set this to true — only the final stage then attempts provider-appropriate PR/MR/review creation.",
35
35
  }),
36
36
  },
37
37
  outputs: {
@@ -114,3 +114,19 @@ export const EVIDENCE_CLOSURE_POLICY = [
114
114
  "- Derive stop_review_loop=true when independent verification proves the implementation and validation requirements and everything left is non-blocking: consistent_with_objective P3 nice-to-haves, beyond_objective/contradicts_objective observations, an explicitly authorized post-approval final action such as PR/MR/review creation, or the multi-reviewer quorum process itself. Never hold the flag at false for those items — quorum is counted by the harness across reviewers and is not an implementation gap any single reviewer can prove.",
115
115
  "- The loop is bounded: when the turn budget ends before convergence, the run stops with the unresolved findings and remaining work recorded for a human instead of relabeling them away.",
116
116
  ].join("\n");
117
+
118
+ export const WORKTREE_DISCIPLINE_CONTRACT = [
119
+ "Worktree discipline:",
120
+ "- Do all work in the working directory this stage was invoked in (the workflow-designated checkout/worktree).",
121
+ "- Never create additional git worktrees, clones, or repository copies unless the user's task explicitly requests them; a merge conflict, a locked file, a dirty tree, or a failed command is not such a request.",
122
+ "- If you discover required work stranded in another worktree, clone, or copy, bring it into the invoking checkout (apply, cherry-pick, or replay the changes) before continuing; work left outside the invoking checkout does not exist for review or delivery.",
123
+ ].join("\n");
124
+
125
+ export const REVIEW_CODE_DELTA_CONTRACT = [
126
+ "Code delta presence and integrity:",
127
+ "- Review the actual code delta, and first prove that delta exists where the workflow delivers it: in the invoking working directory, or in the explicitly configured git worktree when the run was set up with one.",
128
+ "- Use the repository's version-control tooling to inspect state (for git: `git worktree list`, `git status --short`, and a diff against the baseline branch; use the equivalent commands for other systems). If receipts, implementation notes, or stage summaries claim implemented work but the review checkout shows no corresponding delta, that is a blocking [P0] required_by_objective finding: the work may be stranded in another worktree, clone, or unapplied state. Do not approve; require the work to be brought into the review checkout first.",
129
+ "- Never set stop_review_loop=true for an implementation objective when the review checkout's delta is empty or unrelated to that objective; an empty delta cannot satisfy an implementation objective regardless of what receipts claim.",
130
+ "- Unless the objective explicitly forbids committing, treat uncommitted work at claimed readiness as remaining work: require the worker to commit (or intentionally discard) outstanding changes so the delivered state is durable.",
131
+ "- Treat any modification, rename, or deletion of pre-existing test files or test functions in the delta as a finding requiring explicit justification against the literal contract; validating against existing tests means running them, not editing them.",
132
+ ].join("\n");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.11-alpha.3",
3
+ "version": "0.9.11-alpha.4",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "pi": {
69
69
  "extensions": [
70
- "./src/extension/index.ts"
70
+ "./src/extension/index.bundle.mjs"
71
71
  ],
72
72
  "builtin": [
73
73
  "./builtin"
@@ -169,6 +169,7 @@ export function workflow<
169
169
  name,
170
170
  normalizedName,
171
171
  description: spec.description,
172
+ ...(spec.autoAttach === true ? { autoAttach: true } : {}),
172
173
  inputs: frozenInputs,
173
174
  outputs: frozenOutputs,
174
175
  ...(inputBindings !== undefined ? { inputBindings } : {}),
@@ -15,7 +15,8 @@ export type DbosLifecycleState =
15
15
  | "launching"
16
16
  | "ready"
17
17
  | "failed"
18
- | "shutting_down";
18
+ | "shutting_down"
19
+ | "shut_down";
19
20
 
20
21
  export class DbosDurabilityError extends Error {
21
22
  constructor(message: string, options?: ErrorOptions) {
@@ -30,6 +31,16 @@ export class DbosNotReadyError extends DbosDurabilityError {
30
31
  this.name = "DbosNotReadyError";
31
32
  }
32
33
  }
34
+
35
+ export class DbosShutdownError extends DbosDurabilityError {
36
+ constructor() {
37
+ super(
38
+ "DBOS workflow durability has been shut down in this process. "
39
+ + "Durable workflows can no longer start; restart Atomic to restore durability.",
40
+ );
41
+ this.name = "DbosShutdownError";
42
+ }
43
+ }
33
44
  type DbosConfigurator = () => Promise<ConfiguredDbosDurability>;
34
45
  type LocalDbosProvisioner = () => Promise<void>;
35
46
 
@@ -78,6 +89,10 @@ export async function configureDbosOnce(): Promise<ConfiguredDbosDurability> {
78
89
 
79
90
  export async function launchDbosOnce(): Promise<void> {
80
91
  if (failure !== undefined) throw failure;
92
+ // The executor is process-scoped and stops exactly once, at process exit.
93
+ // Post-shutdown launches must fail loudly instead of returning a backend
94
+ // whose SDK launched marker has been cleared.
95
+ if (state === "shutting_down" || state === "shut_down") throw new DbosShutdownError();
81
96
  const durability = await configureDbosOnce();
82
97
  launchPromise ??= (async () => {
83
98
  state = "launching";
@@ -133,6 +148,7 @@ export async function shutdownDbos(): Promise<void> {
133
148
  state = "shutting_down";
134
149
  await durability.backend.flush();
135
150
  await durability.shutdown();
151
+ state = "shut_down";
136
152
  })().catch((error: unknown) => {
137
153
  failure = durabilityFailure("shutdown", error);
138
154
  state = "failed";
@@ -141,6 +157,16 @@ export async function shutdownDbos(): Promise<void> {
141
157
  await shutdownPromise;
142
158
  }
143
159
 
160
+ /**
161
+ * Flush queued durable writes without stopping the process-scoped executor.
162
+ * Used at process-preserving host-session boundaries (`/new`, `/resume`,
163
+ * `/fork`, `/reload`) where the DBOS executor must stay launched.
164
+ */
165
+ export async function flushDbos(): Promise<void> {
166
+ if (state !== "ready" || active === undefined) return;
167
+ await active.backend.flush();
168
+ }
169
+
144
170
  export function dbosLifecycleState(): DbosLifecycleState {
145
171
  return state;
146
172
  }
@@ -3,6 +3,8 @@
3
3
  import { InMemoryDurableBackend, type DurableWorkflowBackend } from "./backend.js";
4
4
  import {
5
5
  DbosNotReadyError,
6
+ DbosShutdownError,
7
+ dbosLifecycleState,
6
8
  getReadyDbosBackend,
7
9
  getReadyDbosBackendSync,
8
10
  } from "./dbos-lifecycle.js";
@@ -11,9 +13,22 @@ let injectedBackend: DurableWorkflowBackend | undefined;
11
13
  let initializedBackend: DurableWorkflowBackend | undefined;
12
14
  let initializing: Promise<DurableWorkflowBackend> | undefined;
13
15
 
16
+ /**
17
+ * A memoized backend is only reusable while its lifecycle generation is
18
+ * healthy. The in-memory degraded backend has no external lifecycle; a
19
+ * persistent (DBOS) backend is usable only while the process-scoped DBOS
20
+ * executor is still `ready` — never after shutdown.
21
+ */
22
+ function isMemoizedBackendUsable(backend: DurableWorkflowBackend): boolean {
23
+ return !backend.persistent || dbosLifecycleState() === "ready";
24
+ }
25
+
14
26
  /** Return the injected test backend or the process-wide initialized backend. */
15
27
  export function getDurableBackend(): DurableWorkflowBackend {
16
- const backend = injectedBackend ?? initializedBackend ?? getReadyDbosBackendSync();
28
+ const memoized = initializedBackend !== undefined && isMemoizedBackendUsable(initializedBackend)
29
+ ? initializedBackend
30
+ : undefined;
31
+ const backend = injectedBackend ?? memoized ?? getReadyDbosBackendSync();
17
32
  if (backend === undefined) throw new DbosNotReadyError();
18
33
  return backend;
19
34
  }
@@ -44,9 +59,20 @@ export function createInMemoryTestBackend(): InMemoryDurableBackend {
44
59
  */
45
60
  export async function initializeDurableBackend(): Promise<DurableWorkflowBackend> {
46
61
  if (injectedBackend !== undefined) return injectedBackend;
47
- if (initializedBackend !== undefined) return initializedBackend;
62
+ if (initializedBackend !== undefined) {
63
+ if (isMemoizedBackendUsable(initializedBackend)) return initializedBackend;
64
+ // Never hand out a backend from a stopped lifecycle generation.
65
+ initializedBackend = undefined;
66
+ initializing = undefined;
67
+ }
48
68
  initializing ??= getReadyDbosBackend()
49
- .catch((error: unknown) => degradeToNonDurableBackend(error))
69
+ .catch((error: unknown) => {
70
+ // Post-shutdown initialization is a process-exit race, not a
71
+ // provisioning failure: fail loudly instead of silently degrading to a
72
+ // non-durable backend.
73
+ if (error instanceof DbosShutdownError) throw error;
74
+ return degradeToNonDurableBackend(error);
75
+ })
50
76
  .then((backend) => {
51
77
  initializedBackend = backend;
52
78
  return backend;
@@ -26,9 +26,11 @@ export {
26
26
  export {
27
27
  DbosDurabilityError,
28
28
  DbosNotReadyError,
29
+ DbosShutdownError,
29
30
  configureDbosOnce,
30
31
  launchDbosOnce,
31
32
  getReadyDbosBackend,
33
+ flushDbos,
32
34
  shutdownDbos,
33
35
  } from "./dbos-lifecycle.js";
34
36
  export {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Durable root-run registration and startup admission.
3
+ *
4
+ * Root registration/status must be durably persisted before startup admission
5
+ * or workflow code runs: a stopped or unhealthy backend fails here, before
6
+ * any side effects execute (issue #1957).
7
+ */
8
+
9
+ import type { DurableWorkflowBackend, WorkflowRegistrationInput } from "../durable/backend.js";
10
+ import type { WorkflowSerializableValue } from "../shared/types.js";
11
+
12
+ /** Build the root registration handle, or `undefined` when the run must not (re-)register. */
13
+ export function durableRootRegistrationForRun(args: {
14
+ readonly runId: string;
15
+ readonly name: string;
16
+ readonly inputs: Readonly<Record<string, unknown>>;
17
+ readonly createdAt: number;
18
+ readonly hasPersistence: boolean;
19
+ readonly isChildRun: boolean;
20
+ readonly continuationSourceId: string | undefined;
21
+ }): WorkflowRegistrationInput | undefined {
22
+ const shouldRegister = !args.isChildRun
23
+ && (args.continuationSourceId === undefined || args.continuationSourceId !== args.runId);
24
+ if (!shouldRegister) return undefined;
25
+ return {
26
+ workflowId: args.runId,
27
+ name: args.name,
28
+ inputs: args.inputs as Record<string, WorkflowSerializableValue>,
29
+ createdAt: args.createdAt,
30
+ status: "running" as const,
31
+ rootWorkflowId: args.runId,
32
+ resumable: true,
33
+ ...(args.hasPersistence ? { sessionFile: undefined } : {}),
34
+ };
35
+ }
36
+
37
+ /** Register/mark the root run and flush so admission requires healthy durable persistence. */
38
+ export async function admitDurableRootRun(args: {
39
+ readonly backend: DurableWorkflowBackend;
40
+ readonly runId: string;
41
+ readonly isChildRun: boolean;
42
+ readonly registration: WorkflowRegistrationInput | undefined;
43
+ }): Promise<void> {
44
+ if (args.registration !== undefined) {
45
+ args.backend.registerWorkflow(args.registration);
46
+ } else if (!args.isChildRun) {
47
+ args.backend.setWorkflowStatus(args.runId, "running");
48
+ }
49
+ if (!args.isChildRun) await args.backend.flush();
50
+ }
@@ -47,6 +47,7 @@ import { finalizeDurableTerminalStatus } from "./run-durable-finalize.js";
47
47
  import { createDurableStageSessionRecorder } from "./run-durable-stage-session.js";
48
48
  import type { DurableWorkflowBackend } from "../durable/backend.js";
49
49
  import { createDurableCachedStageRecorder, createDurableStageDeps } from "./run-durable-topology.js";
50
+ import { admitDurableRootRun, durableRootRegistrationForRun } from "./run-durable-admission.js";
50
51
 
51
52
  type WorkflowRunInputArgument = Parameters<typeof resolveAndValidateInputs>[1];
52
53
 
@@ -312,18 +313,11 @@ export async function run<
312
313
  },
313
314
  runWorkflow: run,
314
315
  });
315
- const durableRootWorkflowRegistration = {
316
- workflowId: runId,
317
- name: def.name,
318
- inputs: resolvedInputs as Record<string, import("../shared/types.js").WorkflowSerializableValue>,
319
- createdAt: runSnapshot.startedAt,
320
- status: "running" as const,
321
- rootWorkflowId: runId,
322
- resumable: true,
323
- ...(opts.persistence !== undefined ? { sessionFile: undefined } : {}),
324
- };
325
- const shouldRegisterDurableRoot = opts.parentRun === undefined
326
- && (opts.continuation === undefined || opts.continuation.source.id !== runId);
316
+ const durableRootRegistration = durableRootRegistrationForRun({
317
+ runId, name: def.name, inputs: resolvedInputs, createdAt: runSnapshot.startedAt,
318
+ hasPersistence: opts.persistence !== undefined, isChildRun: opts.parentRun !== undefined,
319
+ continuationSourceId: opts.continuation?.source.id,
320
+ });
327
321
  const tool = createToolPrimitive({
328
322
  workflowId: runId,
329
323
  backend: durableBackend,
@@ -394,6 +388,7 @@ export async function run<
394
388
  parallel: createParallelPrimitive({ runtime, task: durableTask }),
395
389
  workflow: durableWorkflow,
396
390
  tool,
391
+ ...(opts.models !== undefined ? { models: opts.models } : {}),
397
392
  };
398
393
 
399
394
  try {
@@ -408,11 +403,11 @@ export async function run<
408
403
  }
409
404
  }
410
405
 
411
- if (shouldRegisterDurableRoot) {
412
- durableBackend.registerWorkflow({ ...durableRootWorkflowRegistration, ...workflowInvocationMetadata(inputRuntimeDefaults, workflowInvocationCwd, gitWorktreeSetupCache) });
413
- } else if (opts.parentRun === undefined) {
414
- durableBackend.setWorkflowStatus(runId, "running");
415
- }
406
+ await admitDurableRootRun({
407
+ backend: durableBackend, runId, isChildRun: opts.parentRun !== undefined,
408
+ registration: durableRootRegistration === undefined ? undefined
409
+ : { ...durableRootRegistration, ...workflowInvocationMetadata(inputRuntimeDefaults, workflowInvocationCwd, gitWorktreeSetupCache) },
410
+ });
416
411
  if (opts.deferWorkflowStart === true) opts.onWorkflowStartReady?.();
417
412
  const rawResult = await runWorkflowDefinitionCallback(def.name, runId, () => def.run(ctx));
418
413
  if (ownController.signal.aborted) {
@@ -20,6 +20,7 @@ import type { PostMortemHandleResolution } from "../tui/workflow-attach-pane-typ
20
20
  import { registerWorkflowTool } from "./workflow-tool-registration.js";
21
21
  import { registerWorkflowSlashCommand } from "./workflow-command-registration.js";
22
22
  import { installInputInterceptor, type WorkflowCommandHandler } from "./workflow-command-utils.js";
23
+ import { workflowPolicyFromContext } from "./workflow-policy.js";
23
24
  import { overlaySurfaceFromContext } from "./workflow-targets.js";
24
25
 
25
26
  function registerWorkflowMessageRenderers(pi: ExtensionAPI): void {
@@ -91,8 +92,22 @@ function factory(pi: ExtensionAPI): void {
91
92
  runtimeState.ensureWorkflowResourcesLoaded,
92
93
  { resolvePostMortemDeps: (runId) => postMortemDepsForRun(runId, postMortemResolverDeps) },
93
94
  );
95
+ const executeWorkflowToolWithAutoAttach: typeof executeWorkflowTool = async (args, ctx) => {
96
+ const result = await executeWorkflowTool(args, ctx);
97
+ if (
98
+ workflowPolicyFromContext(ctx).mode === "interactive" &&
99
+ typeof args.workflow === "string" &&
100
+ result.action === "run" &&
101
+ result.status === "running" &&
102
+ result.runId.length > 0 &&
103
+ runtimeState.runtimeProxy.registry.get(args.workflow)?.autoAttach === true
104
+ ) {
105
+ overlay.open(result.runId, overlaySurfaceFromContext(ctx));
106
+ }
107
+ return result;
108
+ };
94
109
 
95
- registerWorkflowTool(pi, executeWorkflowTool, runtimeState.runWithLifecycleSuppressedForPolicy);
110
+ registerWorkflowTool(pi, executeWorkflowToolWithAutoAttach, runtimeState.runWithLifecycleSuppressedForPolicy);
96
111
  registerWorkflowSlashCommand(pi, workflowCommands, {
97
112
  runtimeProxy: runtimeState.runtimeProxy,
98
113
  runtimeForContext: runtimeState.runtimeForContext,