@statelyai/agent 2.0.0-alpha.28 → 2.0.0-alpha.30
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/dist/ai-sdk.cjs +150 -27
- package/dist/ai-sdk.d.cts +59 -36
- package/dist/ai-sdk.d.mts +59 -36
- package/dist/ai-sdk.mjs +148 -24
- package/dist/{decision-RQ0iv0oZ.mjs → decision-BHkvuImD.mjs} +74 -8
- package/dist/{decision-CmM8-HHz.cjs → decision-CIb7ddIv.cjs} +113 -30
- package/dist/{text-logic-f5F5sCdc.cjs → event-log-BZRfBRd7.cjs} +814 -7
- package/dist/{text-logic-CIqHAmBi.mjs → event-log-DJCMHek_.mjs} +680 -4
- package/dist/{event-log-store-BIPOz-di.d.mts → event-log-store-CIIJohjj.d.mts} +90 -5
- package/dist/{event-log-store-2xzFmgr2.d.cts → event-log-store-CLAC9q2Y.d.cts} +90 -5
- package/dist/event-log-store-DZR4IIWe.cjs +95 -0
- package/dist/event-log-store-DsP6GCRh.mjs +84 -0
- package/dist/index.cjs +576 -58
- package/dist/index.d.cts +104 -510
- package/dist/index.d.mts +104 -510
- package/dist/index.mjs +547 -32
- package/dist/log.cjs +77 -20
- package/dist/log.d.cts +5 -5
- package/dist/log.d.mts +5 -5
- package/dist/log.mjs +58 -3
- package/dist/machines.cjs +1 -1
- package/dist/machines.d.cts +15 -15
- package/dist/machines.d.mts +15 -15
- package/dist/machines.mjs +1 -1
- package/dist/model-source-BCKg4qJ1.cjs +44 -0
- package/dist/model-source-BaHwJ1wK.mjs +39 -0
- package/dist/openai.cjs +11 -11
- package/dist/openai.d.cts +28 -26
- package/dist/openai.d.mts +28 -26
- package/dist/openai.mjs +3 -3
- package/dist/otel.cjs +5 -4
- package/dist/otel.d.cts +5 -7
- package/dist/otel.d.mts +5 -7
- package/dist/otel.mjs +5 -3
- package/dist/{run-agent-xNTxpt82.mjs → run-agent-BpHqkaZw.mjs} +181 -44
- package/dist/{run-agent-COXXc0RW.d.mts → run-agent-CAVt5R-r.d.mts} +95 -30
- package/dist/{run-agent-ByO-Bxyd.d.cts → run-agent-CTlMc3Jb.d.cts} +95 -30
- package/dist/{run-agent-Dmf8GV2d.cjs → run-agent-ctLCvNgL.cjs} +230 -88
- package/dist/{setup-agent-BuanDXeh.mjs → setup-agent-7rV_cujV.mjs} +15 -9
- package/dist/setup-agent-CGELn1qZ.d.mts +577 -0
- package/dist/setup-agent-CLPCkXsu.d.cts +577 -0
- package/dist/{setup-agent-DV4cTou-.cjs → setup-agent-DoUk7EdC.cjs} +31 -20
- package/dist/tanstack-ai.cjs +627 -0
- package/dist/tanstack-ai.d.cts +204 -0
- package/dist/tanstack-ai.d.mts +204 -0
- package/dist/tanstack-ai.mjs +618 -0
- package/dist/testing.cjs +204 -985
- package/dist/testing.d.cts +132 -347
- package/dist/testing.d.mts +132 -347
- package/dist/testing.mjs +191 -973
- package/dist/{text-logic-IjfYy9DP.d.cts → text-logic-BRYOPWvY.d.mts} +52 -12
- package/dist/{text-logic-D-czVPYp.d.mts → text-logic-DmciuVUQ.d.cts} +52 -12
- package/dist/{types-BgNbNKnJ.d.mts → types-Dp5_Ru4R.d.cts} +2 -2
- package/dist/{types-hR5f5YXa.d.cts → types-Dp5_Ru4R.d.mts} +2 -2
- package/dist/usage-BkM3z02Y.d.mts +17 -0
- package/dist/usage-Cm_ovz9V.d.cts +17 -0
- package/dist/{utils-_q7F-AZu.d.mts → utils-BnXdWih3.d.mts} +1 -2
- package/dist/{utils-o4YRdHJX.d.cts → utils-ebVpCYoF.d.cts} +1 -2
- package/dist/validate.cjs +239 -8
- package/dist/validate.d.cts +110 -3
- package/dist/validate.d.mts +110 -3
- package/dist/validate.mjs +238 -7
- package/dist/verify-COFWyprJ.mjs +1181 -0
- package/dist/verify-CVlsTZ-V.cjs +1222 -0
- package/dist/verify-Cn86rT8l.d.mts +414 -0
- package/dist/verify-Dzwd4Mvk.d.cts +414 -0
- package/package.json +44 -31
- package/readme.md +40 -28
- package/skills/add-evals/SKILL.md +129 -0
- package/skills/add-traces/SKILL.md +135 -0
- package/skills/generate-machine/SKILL.md +143 -69
- package/skills/graph-engineering/SKILL.md +33 -0
- package/skills/improve-machine/SKILL.md +241 -0
- package/skills/improve-machine/reference.md +101 -0
- package/skills/loop-to-machine/SKILL.md +183 -0
- package/skills/loop-to-machine/policy.md +158 -0
- package/dist/event-log-store-2pAFiWuc.mjs +0 -576
- package/dist/event-log-store-C-COhIXP.cjs +0 -702
- package/dist/usage-BdEjFuIi.mjs +0 -5
- package/dist/usage-efOQPdf_.cjs +0 -10
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { u as ChosenEvent } from "./types-Dp5_Ru4R.cjs";
|
|
2
|
+
import { Q as AgentError } from "./text-logic-DmciuVUQ.cjs";
|
|
3
|
+
import { AnyMachineSnapshot, AnyStateMachine } from "xstate";
|
|
4
|
+
//#region src/verify.d.ts
|
|
5
|
+
/** Severity of an {@link AgentLintDiagnostic}. `error` findings fail CI/the CLI; `warning`s are advisory. */
|
|
6
|
+
type AgentLintSeverity = "error" | "warning";
|
|
7
|
+
/**
|
|
8
|
+
* One static-analysis finding from {@link lintAgentMachine}. `code` names the
|
|
9
|
+
* check (stable, machine-readable), `path` points at the offending state path
|
|
10
|
+
* or config location, and `message` explains the problem and its remedy.
|
|
11
|
+
*/
|
|
12
|
+
interface AgentLintDiagnostic {
|
|
13
|
+
code: "decide-without-events" | "direct-object-src" | "invoke-without-on-error";
|
|
14
|
+
severity: AgentLintSeverity;
|
|
15
|
+
/** State path (`parent.child`) or config pointer (e.g. `(root)`, `context`) the finding is about. */
|
|
16
|
+
path: string;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
/** Options for {@link lintAgentMachine}. */
|
|
20
|
+
interface LintAgentMachineOptions {
|
|
21
|
+
/** Skip these check codes entirely. */
|
|
22
|
+
disable?: AgentLintDiagnostic["code"][];
|
|
23
|
+
/**
|
|
24
|
+
* Throw {@link AgentLintError} on failing diagnostics instead of returning
|
|
25
|
+
* them. Fails on error-severity findings; add `warnings: true` to fail on
|
|
26
|
+
* warnings too.
|
|
27
|
+
*/
|
|
28
|
+
throw?: boolean;
|
|
29
|
+
/** With `throw: true`, also fail on warning-severity findings. Default: errors only. */
|
|
30
|
+
warnings?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Runs static structural checks over a built agent machine and returns the
|
|
34
|
+
* findings ({@link AgentLintDiagnostic}[], empty when clean). Works for
|
|
35
|
+
* TS-authored (`setupAgent(...).createMachine(...)`) and
|
|
36
|
+
* `setupAgent.fromConfig(...)`-compiled machines alike, reading `machine.config`
|
|
37
|
+
* plus the schemas/actor sources the library already retains per machine.
|
|
38
|
+
*
|
|
39
|
+
* No model calls, no API keys — a coding agent that emits an agent machine can
|
|
40
|
+
* call this to catch agent-specific mistakes such as undeliverable decisions,
|
|
41
|
+
* un-rebindable request sources, and an unhandled transcript event. General
|
|
42
|
+
* state-machine lint belongs to XState tooling.
|
|
43
|
+
*
|
|
44
|
+
* Pass `{ throw: true }` for the one-liner form used in tests and generation
|
|
45
|
+
* loops: it returns silently when the machine is clean and throws
|
|
46
|
+
* {@link AgentLintError} (findings on `.diagnostics`) on error-severity
|
|
47
|
+
* findings, or on warnings too with `{ throw: true, warnings: true }`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const errors = lintAgentMachine(machine).filter((d) => d.severity === 'error');
|
|
52
|
+
* if (errors.length) throw new Error(errors.map((e) => `${e.path}: ${e.message}`).join('\n'));
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example Throwing form
|
|
56
|
+
* ```ts
|
|
57
|
+
* test('agent machine is structurally sound', () => {
|
|
58
|
+
* lintAgentMachine(machine, { throw: true });
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function lintAgentMachine(machine: AnyStateMachine, options?: LintAgentMachineOptions): AgentLintDiagnostic[];
|
|
63
|
+
/**
|
|
64
|
+
* Thrown by `lintAgentMachine(machine, { throw: true })` (and its
|
|
65
|
+
* {@link assertAgentMachine} alias) when lint finds failing diagnostics.
|
|
66
|
+
* `diagnostics` holds the findings; the message lists them one per finding,
|
|
67
|
+
* so a test runner's failure output reads like the CLI's lint report.
|
|
68
|
+
*/
|
|
69
|
+
declare class AgentLintError extends AgentError {
|
|
70
|
+
readonly diagnostics: AgentLintDiagnostic[];
|
|
71
|
+
constructor(machineId: string, diagnostics: AgentLintDiagnostic[]);
|
|
72
|
+
}
|
|
73
|
+
/** Options for {@link assertAgentMachine}. */
|
|
74
|
+
interface AssertAgentMachineOptions extends LintAgentMachineOptions {
|
|
75
|
+
/** Also fail on warning-severity findings. Default: errors only. */
|
|
76
|
+
warnings?: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Asserts a machine passes {@link lintAgentMachine}: returns silently when
|
|
80
|
+
* clean, throws {@link AgentLintError} (with the findings on `.diagnostics`)
|
|
81
|
+
* otherwise — sugar for `lintAgentMachine(machine, { ...options, throw: true })`.
|
|
82
|
+
* Fails on error-severity findings; set `warnings: true` to fail on warnings
|
|
83
|
+
* too. The one-liner for tests and generation loops:
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* test('agent machine is structurally sound', () => {
|
|
88
|
+
* assertAgentMachine(machine);
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
declare function assertAgentMachine(machine: AnyStateMachine, options?: AssertAgentMachineOptions): void;
|
|
93
|
+
/**
|
|
94
|
+
* Scripted responses for a {@link simulateAgent} playthrough. Each channel is a
|
|
95
|
+
* by-`src` map of FIFO queues, consumed in the order the machine reaches the
|
|
96
|
+
* requests:
|
|
97
|
+
* - `text` — output values for text requests, keyed by request src (the
|
|
98
|
+
* `setupAgent({ requests })` key, or `agent.generateText`/`agent.streamText`).
|
|
99
|
+
* - `decisions` — the {@link ChosenEvent} to apply for a decision request,
|
|
100
|
+
* keyed by decision src (usually `agent.decide`). An invoke whose src is an
|
|
101
|
+
* inline logic object has only an auto-generated src, so its queue may be
|
|
102
|
+
* keyed by the invoke's `id` instead.
|
|
103
|
+
* - `invokes` — output values for scripted invokes (any actor whose output must
|
|
104
|
+
* be canned), keyed by src.
|
|
105
|
+
* - `errors` — failure values to reject a request or invoke with instead of
|
|
106
|
+
* resolving it, keyed by the same srcs the other channels use. An entry here
|
|
107
|
+
* is consumed BEFORE that src's output queue, so a script can fail the first
|
|
108
|
+
* call and let the second succeed. The invoke is rejected with
|
|
109
|
+
* {@link rejectAgentStep}, so the machine takes its `onError`; for
|
|
110
|
+
* `agent.decide` the entry stands in for retry exhaustion. A value can be
|
|
111
|
+
* anything — an `Error`, a string, or an object such as `{ code: 'truncated' }`
|
|
112
|
+
* for a machine that branches on `event.error.code`.
|
|
113
|
+
* - `events` — a flat FIFO queue of external (human/host-sent) events. When the
|
|
114
|
+
* machine settles idle with no pending request or invoke — a human gate — the
|
|
115
|
+
* next queued event is applied, so a simulation can cross states that a live
|
|
116
|
+
* run crosses via `actor.send(...)`. An event the current state cannot take
|
|
117
|
+
* (no handler, or guard-rejected) throws rather than silently vanishing.
|
|
118
|
+
*/
|
|
119
|
+
interface SimulationScript {
|
|
120
|
+
text?: Record<string, unknown[]>;
|
|
121
|
+
decisions?: Record<string, ChosenEvent[]>;
|
|
122
|
+
invokes?: Record<string, unknown[]>;
|
|
123
|
+
errors?: Record<string, unknown[]>;
|
|
124
|
+
events?: ChosenEvent[];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* One entry in a {@link SimulateAgentResult.trail}: the state after this step,
|
|
128
|
+
* plus what drove the step. The first entry is always the machine's initial
|
|
129
|
+
* state, with no `appliedEvent`/`resolvedRequest` — so `trail.map((e) =>
|
|
130
|
+
* e.state)` is the complete state path, directly comparable with
|
|
131
|
+
* `matchesTrajectory` without prepending the initial state by hand.
|
|
132
|
+
*/
|
|
133
|
+
interface SimulationTrailEntry {
|
|
134
|
+
/** The machine state value after applying this step. */
|
|
135
|
+
state: unknown;
|
|
136
|
+
/** The chosen event applied (for a decision request), or the external event applied (when `external`). */
|
|
137
|
+
appliedEvent?: ChosenEvent;
|
|
138
|
+
/** True when `appliedEvent` came from the script's `events` queue (an external/user event), not a decision. */
|
|
139
|
+
external?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Scripted decisions that failed validation (unknown event, invalid payload,
|
|
142
|
+
* or guard-rejected — live-run retry parity, see {@link simulateAgent})
|
|
143
|
+
* before this step settled. Present with an `appliedEvent` when a later
|
|
144
|
+
* scripted attempt succeeded, or alone when every attempt failed and the
|
|
145
|
+
* exhaustion error was routed through the decision invoke's `onError`.
|
|
146
|
+
*/
|
|
147
|
+
rejectedEvents?: ChosenEvent[];
|
|
148
|
+
/**
|
|
149
|
+
* The request this step settled (a text, decision, or scripted invoke): its
|
|
150
|
+
* kind, src, and how it settled. `outcome` is `'output'` when a scripted
|
|
151
|
+
* output resolved it and `'error'` when a `script.errors` entry rejected it,
|
|
152
|
+
* in which case `error` carries the scripted failure value.
|
|
153
|
+
*/
|
|
154
|
+
resolvedRequest?: {
|
|
155
|
+
kind: "text" | "invoke" | "decision";
|
|
156
|
+
src: string;
|
|
157
|
+
id: string;
|
|
158
|
+
outcome: "output" | "error";
|
|
159
|
+
error?: unknown;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** Options for {@link simulateAgent}. */
|
|
163
|
+
interface SimulateAgentOptions {
|
|
164
|
+
input?: unknown;
|
|
165
|
+
script: SimulationScript;
|
|
166
|
+
/** Max steps before returning `'exhausted'`. Default 100. */
|
|
167
|
+
maxSteps?: number;
|
|
168
|
+
}
|
|
169
|
+
/** The outcome of a {@link simulateAgent} playthrough. */
|
|
170
|
+
interface SimulateAgentResult {
|
|
171
|
+
/** `'done'` = reached a final state; `'idle'` = paused with no pending work; `'exhausted'` = hit `maxSteps`. */
|
|
172
|
+
status: "done" | "idle" | "exhausted";
|
|
173
|
+
snapshot: AnyMachineSnapshot;
|
|
174
|
+
trail: SimulationTrailEntry[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Deterministically plays a machine through, resolving each request from a
|
|
178
|
+
* {@link SimulationScript} instead of a model — no API keys, no model calls.
|
|
179
|
+
* Runs on the pure step path ({@link initialAgentStep} etc.), so it exercises
|
|
180
|
+
* the real transition logic. Returns the terminal `status`, final `snapshot`,
|
|
181
|
+
* and a `trail` of every step taken.
|
|
182
|
+
*
|
|
183
|
+
* Decisions run through the live run's own validation/retry core,
|
|
184
|
+
* {@link resolveDecision}, with the script standing in for the model: each
|
|
185
|
+
* attempt consumes the next queued {@link ChosenEvent} for that src, so an
|
|
186
|
+
* unknown, payload-invalid, or guard-rejected event is NOT silently swallowed
|
|
187
|
+
* — the next queued decision is tried, exactly as a live run re-asks the
|
|
188
|
+
* model. The decision logic's `maxRetries` caps attempts as it would live;
|
|
189
|
+
* when retries continue past the end of the queue, the last queued decision
|
|
190
|
+
* repeats (a scripted model that insists). The repeat applies only within one
|
|
191
|
+
* decision request's retries — each new decision request must have its own
|
|
192
|
+
* queued entry, or the dry-script error throws as usual. Exhausting all
|
|
193
|
+
* attempts delivers
|
|
194
|
+
* the resulting {@link AgentDecisionExhaustedError} to the machine as the
|
|
195
|
+
* decision invoke's error (so an `onError` transition observes it, as it
|
|
196
|
+
* would live); with no `onError` to catch it, it is thrown.
|
|
197
|
+
*
|
|
198
|
+
* Throws a descriptive error when the script runs dry mid-request, naming the
|
|
199
|
+
* pending request's kind, src, and id so the missing scripted response is
|
|
200
|
+
* obvious.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* const { status, snapshot } = simulateAgent(machine, {
|
|
205
|
+
* input: { topic: 'state machines' },
|
|
206
|
+
* script: {
|
|
207
|
+
* decisions: { 'agent.decide': [{ type: 'ESCALATE' }] },
|
|
208
|
+
* events: [{ type: 'APPROVE' }], // crosses the human gate
|
|
209
|
+
* },
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
declare function simulateAgent(machine: AnyStateMachine, options: SimulateAgentOptions): Promise<SimulateAgentResult>;
|
|
214
|
+
/** Options for {@link explorePaths}. */
|
|
215
|
+
interface ExplorePathsOptions {
|
|
216
|
+
input?: unknown;
|
|
217
|
+
/** Max branch points (decisions + external-event forks) along any path. Default 8. */
|
|
218
|
+
maxDepth?: number;
|
|
219
|
+
/** Total path cap before exploration stops (reported via `hitPathCap`). Default 200. */
|
|
220
|
+
maxPaths?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Total branch points expanded before exploration stops (reported via
|
|
223
|
+
* `hitPathCap`). Default 100,000. It bounds work and memory where
|
|
224
|
+
* `maxPaths` cannot: a loopy machine expands many nodes per terminal.
|
|
225
|
+
*/
|
|
226
|
+
maxNodes?: number;
|
|
227
|
+
/**
|
|
228
|
+
* Every output to try per src: a text request's src (the
|
|
229
|
+
* `setupAgent({ requests })` key, or `agent.generateText`/`agent.streamText`)
|
|
230
|
+
* or any other invoked actor's src. A one-element list is a canned output,
|
|
231
|
+
* reused every time that src is reached; a text request resolves with it as
|
|
232
|
+
* its `result` (`{ result, messages: [] }`). A src with two or more outputs
|
|
233
|
+
* forks one branch per output (plus the `errors` branch when the src is
|
|
234
|
+
* listed there too), so a machine whose `onDone` branches on the output is
|
|
235
|
+
* explored down every arm. Each fork counts against `maxDepth`, and each
|
|
236
|
+
* forked branch records `{ type: 'xstate.done.actor.<id>', output }` in the
|
|
237
|
+
* path. A src with no entry (and no `errors` entry) halts that branch with a
|
|
238
|
+
* `needs-output` terminal.
|
|
239
|
+
*/
|
|
240
|
+
outputs?: Record<string, unknown[]>;
|
|
241
|
+
/**
|
|
242
|
+
* A canned failure per src, keyed like `outputs`. A src listed here
|
|
243
|
+
* forks an extra branch at that invoke, where the invoke is rejected and the
|
|
244
|
+
* machine takes its `onError` — so states reachable only through a failure
|
|
245
|
+
* are explored. When the src also has an output, both branches are explored;
|
|
246
|
+
* when it has only an error, only the failing one is. Each fork counts
|
|
247
|
+
* against `maxDepth`, and the failing branch records
|
|
248
|
+
* `{ type: 'xstate.error.actor.<id>' }` in the path.
|
|
249
|
+
*
|
|
250
|
+
* A decision invoke can be keyed here too — by its src (usually
|
|
251
|
+
* `agent.decide`) or by its invoke id — standing in for retry exhaustion.
|
|
252
|
+
* Its success branch is the usual per-candidate-event fork, so the failing
|
|
253
|
+
* branch is explored in addition to, not instead of, the candidates.
|
|
254
|
+
*
|
|
255
|
+
* A rejection that reaches no `onError` errors the machine, and that path
|
|
256
|
+
* ends in an `error` terminal.
|
|
257
|
+
*/
|
|
258
|
+
errors?: Record<string, unknown>;
|
|
259
|
+
/**
|
|
260
|
+
* Payloads to try per event type at each branch point, given the settled
|
|
261
|
+
* snapshot there. At a decision or an idle wait, an event type with an
|
|
262
|
+
* entry forks once per payload as `{ ...payload, type }`; guard-rejected
|
|
263
|
+
* variants count in `prunedByGuard`. An entry here overrides the
|
|
264
|
+
* decision request's own `options` (from the decide input's `payloads`)
|
|
265
|
+
* for that type.
|
|
266
|
+
*
|
|
267
|
+
* A type with no entry and no request option is filled from its event
|
|
268
|
+
* schema when that schema exposes JSON Schema (Standard JSON Schema, e.g.
|
|
269
|
+
* Zod 4) and its fields are finite domains (`enum`, `const`, `boolean`,
|
|
270
|
+
* `null`, or a union of those): it forks over the cartesian product, up to
|
|
271
|
+
* 64 combinations, each optional field also omitted. A type with a required
|
|
272
|
+
* non-finite field, or a product over the cap, is applied as `{ type }`; an
|
|
273
|
+
* optional non-finite field is left out. Either way the type's real variants
|
|
274
|
+
* were not all tried, so it is listed in `unexpandedEvents`. A type with no
|
|
275
|
+
* schema at all is applied as `{ type }`.
|
|
276
|
+
*/
|
|
277
|
+
payloads?: (snapshot: AnyMachineSnapshot) => Partial<Record<string, Record<string, unknown>[]>>;
|
|
278
|
+
/**
|
|
279
|
+
* A projection of the snapshot that determines its future. Settled
|
|
280
|
+
* snapshots with an equal key are explored once (again only when reached
|
|
281
|
+
* at a shallower depth), so payload and output branching that converges
|
|
282
|
+
* stays tractable. Return everything a guard, choice, or output reads:
|
|
283
|
+
* a key that drops a field the machine reads makes the search unsound.
|
|
284
|
+
* Without it, every path is explored in full.
|
|
285
|
+
*/
|
|
286
|
+
dedupeBy?: (snapshot: AnyMachineSnapshot) => string;
|
|
287
|
+
}
|
|
288
|
+
/** A single explored path's terminal outcome. */
|
|
289
|
+
interface AgentPathTerminal {
|
|
290
|
+
/**
|
|
291
|
+
* `'error'` is a scripted failure the machine had no `onError` for: the
|
|
292
|
+
* snapshot is errored, exactly as a live run's unhandled invoke failure.
|
|
293
|
+
*/
|
|
294
|
+
status: "done" | "idle" | "needs-output" | "max-depth" | "error";
|
|
295
|
+
/** The chosen/applied events, in order, that produced this terminal. */
|
|
296
|
+
path: ChosenEvent[];
|
|
297
|
+
/** The final state value on this path. */
|
|
298
|
+
state: unknown;
|
|
299
|
+
/** For `needs-output`: the src with no `outputs` entry. */
|
|
300
|
+
missingSrc?: string;
|
|
301
|
+
/** For `error`: the failure value that errored the machine. */
|
|
302
|
+
error?: unknown;
|
|
303
|
+
}
|
|
304
|
+
/** The report returned by {@link explorePaths}. */
|
|
305
|
+
interface AgentPathReport {
|
|
306
|
+
/** Every distinct state value (JSON) encountered across all explored paths. */
|
|
307
|
+
reachedStates: unknown[];
|
|
308
|
+
/** One entry per explored path. */
|
|
309
|
+
terminals: AgentPathTerminal[];
|
|
310
|
+
/** How many candidate events were pruned because a guard rejected them. */
|
|
311
|
+
prunedByGuard: number;
|
|
312
|
+
/** Notes about coverage the exploration could not complete (missing outputs, depth/path caps). */
|
|
313
|
+
unexplored: string[];
|
|
314
|
+
/** Total paths explored. */
|
|
315
|
+
pathsExplored: number;
|
|
316
|
+
/** True when a cap (`maxPaths` or `maxNodes`) cut exploration short (report is partial). */
|
|
317
|
+
hitPathCap: boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Event types whose payload variants were not all tried: their schema has
|
|
320
|
+
* a non-finite field or a product over the fill cap, and `payloads` has no
|
|
321
|
+
* entry for them. Add a `payloads` entry for each to cover them.
|
|
322
|
+
*/
|
|
323
|
+
unexpandedEvents: string[];
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Enumerates a machine's decision and external-event branches to a bounded
|
|
327
|
+
* depth, model-free, and reports which states are reached and how each path
|
|
328
|
+
* terminates. At each decision request it forks one branch per candidate event
|
|
329
|
+
* (guard-rejected candidates are counted in `prunedByGuard`, not explored); a
|
|
330
|
+
* decision with no takeable candidate exhausts as it does live, taking its
|
|
331
|
+
* `onError` (an `error` terminal without one). At an idle wait it forks per
|
|
332
|
+
* externally-accepted event. Text requests and other
|
|
333
|
+
* invokes resolve from `outputs`, a by-src map of outputs to try (a
|
|
334
|
+
* one-element list is a canned output; several fork one branch each), and a
|
|
335
|
+
* missing src halts that branch with a `needs-output` terminal rather than
|
|
336
|
+
* throwing. A src in
|
|
337
|
+
* `errors` forks an extra branch where that invoke is rejected, so states
|
|
338
|
+
* behind an `onError` are explored too.
|
|
339
|
+
*
|
|
340
|
+
* Events are applied as `{ type }` unless a payload is known: `payloads`
|
|
341
|
+
* forks a type once per listed payload, a decide input's own `payloads`
|
|
342
|
+
* become the branch set, and an event schema whose fields are finite (enums,
|
|
343
|
+
* booleans, consts) expands to their product, each optional field also
|
|
344
|
+
* omitted. A type that could not be fully expanded is listed in
|
|
345
|
+
* `unexpandedEvents`, as is a type with no schema whose transition could read
|
|
346
|
+
* the payload (a function transition, guard, `matches`, or context mapper).
|
|
347
|
+
* `dedupeBy` explores each projected snapshot once, which keeps a large
|
|
348
|
+
* payload table tractable.
|
|
349
|
+
*
|
|
350
|
+
* Combinatorics are bounded by `maxDepth` (default 8), `maxPaths` (default
|
|
351
|
+
* 200), and `maxNodes` (default 100,000), the last two reported via
|
|
352
|
+
* `hitPathCap`.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* const report = await explorePaths(refundMachine, { input: { request: 'x', amount: 5000 } });
|
|
357
|
+
* // report.terminals → both 'refunded' and 'denied'; report.prunedByGuard → 1
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
declare function explorePaths(machine: AnyStateMachine, options?: ExplorePathsOptions): Promise<AgentPathReport>;
|
|
361
|
+
/** The result of a {@link canReach} query. */
|
|
362
|
+
interface CanReachResult {
|
|
363
|
+
/** True when the target state was reached within the exploration bounds. */
|
|
364
|
+
reachable: boolean;
|
|
365
|
+
/** Resolved XState state-node id for a string target. */
|
|
366
|
+
target?: string;
|
|
367
|
+
/** When reachable, the sequence of chosen/applied events that gets there. */
|
|
368
|
+
witness?: ChosenEvent[];
|
|
369
|
+
/**
|
|
370
|
+
* False when the search left a branch it could not rule out unexplored: a
|
|
371
|
+
* cap (`maxPaths` or `maxNodes`) cut it short, a path reached `maxDepth`, a
|
|
372
|
+
* branch stopped `needs-output` on a src with no `outputs` entry, or an event type
|
|
373
|
+
* was not expanded to all its payload variants (`unexpandedEvents` in the
|
|
374
|
+
* {@link explorePaths} report). `reachable: false` is a proof of
|
|
375
|
+
* unreachability only when `complete` is true.
|
|
376
|
+
*/
|
|
377
|
+
complete: boolean;
|
|
378
|
+
}
|
|
379
|
+
/** Thrown when a string reachability target is not a state in the machine. */
|
|
380
|
+
declare class AgentUnknownStateError extends AgentError {
|
|
381
|
+
readonly target: string;
|
|
382
|
+
constructor(machineId: string, target: string);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Answers "can the machine reach this?" by exploring its branches (a thin
|
|
386
|
+
* wrapper over {@link explorePaths}). The target is either a state path string
|
|
387
|
+
* (`snapshot.matches(...)` semantics) or a snapshot predicate — the predicate
|
|
388
|
+
* form checks any property (a context invariant, a tag, a state+context
|
|
389
|
+
* combination) without reifying a sentinel state for it. Returns
|
|
390
|
+
* `{ reachable: true, witness }` with the event sequence that reaches it, or
|
|
391
|
+
* `{ reachable: false }`. Check `complete` before treating `reachable: false`
|
|
392
|
+
* as a proof: it is false when a cap or `maxDepth` cut exploration short, a
|
|
393
|
+
* branch needed an `outputs` entry it did not have, or an event's payloads were
|
|
394
|
+
* not all tried.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```ts
|
|
398
|
+
* const { reachable, witness } = await canReach(refundMachine, 'denied', { input: { request: 'x', amount: 5000 } });
|
|
399
|
+
* // reachable → true; witness → [{ type: 'NEEDS_REVIEW' }, { type: 'DENY' }]
|
|
400
|
+
* ```
|
|
401
|
+
*
|
|
402
|
+
* @example Predicate target — a violation property, no sentinel state needed
|
|
403
|
+
* ```ts
|
|
404
|
+
* const violation = await canReach(
|
|
405
|
+
* refundMachine,
|
|
406
|
+
* (snapshot) => snapshot.matches('issued') && !snapshot.context.approved,
|
|
407
|
+
* { input: { amount: 5000 } },
|
|
408
|
+
* );
|
|
409
|
+
* // violation.reachable → false is the safety proof
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
declare function canReach(machine: AnyStateMachine, target: string | ((snapshot: AnyMachineSnapshot) => boolean), options?: ExplorePathsOptions): Promise<CanReachResult>;
|
|
413
|
+
//#endregion
|
|
414
|
+
export { explorePaths as _, AgentPathTerminal as a, CanReachResult as c, SimulateAgentOptions as d, SimulateAgentResult as f, canReach as g, assertAgentMachine as h, AgentPathReport as i, ExplorePathsOptions as l, SimulationTrailEntry as m, AgentLintError as n, AgentUnknownStateError as o, SimulationScript as p, AgentLintSeverity as r, AssertAgentMachineOptions as s, AgentLintDiagnostic as t, LintAgentMachineOptions as u, lintAgentMachine as v, simulateAgent as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/agent",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.30",
|
|
4
4
|
"description": "Make invalid agent actions impossible. Agent logic as state machines: deterministic, inspectable, resumable, runs anywhere.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -88,6 +88,16 @@
|
|
|
88
88
|
"default": "./dist/otel.cjs"
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
|
+
"./tanstack-ai": {
|
|
92
|
+
"import": {
|
|
93
|
+
"types": "./dist/tanstack-ai.d.mts",
|
|
94
|
+
"default": "./dist/tanstack-ai.mjs"
|
|
95
|
+
},
|
|
96
|
+
"require": {
|
|
97
|
+
"types": "./dist/tanstack-ai.d.cts",
|
|
98
|
+
"default": "./dist/tanstack-ai.cjs"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
91
101
|
"./testing": {
|
|
92
102
|
"import": {
|
|
93
103
|
"types": "./dist/testing.d.mts",
|
|
@@ -114,50 +124,53 @@
|
|
|
114
124
|
"access": "public"
|
|
115
125
|
},
|
|
116
126
|
"devDependencies": {
|
|
117
|
-
"@ai-sdk/openai": "^4.0.
|
|
118
|
-
"@ai-sdk/typesafe-ai": "3.0.
|
|
119
|
-
"@anthropic-ai/sdk": "^0.
|
|
120
|
-
"@changesets/changelog-github": "^0.
|
|
121
|
-
"@changesets/cli": "^
|
|
122
|
-
"@
|
|
123
|
-
"@
|
|
124
|
-
"@
|
|
125
|
-
"@langchain/openai": "^1.5.10",
|
|
126
|
-
"@mastra/core": "^1.61.0",
|
|
127
|
+
"@ai-sdk/openai": "^4.0.89",
|
|
128
|
+
"@ai-sdk/typesafe-ai": "3.0.16",
|
|
129
|
+
"@anthropic-ai/sdk": "^0.132.1",
|
|
130
|
+
"@changesets/changelog-github": "^1.0.1",
|
|
131
|
+
"@changesets/cli": "^3.0.3",
|
|
132
|
+
"@langchain/core": "^1.2.17",
|
|
133
|
+
"@langchain/openai": "^1.6.2",
|
|
134
|
+
"@mastra/core": "^1.75.0",
|
|
127
135
|
"@opentelemetry/api": "^1.9.1",
|
|
128
|
-
"@opentelemetry/context-async-hooks": "^2.
|
|
129
|
-
"@opentelemetry/sdk-trace-base": "^2.
|
|
130
|
-
"@
|
|
131
|
-
"@types/
|
|
132
|
-
"
|
|
136
|
+
"@opentelemetry/context-async-hooks": "^2.12.0",
|
|
137
|
+
"@opentelemetry/sdk-trace-base": "^2.12.0",
|
|
138
|
+
"@tanstack/ai": "^0.66.0",
|
|
139
|
+
"@types/node": "^22.20.5",
|
|
140
|
+
"@types/react": "^19.3.0",
|
|
141
|
+
"@typescript/typescript6": "^6.0.2",
|
|
142
|
+
"ai": "^7.0.133",
|
|
133
143
|
"ajv": "^8.20.0",
|
|
134
|
-
"braintrust": "^3.
|
|
135
|
-
"dotenv": "^
|
|
136
|
-
"knip": "6.
|
|
137
|
-
"langchain": "^1.5.
|
|
138
|
-
"openai": "^7.
|
|
139
|
-
"oxfmt": "0.
|
|
140
|
-
"oxlint": "1.
|
|
141
|
-
"tsdown": "^0.
|
|
142
|
-
"tsx": "^4.23.
|
|
144
|
+
"braintrust": "^3.37.1",
|
|
145
|
+
"dotenv": "^18.0.6",
|
|
146
|
+
"knip": "6.40.0",
|
|
147
|
+
"langchain": "^1.5.16",
|
|
148
|
+
"openai": "^7.30.1",
|
|
149
|
+
"oxfmt": "0.72.0",
|
|
150
|
+
"oxlint": "1.87.0",
|
|
151
|
+
"tsdown": "^0.23.0",
|
|
152
|
+
"tsx": "^4.23.15",
|
|
143
153
|
"twoslash": "^0.3.9",
|
|
144
|
-
"typescript": "^
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"zod": "^4.4.3"
|
|
154
|
+
"typescript": "^7.0.2",
|
|
155
|
+
"vitest": "^5.0.3",
|
|
156
|
+
"xstate": "6.0.0-alpha.69",
|
|
157
|
+
"zod": "^4.6.5"
|
|
149
158
|
},
|
|
150
159
|
"peerDependencies": {
|
|
151
160
|
"@opentelemetry/api": "^1",
|
|
161
|
+
"@tanstack/ai": ">=0.66.0 <1",
|
|
152
162
|
"ai": "^7.0.0",
|
|
153
163
|
"ajv": "^8.20.0",
|
|
154
164
|
"openai": ">=5.0.0 <8",
|
|
155
|
-
"xstate": "6.0.0-alpha.
|
|
165
|
+
"xstate": "6.0.0-alpha.69"
|
|
156
166
|
},
|
|
157
167
|
"peerDependenciesMeta": {
|
|
158
168
|
"@opentelemetry/api": {
|
|
159
169
|
"optional": true
|
|
160
170
|
},
|
|
171
|
+
"@tanstack/ai": {
|
|
172
|
+
"optional": true
|
|
173
|
+
},
|
|
161
174
|
"ai": {
|
|
162
175
|
"optional": true
|
|
163
176
|
},
|
package/readme.md
CHANGED
|
@@ -10,22 +10,29 @@ Stately Agent adds model requests and decisions to XState:
|
|
|
10
10
|
- Your application chooses the model, runs the requests, and stores the state.
|
|
11
11
|
- The model proposes an event; the machine decides whether it is allowed and what happens next.
|
|
12
12
|
|
|
13
|
+
Environment and policy:
|
|
14
|
+
|
|
15
|
+
- The environment is code you own and can verify; the policy is something a model can write inside it.
|
|
16
|
+
- The environment holds the rules and invariants as TypeScript states and guards, fixed and checked with `canReach`.
|
|
17
|
+
- The policy is a JSON machine a model can author and improve from traces, gated before it runs and promoted only on a measured gain. See [Environment and policy](docs/environment-and-policy.md).
|
|
18
|
+
|
|
13
19
|
Stately Agent 2 is in alpha. APIs may change before the stable release.
|
|
14
20
|
|
|
15
21
|
[Documentation](https://stately.ai/docs/packages/agent) · [Examples](examples/README.md) · [XState](https://github.com/statelyai/xstate)
|
|
16
22
|
|
|
17
|
-
##
|
|
23
|
+
## Four starting points
|
|
18
24
|
|
|
19
|
-
- **Author a new agent.** Build a machine from states, decisions, and typed requests; run it locally with `
|
|
25
|
+
- **Author a new agent.** Build a machine from states, decisions, and typed requests; run it locally with `runAgent`, test it with no API key, then use it in any framework or runtime with zero machine changes. See the [Quickstart](docs/quickstart.md) and [Use in any stack](docs/any-stack.md).
|
|
20
26
|
- **Retrofit an existing agent.** Turn a `while` loop into a machine: your SDK calls, tools, and retry code become the executors; the machine replaces only the control flow. See [Migrating from a hand-rolled loop](docs/from-a-loop.md).
|
|
21
27
|
- **Copy a known pattern.** ReAct, reflection, plan-and-execute, RAG, supervisor, and more, each a single runnable file you lift in 60 seconds. See [Agent patterns](docs/patterns.md).
|
|
28
|
+
- **Let an agent improve its own logic.** Keep the rules in a fixed TypeScript environment and let a model rewrite the JSON policy inside it, gated by `verifyAgentConfig` and promoted only when held-out scenarios improve. See [Environment and policy](docs/environment-and-policy.md) and the [`improve-machine` skill](skills/improve-machine/SKILL.md).
|
|
22
29
|
|
|
23
30
|
## Install
|
|
24
31
|
|
|
25
32
|
<!-- install command matching the package prerelease channel and package.json peers -->
|
|
26
33
|
|
|
27
34
|
```sh
|
|
28
|
-
pnpm add @statelyai/agent@alpha xstate@6.0.0-alpha.
|
|
35
|
+
pnpm add @statelyai/agent@alpha xstate@6.0.0-alpha.69 zod
|
|
29
36
|
```
|
|
30
37
|
|
|
31
38
|
XState is pinned while its experimental durable restoration API evolves.
|
|
@@ -42,22 +49,28 @@ For the optional raw OpenAI SDK executor (`@statelyai/agent/openai`):
|
|
|
42
49
|
pnpm add openai
|
|
43
50
|
```
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
For the optional TanStack AI executor (`@statelyai/agent/tanstack-ai`):
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pnpm add @tanstack/ai @tanstack/ai-openai
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Classification, grading, relevance, and yes/no checks are judgments: call the AI SDK's `experimental_decide` with `@ai-sdk/typesafe-ai` (`pnpm add ai @ai-sdk/typesafe-ai`) from an ordinary actor; see [Judgments with the AI SDK](docs/judgments.md).
|
|
46
59
|
|
|
47
60
|
Requirements:
|
|
48
61
|
|
|
49
|
-
- Node 22.18 or newer, and XState
|
|
62
|
+
- Node 22.18 or newer, and XState `6.0.0-alpha.69`.
|
|
50
63
|
- The package is ESM-first. CommonJS builds are published too, so `require()` works.
|
|
51
64
|
- Provider packages must match your `ai` major: `@ai-sdk/openai@^4` pairs with `ai@^7`. A bare `@ai-sdk/openai` resolves to `@latest`, which can mismatch the `ai` peer.
|
|
52
65
|
|
|
53
66
|
## Quick start
|
|
54
67
|
|
|
55
|
-
<!-- refund decision example using setupAgent, agent.decide, a machine guard, and the AI SDK
|
|
68
|
+
<!-- refund decision example using setupAgent, agent.decide, a machine guard, and the AI SDK runAgent host -->
|
|
56
69
|
|
|
57
70
|
This agent reviews refund requests. The model may propose an automatic refund, but the state machine owns the $100 limit.
|
|
58
71
|
|
|
59
72
|
```ts
|
|
60
|
-
import {
|
|
73
|
+
import { runAgent, setupAgent } from "@statelyai/agent";
|
|
61
74
|
import { z } from "zod";
|
|
62
75
|
|
|
63
76
|
const agentSetup = setupAgent({
|
|
@@ -108,19 +121,15 @@ const refundMachine = agentSetup.createMachine({
|
|
|
108
121
|
},
|
|
109
122
|
});
|
|
110
123
|
|
|
111
|
-
const result = await
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
executors: { decide: async () => ({ event: { type: "AUTO_REFUND" } }) },
|
|
116
|
-
}),
|
|
117
|
-
{
|
|
118
|
-
input: {
|
|
119
|
-
request: "I was charged twice for the same order.",
|
|
120
|
-
amount: 75,
|
|
121
|
-
},
|
|
124
|
+
const result = await runAgent(refundMachine, {
|
|
125
|
+
input: {
|
|
126
|
+
request: "I was charged twice for the same order.",
|
|
127
|
+
amount: 75,
|
|
122
128
|
},
|
|
123
|
-
|
|
129
|
+
// An executor is a plain function, so this run needs no API key and no
|
|
130
|
+
// provider package.
|
|
131
|
+
executors: { decide: async () => ({ event: { type: "AUTO_REFUND" } }) },
|
|
132
|
+
});
|
|
124
133
|
|
|
125
134
|
if (result.status === "done") {
|
|
126
135
|
console.log(result.output);
|
|
@@ -135,29 +144,32 @@ When the machine reaches `refunded`, the result is:
|
|
|
135
144
|
|
|
136
145
|
The model chooses between the events allowed in `deciding`. The `AUTO_REFUND` transition only works when the amount is at most $100. If the model chooses it for a larger amount, the guard rejects the choice and the decision is tried again.
|
|
137
146
|
|
|
138
|
-
To call a real model, leave the
|
|
147
|
+
To call a real model, leave the states unchanged. Declare the model once on the setup by adding `models` to the `setupAgent` call above, and hand the machine to the AI SDK adapter:
|
|
139
148
|
|
|
140
149
|
```ts
|
|
141
150
|
import { openai } from "@ai-sdk/openai";
|
|
142
151
|
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
153
|
+
// In the setupAgent({ ... }) call above:
|
|
154
|
+
// models: { fast: openai("gpt-6-luna") },
|
|
155
|
+
|
|
156
|
+
const liveResult = await runAgent(refundMachine, {
|
|
157
|
+
input: { request: "I was charged twice for the same order.", amount: 75 },
|
|
158
|
+
executors: createAiSdkExecutors(refundMachine),
|
|
159
|
+
});
|
|
150
160
|
```
|
|
151
161
|
|
|
162
|
+
`createAiSdkExecutors(refundMachine)` reads the `models` declared in `setupAgent({ models })`, which also type the machine's model refs. `createAiSdkExecutors({ models })` still works when the host owns the map. Executors are always explicit, and core does not import the AI SDK. See [Models and providers](docs/models-and-providers.md) and [Hosts and executors](docs/hosts.md).
|
|
163
|
+
|
|
152
164
|
For a machine with several requests, the executor routes on `request.name`, or `MockLanguageModelV3` from `ai/test` stands in for the provider behind `createAiSdkExecutors`. See [Evals](docs/evals.md#testing-without-a-provider).
|
|
153
165
|
|
|
154
|
-
|
|
166
|
+
A host that needs the transition loop itself (a durable object, events pushed mid-run, a custom checkpoint policy) uses `createAgentRuntime` directly. See [Write your own host loop](docs/hosts.md#write-your-own-host-loop).
|
|
155
167
|
|
|
156
168
|
## Architecture
|
|
157
169
|
|
|
158
170
|
```mermaid
|
|
159
171
|
flowchart LR
|
|
160
|
-
M["Agent machine<br/>states · guards · requests"] -->|request| R["
|
|
172
|
+
M["Agent machine<br/>states · guards · requests"] -->|request| R["runAgent"]
|
|
161
173
|
R -->|executor call| E["Host executors<br/>generateText · streamText · decide"]
|
|
162
174
|
E -->|API call| L["Model"]
|
|
163
175
|
L -->|result| E
|