@dna113p/machines 0.1.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/dist/codex/machines/ui/machines.html +286 -0
  4. package/dist/examples/agent.d.ts +1 -0
  5. package/dist/examples/agent.js +19 -0
  6. package/dist/examples/choices.d.ts +1 -0
  7. package/dist/examples/choices.js +17 -0
  8. package/dist/examples/host.d.ts +3 -0
  9. package/dist/examples/host.js +32 -0
  10. package/dist/examples/human.d.ts +1 -0
  11. package/dist/examples/human.js +17 -0
  12. package/dist/examples/named-agents.d.ts +1 -0
  13. package/dist/examples/named-agents.js +38 -0
  14. package/dist/examples/operation.d.ts +1 -0
  15. package/dist/examples/operation.js +10 -0
  16. package/dist/examples/pi.d.ts +1 -0
  17. package/dist/examples/pi.js +29 -0
  18. package/dist/mcp/server.d.ts +17 -0
  19. package/dist/mcp/server.js +313 -0
  20. package/dist/pi-extension/index.d.ts +52 -0
  21. package/dist/pi-extension/index.js +390 -0
  22. package/dist/src/acp.d.ts +7 -0
  23. package/dist/src/acp.js +184 -0
  24. package/dist/src/agent-bindings.d.ts +15 -0
  25. package/dist/src/agent-bindings.js +113 -0
  26. package/dist/src/catalog.d.ts +31 -0
  27. package/dist/src/catalog.js +68 -0
  28. package/dist/src/cli.d.ts +2 -0
  29. package/dist/src/cli.js +392 -0
  30. package/dist/src/discovery-worker.d.ts +3 -0
  31. package/dist/src/discovery-worker.js +52 -0
  32. package/dist/src/discovery.d.ts +8 -0
  33. package/dist/src/discovery.js +94 -0
  34. package/dist/src/host.d.ts +25 -0
  35. package/dist/src/host.js +357 -0
  36. package/dist/src/index.d.ts +210 -0
  37. package/dist/src/index.js +204 -0
  38. package/dist/src/launcher.d.ts +19 -0
  39. package/dist/src/launcher.js +98 -0
  40. package/dist/src/machine-module.d.ts +3 -0
  41. package/dist/src/machine-module.js +55 -0
  42. package/dist/src/session.d.ts +40 -0
  43. package/dist/src/session.js +165 -0
  44. package/dist/src/terminal-human.d.ts +2 -0
  45. package/dist/src/terminal-human.js +100 -0
  46. package/docs/architecture.md +111 -0
  47. package/docs/authoring.md +193 -0
  48. package/docs/integrations.md +142 -0
  49. package/package.json +58 -0
  50. package/skills/machine-builder/SKILL.md +34 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Machines contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # Machines
