@owlmeans/agent 0.1.18-rc.16 → 0.1.18-rc.17

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 (79) hide show
  1. package/README.md +18 -4
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent/SKILL.md +109 -15
  4. package/build/checkpoint/index.d.ts +2 -0
  5. package/build/checkpoint/index.d.ts.map +1 -0
  6. package/build/checkpoint/index.js +2 -0
  7. package/build/checkpoint/index.js.map +1 -0
  8. package/build/checkpoint/saver.d.ts +7 -0
  9. package/build/checkpoint/saver.d.ts.map +1 -0
  10. package/build/checkpoint/saver.js +135 -0
  11. package/build/checkpoint/saver.js.map +1 -0
  12. package/build/helpers/tools.d.ts +7 -1
  13. package/build/helpers/tools.d.ts.map +1 -1
  14. package/build/helpers/tools.js +10 -1
  15. package/build/helpers/tools.js.map +1 -1
  16. package/build/index.d.ts +2 -1
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +2 -1
  19. package/build/index.js.map +1 -1
  20. package/build/model.d.ts +11 -6
  21. package/build/model.d.ts.map +1 -1
  22. package/build/model.js +35 -20
  23. package/build/model.js.map +1 -1
  24. package/build/pipeline/index.d.ts +3 -0
  25. package/build/pipeline/index.d.ts.map +1 -0
  26. package/build/pipeline/index.js +2 -0
  27. package/build/pipeline/index.js.map +1 -0
  28. package/build/pipeline/runner.d.ts +22 -0
  29. package/build/pipeline/runner.d.ts.map +1 -0
  30. package/build/pipeline/runner.js +566 -0
  31. package/build/pipeline/runner.js.map +1 -0
  32. package/build/pipeline/types.d.ts +190 -0
  33. package/build/pipeline/types.d.ts.map +1 -0
  34. package/build/pipeline/types.js +2 -0
  35. package/build/pipeline/types.js.map +1 -0
  36. package/build/plugins/export.d.ts +2 -0
  37. package/build/plugins/export.d.ts.map +1 -1
  38. package/build/plugins/export.js +1 -0
  39. package/build/plugins/export.js.map +1 -1
  40. package/build/plugins/inquiry.d.ts +36 -0
  41. package/build/plugins/inquiry.d.ts.map +1 -0
  42. package/build/plugins/inquiry.js +126 -0
  43. package/build/plugins/inquiry.js.map +1 -0
  44. package/build/service.d.ts +1 -1
  45. package/build/service.d.ts.map +1 -1
  46. package/build/service.js +30 -0
  47. package/build/service.js.map +1 -1
  48. package/build/stores/memory.d.ts +3 -2
  49. package/build/stores/memory.d.ts.map +1 -1
  50. package/build/stores/memory.js +0 -0
  51. package/build/stores/memory.js.map +1 -1
  52. package/build/stores/types.d.ts +74 -4
  53. package/build/stores/types.d.ts.map +1 -1
  54. package/build/types.d.ts +36 -1
  55. package/build/types.d.ts.map +1 -1
  56. package/package.json +26 -10
  57. package/src/checkpoint/index.ts +1 -0
  58. package/src/checkpoint/saver.ts +178 -0
  59. package/src/helpers/tools.ts +12 -1
  60. package/src/index.ts +2 -1
  61. package/src/model.ts +39 -19
  62. package/src/pipeline/index.ts +2 -0
  63. package/src/pipeline/runner.ts +724 -0
  64. package/src/pipeline/types.ts +202 -0
  65. package/src/plugins/export.ts +3 -0
  66. package/src/plugins/inquiry.ts +169 -0
  67. package/src/service.ts +31 -1
  68. package/src/stores/memory.ts +0 -0
  69. package/src/stores/types.ts +80 -5
  70. package/src/types.ts +40 -1
  71. package/tests/agent.spec.ts +22 -0
  72. package/tests/checkpoint.spec.ts +158 -0
  73. package/tests/inquiry.spec.ts +121 -0
  74. package/tests/pipeline-inquiry.spec.ts +299 -0
  75. package/tests/pipeline-resume.spec.ts +320 -0
  76. package/tests/pipeline.spec.ts +297 -0
  77. package/tests/runtime.spec.ts +80 -41
  78. package/tests/tools.spec.ts +23 -0
  79. package/src/runtime/checkpoint.ts +0 -89
package/README.md CHANGED
@@ -39,6 +39,7 @@ const agent = context.agents().agent({ exec, tools })
39
39
  | `summarizePlugin` | Compacts each finished run into `summary` + `advice`, and replays the last few on the way in |
40
40
  | `memoryGraphPlugin` | Durable notes filed by subsystem, with links — index injected, content pulled by tool |
41
41
  | `memoryEventsPlugin` | A bounded, ordered record of what happened |
42
+ | `inquiryPlugin` | The `ask_user` tool — offered only when a channel to a person is wired |
42
43
 
43
44
  Both memory plugins also export a plain API (`memoryGraph`, `memoryEvents`) usable with no agent at
44
45
  all, so a pipeline helper writes to the same store an agent reads.
@@ -72,9 +73,22 @@ implements. An unbound port is not an error — the plugin that needs it becomes
72
73
  purpose is streamed to the client, so without it the summary of a run types itself out in the
