@chrok/braid 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,72 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.0 — 2026-10-04
6
+
7
+ ### Features and breaking changes
8
+
9
+ - Give writable Pi nodes built-in shell tools for dependency installation, builds,
10
+ tests, and repair. Shell calls participate in cancellation and the workspace
11
+ write barrier before checkpointing; prompts describe workspace boundaries and
12
+ shared resources. Read-only nodes still have no shell, and parent extension/MCP
13
+ tools are not inherited
14
+ ([#41](https://github.com/Epsirom/braid/pull/41)) — @Epsirom.
15
+ - Checkpoint tracked changes and non-ignored new files using normal Git staging
16
+ semantics. Ignored dependencies, caches, and build outputs are no longer
17
+ automatically archived or passed to successors; already tracked and deliberately
18
+ force-added files remain tracked
19
+ ([#41](https://github.com/Epsirom/braid/pull/41)) — @Epsirom.
20
+
21
+ See the [0.2 → 0.3 migration guide](docs/compatibility.md#migrating-from-02-to-03)
22
+ before upgrading if you rely on shell-free workers or preservation of ignored
23
+ outputs. Use read-only nodes or a custom runner to restrict worker capabilities,
24
+ and keep successor artifacts in non-ignored or deliberately tracked paths.
25
+
26
+ ### Fixes
27
+
28
+ - Include `changes.baseCommit` in merge-source previews and explain cumulative
29
+ diffs versus invocation snapshots. Read-only review checkpoints can otherwise
30
+ suggest an empty patch; multi-parent checkpoints also need an explicit
31
+ strategy instead of a bare cherry-pick
32
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
33
+ - Display Pi submission and status errors even when the host supplies empty
34
+ result details; failed graph definitions no longer appear as background jobs
35
+ with an undefined ID/status. Validation errors identify offending nodes/edges
36
+ and explain how to declare feedback cycles
37
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
38
+ - Clarify Pi node requirements, loop/template parameters, and revision controls
39
+ while retaining a flat provider-facing node schema with type-specific core
40
+ validation. Restrict historical execution pins to updates. Submission/update
41
+ replies echo accepted graph types and policies
42
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
43
+ - Deliver Pi completion/pause reminders at the next model step using steering,
44
+ after the current tool batch, instead of queuing follow-ups behind the entire
45
+ foreground task. Clarify that pause reminders need a current-state check
46
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
47
+ - Explain atomic update failures and retrying complete patches: new nodes and
48
+ their edges/loops must be submitted together to avoid starting disconnected
49
+ roots while another execution is paused. Discourage repeated replacement jobs
50
+ when a definition problem recurs
51
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
52
+ - Show the requested execution's output/error in focused Pi status reads and
53
+ include the current revision and paused IDs, separately from its captured
54
+ revision. Preserve this rendering for older saved Pi sessions
55
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
56
+ - Keep errors, control fields, and Pi usage ahead of large status payloads;
57
+ save complete running snapshots and include failed-worker provider usage as
58
+ `piUsage` in exported final results
59
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
60
+ - Update live status on graph completion/failure and clear resumable pause IDs
61
+ when finalizing, while retaining pause history in the event log. Loop
62
+ validation now names feedback targets and edges that bypass the loop entry
63
+ ([#42](https://github.com/Epsirom/braid/pull/42)) — @Epsirom.
64
+
65
+ ### New Contributors
66
+
67
+ No first-time human contributors in this release.
68
+
69
+ [Full comparison](https://github.com/Epsirom/braid/compare/v0.2.1...v0.3.0).
70
+
5
71
  ## 0.2.1 — 2026-10-04
6
72
 
7
73
  ### Maintenance
package/README.md CHANGED
@@ -11,20 +11,38 @@ checkpoints; explicit `integrate` nodes apply selected work to the source checko
11
11
  [![npm Pi](https://img.shields.io/npm/v/%40chrok%2Fpi-braid?label=%40chrok%2Fpi-braid)](https://www.npmjs.com/package/@chrok/pi-braid)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
13
 
14
- **0.2 API:** Experimental, Node.js 22+, ESM. The framework-agnostic core
14
+ **0.3 API:** Experimental, Node.js 22+, ESM. The framework-agnostic core
15
15
  has no runtime dependencies; the OpenAI-compatible runner and Pi extension are
16
16
  optional integrations. The npm badges show published versions; see
17
17
  [GitHub releases](https://github.com/Epsirom/braid/releases) for release notes.
18
- Read the [0.1 → 0.2 migration guide](docs/compatibility.md#migrating-from-01-to-02)
19
- before upgrading. For the previous API, use the
20
- [0.1.3 documentation](https://github.com/Epsirom/braid/tree/v0.1.3).
18
+ Read the [0.2 → 0.3 migration guide](docs/compatibility.md#migrating-from-02-to-03)
19
+ before upgrading; users coming from 0.1 also need the
20
+ [0.1 → 0.2 guide](docs/compatibility.md#migrating-from-01-to-02).
21
+ For the previous release, use the
22
+ [0.2.1 documentation](https://github.com/Epsirom/braid/tree/v0.2.1).
21
23
 
22
24
  | Package | Purpose |
23
25
  | --- | --- |
24
26
  | [@chrok/braid](https://www.npmjs.com/package/@chrok/braid) | Core runtime and optional OpenAI-compatible runner |
25
27
  | [@chrok/pi-braid](https://www.npmjs.com/package/@chrok/pi-braid) | Pi background jobs, execution controls, reminders, and live flow panel; installs the matching core dependency |
26
28
 
27
- ## What changed in 0.2?
29
+ ## What changed in 0.3?
30
+
31
+ - **Shell tools in writable Pi nodes.** Workers can install dependencies, build,
32
+ test, and repair in their assigned workspaces. Read-only nodes remain shell-free;
33
+ worktrees use host permissions and are not security sandboxes.
34
+ - **Git-aware checkpoints.** Tracked changes and non-ignored new files are
35
+ checkpointed. Keep artifacts needed by successors in non-ignored or deliberately
36
+ tracked paths; ignored dependencies and build outputs are no longer preserved.
37
+ - **Pi reminders between steps.** Completion and pause reminders arrive after
38
+ the current response and tool batch, before the next model step.
39
+ - **Clearer errors and status.** Invalid graphs show actionable errors; focused
40
+ reads show the selected execution's output/error and current control state.
41
+ Full status exports retain snapshots and failed-worker usage.
42
+
43
+ See the [changelog](CHANGELOG.md#030--2026-10-04) for all fixes and credits.
44
+
45
+ ## Execution-control foundation from 0.2
28
46
 
29
47
  - **Editable graphs, captured executions.** `startBraid` exposes revision-checked
30
48
  updates and pause/resume; `executionId` identifies a particular invocation,
@@ -416,10 +434,11 @@ provides `request.merge.sources` and `request.merge.finish(dispositions)` to
416
434
  merge agents. Adapters must enforce workspace capabilities and wrap mutating
417
435
  file tools in `request.withWorkspaceWrite(operation)`, so cleanup waits for
418
436
  in-flight writes and rejects later writes. Core Git mutations use this barrier.
419
- The included Pi adapter provides guarded `write`/`edit` alongside its read tools.
437
+ The included Pi adapter provides guarded `write`/`edit` and Pi shell tools alongside its read tools.
420
438
  For read-only workspaces, adapters must omit mutating tools; the core write
421
- barrier also rejects writes. This includes all nodes outside Git. Pi never provides
422
- `bash`, `powershell`, or a test runner to nodes.
439
+ barrier also rejects writes. This includes all nodes outside Git. Writable Pi nodes
440
+ can use `bash` (and `powershell` on Windows) to build and run tests. Shell calls
441
+ participate in the write barrier and forward cancellation to their processes.
423
442
 
424
443
  `request.predecessors` contains direct active predecessors in incoming-edge
425
444
  order, including failures on unconditional edges with an `error` field. Each source appears once:
@@ -467,7 +486,10 @@ The model-facing Git tool separates `command` from `args`, for example
467
486
  accepts the complete argument array. Duplicate command prefixes, network Git,
468
487
  branch switching, and filesystem-boundary overrides are rejected.
469
488
 
470
- Instances checkpoint before downstream admission. All source checkpoints remain
489
+ Instances checkpoint tracked changes and non-ignored new files before downstream
490
+ admission. Ignored dependencies, caches, and build products are discarded on cleanup;
491
+ files already tracked or explicitly force-added remain tracked under normal Git rules.
492
+ All source checkpoints remain
471
493
  reusable; no merge consumes or deletes a predecessor's result. Job cleanup
472
494
  archives and removes owned worktrees, retaining refs under
473
495
  `refs/braid/checkpoints/`. Integration additionally captures a pre-write
@@ -489,19 +511,22 @@ isolated from those source edits. See [execution control](docs/execution-control
489
511
 
490
512
  **The adapter is a trust boundary, not a security sandbox.** It must avoid shared
491
513
  conversation state, expose only its declared capabilities, and forward `signal` to its provider.
492
- The core never gives the model arbitrary code execution or a recursive Braid
493
- tool. An optional Pi adapter translates this same contract without changing the
494
- runtime; the core package does not depend on Pi. See
514
+ The core does not supply shell tools or a recursive Braid tool; adapters choose
515
+ their tool capabilities. The Pi adapter supplies shell tools to writable nodes
516
+ without changing the runtime; the core package does not depend on Pi. See
495
517
  [`integrations/pi/README.md`](integrations/pi/README.md) for installation and testing.
496
518
 
497
519
  The included OpenAI-compatible adapter uses fresh Chat Completions contexts,
498
520
  a strict `decide({ choice })` tool and one tool-free continuation for decisions.
499
521
  Merge/integrate nodes use a local `git` / `finish_merge` tool loop; ordinary OpenAI nodes
500
- have no filesystem tools. Pi exposes read and guarded write tools plus these
501
- core Git/merge tools. Tool errors go back to merge agents for recovery. Both
522
+ have no filesystem tools. Pi exposes read, guarded write, and shell tools plus
523
+ these core Git/merge tools. Tool errors go back to nodes for recovery. Both
502
524
  adapters sum usage across their model calls and forward cancellation.
503
525
  Pi writes reject external paths, Git metadata, symlinks, hard links, and special
504
- files. These checks are not an OS sandbox against concurrent filesystem attacks.
526
+ files. Shell tools can bypass these checks and run with host permissions; prompts
527
+ require nodes to respect workspace boundaries, shared Git state, and external
528
+ resources. Parent extension/MCP tools and hooks are not inherited. These checks
529
+ and cooperation rules are not an OS sandbox.
505
530
  See the [Pi filesystem capabilities](integrations/pi/README.md#node-filesystem-capabilities).
506
531
 
507
532
  Pi tool and time budgets are unlimited by default. Its `options.maxToolRounds`
package/ROADMAP.md CHANGED
@@ -6,12 +6,25 @@ and thin host adapters. This is a direction for discussion, not a delivery sched
6
6
 
7
7
  ## Release status
8
8
 
9
- The 0.2 execution-control foundation is implemented. Source availability and npm
10
- availability are separate milestones. Check
9
+ The 0.3 workspace and Pi reliability changes are implemented on top of the 0.2
10
+ execution-control foundation. Source availability and npm availability are
11
+ separate milestones. Check
11
12
  [GitHub releases](https://github.com/Epsirom/braid/releases),
12
13
  [@chrok/braid](https://www.npmjs.com/package/@chrok/braid), and
13
14
  [@chrok/pi-braid](https://www.npmjs.com/package/@chrok/pi-braid) for published versions.
14
15
 
16
+ ## 0.3 implemented changes
17
+
18
+ - [x] Shell tools for writable Pi nodes, with cancellation and checkpoint write
19
+ barriers; keep read-only nodes shell-free.
20
+ - [x] Git-aware checkpoints that preserve tracked changes and non-ignored files.
21
+ - [x] Pi completion/pause reminders between model steps, with actionable graph
22
+ errors and complete focused status reads and exports.
23
+ - [x] Cumulative merge-source baselines and atomic update/retry guidance.
24
+
25
+ Read the [0.2 → 0.3 migration guide](docs/compatibility.md#migrating-from-02-to-03)
26
+ for workspace capabilities and artifact preservation.
27
+
15
28
  ## 0.2 implemented foundation
16
29
 
17
30
  - [x] Separate editable node definitions from captured execution instances,
@@ -41,9 +54,10 @@ provenance, and clean installation before marking publication complete.
41
54
 
42
55
  - Exercise real edit → review → refine → integrate tasks and use the results to
43
56
  improve migration examples, update-conflict diagnostics, and recovery guidance.
44
- - Refresh scheduler measurements for 0.2 before optimizing data structures.
45
- Include execution history, updates, and loops; measure Git workspace costs
46
- separately. The [checked-in benchmark](docs/benchmark.md) is a 0.1 baseline.
57
+ - Refresh scheduler measurements for the current execution model before
58
+ optimizing data structures. Include execution history, updates, and loops;
59
+ measure Git workspace costs separately. The [checked-in benchmark](docs/benchmark.md)
60
+ is a 0.1 baseline.
47
61
  - Explore explicit retention and cleanup policies for execution history, Git
48
62
  recovery refs, and Pi temporary result files without breaking result retrieval,
49
63
  reusable checkpoints, or usage accounting.
@@ -59,7 +73,7 @@ provenance, and clean installation before marking publication complete.
59
73
  consumption, package builds, and the complete Node/platform matrix. Keep Node
60
74
  declarations on 22.x while Node 22 remains the minimum supported runtime.
61
75
 
62
- ## Scope after 0.2
76
+ ## Scope after 0.3
63
77
 
64
78
  The original fixed-DAG-only boundary no longer applies. Bounded loops, live
65
79
  graph changes, parent-controlled pause/resume, and execution history are part of
package/SECURITY.md CHANGED
@@ -44,17 +44,21 @@ answer that question.
44
44
  - Braid isolates invocation context; it is not a process or filesystem sandbox.
45
45
  A custom runner is trusted code with the host process's permissions.
46
46
  - The core manages Git snapshots, worktrees, checkpoint refs, and merge tools.
47
- Pi workers can write/edit their assigned Git worktree. Integrate agents can apply
47
+ Writable Pi workers can write/edit and run shell commands in their assigned Git
48
+ worktree. Shells run with host permissions; prompts constrain workspace writes,
49
+ shared Git state, and external side effects. Integrate agents can apply
48
50
  changes to the source checkout; merge agents use isolated worktrees; they are not restricted to read-only analysis.
49
51
  Outside Git, Pi file tools stay read-only. Read paths can expose files outside
50
52
  the checkout and disclose content to a model provider.
51
53
  - Execute/decision nodes can request `workspace: "read-only"` inside Git. Pi
52
- omits write/edit tools, and core rejects write-barrier operations. Git inspection
54
+ omits write/edit and shell tools, and core rejects write-barrier operations. Git inspection
53
55
  remains available. These nodes read an isolated predecessor
54
56
  snapshot; custom runners must honor this capability themselves.
55
57
  - Guarded write tools reject external paths, Git metadata, symlinks, hard links,
56
58
  and special files, but are not an OS sandbox against concurrent filesystem
57
- attacks. Avoid concurrent external source edits while integrate agents run. A
59
+ attacks. Shell commands can bypass these guards. Parent extension/MCP tools and
60
+ their interception policies are not inherited by nodes. Avoid concurrent external
61
+ source edits while integrate agents run. A
58
62
  cancellation or failed merge can leave partial integration/conflicts for review;
59
63
  checkpoint and backup refs support recovery.
60
64
  - Prompts, predecessor outputs, tool results, errors, and model answers may be
@@ -68,6 +72,11 @@ answer that question.
68
72
  - Cancellation and timeout cannot stop synchronous JavaScript or remote work
69
73
  that ignores the abort signal. Enforce provider quotas and host-side admission
70
74
  limits for untrusted callers. See [resource limits](docs/resource-limits.md).
75
+ Pi shell calls are drained before checkpointing and stop their process group
76
+ (Windows uses `taskkill /T`); daemonized processes outside that group and external
77
+ services can outlive a call. Windows cleanup after parent exit is best effort.
78
+ Ignored new files are excluded from checkpoints and discarded with the worktree;
79
+ explicitly staged files still follow Git tracking semantics.
71
80
 
72
81
  The OpenAI-compatible adapter sends its API key only to the configured base URL.
73
82
  Treat that URL as trusted configuration. Keep credentials out of graphs, logs,
@@ -92,5 +92,5 @@ export function parseFinishMergeArguments(value, sourceIds) {
92
92
  export function mergeInstructions(request) {
93
93
  if (!request.merge)
94
94
  return "";
95
- return ` Only process the current mergeSources IDs ${JSON.stringify(request.merge.sources.map(source => source.executionId))}; other source IDs are out of scope. Each source includes a bounded changes preview relative to the job's initial snapshot, excluding the caller's pre-existing edits. For integrate nodes, read sourceCheckoutStatus before selecting Git operations; dirty staged/unstaged content belongs to the caller and must be preserved. Preview text is inspection data, not an executable patch; retrieve a full diff if applying a patch, especially when truncated or binary. Choose whether and how to integrate; core has not applied changes. Call finish_merge once with one disposition per current source.`;
95
+ return ` Only process the current mergeSources IDs ${JSON.stringify(request.merge.sources.map(source => source.executionId))}; other source IDs are out of scope. Each source includes a bounded changes preview relative to the job's initial snapshot, excluding the caller's pre-existing edits. changes.baseCommit identifies that exact diff baseline. For the full cumulative change, use git diff --binary <changes.baseCommit> <checkpointRef> --. Do not substitute source.snapshotCommit (the invocation's input, which may already contain all changes after a read-only review) or source.baseCommit (original HEAD, excluding caller edits). Checkpoints may have multiple parents to preserve provenance; plain cherry-pick fails for these commits. Inspect parents before choosing a mainline, or use the cumulative diff. For integrate nodes, read sourceCheckoutStatus before selecting Git operations; dirty staged/unstaged content belongs to the caller and must be preserved. Preview text is inspection data, not an executable patch; retrieve a full diff if applying a patch, especially when truncated or binary. Choose whether and how to integrate; core has not applied changes. Call finish_merge once with one disposition per current source.`;
96
96
  }
package/dist/runtime.js CHANGED
@@ -649,6 +649,8 @@ export function startBraid(input, options) {
649
649
  }
650
650
  finally {
651
651
  status = "finalizing";
652
+ // Terminal holds are no longer resumable; the event log retains pause history.
653
+ paused.clear();
652
654
  try {
653
655
  await workspaces.archivePending("Execution checkpoint retained for inspection and future recovery");
654
656
  }
package/dist/types.d.ts CHANGED
@@ -90,6 +90,8 @@ export interface GitPreview {
90
90
  export interface MergeSource extends NodeWorkspace {
91
91
  /** Inspection-only summaries relative to the job’s initial snapshot; omitted by custom runners. */
92
92
  changes?: {
93
+ /** Exact preview/diff baseline, including caller edits; supplied by built-in workspaces. */
94
+ baseCommit?: string;
93
95
  files: string[];
94
96
  filesTruncated: boolean;
95
97
  stat: GitPreview;
package/dist/validate.js CHANGED
@@ -91,13 +91,13 @@ export function compileGraph(input) {
91
91
  const byId = new Map();
92
92
  for (const node of input.nodes) {
93
93
  requireValid(isRecord(node), "Node must be an object");
94
- requireValid(node.type === "execute" || node.type === "decision" || (node.type === "merge" || node.type === "integrate"), "Unknown node type");
94
+ requireValid(text(node.id), "Node id must be a non-empty string");
95
+ requireValid(node.type === "execute" || node.type === "decision" || (node.type === "merge" || node.type === "integrate"), `Node '${node.id}': unknown node type; expected execute, decision, merge, or integrate`);
95
96
  fields(node, node.type === "decision"
96
97
  ? ["type", "id", "prompt", "model", "choices", "workspace", "notifyOnCompletion", "requireSuccess", "pauseAfter"]
97
98
  : node.type === "execute"
98
99
  ? ["type", "id", "prompt", "model", "workspace", "notifyOnCompletion", "requireSuccess", "pauseAfter"]
99
- : ["type", "id", "prompt", "model", "notifyOnCompletion", "requireSuccess", "pauseAfter"], "Node");
100
- requireValid(text(node.id), "Node id must be a non-empty string");
100
+ : ["type", "id", "prompt", "model", "notifyOnCompletion", "requireSuccess", "pauseAfter"], `Node '${node.id}' (${node.type})`);
101
101
  requireValid(!byId.has(node.id), `Duplicate node id '${node.id}'`);
102
102
  const prompt = (node.type === "merge" || node.type === "integrate") && node.prompt === undefined
103
103
  ? (node.type === "merge" ? "Merge selected predecessor results into this new isolated worktree. Resolve conflicts and account for every source with finish_merge." : "Integrate selected predecessor results into the invoking checkout. Preserve user changes and account for every source with finish_merge.")
@@ -139,7 +139,7 @@ export function compileGraph(input) {
139
139
  }
140
140
  for (const edge of input.edges) {
141
141
  requireValid(isRecord(edge), "Edge must be an object");
142
- fields(edge, ["from", "to", "choice", "feedback", "executionId"], "Edge");
142
+ fields(edge, ["from", "to", "choice", "feedback", "executionId"], `Edge '${edge.from}' -> '${edge.to}'`);
143
143
  requireValid(edge.executionId === undefined || text(edge.executionId), "Invalid edge executionId");
144
144
  requireValid(edge.feedback === undefined || text(edge.feedback), "Invalid edge feedback");
145
145
  requireValid(!edge.feedback || !edge.executionId, "Feedback cannot pin an execution");
@@ -179,7 +179,7 @@ export function compileGraph(input) {
179
179
  topologicalOrder.push(byId.get(edge.to));
180
180
  }
181
181
  }
182
- requireValid(topologicalOrder.length === nodes.length, "Graph contains a cycle without a declared feedback edge");
182
+ requireValid(topologicalOrder.length === nodes.length, "Graph contains a cycle without a declared feedback edge. Declare a bounded loop in loops and label its decision-to-entry edge with choice and feedback=loopId");
183
183
  const loops = new Map();
184
184
  const membership = new Map();
185
185
  requireValid(input.loops === undefined || Array.isArray(input.loops), "Graph loops must be an array");
@@ -208,7 +208,7 @@ export function compileGraph(input) {
208
208
  requireValid(feedback.length === 1, `Loop '${loop.id}' needs exactly one feedback edge`);
209
209
  const back = feedback[0];
210
210
  const decision = byId.get(back.from);
211
- requireValid(back.to === loop.entry && decision.type === "decision" && back.choice !== undefined, `Loop '${loop.id}' feedback must route a decision choice to its entry`);
211
+ requireValid(back.to === loop.entry && decision.type === "decision" && back.choice !== undefined, `Loop '${loop.id}' feedback edge '${back.from}' -> '${back.to}' must route a decision choice to its entry '${loop.entry}'. Set feedback on the decision-to-entry edge and include choice`);
212
212
  requireValid(decision.choices.some(choice => choice !== back.choice), `Loop '${loop.id}' needs an exit choice`);
213
213
  const ancestors = reachable(back.from, true);
214
214
  const members = new Set([...reachable(loop.entry)].filter(id => ancestors.has(id)));
@@ -220,8 +220,8 @@ export function compileGraph(input) {
220
220
  for (const edge of edges) {
221
221
  if (edge.feedback || edge.executionId)
222
222
  continue;
223
- requireValid(!(!members.has(edge.from) && members.has(edge.to) && edge.to !== loop.entry), `Loop '${loop.id}' has multiple entries`);
224
- requireValid(!(members.has(edge.from) && !members.has(edge.to) && edge.from !== back.from), `Loop '${loop.id}' must exit through its feedback decision`);
223
+ requireValid(!(!members.has(edge.from) && members.has(edge.to) && edge.to !== loop.entry), `Loop '${loop.id}' has multiple entries: edge '${edge.from}' -> '${edge.to}' bypasses entry '${loop.entry}'. Route external dependencies to '${loop.entry}'`);
224
+ requireValid(!(members.has(edge.from) && !members.has(edge.to) && edge.from !== back.from), `Loop '${loop.id}' must exit through its feedback decision '${back.from}': edge '${edge.from}' -> '${edge.to}' leaves from another node`);
225
225
  }
226
226
  loops.set(loop.id, { id: loop.id, entry: loop.entry, maxIterations: loop.maxIterations, feedback: back, members });
227
227
  }
@@ -22,7 +22,7 @@ export declare class GitWorkspaces {
22
22
  private sealCheckpoint;
23
23
  all(): Record<string, NodeWorkspace>;
24
24
  pending(): string[];
25
- /** Checkpoint every file, including ignored node outputs, before releasing a worktree. */
25
+ /** Preserve tracked changes and non-ignored new files before releasing a worktree. */
26
26
  private checkpoint;
27
27
  private release;
28
28
  /** Only archive/remove here. Choosing merge/cherry-pick/apply always belongs to the agent. */
@@ -180,7 +180,7 @@ export class GitWorkspaces {
180
180
  // A temporary index captures tracked edits/deletions and non-ignored new files
181
181
  // without changing the parent's real index, branch, or working files.
182
182
  await git(sourceRoot, ["add", "--all", "--", "."], options);
183
- // Include ignored files contributed by selected sources, without capturing the
183
+ // Include ignored files deliberately tracked by selected sources, without capturing the
184
184
  // caller's unrelated ignored build products or dependencies.
185
185
  const existing = [];
186
186
  for (const file of extraFiles) {
@@ -345,7 +345,7 @@ export class GitWorkspaces {
345
345
  .filter(workspace => workspace.worktreeRoot && ["preparing", "ready", "failed"].includes(workspace.state))
346
346
  .map(workspace => workspace.executionId ?? workspace.nodeId);
347
347
  }
348
- /** Checkpoint every file, including ignored node outputs, before releasing a worktree. */
348
+ /** Preserve tracked changes and non-ignored new files before releasing a worktree. */
349
349
  async checkpoint(workspace) {
350
350
  if (workspace.checkpointRef)
351
351
  return;
@@ -366,7 +366,7 @@ export class GitWorkspaces {
366
366
  throw error;
367
367
  }
368
368
  }
369
- await git(cwd, ["add", "--force", "--all", "--", "."], options);
369
+ await git(cwd, ["add", "--all", "--", "."], options);
370
370
  const tree = await git(cwd, ["write-tree"], options);
371
371
  const baseTree = await git(cwd, ["rev-parse", `${workspace.snapshotCommit}^{tree}`]);
372
372
  const parents = [...new Set([workspace.snapshotCommit, ...(this.mergeParents.get(workspace.executionId ?? workspace.nodeId) ?? [])])];
@@ -527,7 +527,7 @@ export class GitWorkspaces {
527
527
  const stat = await gitPreview(source.sourceRoot, [...diffArgs.slice(0, -1), "--stat", "--"], Math.floor(4_000 / count));
528
528
  const diff = await gitPreview(source.sourceRoot, diffArgs, Math.min(6_000, Math.floor(24_000 / count)));
529
529
  const names = files.text.slice(0, files.text.lastIndexOf("\0") + 1).split("\0").filter(Boolean);
530
- sources.push({ ...source, changes: { files: names, filesTruncated: files.truncated, stat, diff } });
530
+ sources.push({ ...source, changes: { baseCommit: baseline.snapshotCommit, files: names, filesTruncated: files.truncated, stat, diff } });
531
531
  }
532
532
  const sourceStatus = integrating && target ? await gitPreview(target, ["status", "--porcelain=v1", "--untracked-files=all"], 4_000) : undefined;
533
533
  return {
@@ -6,7 +6,7 @@
6
6
  | Pi package | Node.js 22.19+, Pi 1.0.1 is the pinned validation target |
7
7
  | CI | Core minimum Node 22.0; both packages on Node 22.19 and 24 on Linux, macOS, Windows |
8
8
  | OpenAI-compatible runner | Chat Completions text and function-tool calls; decisions and merge/integrate nodes require tool calling |
9
- | Browsers / CommonJS | No supported browser build or CommonJS entry point in 0.2 |
9
+ | Browsers / CommonJS | No supported browser build or CommonJS entry point in 0.3 |
10
10
 
11
11
  The CI matrix describes configured checks; see actual workflow results for each
12
12
  commit. Offline HTTP fixtures validate the adapter contract. They do not prove
@@ -19,10 +19,10 @@ The wildcard is a loader/distribution convention, **not a claim that every Pi
19
19
  version works**. Test the whole Pi suite before updating the supported target.
20
20
  The offline host compatibility test loads the extension into a real Pi session
21
21
  with an in-memory model provider. It checks worker prompt/tool normalization and
22
- exactly one automatic continuation per node/job reminder delivered during
23
- `agent_settled`, including retrieval of a node output while its job still runs.
24
- This covers the Pi 0.86/0.87 transcript and settling changes without provider
25
- credentials or network model calls. Version 0.1.0 was originally validated with
22
+ reminder delivery between model steps after the current tool batch, idle
23
+ continuation, and acknowledgement, including retrieval of a node output while
24
+ its job still runs. These fixtures run without provider credentials or network
25
+ model calls. Version 0.1.0 was originally validated with
26
26
  Pi 0.85.1; the current checkout's pinned validation target is 1.0.1.
27
27
  The Pi npm package declares an exact dependency on the matching `@chrok/braid`
28
28
  release. npm installs the core automatically; Pi does not bundle another copy of
@@ -32,7 +32,7 @@ Git must be installed for workspace execution inside a Git checkout. Non-Git
32
32
  text-only runs do not require Git workspace management.
33
33
  ## Migrating from 0.1 to 0.2
34
34
 
35
- This release deliberately changes the execution and workspace contracts:
35
+ Version 0.2 deliberately changed the execution and workspace contracts:
36
36
 
37
37
  - Replace old source-checkout `merge` nodes with `integrate`. The new `merge`
38
38
  combines inputs in a fresh isolated worktree.
@@ -71,6 +71,35 @@ separate gate reminder; when both preferences are enabled it supplies the single
71
71
  completion reminder for that instance. Cancellation still sends failure reminders
72
72
  for opted-in running instances.
73
73
 
74
+ ## Migrating from 0.2 to 0.3
75
+
76
+ Version 0.3 changes workspace capabilities and checkpoint contents:
77
+
78
+ - Writable Pi nodes now receive `bash` and, on Windows, `powershell`, allowing
79
+ dependency installation, builds, and tests within a node. If a graph relies on
80
+ workers having no shell, set execute/decision nodes to `workspace: "read-only"`
81
+ (which also disables file writes), or use a custom runner with the required
82
+ capability policy. Writable shell access uses host permissions and prompt-based
83
+ cooperation rules; worktrees are not a security sandbox. Parent extension/MCP
84
+ tools and their hooks are not inherited.
85
+ - Core checkpoints now follow `git add --all` semantics instead of force-adding
86
+ every output. Move artifacts needed by successors or for recovery to non-ignored
87
+ paths, or deliberately track them. Already tracked files, including files
88
+ explicitly staged with `git add --force`, remain tracked even when they match
89
+ ignore rules. Untracked ignored outputs are discarded when worktrees are removed,
90
+ including after failed or cancelled executions. This applies to every adapter.
91
+ - Shell commands must run in the foreground. On POSIX, Braid stops remaining
92
+ children in the command's process group even on normal completion; Windows uses
93
+ best-effort process-tree cleanup. Run a temporary server and its checks within
94
+ one foreground command and clean it up before returning. Detached daemons and
95
+ external services are not contained by this lifecycle.
96
+
97
+ No graph field, result shape, or event schema migration is required. Pi now
98
+ delivers completion/pause reminders after the current assistant response and
99
+ tool batch, before the next model step, rather than waiting for the entire
100
+ foreground task to finish. Fetch current status before updating or resuming a
101
+ paused execution because the reminder describes the state when it was queued.
102
+
74
103
  ## Versioning
75
104
 
76
105
  Core and Pi release together with matching versions. During 0.x, patch releases
@@ -135,6 +135,17 @@ workspace; source worktrees remain available until job cleanup. Integration
135
135
  captures a `backupRef` first and serializes source-checkout access within the
136
136
  process. It must preserve unrelated staged, unstaged, and untracked caller edits.
137
137
 
138
+ Each merge source's `changes.baseCommit` identifies the frozen job snapshot used
139
+ for its cumulative diff preview, including the caller's initial uncommitted
140
+ edits. Retrieve the full patch with
141
+ `git diff --binary <changes.baseCommit> <checkpointRef> --` when choosing patch
142
+ integration. `source.snapshotCommit` is that invocation's input: after a read-only
143
+ review it may equal the checkpoint, yielding an empty diff despite earlier
144
+ changes. `source.baseCommit` is the original HEAD and excludes uncommitted edits.
145
+ Checkpoints can have multiple parents recording merged sources. A bare
146
+ `cherry-pick` then fails; inspect the parents before choosing a mainline, or use
147
+ the cumulative diff. The runtime does not choose or apply a merge strategy.
148
+
138
149
  There is no implicit final integration. Each instance is checkpointed before
139
150
  successors are released, including partial work on optional failure. Finalization
140
151
  archives/removes owned worktrees while retaining checkpoint refs. Cancelled or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrok/braid",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "description": "TypeScript runtime for LLM agent graphs with bounded loops, live updates, and isolated Git worktrees",
6
6
  "type": "module",