@chrok/braid 0.1.3 → 0.2.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 +153 -20
- package/README.md +231 -190
- package/ROADMAP.md +77 -27
- package/SECURITY.md +4 -4
- package/dist/adapters/openai.js +4 -4
- package/dist/budgets.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/merge-tools.d.ts +1 -1
- package/dist/merge-tools.js +8 -8
- package/dist/runtime.d.ts +4 -2
- package/dist/runtime.js +392 -242
- package/dist/types.d.ts +139 -20
- package/dist/validate.d.ts +7 -1
- package/dist/validate.js +140 -15
- package/dist/workspaces.d.ts +14 -4
- package/dist/workspaces.js +161 -116
- package/docs/benchmark.md +3 -0
- package/docs/compatibility.md +44 -8
- package/docs/examples.md +2 -1
- package/docs/execution-control.md +160 -0
- package/docs/releasing.md +66 -1
- package/docs/repository-settings.md +34 -0
- package/docs/resource-limits.md +13 -5
- package/examples/execution-control.ts +38 -0
- package/examples/failure-handling.ts +2 -2
- package/package.json +10 -4
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Execution control
|
|
2
|
+
|
|
3
|
+
Braid 0.2 separates mutable node definitions from execution instances. This is a
|
|
4
|
+
breaking change from 0.1, covering bounded loops (#30) and live graph updates (#31).
|
|
5
|
+
|
|
6
|
+
## Definitions and instances
|
|
7
|
+
|
|
8
|
+
A node is a reusable definition identified by `id`. An execution has a unique
|
|
9
|
+
`executionId`, a captured node definition, graph `revision`, predecessor execution
|
|
10
|
+
IDs, and, within a loop, `loopId` and a one-based `iteration`. Editing or deleting
|
|
11
|
+
a definition never changes or cancels an already admitted execution. Failure,
|
|
12
|
+
notification, pause, model, prompt, and workspace policies are captured at admission.
|
|
13
|
+
|
|
14
|
+
`result.executions` retains every instance, including earlier rounds and deleted
|
|
15
|
+
nodes. `result.nodes` is a convenience projection of the latest instance per node.
|
|
16
|
+
Workspace records are keyed by execution ID. `terminalExecutionIds` identifies
|
|
17
|
+
successful instances whose outputs were not consumed by another admitted instance;
|
|
18
|
+
`terminalOutputs` provides their latest outputs keyed by node ID. Failed instances
|
|
19
|
+
remain available separately, even when the overall job completes.
|
|
20
|
+
|
|
21
|
+
## Structured loops
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const graph = {
|
|
25
|
+
goal: "Refine until the review passes",
|
|
26
|
+
nodes: [
|
|
27
|
+
{ type: "execute", id: "edit", prompt: "Implement the feedback." },
|
|
28
|
+
{ type: "decision", id: "review", prompt: "Review the result.",
|
|
29
|
+
choices: ["again", "done"], workspace: "read-only" },
|
|
30
|
+
{ type: "integrate", id: "apply" },
|
|
31
|
+
],
|
|
32
|
+
loops: [{ id: "refinement", entry: "edit", maxIterations: 3 }],
|
|
33
|
+
edges: [
|
|
34
|
+
{ from: "edit", to: "review" },
|
|
35
|
+
{ from: "review", to: "edit", choice: "again", feedback: "refinement" },
|
|
36
|
+
{ from: "review", to: "apply", choice: "done" },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Each loop declares one entry, one decision feedback edge, and a finite positive
|
|
42
|
+
iteration limit. Ignoring feedback and historical references, the graph must be
|
|
43
|
+
acyclic. Loop bodies may fork and join; external edges enter only through the
|
|
44
|
+
entry and leave only through the feedback decision. Nested/overlapping loops and
|
|
45
|
+
arbitrary cycles are rejected. Sequential and independent loops are supported.
|
|
46
|
+
|
|
47
|
+
The first round uses external predecessors or the job's root snapshot. A feedback
|
|
48
|
+
choice creates another round only after all admitted instances in the current
|
|
49
|
+
round have settled and their pause gates have been released. The next entry also
|
|
50
|
+
receives the previous feedback execution. External inputs remain available in
|
|
51
|
+
subsequent rounds. Exits wait for the loop to finish and use the final round.
|
|
52
|
+
Choosing feedback at `maxIterations` fails the job with `LOOP_LIMIT`.
|
|
53
|
+
|
|
54
|
+
Loops do not overlap their own rounds. Live updates can change definitions,
|
|
55
|
+
edges, and loop topology during an iteration; no boundary pause is required.
|
|
56
|
+
Already admitted instances drain normally. Updates do not replay a completed
|
|
57
|
+
activation just because its definition or incoming edges changed. Use a new node
|
|
58
|
+
ID for new work outside a subsequent loop round.
|
|
59
|
+
|
|
60
|
+
## Live updates and gates
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { startBraid } from "@chrok/braid";
|
|
64
|
+
|
|
65
|
+
const run = startBraid({
|
|
66
|
+
goal: "Inspect, then decide the next steps",
|
|
67
|
+
nodes: [{ type: "execute", id: "inspect", prompt: "Find the problem.", pauseAfter: true }],
|
|
68
|
+
edges: [],
|
|
69
|
+
}, { runner });
|
|
70
|
+
|
|
71
|
+
// After execution_paused (or after obtaining a snapshot from your host):
|
|
72
|
+
const state = run.snapshot();
|
|
73
|
+
const executionId = state.pausedExecutionIds[0];
|
|
74
|
+
run.update({
|
|
75
|
+
expectedRevision: state.revision,
|
|
76
|
+
upsertNodes: [{ type: "execute", id: "fix", prompt: "Implement the fix." }],
|
|
77
|
+
addEdges: [{ from: "inspect", to: "fix", executionId }],
|
|
78
|
+
resume: [executionId],
|
|
79
|
+
});
|
|
80
|
+
const result = await run.result;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`startBraid` returns synchronously with `runId`, `result`, `snapshot()`,
|
|
84
|
+
`update(patch)`, `resume(executionIds, expectedRevision)`, and `cancel()`.
|
|
85
|
+
`braid(input, options)` is the convenience API for awaiting the result.
|
|
86
|
+
|
|
87
|
+
Updates require the current `expectedRevision` and commit atomically. A successful
|
|
88
|
+
update increments the graph revision once. A stale revision or invalid candidate
|
|
89
|
+
changes nothing, including pause gates. `upsertNodes` replaces whole definitions;
|
|
90
|
+
`removeNodeIds` also removes incident edges. `addEdges` and `removeEdges` use full
|
|
91
|
+
edge identities, including choice, feedback, and execution ID. `promptTemplates`
|
|
92
|
+
and `loops` replace their respective collections when supplied. Template edits
|
|
93
|
+
are rendered for future admissions; running prompts remain unchanged.
|
|
94
|
+
|
|
95
|
+
Completion routes through the latest graph. If A is running and A→B is replaced
|
|
96
|
+
with A→C, A's completion can admit C. A previously admitted B continues with its
|
|
97
|
+
captured inputs. A historical dependency pins `edge.executionId` and matching
|
|
98
|
+
`from` to a settled execution, even if its definition was removed. Initial
|
|
99
|
+
submissions cannot reference history from another job.
|
|
100
|
+
|
|
101
|
+
`pauseAfter` holds an execution's outgoing scheduling and keeps the job alive,
|
|
102
|
+
even at a leaf. Independent branches continue. Pi sends a pause reminder;
|
|
103
|
+
`notifyOnCompletion` alone only sends a reminder. Resume specific execution IDs,
|
|
104
|
+
either on its own or in the same transaction as a graph update. Removing a paused
|
|
105
|
+
node definition does not discard its gate: explicitly resume or cancel it.
|
|
106
|
+
|
|
107
|
+
A job accepts updates while running or waiting. Once no work or gates remain,
|
|
108
|
+
it enters finalization and rejects further updates. Completed jobs cannot be
|
|
109
|
+
reopened. These are in-memory controls; there is no restart or crash recovery of
|
|
110
|
+
the scheduler itself.
|
|
111
|
+
|
|
112
|
+
## Workspaces and merge operations
|
|
113
|
+
|
|
114
|
+
Every execution gets a new Git worktree, including read-only executions and
|
|
115
|
+
later loop visits. Root worktrees derive from one initial snapshot; successors
|
|
116
|
+
derive from predecessor checkpoints. Read-only is a capability restriction,
|
|
117
|
+
not a live view of the caller. Outside Git, filesystem tools remain read-only.
|
|
118
|
+
|
|
119
|
+
If inputs have identical trees, or one checkpoint contains all the others in its
|
|
120
|
+
ancestry, an ordinary worker can use that snapshot. Independent changed inputs
|
|
121
|
+
require an explicit `merge`; the worker fails with `WORKSPACE_MERGE_REQUIRED`
|
|
122
|
+
instead of silently selecting or combining them. All active predecessors still
|
|
123
|
+
provide text/error context.
|
|
124
|
+
|
|
125
|
+
| Type | Target | Result |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| `merge` | New isolated worktree based on the initial job snapshot | Selected predecessor changes combined into a reusable checkpoint |
|
|
128
|
+
| `integrate` | Invoking source checkout / working branch | Selected changes applied to the caller and captured as a checkpoint |
|
|
129
|
+
|
|
130
|
+
Neither operation applies changes automatically. The runner chooses Git/file
|
|
131
|
+
operations, resolves conflicts, and calls `finish_merge` once with one
|
|
132
|
+
`{ executionId, disposition, reason }` per source. Sources are immutable and
|
|
133
|
+
reusable by multiple consumers. Dispositions are recorded on the target
|
|
134
|
+
workspace; source worktrees remain available until job cleanup. Integration
|
|
135
|
+
captures a `backupRef` first and serializes source-checkout access within the
|
|
136
|
+
process. It must preserve unrelated staged, unstaged, and untracked caller edits.
|
|
137
|
+
|
|
138
|
+
There is no implicit final integration. Each instance is checkpointed before
|
|
139
|
+
successors are released, including partial work on optional failure. Finalization
|
|
140
|
+
archives/removes owned worktrees while retaining checkpoint refs. Cancelled or
|
|
141
|
+
failed integration may leave partial source edits or conflicts for inspection;
|
|
142
|
+
it does not reset the caller's checkout.
|
|
143
|
+
|
|
144
|
+
## Failure and budgets
|
|
145
|
+
|
|
146
|
+
`requireSuccess` defaults to false. Optional failures keep their errors and partial
|
|
147
|
+
artifacts; unconditional successors can recover. A failed decision never activates
|
|
148
|
+
a choice edge. A job can complete with failed optional executions.
|
|
149
|
+
|
|
150
|
+
A captured `requireSuccess: true` failure immediately stops admission, aborts
|
|
151
|
+
running siblings, waits for tracked writes and cleanup, and fails the job with
|
|
152
|
+
`REQUIRED_NODE_FAILED`. Editing the definition's policy later has no effect on
|
|
153
|
+
that instance. Infrastructure, checkpoint/cleanup, cancellation, graph deadline,
|
|
154
|
+
loop limit, and total execution limit failures always fail the job.
|
|
155
|
+
|
|
156
|
+
`maxExecutions` defaults to 1000 and must be a positive safe integer. It bounds
|
|
157
|
+
all materialized execution records, including skipped branches, across all rounds
|
|
158
|
+
and graph updates. `maxIterations` bounds each loop. The graph deadline includes
|
|
159
|
+
waiting at gates and is never reset by an update or resume. Usage sums all
|
|
160
|
+
instances once; Pi only claims job usage on the first terminal job retrieval.
|
package/docs/releasing.md
CHANGED
|
@@ -4,6 +4,34 @@ Core and Pi are separate public npm packages built from one commit. Both use the
|
|
|
4
4
|
same version. The core has no runtime dependencies. Pi declares an exact
|
|
5
5
|
`@chrok/braid` dependency and includes only its own compiled integration code.
|
|
6
6
|
|
|
7
|
+
## Registry and source association
|
|
8
|
+
|
|
9
|
+
The public packages are [@chrok/braid](https://www.npmjs.com/package/@chrok/braid)
|
|
10
|
+
and [@chrok/pi-braid](https://www.npmjs.com/package/@chrok/pi-braid) on npmjs.
|
|
11
|
+
Keep `publishConfig.registry` set to `https://registry.npmjs.org`. Both manifests
|
|
12
|
+
link to `Epsirom/braid`; Pi's `repository.directory` is `integrations/pi`.
|
|
13
|
+
The repository About website and README badges provide the reverse links.
|
|
14
|
+
GitHub Packages is a separate registry and is not a mirror in this release flow.
|
|
15
|
+
|
|
16
|
+
Package descriptions, keywords, source links, and README content are uploaded
|
|
17
|
+
with the package release. A commit on `main` does not update the published npm
|
|
18
|
+
metadata. Keep development-version notices accurate until publication, and link
|
|
19
|
+
users of the current npm release to its tagged documentation.
|
|
20
|
+
|
|
21
|
+
To inspect the public release without npm account credentials:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm view @chrok/braid dist-tags description repository homepage gitHead dist.attestations --json
|
|
25
|
+
npm view @chrok/pi-braid dist-tags description repository homepage gitHead dist.attestations --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For a release, query the exact `@X.Y.Z` versions too. Confirm both `gitHead`
|
|
29
|
+
values match the release commit, the source links point to this repository, and
|
|
30
|
+
provenance identifies this repository's release workflow. The successful
|
|
31
|
+
[Publish runs](https://github.com/Epsirom/braid/actions/workflows/release.yml)
|
|
32
|
+
and public npm attestations provide release evidence; inspecting or changing
|
|
33
|
+
the trusted-publisher account settings requires npm authentication.
|
|
34
|
+
|
|
7
35
|
## Prepare a release
|
|
8
36
|
|
|
9
37
|
Version tags (`v*`) cannot be moved or deleted. New GitHub releases are immutable:
|
|
@@ -18,10 +46,47 @@ corrections require a new version. See [repository settings](repository-settings
|
|
|
18
46
|
licenses, and Pi registration in a temporary consumer outside the checkout.
|
|
19
47
|
A temporary local registry serves the unpublished core tarball; installing
|
|
20
48
|
only the Pi tarball must fetch core transitively through its version dependency.
|
|
21
|
-
3. Update the changelog
|
|
49
|
+
3. Update the changelog, migration guidance, package descriptions/keywords, and
|
|
50
|
+
supported Pi version. Reconcile README/Pi development notices and ROADMAP
|
|
51
|
+
status with the release being prepared; mark publication complete only after
|
|
52
|
+
both packages are available. Commit and review the changes;
|
|
22
53
|
require the CI matrix to pass before tagging that commit `vX.Y.Z`.
|
|
23
54
|
4. Inspect `npm pack --dry-run` and `npm pack --dry-run` from `integrations/pi`.
|
|
24
55
|
`prepack` rebuilds each package. Never publish stale prebuilt output.
|
|
56
|
+
5. Prepare the GitHub release draft using the release-note policy below. After
|
|
57
|
+
publishing, verify both registry versions, `latest` tags, provenance, and a
|
|
58
|
+
clean install before reporting the release complete.
|
|
59
|
+
|
|
60
|
+
## Release notes and contributor credit
|
|
61
|
+
|
|
62
|
+
Every release must describe user-visible features, fixes, and breaking changes
|
|
63
|
+
in both `CHANGELOG.md` and the GitHub release notes. For each item, link the
|
|
64
|
+
implementing pull request and credit its author by GitHub handle, for example:
|
|
65
|
+
`Add graph-local prompt templates ([#33](https://github.com/Epsirom/braid/pull/33)) — @Epsirom.`
|
|
66
|
+
Use the PR author, not the person who merged it. Issue links can add context but
|
|
67
|
+
do not replace PR links. Credit each relevant PR/author when an item combines
|
|
68
|
+
several contributions; credit direct-commit authors with commit links if no PR
|
|
69
|
+
exists. Keep dependency and release maintenance separate from feature summaries.
|
|
70
|
+
|
|
71
|
+
Compare the previous release tag with the exact candidate commit. Inspect the
|
|
72
|
+
merged PRs in that range and their authors; GitHub's generated release notes
|
|
73
|
+
are a useful starting point, not a substitute for checking the actual changes.
|
|
74
|
+
Keep the changelog and GitHub notes consistent, include migration guidance for
|
|
75
|
+
breaking changes, and link the full tag-to-tag comparison.
|
|
76
|
+
|
|
77
|
+
Mark a human author's first contribution to this repository with
|
|
78
|
+
`**First-time contributor**` next to their credit. Also add a **New Contributors**
|
|
79
|
+
section that thanks them and links their first included PR (or direct commit).
|
|
80
|
+
Verify this against all earlier merged PRs and commit history through the
|
|
81
|
+
previous release, not just the latest release notes. An existing contributor's
|
|
82
|
+
first PR in this release is not their first repository contribution. Exclude
|
|
83
|
+
bot accounts from newcomer thanks, while retaining their maintenance credits.
|
|
84
|
+
If there are no first-time human contributors, say so in that section; do not
|
|
85
|
+
infer newcomer status from a missing credit in an older release.
|
|
86
|
+
|
|
87
|
+
Before publishing the draft, check that every feature/fix has its implementing
|
|
88
|
+
PR link and author, newcomer labels match the history, and version, date,
|
|
89
|
+
comparison, and migration links refer to the release being published.
|
|
25
90
|
|
|
26
91
|
## First publication
|
|
27
92
|
|
|
@@ -6,6 +6,40 @@ copies of the API payloads; committing a change to them does not apply it to
|
|
|
6
6
|
GitHub automatically. Update the existing ruleset in Settings or through the
|
|
7
7
|
REST API after reviewing a policy change, then verify the live settings.
|
|
8
8
|
|
|
9
|
+
## Repository identity and npm links
|
|
10
|
+
|
|
11
|
+
The GitHub About section describes the current `main` capabilities:
|
|
12
|
+
|
|
13
|
+
> TypeScript runtime for LLM agent graphs with bounded loops, live updates,
|
|
14
|
+
> isolated Git worktrees, and explicit integration. Framework-agnostic core,
|
|
15
|
+
> OpenAI-compatible runner, and Pi extension.
|
|
16
|
+
|
|
17
|
+
The repository website points to
|
|
18
|
+
[@chrok/braid on npm](https://www.npmjs.com/package/@chrok/braid). The root README
|
|
19
|
+
links both published packages and displays their npm version badges. Topics are
|
|
20
|
+
`llm`, `ai-agents`, `agent-runtime`, `agent-orchestration`, `multi-agent`,
|
|
21
|
+
`graph-execution`, `bounded-loops`, `git-worktree`, `parallel-execution`,
|
|
22
|
+
`typescript`, `nodejs`, `openai-compatible`, and `pi-package`.
|
|
23
|
+
The old DAG-only and workflow-engine labels do not describe the 0.2 scope.
|
|
24
|
+
|
|
25
|
+
Both packages publish to `https://registry.npmjs.org`:
|
|
26
|
+
|
|
27
|
+
| Package | Repository location |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| [@chrok/braid](https://www.npmjs.com/package/@chrok/braid) | Repository root |
|
|
30
|
+
| [@chrok/pi-braid](https://www.npmjs.com/package/@chrok/pi-braid) | `integrations/pi` |
|
|
31
|
+
|
|
32
|
+
Each manifest declares `repository`, `homepage`, and `bugs`; Pi additionally sets
|
|
33
|
+
`repository.directory`. These fields link npm pages back to the correct source
|
|
34
|
+
and issue tracker. Descriptions and keywords take effect on npm when a new
|
|
35
|
+
version is published; editing `main` does not change existing registry versions.
|
|
36
|
+
|
|
37
|
+
GitHub's **Packages** section represents its separate registry. It does not list
|
|
38
|
+
an npmjs package just because that package points to this repository. The project
|
|
39
|
+
uses npmjs only; no GitHub Packages mirror or additional package scope is
|
|
40
|
+
maintained. See [GitHub's npm registry guide](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)
|
|
41
|
+
and the [release guide](releasing.md#registry-and-source-association).
|
|
42
|
+
|
|
9
43
|
## Main branch
|
|
10
44
|
|
|
11
45
|
[Protect main](https://github.com/Epsirom/braid/rules/24072177) applies to `main`,
|
package/docs/resource-limits.md
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
# Resource limits and deployment responsibility
|
|
2
2
|
|
|
3
3
|
Braid targets small reasoning graphs. Graph size, prompt/output size, event-log
|
|
4
|
-
size, and the number of simultaneous graph submissions have no hard cap
|
|
4
|
+
size, and the number of simultaneous graph submissions have no hard cap. Total materialized executions, including skipped branches,
|
|
5
|
+
are capped at 1000 by default; loops also require a finite iteration limit.
|
|
5
6
|
The scheduler rescans the graph as work settles. Consult the
|
|
6
7
|
[benchmark](benchmark.md) for measured local overhead rather than treating the
|
|
7
8
|
validator's deep-graph tests as a production capacity guarantee.
|
|
8
9
|
|
|
10
|
+
Submission-local prompt templates reduce graph/tool-call argument size, not
|
|
11
|
+
worker context size. Core expands them before execution and checks rendered
|
|
12
|
+
prompts are non-empty. There is no raw or rendered prompt length/token cap;
|
|
13
|
+
hosts imposing their own size budgets must account for expanded prompts as
|
|
14
|
+
well as the compact definition.
|
|
15
|
+
|
|
9
16
|
| Control | Core default | Pi default |
|
|
10
17
|
| --- | --- | --- |
|
|
11
18
|
| Active runtime-managed invocations per graph | 4 | 4 |
|
|
19
|
+
| Materialized executions across loops/updates | 1000 (`maxExecutions`) | 1000 (`maxExecutions`) |
|
|
12
20
|
| Node timeout (starts at admission) | 60 seconds | Unlimited |
|
|
13
|
-
| Graph timeout (includes queueing) | 5 minutes | Unlimited |
|
|
21
|
+
| Graph timeout (includes queueing and paused gates) | 5 minutes | Unlimited |
|
|
14
22
|
| Caller cancellation | `AbortSignal` | `braid_cancel` / panel `c` |
|
|
15
23
|
| Worker tool loop | Decision continuation / merge Git-tool loop, bounded by deadlines | Unlimited by default; optional `maxToolRounds` and `maxToolCalls` |
|
|
16
24
|
| Result preview | Full result | 50 KB / 2,000 lines, then temporary full-result file |
|
|
@@ -43,9 +51,9 @@ after exporting needed results. Reloading also cancels outstanding work.
|
|
|
43
51
|
|
|
44
52
|
Git worktree preparation, checkpointing, tracked writes, and cleanup add disk,
|
|
45
53
|
Git-process, and elapsed-time overhead. Cleanup may extend wall time beyond a
|
|
46
|
-
model deadline.
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
model deadline. Every loop visit creates a new invocation and worktree. Explicit merge/integrate
|
|
55
|
+
nodes count toward the same limits; no final integration call is appended.
|
|
56
|
+
Updates/resumes never reset the graph deadline or execution counter.
|
|
49
57
|
Benchmark results measured outside Git do not include this lifecycle. Recoverable
|
|
50
58
|
refs retain Git objects until removed.
|
|
51
59
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { startBraid, type BraidRun } from "../src/index.js";
|
|
3
|
+
import { inTemporaryDirectory } from "./support.js";
|
|
4
|
+
|
|
5
|
+
await inTemporaryDirectory(async cwd => {
|
|
6
|
+
let run!: BraidRun;
|
|
7
|
+
run = startBraid({
|
|
8
|
+
goal: "Refine a proposal, then add the next step using its exact result.",
|
|
9
|
+
nodes: [
|
|
10
|
+
{ type: "execute", id: "draft", prompt: "Refine the proposal." },
|
|
11
|
+
{ type: "decision", id: "review", prompt: "Review it.", choices: ["again", "done"], pauseAfter: true },
|
|
12
|
+
],
|
|
13
|
+
loops: [{ id: "refinement", entry: "draft", maxIterations: 2 }],
|
|
14
|
+
edges: [{ from: "draft", to: "review" }, { from: "review", to: "draft", choice: "again", feedback: "refinement" }],
|
|
15
|
+
}, {
|
|
16
|
+
cwd,
|
|
17
|
+
runner: async request => {
|
|
18
|
+
request.decide?.(request.execution.iteration === 2 ? "done" : "again");
|
|
19
|
+
return { output: `${request.node.id}: iteration ${request.execution.iteration ?? "outside loop"}` };
|
|
20
|
+
},
|
|
21
|
+
onEvent: event => {
|
|
22
|
+
if (event.type !== "execution_paused") return;
|
|
23
|
+
if (event.iteration === 1) run.resume([event.executionId!], run.snapshot().revision);
|
|
24
|
+
else run.update({
|
|
25
|
+
expectedRevision: run.snapshot().revision,
|
|
26
|
+
upsertNodes: [{ type: "execute", id: "next", prompt: "Summarize the accepted proposal." }],
|
|
27
|
+
addEdges: [{ from: "review", to: "next", executionId: event.executionId! }],
|
|
28
|
+
resume: [event.executionId!],
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const result = await run.result;
|
|
33
|
+
assert.equal(result.status, "completed");
|
|
34
|
+
assert.equal(Object.keys(result.executions).length, 5);
|
|
35
|
+
assert.equal(result.revision, 1);
|
|
36
|
+
console.log(`${Object.keys(result.executions).length} executions; revision ${result.revision}`);
|
|
37
|
+
console.log(result.terminalOutputs.next!.output);
|
|
38
|
+
});
|
|
@@ -20,10 +20,10 @@ const result = await inTemporaryDirectory(cwd => braid({
|
|
|
20
20
|
return { output: "Known local facts are still available." };
|
|
21
21
|
},
|
|
22
22
|
}));
|
|
23
|
-
assert.equal(result.status, "
|
|
23
|
+
assert.equal(result.status, "completed");
|
|
24
24
|
assert.equal(result.nodes.join!.status, "completed");
|
|
25
25
|
assert.match(result.terminalOutputs.join!.output, /remote: unavailable \(MODEL_ERROR\)/);
|
|
26
26
|
// Unconditional successors receive failed predecessors as explicit error context.
|
|
27
|
-
//
|
|
27
|
+
// The graph completes while the optional node failure stays in execution history.
|
|
28
28
|
console.log(`status: ${result.status}; join: ${result.nodes.join!.status}`);
|
|
29
29
|
console.log(result.terminalOutputs.join!.output);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrok/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "TypeScript runtime for LLM agent graphs with bounded loops, live updates, and isolated Git worktrees",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"build:pi": "npm run build --workspace @chrok/pi-braid",
|
|
41
41
|
"test:package": "node scripts/package-smoke.mjs",
|
|
42
42
|
"verify": "npm run check && npm test && npm run check:pi && npm run test:pi && npm run examples && npm run test:package",
|
|
43
|
-
"examples": "tsx examples/basic.ts && tsx examples/code-review.ts && tsx examples/failure-handling.ts && tsx examples/custom-runner.ts",
|
|
43
|
+
"examples": "tsx examples/basic.ts && tsx examples/code-review.ts && tsx examples/failure-handling.ts && tsx examples/custom-runner.ts && tsx examples/execution-control.ts",
|
|
44
44
|
"bench": "npm run build && node --expose-gc scripts/benchmark.mjs",
|
|
45
45
|
"demo:panel": "tsx scripts/panel-demo.ts",
|
|
46
46
|
"test:pi:live": "npm run build:pi && node integrations/pi/test/live/run.mjs"
|
|
@@ -61,9 +61,15 @@
|
|
|
61
61
|
},
|
|
62
62
|
"keywords": [
|
|
63
63
|
"llm",
|
|
64
|
+
"ai-agents",
|
|
64
65
|
"agents",
|
|
65
|
-
"
|
|
66
|
+
"agent-runtime",
|
|
67
|
+
"agent-orchestration",
|
|
68
|
+
"graph",
|
|
69
|
+
"bounded-loops",
|
|
70
|
+
"git-worktree",
|
|
66
71
|
"orchestration",
|
|
72
|
+
"openai-compatible",
|
|
67
73
|
"typescript"
|
|
68
74
|
],
|
|
69
75
|
"publishConfig": {
|