73
74
  user's view of that run, right after it finished.
74
75
 
75
- **No LangGraph checkpointer.** Recoverability lives in the OwlMeans execution and flow layers, which
76
- already own a serializable state model; `makeAgentExecutionPlugin` is the first real implementation
77
- of `@owlmeans/llm`'s `ExecutionPlugin` seam.
76
+ **An agent run is not resumable; a PIPELINE is.** A turn's tool calls are effects already applied
77
+ to the world, so re-entering one re-applies them — `makeAgentModel` therefore keeps no checkpoint,
78
+ and `makeAgentExecutionPlugin` is the first real implementation of `@owlmeans/llm`'s
79
+ `ExecutionPlugin` seam. What resumes is `makePipeline`, a state machine over named steps whose
80
+ position is written to a run row at every boundary; an agent run belongs inside one of its steps.
81
+ The row is the authority on where a run stands — never the LangGraph checkpoint, which is
82
+ size-guarded and expires. A checkpointer is optional and buys replay, never correctness.
83
+
84
+ **A step asks a person with `ctx.ask(inquiry)`, and a run nobody can answer parks `Waiting`.** An
85
+ answer already in the state comes straight back, so a question is never asked twice; that only
86
+ holds if `Inquiry.id` is DERIVED from the step and the thing being decided rather than minted per
87
+ call. Answers are merged by the runner, in `invoke` and in `resume({ answers })`, never by a
88
+ caller's mapping. An agent that installs `inquiryPlugin` must pass
89
+ `fatal: e => isFatalError(e) != null` into its tool invocation: the tool rethrows
90
+ `InquiryUnavailable` alone — a channel that has gone is terminal, and without the predicate the
91
+ loop spends its whole turn budget on it.
78
92
 
79
93
  <!-- owlmeans:agent-guidance:start -->
80
94
  ## Agent guidance
@@ -84,7 +98,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
84
98
  your project's skill store (`.agents/skills/`):
85
99
 
86
100
  ```sh
87
- npx @owlmeans/agent-skills@^0.1.18-rc.12
101
+ npx @owlmeans/agent-skills@^0.1.18-rc.13
88
102
  ```
89
103
 
90
104
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/agent",
4
- "version": "0.1.18-rc.16",
5
- "generatedAt": "2026-09-04T22:43:25.447Z",
4
+ "version": "0.1.18-rc.17",
5
+ "generatedAt": "2026-09-10T18:54:42.500Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agent
3
- description: How to use @owlmeans/agent — context-aware LLM agents and pipelines over the LangGraph functional API, with the AgentPlugin seam, conversation-summarization and memory plugins, storage-independent ports, and the ExecutionPlugin checkpoint implementation. Auto-invoked when importing makeAgentModel, appendAgentsService, an agent plugin, safeInvokeTool, or an agent store.
3
+ description: How to use @owlmeans/agent — context-aware LLM agents over the LangGraph functional API, and resumable PIPELINES over a checkpointed StateGraph, with the AgentPlugin seam, conversation-summarization and memory plugins, and storage-independent ports. Auto-invoked when importing makeAgentModel, makePipeline, makeCheckpointSaver, appendAgentsService, an agent plugin, safeInvokeTool, or an agent store.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/agent
9
9
 
10
10
  **Layer:** Cross-cutting domain
11
- **Install:** `"@owlmeans/agent": "^0.1.18-rc.16"` in `dependencies`, plus the `@langchain/core` and
11
+ **Install:** `"@owlmeans/agent": "^0.1.18-rc.17"` in `dependencies`, plus the `@langchain/core` and
12
12
  `@langchain/langgraph` **peers**
13
13
 
14
14
  The agent runtime. Contracts live in `@owlmeans/agent-common`.
@@ -18,20 +18,58 @@ The agent runtime. Contracts live in `@owlmeans/agent-common`.
18
18
  | Export | Description |
19
19
  |---|---|
20
20
  | `makeAgentModel(options)` | An agent over the LangGraph functional API: `invoke`, `use`, `conversation`. |
21
+ | `makePipeline(spec, options)` | A resumable state machine over a checkpointed LangGraph `StateGraph`: `invoke`, `resume`, `snapshot`, `asStep`. |
22
+ | `makeCheckpointSaver(store)` | A `BaseCheckpointSaver` over the `CheckpointStore` port. |
21
23
  | `makeAgentsService(options?, alias?)` · `appendAgentsService(ctx, options?, alias?)` · `agentServiceApi(options, self)` | The service, and its half without `createService` for composition. |
22
24
  | `summarizePlugin(options?)` | Compacts each finished run into `summary` + `advice`; replays the last few. |
23
25
  | `memoryGraphPlugin(options?)` · `memoryGraph(store, options?)` | Durable notes filed by subsystem, with links. Plugin **and** plain API. |
24
26
  | `memoryEventsPlugin(options?)` · `memoryEvents(store, options?)` | A bounded, ordered record of what happened. |
27
+ | `inquiryPlugin(options?)` · `INQUIRY_PLUGIN` · `ASK_USER_TOOL` | The `ask_user` tool and its one Context paragraph — offered only when a channel is wired. |
25
28
  | `safeInvokeTool`, `toErrorResponse`, `isToolError` | The never-throwing tool contract. |