2
+
3
+ Reusable workflows that coordinate agents, local operations, and human decisions.
4
+
5
+ A Machine makes a repeatable process explicit: ask an Agent to implement a change,
6
+ run an Operation to check it, and ask a Human to approve or provide feedback. Each
7
+ state returns an event; the Machine decides what happens next. XState handles the
8
+ transitions, while the same workflow can run from a terminal, Pi, or Codex.
9
+
10
+ ```text
11
+ implement (Agent) → check (Operation) → review (Human) → done
12
+ ↑ │
13
+ └──────────── feedback ────────────┘
14
+ ```
15
+
16
+ Use Machines when a sequence and its decision points are worth repeating. A
17
+ one-off prompt or shell script remains useful for work that needs no workflow.
18
+
19
+ ## Quickstart
20
+
21
+ Requires **Node.js 24 or later** and npm. From a checkout of this repository:
22
+
23
+ ```bash
24
+ npm ci
25
+ npm run check
26
+ npm run example
27
+ ```
28
+
29
+ The deterministic example needs no credentials or Agent installation and prints:
30
+
31
+ ```text
32
+ calculate --completed--> done
33
+ ```
34
+
35
+ Here is its complete implementation, runnable as `demo.ts` in the checkout root:
36
+
37
+ ```ts
38
+ import { final, machine, operation, run } from "./src/index.ts";
39
+
40
+ const workflow = machine({
41
+ initial: "calculate",
42
+ states: {
43
+ calculate: operation(
44
+ () => ({ type: "completed" }),
45
+ { completed: "done" },
46
+ ),
47
+ done: final(),
48
+ },
49
+ });
50
+
51
+ const result = await run(workflow);
52
+ console.log(`calculate --completed--> ${String(result.value)}`);
53
+ ```
54
+
55
+ Run it with `node demo.ts`. No build is needed to develop against the source.
56
+
57
+ | Example | Command | Requirements |
58
+ | --- | --- | --- |
59
+ | Deterministic Operation | `npm run example` | Node only |
60
+ | Human input | `node examples/human.ts` | Terminal input |
61
+ | Suggestions and choices | `node examples/choices.ts` | Terminal input |
62
+ | Agent contract | `node examples/agent.ts` | Built-in fake runner |
63
+ | Agent escalation | `npm run example:agents` | Built-in fake runners |
64
+ | Child host and Human response | `npm run example:host` | Node only |
65
+ | Real Agent writes and verifies a file | `npm run example:pi` | Configured Pi through `pi-acp`; may use network and model credits |
66
+
67
+ ## Run your own workflows
68
+
69
+ Save this as `.machines/hello.ts` in your project:
70
+
71
+ ```ts
72
+ import type { MachinePrimitives } from "@dna113p/machines";
73
+
74
+ export const description = "Prints the supplied message without an Agent.";
75
+
76
+ export default function hello(
77
+ { machine, operation, final }: MachinePrimitives,
78
+ input: string,
79
+ ) {
80
+ return machine({
81
+ initial: "print",
82
+ states: {
83
+ print: operation(() => {
84
+ console.log(input || "Hello from Machines.");
85
+ return { type: "completed" };
86
+ }, { completed: "done" }),
87
+ done: final(),
88
+ },
89
+ });
90
+ }
91
+ ```
92
+
93
+ From the checkout, run:
94
+
95
+ ```bash
96
+ ./machine list
97
+ ./machine show hello
98
+ ./machine run hello "Hello from Machines."
99
+ ```
100
+
101
+ The command reports the Machine path and state progress, then prints `--> done`.
102
+ In another project, use the checkout's absolute `machine` path or install the npm
103
+ package below. The `MachinePrimitives` import is type-only; executing this definition
104
+ uses the primitives supplied by the launcher.
105
+
106
+ Discovery checks the nearest project `.machines/` and then `~/.machines/`.
107
+ Project definitions replace global definitions with the same name. Files can be
108
+ `name.ts` or `name/index.ts`; the single file wins if both exist. `agents.ts` is
109
+ reserved for Agent presets. Invalid definitions remain visible with their own
110
+ diagnostics; other valid Machines remain usable.
111
+
112
+ Read [Authoring Machines](docs/authoring.md) for Human input, Agent roles and
113
+ presets, feedback loops, and the programmatic launcher.
114
+
115
+ ## Install from npm
116
+
117
+ Install the package in your project:
118
+
119
+ ```bash
120
+ npm install @dna113p/machines
121
+ npx machine list
122
+ ```
123
+
124
+ Or run the CLI without adding a project dependency:
125
+
126
+ ```bash
127
+ npx @dna113p/machines list
128
+ ```
129
+
130
+ The command is still named `machine`. For a global installation, run
131
+ `npm install --global @dna113p/machines`, then use `machine list` or `machine run`.
132
+
133
+ To install a build from a checkout instead:
134
+
135
+ ```bash
136
+ npm ci
137
+ npm pack
138
+ ```
139
+
140
+ Then install the generated `dna113p-machines-0.1.0.tgz` into a project:
141
+
142
+ ```bash
143
+ npm install /absolute/path/to/machines/dna113p-machines-0.1.0.tgz
144
+ npx machine list
145
+ ```
146
+
147
+ The package contains JavaScript and TypeScript declarations. Application code can
148
+ import `{ machine, agent, human, operation, final, run }` from `"@dna113p/machines"`.
149
+ Workflow definitions remain ordinary TypeScript outside `node_modules`.
150
+
151
+ For **Codex MCP**, **the portable Codex plugin**, or **the Pi extension**, follow
152
+ [Integrations](docs/integrations.md). No personal marketplace or developer-only
153
+ installation script is required for the direct MCP setup.
154
+
155
+ ## Execution model and limitations
156
+
157
+ Machine definitions and Agent preset files are **trusted executable code**.
158
+ Discovery imports them to inspect metadata, including their relative imports.
159
+ Keep module initialization free of side effects and do work inside states.
160
+ Disposable discovery workers refresh imported metadata; they are not a sandbox.
161
+ Operations and configured Agents run with the launching process's available access
162
+ and can modify files or contact external services.
163
+
164
+ Pi and MCP runs belong to the current session. Closing or reloading that session
165
+ terminates its owned hosts; runs are not persisted or resumable. Process shutdown
166
+ does not undo files or Git changes. A Machine owns its own retry, approval, merge,
167
+ and cleanup decisions. See [Architecture](docs/architecture.md) for ownership and
168
+ process-lifecycle limits.
169
+
170
+ ACP permission requests are currently unsupported and fail the Agent state. Choose
171
+ a harness configuration that can carry out the intended bounded task and put
172
+ workflow decisions in Human states. The launcher defaults unnamed Agents to
173
+ `npx -y pi-acp`; [Agent presets](docs/authoring.md#agent-presets) let you select
174
+ another ACP command or runner explicitly.
175
+
176
+ ## Checks
177
+
178
+ ```bash
179
+ npm run check # typecheck and behavior tests
180
+ npm run build # JavaScript, declarations, examples, and UI assets
181
+ npm run smoke:package # install a tarball in a temporary project and exercise it
182
+ npm run build:plugin
183
+ npm run smoke:plugin # exercise a relocated standalone plugin
184
+ ```
185
+
186
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,286 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <style>
7
+ :root {
8
+ color-scheme: light dark;
9
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
10
+ --bg: light-dark(#f7f7f5, #171715);
11
+ --panel: light-dark(#ffffff, #22221f);
12
+ --line: light-dark(#deded8, #3d3d38);
13
+ --text: light-dark(#20201d, #f1f1ec);
14
+ --muted: light-dark(#6d6d66, #aaa9a0);
15
+ --accent: light-dark(#287a6a, #62c8b1);
16
+ --warn: light-dark(#8a5b00, #f2c45e);
17
+ --bad: light-dark(#a2352a, #ff8d82);
18
+ --good: light-dark(#247342, #68cf8a);
19
+ }
20
+ * { box-sizing: border-box; }
21
+ body { margin: 0; color: var(--text); background: transparent; }
22
+ main {
23
+ overflow: hidden;
24
+ border: 1px solid var(--line);
25
+ border-radius: 14px;
26
+ background: var(--panel);
27
+ box-shadow: 0 10px 28px color-mix(in srgb, #000 12%, transparent);
28
+ }
29
+ header { display: flex; align-items: center; gap: 10px; padding: 14px 16px 10px; }
30
+ .mark { color: var(--accent); font-size: 18px; line-height: 1; }
31
+ h1 { min-width: 0; flex: 1; margin: 0; overflow: hidden; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; }
32
+ .badge { border-radius: 999px; padding: 4px 8px; background: color-mix(in srgb, var(--accent) 13%, transparent); color: var(--accent); font-size: 12px; font-weight: 650; }
33
+ .badge.waiting { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
34
+ .badge.completed { background: color-mix(in srgb, var(--good) 14%, transparent); color: var(--good); }
35
+ .badge.failed { background: color-mix(in srgb, var(--bad) 14%, transparent); color: var(--bad); }
36
+ .body { padding: 0 16px 15px; }
37
+ .state { display: flex; align-items: baseline; gap: 8px; font-size: 14px; }
38
+ .state strong { font-weight: 680; }
39
+ .elapsed, .meta { color: var(--muted); font-size: 12px; }
40
+ .meta { margin-top: 5px; }
41
+ .human { margin-top: 13px; border-top: 1px solid var(--line); padding-top: 12px; }
42
+ .prompt { margin: 0 0 10px; font-size: 14px; line-height: 1.45; white-space: pre-wrap; }
43
+ .actions { display: flex; flex-wrap: wrap; gap: 7px; }
44
+ button, input { font: inherit; }
45
+ button {
46
+ border: 1px solid var(--line);
47
+ border-radius: 8px;
48
+ padding: 7px 10px;
49
+ color: var(--text);
50
+ background: var(--bg);
51
+ cursor: pointer;
52
+ }
53
+ button:hover { border-color: var(--accent); }
54
+ button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
55
+ button:disabled, input:disabled { cursor: default; opacity: .55; }
56
+ form { display: flex; width: 100%; gap: 7px; margin-top: 8px; }
57
+ input { min-width: 0; flex: 1; border: 1px solid var(--line); border-radius: 8px; padding: 7px 9px; color: var(--text); background: var(--bg); }
58
+ .error { margin: 12px 0 0; color: var(--bad); font-size: 13px; line-height: 1.4; }
59
+ .done { margin: 11px 0 0; color: var(--muted); font-size: 13px; }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <main aria-live="polite">
64
+ <header>
65
+ <span class="mark" aria-hidden="true">◆</span>
66
+ <h1 id="machine">Machine</h1>
67
+ <span class="badge" id="status">starting</span>
68
+ </header>
69
+ <section class="body">
70
+ <div class="state"><strong id="state">starting</strong><span class="elapsed" id="elapsed"></span></div>
71
+ <div class="meta" id="agent"></div>
72
+ <div class="human" id="human" hidden>
73
+ <p class="prompt" id="prompt"></p>
74
+ <div class="actions" id="actions"></div>
75
+ </div>
76
+ <p class="error" id="error" hidden></p>
77
+ <p class="done" id="done" hidden></p>
78
+ </section>
79
+ </main>
80
+ <script>
81
+ const elements = Object.fromEntries(
82
+ ["machine", "status", "state", "elapsed", "agent", "human", "prompt", "actions", "error", "done"]
83
+ .map((id) => [id, document.getElementById(id)])
84
+ );
85
+ const pending = new Map();
86
+ let requestId = 1;
87
+ let run;
88
+ let timer;
89
+ let busy = false;
90
+ let refreshing = false;
91
+ let closed = false;
92
+ let revision = 0;
93
+ let renderedHumanKey;
94
+ let responseError;
95
+ let statusError;
96
+
97
+ function callTool(name, args) {
98
+ if (window.openai?.callTool) return window.openai.callTool(name, args);
99
+ const id = requestId++;
100
+ return new Promise((resolve, reject) => {
101
+ pending.set(id, { resolve, reject });
102
+ window.parent.postMessage({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } }, "*");
103
+ });
104
+ }
105
+
106
+ function toolData(result) {
107
+ const payload = result?.result ?? result;
108
+ if (payload?.isError) {
109
+ const message = payload.content?.filter((item) => item.type === "text").map((item) => item.text).join("\n");
110
+ throw new Error(message || "The Machine tool failed.");
111
+ }
112
+ const data = payload?.structuredContent;
113
+ if (!data?.run && !data?.runs?.length) throw new Error("The Machine tool did not return a run.");
114
+ return data;
115
+ }
116
+
117
+ function receive(data) {
118
+ const next = data?.run ?? data?.runs?.[0];
119
+ if (!next || closed) return;
120
+ if (humanKey(next) !== humanKey(run)) responseError = undefined;
121
+ statusError = undefined;
122
+ revision += 1;
123
+ run = next;
124
+ render();
125
+ schedule();
126
+ }
127
+
128
+ function humanKey(snapshot) {
129
+ return snapshot?.status === "waiting" && snapshot.human
130
+ ? JSON.stringify([snapshot.id, snapshot.human.requestId])
131
+ : undefined;
132
+ }
133
+
134
+ function renderError() {
135
+ const error = run?.error ?? responseError ?? statusError;
136
+ elements.error.hidden = !error;
137
+ elements.error.textContent = error ?? "";
138
+ }
139
+
140
+ function render() {
141
+ const terminal = run.status === "completed" || run.status === "failed";
142
+ elements.machine.textContent = run.machine;
143
+ elements.status.textContent = run.status === "waiting" ? "input needed" : run.status;
144
+ elements.status.className = `badge ${run.status}`;
145
+ elements.state.textContent = typeof run.state === "string" ? run.state : run.state ? JSON.stringify(run.state) : "starting";
146
+ elements.elapsed.textContent = `${run.elapsedSeconds}s`;
147
+ elements.agent.textContent = run.agent
148
+ ? [run.agent.harness, run.agent.model, run.agent.thinking ? `thinking ${run.agent.thinking}` : undefined].filter(Boolean).join(" · ")
149
+ : "";
150
+ renderError();
151
+ elements.done.hidden = !terminal || Boolean(run.error);
152
+ elements.done.textContent = run.status === "completed" ? "Finished." : "";
153
+ renderHuman();
154
+ }
155
+
156
+ function renderHuman() {
157
+ const human = run.status === "waiting" ? run.human : undefined;
158
+ elements.human.hidden = !human;
159
+ const key = humanKey(run);
160
+ if (key === renderedHumanKey) {
161
+ for (const control of elements.actions.querySelectorAll("button, input")) control.disabled = busy;
162
+ return;
163
+ }
164
+ renderedHumanKey = key;
165
+ elements.actions.replaceChildren();
166
+ if (!human) return;
167
+ elements.prompt.textContent = human.prompt;
168
+ const target = { runId: run.id, requestId: human.requestId };
169
+ const values = human.choices ?? human.suggestions ?? [];
170
+ for (const value of values) {
171
+ const button = document.createElement("button");
172
+ button.type = "button";
173
+ button.textContent = value;
174
+ button.disabled = busy;
175
+ button.onclick = () => respond(target, value);
176
+ elements.actions.append(button);
177
+ }
178
+ if (!human.choices) {
179
+ const form = document.createElement("form");
180
+ const input = document.createElement("input");
181
+ const submit = document.createElement("button");
182
+ input.placeholder = "Other response";
183
+ input.setAttribute("aria-label", "Machine response");
184
+ input.disabled = busy;
185
+ submit.type = "submit";
186
+ submit.textContent = "Send";
187
+ submit.disabled = busy;
188
+ form.append(input, submit);
189
+ form.onsubmit = (event) => {
190
+ event.preventDefault();
191
+ if (input.value !== "") respond(target, input.value);
192
+ };
193
+ elements.actions.append(form);
194
+ }
195
+ }
196
+
197
+ async function respond(target, response) {
198
+ const key = JSON.stringify([target.runId, target.requestId]);
199
+ if (closed || busy || key !== humanKey(run)) return;
200
+ const focused = elements.actions.contains(document.activeElement) ? document.activeElement : undefined;
201
+ busy = true;
202
+ responseError = undefined;
203
+ const startedAt = ++revision;
204
+ clearTimeout(timer);
205
+ renderHuman();
206
+ renderError();
207
+ try {
208
+ const result = await callTool("machine_respond", { ...target, response });
209
+ if (!closed && revision === startedAt) receive(toolData(result));
210
+ } catch (error) {
211
+ if (!closed && key === humanKey(run)) {
212
+ responseError = error?.message ?? String(error);
213
+ renderError();
214
+ }
215
+ } finally {
216
+ busy = false;
217
+ if (!closed) {
218
+ renderHuman();
219
+ if (focused && elements.actions.contains(focused) && (!document.activeElement || document.activeElement === document.body)) focused.focus();
220
+ schedule();
221
+ }
222
+ }
223
+ }
224
+
225
+ function schedule(delay = 1000) {
226
+ clearTimeout(timer);
227
+ if (closed || busy || refreshing || !run || run.status === "completed" || run.status === "failed") return;
228
+ timer = setTimeout(refresh, delay);
229
+ }
230
+
231
+ async function refresh() {
232
+ if (closed || refreshing || busy || !run) return;
233
+ refreshing = true;
234
+ const startedAt = revision;
235
+ let delay = 1000;
236
+ try {
237
+ const result = await callTool("machine_status", { runId: run.id });
238
+ if (!closed && revision === startedAt) {
239
+ const data = toolData(result);
240
+ receive(data);
241
+ }
242
+ } catch (error) {
243
+ if (!closed && revision === startedAt) {
244
+ statusError = error?.message ?? String(error);
245
+ renderError();
246
+ delay = 3000;
247
+ }
248
+ } finally {
249
+ refreshing = false;
250
+ schedule(delay);
251
+ }
252
+ }
253
+
254
+ window.addEventListener("message", (event) => {
255
+ if (closed || event.source !== window.parent) return;
256
+ const message = event.data;
257
+ if (!message || message.jsonrpc !== "2.0") return;
258
+ if (message.id !== undefined && pending.has(message.id)) {
259
+ const request = pending.get(message.id);
260
+ pending.delete(message.id);
261
+ if (message.error) request.reject(message.error);
262
+ else request.resolve(message.result);
263
+ return;
264
+ }
265
+ if (message.method === "ui/notifications/tool-result") {
266
+ try {
267
+ receive(toolData(message.params));
268
+ } catch (error) {
269
+ statusError = error?.message ?? String(error);
270
+ renderError();
271
+ schedule(3000);
272
+ }
273
+ }
274
+ }, { passive: true });
275
+
276
+ window.addEventListener("pagehide", () => {
277
+ closed = true;
278
+ clearTimeout(timer);
279
+ for (const request of pending.values()) request.reject(new Error("The Machine card was closed."));
280
+ pending.clear();
281
+ }, { once: true });
282
+
283
+ receive(window.openai?.toolOutput);
284
+ </script>
285
+ </body>
286
+ </html>
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { agent, final, human, machine, operation, run, } from "../src/index.js";
2
+ const fakeAgent = (request) => {
3
+ console.log("Agent request:");
4
+ console.log(`prompt: ${request.prompt}`);
5
+ console.log(`cwd: ${request.cwd}`);
6
+ console.log(`outcomes: ${request.outcomes.join(", ")}`);
7
+ return { type: "completed" };
8
+ };
9
+ const example = machine({
10
+ initial: "work",
11
+ states: {
12
+ work: agent("Implement the tiny task", { completed: "confirm" }, { cwd: "/tmp/machines-fake" }),
13
+ confirm: human("Continue to verification?", { submitted: "verify" }),
14
+ verify: operation(() => ({ type: "passed" }), { passed: "done" }),
15
+ done: final(),
16
+ },
17
+ });
18
+ const result = await run(example, { agents: { default: fakeAgent } });
19
+ console.log(`verified --> ${String(result.value)}`);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { final, human, machine, run, } from "../src/index.js";
2
+ const example = machine({
3
+ initial: "choose",
4
+ states: {
5
+ choose: human("Approve or deny?", {
6
+ submitted: {
7
+ target: "done",
8
+ actions: ({ event }) => {
9
+ console.log(`submitted "${String(event.value)}"`);
10
+ },
11
+ },
12
+ }, { choices: ["approve", "deny"] }),
13
+ done: final(),
14
+ },
15
+ });
16
+ const result = await run(example);
17
+ console.log(`--> ${String(result.value)}`);
@@ -0,0 +1,3 @@
1
+ import type { MachinePrimitives } from "../src/index.ts";
2
+ export declare const description = "Demonstrates one asynchronous child-hosted Machine.";
3
+ export default function hostedExample({ final, human, machine, operation }: MachinePrimitives): ReturnType<MachinePrimitives["machine"]>;
@@ -0,0 +1,32 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { startMachineHost } from "../src/host.js";
3
+ export const description = "Demonstrates one asynchronous child-hosted Machine.";
4
+ export default function hostedExample({ final, human, machine, operation }) {
5
+ return machine({
6
+ initial: "work",
7
+ states: {
8
+ work: operation(async () => {
9
+ await new Promise((resolve) => setTimeout(resolve, 100));
10
+ return { type: "completed" };
11
+ }, { completed: "review" }),
12
+ review: human("Approve the hosted example?", { submitted: "done" }, { choices: ["approve", "deny"] }),
13
+ done: final(),
14
+ },
15
+ });
16
+ }
17
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
18
+ let resolveHuman;
19
+ const human = new Promise((resolve) => {
20
+ resolveHuman = resolve;
21
+ });
22
+ const hosted = await startMachineHost({
23
+ machine: fileURLToPath(import.meta.url),
24
+ onState: (state) => console.log(`state: ${String(state)}`),
25
+ onHumanRequest: resolveHuman,
26
+ });
27
+ console.log(`started: ${hosted.name}`);
28
+ const request = await human;
29
+ console.log(`human: ${request.prompt}`);
30
+ await hosted.respond("approve", request.requestId);
31
+ console.log(`completed: ${String((await hosted.result).state)}`);
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { final, human, machine, run, } from "../src/index.js";
2
+ const example = machine({
3
+ initial: "choose",
4
+ states: {
5
+ choose: human("What should happen?", {
6
+ submitted: {
7
+ target: "done",
8
+ actions: ({ event }) => {
9
+ console.log(`submitted "${String(event.value)}"`);
10
+ },
11
+ },
12
+ }, { suggestions: ["Use the default"] }),
13
+ done: final(),
14
+ },
15
+ });
16
+ const result = await run(example);
17
+ console.log(`--> ${String(result.value)}`);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { agent, final, machine, operation, run, } from "../src/index.js";
2
+ let failedReviews = 0;
3
+ const fastReviewer = (request, report) => {
4
+ report?.({
5
+ type: "identity",
6
+ harness: "antigravity",
7
+ model: "gemini-flash",
8
+ });
9
+ console.log(`fastReviewer: ${request.prompt}`);
10
+ return { type: "changesRequested" };
11
+ };
12
+ const strongReviewer = (request, report) => {
13
+ report?.({
14
+ type: "identity",
15
+ harness: "claude-code",
16
+ model: "opus-5",
17
+ thinking: "high",
18
+ });
19
+ console.log(`strongReviewer: ${request.prompt}`);
20
+ return { type: "approved" };
21
+ };
22
+ const example = machine({
23
+ initial: "review",
24
+ states: {
25
+ review: agent("Review the implementation quickly", { changesRequested: "countFailure" }, { using: "fastReviewer" }),
26
+ countFailure: operation(() => {
27
+ failedReviews += 1;
28
+ return { type: failedReviews >= 2 ? "escalate" : "retry" };
29
+ }, { retry: "review", escalate: "escalatedReview" }),
30
+ escalatedReview: agent("Perform an escalated review", { approved: "done" }, { using: "strongReviewer" }),
31
+ done: final(),
32
+ },
33
+ });
34
+ const result = await run(example, {
35
+ agents: { fastReviewer, strongReviewer },
36
+ });
37
+ console.log(`Failed fast reviews: ${failedReviews}`);
38
+ console.log(`--> ${String(result.value)}`);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { final, machine, operation, run, } from "../src/index.js";
2
+ const example = machine({
3
+ initial: "calculate",
4
+ states: {
5
+ calculate: operation(() => ({ type: "completed" }), { completed: "done" }),
6
+ done: final(),
7
+ },
8
+ });
9
+ const result = await run(example);
10
+ console.log(`calculate --completed--> ${String(result.value)}`);
@@ -0,0 +1 @@
1
+ export {};