26
29
  | `composeCompaction`, `composeRollingSummary`, `renderTranscript`, `messageText` | Summary primitives; both composers are total. |
27
- | `makeAgentExecutionPlugin(options)` | The first real implementation of `@owlmeans/llm`'s `ExecutionPlugin`. |
28
30
  | `makeStaticFlowProvider(flows)` | The server-side `FlowProvider` `@owlmeans/flow` does not ship. |
29
31
  | `inProcessTransport()`, `AgentTransport` | The scaling seam; default carries messages by direct call. |
30
- | `createMemory*Store()` | In-memory reference implementations of every port. |
32
+ | `createMemory*Store()` | In-memory reference implementations of every port, including `createMemoryPipelineRunStore` and `createMemoryCheckpointStore`. |
31
33
  | `AgentError` · `AgentMissconfiguredError` · `AgentLoopExhaustedError` | The `ResilientError` family. `AgentMissconfiguredError` is a model or tool set the agent was built without; `AgentLoopExhaustedError` is the tool loop hitting its ceiling. |
32
34
  | `DEFAULT_MAX_TURNS` (64) · `DEFAULT_PLUGIN_ORDER` · `DEFAULT_ACTION` · `DEFAULT_ENTRYPOINT` | The loop and plugin defaults. |
33
35
 
34
- Subpath exports: `./plugins`, `./helpers`, `./stores`.
36
+ Subpath exports: `./plugins`, `./helpers`, `./stores`, `./pipeline`, `./checkpoint`.
37
+
38
+ ## Pipelines
39
+
40
+ A pipeline is a **state machine over named steps** whose position is persisted at every step
41
+ boundary, so a run killed anywhere continues from the step it stopped at rather than from zero.
42
+
43
+ ```ts
44
+ const spec: PipelineSpec = {
45
+ alias: 'vib:story:design', version: 1,
46
+ steps: [
47
+ { step: 'screens' },
48
+ { step: 'ux', after: ['screens'] },
49
+ { step: 'data', after: ['screens'] }, // runs BESIDE `ux`
50
+ { step: 'persist', after: ['ux', 'data'] }, // joins BOTH
51
+ ],
52
+ }
53
+
54
+ const pipeline = makePipeline<State, Deps>(spec, { steps, runs, checkpointer, fatal })
55
+ await pipeline.invoke(seed, { runId, deps, scope: projectId })
56
+ await pipeline.resume(runId, { deps, from: 'data' })
57
+ ```
58
+
59
+ **`StateGraph`, not the functional API.** A resume must be able to name the step it starts at, and
60
+ only a graph has named nodes; `entrypoint`/`task` identifies a task by its positional call ordinal,
61
+ which renumbers on any edit to the function — under a file watcher that is the common case, not the
62
+ corner case. The functional API stays exactly where it is, inside `makeAgentModel`'s ReAct loop.
63
+
64
+ **Edges are `after: string[]`.** Several steps naming one predecessor fan out; one step naming
65
+ several joins them, and the join waits for ALL of them (the array form of `addEdge`, which is a
66
+ barrier — separate single edges would fire on the first). Branching is `skipWhen`, never a
67
+ conditional edge: a branch expressed as an edge is invisible to a resume, while a branch expressed
68
+ as a guard is the same mechanism that makes a resume correct.
69
+
70
+ **The graph is compiled per run and the steps close over their dependencies.** Passing collaborators
71
+ through the engine's config would make a run depend on which config keys a given LangGraph minor
72
+ propagates into a node body. A closure cannot be lost.
35
73
 
36
74
  ## The plugin seam
37
75
 
@@ -101,8 +139,8 @@ a prompt is a request. Both composers (`composeCompaction`, `composeRollingSumma
101
139
  no model, a failing model or an empty answer they fall back deterministically, so a caller can record
102
140
  history unconditionally. A failed fold costs detail, never the event.
103
141
 
104
- **Ports, not resources.** `ConversationStore`, `MemoryGraphStore`, `MemoryEventStore` and
105
- `AgentRunStateStore` are narrow interfaces a consumer implements. A port names exactly what the
142
+ **Ports, not resources.** `ConversationStore`, `MemoryGraphStore`, `MemoryEventStore`,
143
+ `PipelineRunStore` and `CheckpointStore` are narrow interfaces a consumer implements. A port names exactly what the
106
144
  plugin needs, which is a far smaller surface than CRUD, and anything can satisfy it — a `Resource`,
107
145
  or a file on disk, which is what the project-history equivalent is. An unbound port is a no-op, not
108
146
  an error.
@@ -115,13 +153,67 @@ forgetting, which is not a decision one caller has the standing to take. A node
115
153
  the context window on knowledge the run cannot tell apart from what it needs; the agent pulls what it
116
154
  wants by name.
117
155
 
118
- **Checkpoints are size-guarded.** A project-level execution carries the whole project specification;
119
- writing that on every checkpoint is a storage problem that surfaces much later and much worse than a
120
- skipped write.
121
-
122
- **No LangGraph checkpointer, and the `entrypoint` is created inside `invoke()`.** Recoverability
123
- lives in the OwlMeans execution and flow layers, which already own a serializable state model;
124
- adopting a second one would leave two half-truths about where a crashed run stands.
156
+ **The run ROW is the authority on where a run stands — never the LangGraph checkpoint.** The
157
+ checkpoint is size-guarded and expires, so a design that reads a run's position out of it has a
158
+ silent hole exactly where a crashed run needs an answer. Every reader of a position — a resume, a
159
+ reconciler, an operator, a status endpoint — reads the row. With no checkpointer bound at all, a
160
+ resume is still correct; the checkpointer buys replay, never correctness.
161
+
162
+ **Every step is guarded twice.** The runner refuses to re-enter a step its row calls complete, and
163
+ the application's `skipWhen` reads a durable marker the step itself wrote. The first covers a clean
164
+ crash; the second covers a crash BETWEEN the side effect and the row write, which the first cannot
165
+ see. A step that does N undoable things in a loop marks a cursor with `ctx.mark`.
166
+
167
+ **A pipeline state is scalars and KEYS.** Ids, revisions, markers, lists of codes — never an
168
+ artifact. The state is serialized into the row at every step boundary, so a field carrying a
169
+ document writes one copy of it per step. Over `maxStateChars` the step FAILS: refused, never
170
+ truncated, because a resume replaying from a cut-down state replays from a state that never existed.
171
+
172
+ **A step failure is an OUTCOME, not a throw.** `invoke`/`resume` return
173
+ `{ status: Failed, failedAt, error }`, because every caller has a status, a warning or a record to
174
+ write before it decides anything, and a throwing runner puts that in a `catch` where it gets
175
+ forgotten. The one exception is `options.fatal`: those are written to the row Failed FIRST and then
176
+ rethrown, so an exhausted budget cannot become a result a caller carries on from.
177
+
178
+ **No default retry.** `attempts` defaults to 1, because retry budgets in this family MULTIPLY — an
179
+ outer retry around a model's own inner retry is their product. `attempts > 1` on a `nonIdempotent`
180
+ step is refused at build time: an automatic retry is exactly what that flag says must not happen.
181
+
182
+ **`nonIdempotent` marks a step whose side effect cannot be undone** — a wipe, a purge, a claim
183
+ against a rate-limited authority, a model call whose output is already on disk. `resume({ from })`
184
+ refuses to re-enter a completed one without `force`.
185
+
186
+ **A ReAct run is not a resumable unit, and that is why `makeAgentModel` still builds its
187
+ `entrypoint` inside `invoke()`.** Its state is an unbounded message list whose tool results are side
188
+ effects already applied to the world: re-entering a turn re-applies them. What IS resumable is a
189
+ pipeline — and an agent run belongs inside one of its steps.
190
+
191
+ **A step asks with `ctx.ask(inquiry)`, and a run nobody can answer parks `Waiting`.** Three
192
+ outcomes in order: an answer already in the state comes straight back (a question is never asked
193
+ twice); a live `options.inquiry.ask` answer is RECORDED in the state — `stateAnswerOf(capAnswer(…))`
194
+ under `state[INQUIRY_ANSWERS_KEY][id]`, the decision whole and the prose cut — and the FULL answer
195
+ returned to the step; otherwise the run stops `Waiting` with the inquiry on its row, and with no run
196
+ store it throws `PipelineNotResumableError` instead. A throwing channel is not a park: it fails the
197
+ step as an ordinary outcome, because the asking failed rather than the answer being no. `Inquiry.id`
198
+ is the only thing an answer is matched by, so DERIVE it from the step and the thing being decided —
199
+ an id minted per call never matches the state, and the run re-asks and parks forever.
200
+
201
+ **Answers are merged by the RUNNER, in `invoke` and in `resume({ answers })`, never by a mapping.**
202
+ `invoke` builds `{ ...restored, ...seed }`, so a mapping that forwarded the answers map would
203
+ replace the child's own recorded answers wholesale — and write `undefined` over them when the parent
204
+ had none, which re-asks question 1 on every resume. When a composed child parks, `asStep` relays its
205
+ question to the parent's `ctx.ask` and re-invokes the child with the answer (bounded at 8 questions
206
+ per composing step); the parent parks only when its own `ask` parks, and never throws a plain error
207
+ in that path.
208
+
209
+ **An agent that installs `inquiryPlugin` must pass `fatal: e => isFatalError(e) != null`.** The tool
210
+ rethrows `InquiryUnavailable` alone — no channel is an answerable situation, a channel that has GONE
211
+ is terminal — and `safeInvokeTool` contains everything else by default, so without that predicate
212
+ the loop spends its whole turn budget on a dead channel. See [[inquiry]] for the whole primitive.
213
+
214
+ **`safeInvokeTool(tools, call, fatal?)` takes a fatal predicate.** Containment is right for a bad
215
+ argument and wrong for an exhausted budget: a tool may be a whole pipeline behind one call, and
216
+ handing the model a readable "out of tokens" is an invitation to pick another tool and spend again.
125
217
 
126
218
  **`makeStaticFlowProvider` must throw on an unknown flow.** `makeFlowModel` reads a string as a flow
127
219
  name first and only re-reads it as a serialized token once the provider throws — returning null
@@ -136,7 +228,9 @@ boundary — never for an `@owlmeans/*` package.
136
228
 
137
229
  ## Related
138
230
 
231
+ - [[inquiry]] — the human-in-the-loop primitive `ctx.ask`, `Waiting` and `ask_user` belong to
139
232
  - [[agent-common]] — the serializable records and the run lifecycle flow
140
- - [[llm]] — `Execution`, the model contract and `ExecutionPlugin`
233
+ - [[llm]] — `Execution`, the model contract and the `advise`-only `ExecutionPlugin`
234
+ - [[agent-checkpoint]] — the durable Mongo implementation of both storage ports (internal)
141
235
  - [[llm-prompt-caching]] — which block contributed context lands in, and why
142
236
  - [[agent-skills]] — `projectSkillsAgentPlugin` and the `read_skill` tool
@@ -0,0 +1,2 @@
1
+ export * from './saver.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkpoint/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './saver.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checkpoint/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { BaseCheckpointSaver } from '@langchain/langgraph';
2
+ import type { SerializerProtocol } from '@langchain/langgraph-checkpoint';
3
+ import type { CheckpointStore } from '../stores/types.js';
4
+ export declare const makeCheckpointSaver: (store: CheckpointStore, options?: {
5
+ serde?: SerializerProtocol;
6
+ }) => BaseCheckpointSaver;
7
+ //# sourceMappingURL=saver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saver.d.ts","sourceRoot":"","sources":["../../src/checkpoint/saver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,KAAK,EAEqB,kBAAkB,EAClD,MAAM,iCAAiC,CAAA;AAExC,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,oBAAoB,CAAA;AAwKhF,eAAO,MAAM,mBAAmB,UACvB,eAAe,YAAW;IAAE,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAAE,KAC9D,mBAAoE,CAAA"}
@@ -0,0 +1,135 @@
1
+ import { BaseCheckpointSaver } from '@langchain/langgraph';
2
+ import { WRITES_IDX_MAP } from '@langchain/langgraph-checkpoint';
3
+ /**
4
+ * Where each half of LangGraph's checkpoint API comes from, and why it is two imports.
5
+ *
6
+ * The CLASS is imported from `@langchain/langgraph`'s root, because a checkpointer crosses a
7
+ * package boundary and a class with protected members is nominally distinct per installed copy —
8
+ * two copies and `entrypoint({ checkpointer })` stops type-checking, or worse, stops recognising
9
+ * what it was handed. The protocol VALUES (`WRITES_IDX_MAP`, and the types beside it) are not in
10
+ * langgraph's root export list at all, so they come from `@langchain/langgraph-checkpoint` — which
11
+ * is a hard dependency of langgraph, so there is exactly one of it either way.
12
+ */
13
+ const encode = (data) => Buffer.from(data).toString('base64');
14
+ const decode = (data) => new Uint8Array(Buffer.from(data, 'base64'));
15
+ const threadOf = (config) => ({
16
+ thread: String(config.configurable?.thread_id ?? ''),
17
+ ns: String(config.configurable?.checkpoint_ns ?? ''),
18
+ id: config.configurable?.checkpoint_id != null
19
+ ? String(config.configurable.checkpoint_id)
20
+ : undefined,
21
+ });
22
+ const configFor = (thread, ns, id) => ({
23
+ configurable: { thread_id: thread, checkpoint_ns: ns, checkpoint_id: id },
24
+ });
25
+ /**
26
+ * A {@link BaseCheckpointSaver} over the package's storage-agnostic {@link CheckpointStore} port.
27
+ *
28
+ * Everything protocol-shaped lives here; everything storage-shaped lives behind the port. That
29
+ * split is what lets an application bind Mongo, Redis or nothing at all without this file — or
30
+ * LangGraph's version of it — being a consideration.
31
+ *
32
+ * Payloads are base64 of whatever the serde produced. Never `JSON.stringify`: the checkpoint holds
33
+ * `BaseMessage` instances, and a plain-object round trip returns something no `instanceof` in a
34
+ * provider adapter recognises again — a resumed run would silently drop its tool calls.
35
+ */
36
+ class PortCheckpointSaver extends BaseCheckpointSaver {
37
+ store;
38
+ constructor(store, serde) {
39
+ super(serde);
40
+ this.store = store;
41
+ }
42
+ async toTuple(thread, ns, record) {
43
+ const checkpoint = await this.serde.loadsTyped(record.type, decode(record.checkpoint));
44
+ const metadata = await this.serde.loadsTyped(record.type, decode(record.metadata));
45
+ const writes = await this.store.writesFor(thread, ns, [record.checkpointId]);
46
+ const pendingWrites = await Promise.all([...writes].sort((a, b) => a.idx - b.idx).map(async (write) => [
47
+ write.taskId, write.channel, await this.serde.loadsTyped(write.type, decode(write.value)),
48
+ ]));
49
+ return {
50
+ config: configFor(thread, ns, record.checkpointId),
51
+ checkpoint,
52
+ metadata,
53
+ ...(record.parentCheckpointId != null
54
+ ? { parentConfig: configFor(thread, ns, record.parentCheckpointId) }
55
+ : {}),
56
+ pendingWrites,
57
+ };
58
+ }
59
+ async getTuple(config) {
60
+ const { thread, ns, id } = threadOf(config);
61
+ if (thread === '') {
62
+ return undefined;
63
+ }
64
+ const record = await this.store.one(thread, ns, id);
65
+ return record != null ? await this.toTuple(thread, ns, record) : undefined;
66
+ }
67
+ async *list(config, options) {
68
+ const { thread, ns } = threadOf(config);
69
+ if (thread === '') {
70
+ return;
71
+ }
72
+ const before = options?.before?.configurable?.checkpoint_id;
73
+ // `limit` is applied by the STORE, not after decoding: a caller asking for the last three
74
+ // checkpoints must not pay for deserializing a thread's whole history to get them.
75
+ const records = await this.store.page({
76
+ threadId: thread,
77
+ ...(config.configurable?.checkpoint_ns != null ? { ns } : {}),
78
+ ...(before != null ? { beforeId: String(before) } : {}),
79
+ ...(options?.limit != null ? { limit: options.limit } : {}),
80
+ });
81
+ for (const record of records) {
82
+ yield await this.toTuple(thread, record.ns, record);
83
+ }
84
+ }
85
+ async put(config, checkpoint, metadata, _newVersions) {
86
+ const { thread, ns, id } = threadOf(config);
87
+ const [type, serialized] = await this.serde.dumpsTyped(checkpoint);
88
+ const [metadataType, serializedMetadata] = await this.serde.dumpsTyped(metadata);
89
+ if (type !== metadataType) {
90
+ throw new Error(`Mismatched checkpoint serialization types: ${type} vs ${metadataType}`);
91
+ }
92
+ await this.store.put({
93
+ threadId: thread,
94
+ ns,
95
+ checkpointId: checkpoint.id,
96
+ ...(id != null ? { parentCheckpointId: id } : {}),
97
+ type,
98
+ checkpoint: encode(serialized),
99
+ metadata: encode(serializedMetadata),
100
+ createdAt: new Date().toISOString(),
101
+ });
102
+ return configFor(thread, ns, checkpoint.id);
103
+ }
104
+ async putWrites(config, writes, taskId) {
105
+ const { thread, ns, id } = threadOf(config);
106
+ if (id == null) {
107
+ return;
108
+ }
109
+ const taskPath = config.configurable?.checkpoint_task_path;
110
+ const records = await Promise.all(writes.map(async ([channel, value], position) => {
111
+ const [type, serialized] = await this.serde.dumpsTyped(value);
112
+ return {
113
+ threadId: thread,
114
+ ns,
115
+ checkpointId: id,
116
+ taskId,
117
+ // A control channel has a well-known NEGATIVE index; a task's own output keeps its
118
+ // position. The sign is the write rule, and the store reads it: positive is first-wins,
119
+ // so a retried task cannot overwrite the answer its first attempt committed; negative is
120
+ // last-wins, because that is what an error or an interrupt is.
121
+ idx: WRITES_IDX_MAP[channel] ?? position,
122
+ channel,
123
+ type,
124
+ value: encode(serialized),
125
+ ...(taskPath != null ? { taskPath: String(taskPath) } : {}),
126
+ };
127
+ }));
128
+ await this.store.putWrites(records);
129
+ }
130
+ async deleteThread(threadId) {
131
+ await this.store.dropThread(threadId);
132
+ }
133
+ }
134
+ export const makeCheckpointSaver = (store, options = {}) => new PortCheckpointSaver(store, options.serde);
135
+ //# sourceMappingURL=saver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saver.js","sourceRoot":"","sources":["../../src/checkpoint/saver.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,12 @@ export declare const isToolError: (result: unknown) => result is ToolErrorRespon
24
24
  * that is what the model calls — a map keyed by a local variable silently loses any tool whose two
25
25
  * names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
26
26
  * as a fallback so a caller may still address a tool by it.
27
+ *
28
+ * `fatal` is the ONE exception to "never throws", and it exists because containment has a cost the
29
+ * containment itself cannot see. A tool may be a whole pipeline behind a single call; when the
30
+ * thing that stopped it is an exhausted budget or a refusal, handing the model a readable error
31
+ * message is an invitation to pick another tool and spend again. A caller that knows which errors
32
+ * mean "stop" says so here, and those alone travel out.
27
33
  */
28
- export declare const safeInvokeTool: (tools: AgentToolSet, toolCall: ToolCall) => Promise<unknown>;
34
+ export declare const safeInvokeTool: (tools: AgentToolSet, toolCall: ToolCall, fatal?: (e: unknown) => boolean) => Promise<unknown>;
29
35
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AAEpD,eAAO,MAAM,eAAe,MAAO,OAAO,KAAG,iBAC2C,CAAA;AAExF,eAAO,MAAM,WAAW,WAAY,OAAO,KAAG,MAAM,IAAI,iBACW,CAAA;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,UAAiB,YAAY,YAAY,QAAQ,KAAG,OAAO,CAAC,OAAO,CAc7F,CAAA"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAAG,KAAK,EAAE,MAAM,CAAA;CAAE;AAEpD,eAAO,MAAM,eAAe,MAAO,OAAO,KAAG,iBAC2C,CAAA;AAExF,eAAO,MAAM,WAAW,WAAY,OAAO,KAAG,MAAM,IAAI,iBACW,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,cAAc,UAClB,YAAY,YAAY,QAAQ,UAAU,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,KACvE,OAAO,CAAC,OAAO,CAiBjB,CAAA"}
@@ -18,8 +18,14 @@ export const isToolError = (result) => typeof result === 'object' && result != n
18
18
  * that is what the model calls — a map keyed by a local variable silently loses any tool whose two
19
19
  * names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
20
20
  * as a fallback so a caller may still address a tool by it.
21
+ *
22
+ * `fatal` is the ONE exception to "never throws", and it exists because containment has a cost the
23
+ * containment itself cannot see. A tool may be a whole pipeline behind a single call; when the
24
+ * thing that stopped it is an exhausted budget or a refusal, handing the model a readable error
25
+ * message is an invitation to pick another tool and spend again. A caller that knows which errors
26
+ * mean "stop" says so here, and those alone travel out.
21
27
  */
22
- export const safeInvokeTool = async (tools, toolCall) => {
28
+ export const safeInvokeTool = async (tools, toolCall, fatal) => {
23
29
  const tool = tools[toolCall.name]
24
30
  ?? Object.values(tools).find(entry => entry.name === toolCall.name);
25
31
  if (tool == null) {
@@ -29,6 +35,9 @@ export const safeInvokeTool = async (tools, toolCall) => {
29
35
  return await tool.invoke(toolCall.args);
30
36
  }
31
37
  catch (e) {
38
+ if (fatal?.(e) === true) {
39
+ throw e;
40
+ }
32
41
  console.warn(`Error during tool call ${toolCall.name}:`, e);
33
42
  return toErrorResponse(e);
34
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC/D,CAAC,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAExF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAe,EAA+B,EAAE,CAC1E,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,CAAA;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,KAAmB,EAAE,QAAkB,EAAoB,EAAE;IAChG,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;WAC5B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAA;IAErE,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,IAAI,YAAY,CAAC,CAAC,CAAA;IACtE,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,0BAA0B,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;QAC3D,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/helpers/tools.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAU,EAAqB,EAAE,CAC/D,CAAC,EAAE,KAAK,EAAE,2BAA2B,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAExF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAe,EAA+B,EAAE,CAC1E,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,CAAA;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,KAAmB,EAAE,QAAkB,EAAE,KAA+B,EACtD,EAAE;IACpB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;WAC5B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAA;IAErE,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,OAAO,eAAe,CAAC,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,IAAI,YAAY,CAAC,CAAC,CAAA;IACtE,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,CAAC,CAAA;QACT,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,0BAA0B,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;QAC3D,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -5,8 +5,9 @@ export * from './model.js';
5
5
  export * from './service.js';
6
6
  export * from './helpers/index.js';
7
7
  export * from './stores/index.js';
8
+ export * from './pipeline/index.js';
9
+ export * from './checkpoint/index.js';
8
10
  export * from './runtime/provider.js';
9
11
  export * from './runtime/transport.js';
10
- export * from './runtime/checkpoint.js';
11
12
  export * from './plugins/export.js';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA"}
package/build/index.js CHANGED
@@ -4,8 +4,9 @@ export * from './model.js';
4
4
  export * from './service.js';
5
5
  export * from './helpers/index.js';
6
6
  export * from './stores/index.js';
7
+ export * from './pipeline/index.js';
8
+ export * from './checkpoint/index.js';
7
9
  export * from './runtime/provider.js';
8
10
  export * from './runtime/transport.js';
9
- export * from './runtime/checkpoint.js';
10
11
  export * from './plugins/export.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA"}
package/build/model.d.ts CHANGED
@@ -3,13 +3,18 @@ import type { AgentModel, AgentOptions } from './types.js';
3
3
  * An OwlMeans agent over the LangGraph functional API.
4
4
  *
5
5
  * The loop is deliberately the plain one: ask the model, run whatever tools it asked for, feed the
6
- * results back, repeat until it stops asking. No `StateGraph`, and no LangGraph checkpointer — this
7
- * family's recoverability lives in the OwlMeans execution and flow layers, which already own a
8
- * serializable state model, and adopting a second one would leave two half-truths about where a
9
- * crashed run stands.
6
+ * results back, repeat until it stops asking.
10
7
  *
11
- * The `entrypoint` is created INSIDE `invoke()`, so nothing survives a call. What continuity a
12
- * conversation has comes from plugins putting it back into the prompt, not from the graph.
8
+ * The `entrypoint` is created INSIDE `invoke()` and takes no checkpointer, and that is not for want
9
+ * of one — `makePipeline` in this same package is a checkpointed `StateGraph`. **A ReAct run is not
10
+ * a resumable unit.** Its state is an unbounded message list whose tool results are side effects
11
+ * already applied to the world: re-entering a turn re-applies them, and the identity a functional
12
+ * replay would key on is the positional call ordinal, which renumbers on any edit to the loop. What
13
+ * IS resumable is a pipeline, whose steps are named, whose state is scalars, and whose side effects
14
+ * are guarded per step — and an agent run belongs INSIDE one of those steps.
15
+ *
16
+ * So nothing survives a call here. What continuity a conversation has comes from plugins putting it
17
+ * back into the prompt, not from the graph.
13
18
  */
14
19
  export declare const makeAgentModel: (options: AgentOptions) => AgentModel;
15
20
  //# sourceMappingURL=model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,UAAU,EAAE,YAAY,EACzB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,YAAa,YAAY,KAAG,UA8OtD,CAAA"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,UAAU,EAAE,YAAY,EACzB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,YAAa,YAAY,KAAG,UA6PtD,CAAA"}
package/build/model.js CHANGED
@@ -11,13 +11,18 @@ import { safeInvokeTool } from './helpers/tools.js';
11
11
  * An OwlMeans agent over the LangGraph functional API.
12
12
  *
13
13
  * The loop is deliberately the plain one: ask the model, run whatever tools it asked for, feed the
14
- * results back, repeat until it stops asking. No `StateGraph`, and no LangGraph checkpointer — this
15
- * family's recoverability lives in the OwlMeans execution and flow layers, which already own a
16
- * serializable state model, and adopting a second one would leave two half-truths about where a
17
- * crashed run stands.
14
+ * results back, repeat until it stops asking.
18
15
  *
19
- * The `entrypoint` is created INSIDE `invoke()`, so nothing survives a call. What continuity a
20
- * conversation has comes from plugins putting it back into the prompt, not from the graph.
16
+ * The `entrypoint` is created INSIDE `invoke()` and takes no checkpointer, and that is not for want
17
+ * of one — `makePipeline` in this same package is a checkpointed `StateGraph`. **A ReAct run is not
18
+ * a resumable unit.** Its state is an unbounded message list whose tool results are side effects
19
+ * already applied to the world: re-entering a turn re-applies them, and the identity a functional
20
+ * replay would key on is the positional call ordinal, which renumbers on any edit to the loop. What
21
+ * IS resumable is a pipeline, whose steps are named, whose state is scalars, and whose side effects
22
+ * are guarded per step — and an agent run belongs INSIDE one of those steps.
23
+ *
24
+ * So nothing survives a call here. What continuity a conversation has comes from plugins putting it
25
+ * back into the prompt, not from the graph.
21
26
  */
22
27
  export const makeAgentModel = (options) => {
23
28
  const { exec, tools, entrypoint: entrypointName = DEFAULT_ENTRYPOINT, maxTurns = DEFAULT_MAX_TURNS, autoFinish = true, spectate, } = options;
@@ -29,22 +34,32 @@ export const makeAgentModel = (options) => {
29
34
  const prompts = options.prompts ?? exec.prompts;
30
35
  const provider = options.provider ?? pluginFor(agentModel);
31
36
  const purpose = exec.purpose;
32
- const registry = [...(options.plugins ?? [])];
37
+ const registry = [];
33
38
  const ordered = () => [...registry].sort((a, b) => (a.order ?? DEFAULT_PLUGIN_ORDER) - (b.order ?? DEFAULT_PLUGIN_ORDER));
39
+ /**
40
+ * Seat by alias, keeping the original position.
41
+ *
42
+ * Registering the same plugin twice is a wiring accident, and the failure it causes — every
43
+ * context block emitted twice — is silent and expensive rather than loud. The options list goes
44
+ * through this same path, which is the whole point: a service composes its plugins ahead of an
45
+ * agent's own precisely so the agent's can REPLACE one by alias, and a constructor that simply
46
+ * spread the array made "replace" mean "run both".
47
+ */
48
+ const seat = (plugin) => {
49
+ const at = registry.findIndex(entry => entry.alias === plugin.alias);
50
+ if (at < 0) {
51
+ registry.push(plugin);
52
+ }
53
+ else {
54
+ registry[at] = plugin;
55
+ }
56
+ };
57
+ for (const plugin of options.plugins ?? []) {
58
+ seat(plugin);
59
+ }
34
60
  const model = {
35
61
  conversation: () => conversation,
36
- use: plugin => {
37
- // Seat by alias, keeping the original position: registering the same plugin twice is a wiring
38
- // accident, and the failure it would otherwise cause — every context block emitted twice — is
39
- // silent and expensive rather than loud.
40
- const at = registry.findIndex(entry => entry.alias === plugin.alias);
41
- if (at < 0) {
42
- registry.push(plugin);
43
- }
44
- else {
45
- registry[at] = plugin;
46
- }
47
- },
62
+ use: seat,
48
63
  invoke: async (input, args = {}) => {
49
64
  const action = args.action ?? DEFAULT_ACTION;
50
65
  const opening = typeof input === 'string' ? new HumanMessage({ content: input }) : input;
@@ -104,7 +119,7 @@ export const makeAgentModel = (options) => {
104
119
  });
105
120
  // A rejected task aborts the whole superstep, killing every sibling call in the same batch —
106
121
  // `safeInvokeTool` is what keeps a bad argument from costing the work the others finished.
107
- const call = task('call-tool', async (toolCall) => safeInvokeTool(toolSet, toolCall));
122
+ const call = task('call-tool', async (toolCall) => safeInvokeTool(toolSet, toolCall, options.fatal));
108
123
  const agent = entrypoint(entrypointName, async (messages) => {
109
124
  let response = await ask(messages);
110
125
  let turn = 0;