agent-state-machine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +299 -0
- package/bin/cli.js +246 -0
- package/lib/index.js +110 -0
- package/lib/index.mjs +9 -0
- package/lib/llm.js +472 -0
- package/lib/runtime/agent.js +359 -0
- package/lib/runtime/index.js +35 -0
- package/lib/runtime/memory.js +88 -0
- package/lib/runtime/parallel.js +66 -0
- package/lib/runtime/prompt.js +118 -0
- package/lib/runtime/runtime.js +387 -0
- package/lib/setup.js +398 -0
- package/lib/state-machine.js +1359 -0
- package/package.json +32 -0
package/lib/setup.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: /lib/setup.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Setup a new workflow with directory structure
|
|
10
|
+
*/
|
|
11
|
+
async function setup(workflowName) {
|
|
12
|
+
const workflowsDir = path.join(process.cwd(), 'workflows');
|
|
13
|
+
const workflowDir = path.join(workflowsDir, workflowName);
|
|
14
|
+
|
|
15
|
+
// Check if workflow already exists
|
|
16
|
+
if (fs.existsSync(workflowDir)) {
|
|
17
|
+
console.error(`Error: Workflow '${workflowName}' already exists at ${workflowDir}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`\nCreating workflow: ${workflowName}`);
|
|
22
|
+
console.log('─'.repeat(40));
|
|
23
|
+
|
|
24
|
+
// Create directory structure (native JS workflow only)
|
|
25
|
+
const dirs = [
|
|
26
|
+
workflowDir,
|
|
27
|
+
path.join(workflowDir, 'agents'),
|
|
28
|
+
path.join(workflowDir, 'interactions'),
|
|
29
|
+
path.join(workflowDir, 'state'),
|
|
30
|
+
path.join(workflowDir, 'steering'),
|
|
31
|
+
path.join(workflowDir, 'scripts')
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
dirs.forEach((dir) => {
|
|
35
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
36
|
+
console.log(` Created: ${path.relative(process.cwd(), dir)}/`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Ensure this workflow folder is ESM (so workflow.js + agents/*.js can use import/export)
|
|
40
|
+
// const workflowPkg = {
|
|
41
|
+
// name: `workflow-${workflowName}`,
|
|
42
|
+
// private: true,
|
|
43
|
+
// type: 'module'
|
|
44
|
+
// };
|
|
45
|
+
// const workflowPkgFile = path.join(workflowDir, 'package.json');
|
|
46
|
+
// fs.writeFileSync(workflowPkgFile, JSON.stringify(workflowPkg, null, 2));
|
|
47
|
+
// console.log(` Created: ${path.relative(process.cwd(), workflowPkgFile)}`);
|
|
48
|
+
|
|
49
|
+
// Create workflow.js (native JS format)
|
|
50
|
+
const workflowJs = `/**
|
|
51
|
+
/**
|
|
52
|
+
* ${workflowName} Workflow
|
|
53
|
+
*
|
|
54
|
+
* Native JavaScript workflow - write normal async/await code!
|
|
55
|
+
*
|
|
56
|
+
* Features:
|
|
57
|
+
* - memory object auto-persists to disk (use memory guards for idempotency)
|
|
58
|
+
* - Use standard JS control flow (if, for, etc.)
|
|
59
|
+
* - Interactive prompts pause and wait for user input
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
|
|
63
|
+
import { notify } from './scripts/mac-notification.js';
|
|
64
|
+
|
|
65
|
+
// Model configuration (also supports models in a separate config export)
|
|
66
|
+
export const config = {
|
|
67
|
+
models: {
|
|
68
|
+
low: "gemini",
|
|
69
|
+
med: "codex --model gpt-5.2",
|
|
70
|
+
high: "claude -m claude-opus-4-20250514 -p",
|
|
71
|
+
},
|
|
72
|
+
apiKeys: {
|
|
73
|
+
gemini: process.env.GEMINI_API_KEY,
|
|
74
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
75
|
+
openai: process.env.OPENAI_API_KEY,
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default async function() {
|
|
80
|
+
console.log('Starting ${workflowName} workflow...');
|
|
81
|
+
|
|
82
|
+
// Example: Get user input (saved to memory)
|
|
83
|
+
const answer = await initialPrompt('Where do you live?');
|
|
84
|
+
console.log('Example prompt answer:', answer);
|
|
85
|
+
|
|
86
|
+
const userInfo = await agent('yoda-name-collector');
|
|
87
|
+
memory.userInfo = userInfo;
|
|
88
|
+
|
|
89
|
+
// Provide context
|
|
90
|
+
// const userInfo = await agent('yoda-name-collector', { name: 'Luke' });
|
|
91
|
+
|
|
92
|
+
console.log('Example agent memory.userInfo:', memory.userInfo || userInfo);
|
|
93
|
+
|
|
94
|
+
// Context is provided automatically
|
|
95
|
+
const { greeting } = await agent('yoda-greeter');
|
|
96
|
+
console.log('Example agent greeting:', greeting);
|
|
97
|
+
|
|
98
|
+
// Or you can provide context manually
|
|
99
|
+
// await agent('yoda-greeter', userInfo);
|
|
100
|
+
|
|
101
|
+
// Example: Parallel execution
|
|
102
|
+
// const [a, b] = await parallel([
|
|
103
|
+
// agent('example', { which: 'a' }),
|
|
104
|
+
// agent('example', { which: 'b' })
|
|
105
|
+
// ]);
|
|
106
|
+
|
|
107
|
+
notify(['${workflowName}', userInfo.name || userInfo + ' has been greeted!']);
|
|
108
|
+
|
|
109
|
+
console.log('Workflow completed!');
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const workflowFile = path.join(workflowDir, 'workflow.js');
|
|
114
|
+
fs.writeFileSync(workflowFile, workflowJs);
|
|
115
|
+
console.log(` Created: ${path.relative(process.cwd(), workflowFile)}`);
|
|
116
|
+
|
|
117
|
+
// Create example JS agent (ESM)
|
|
118
|
+
// Create example JS agent (ESM)
|
|
119
|
+
const exampleAgent = `/**
|
|
120
|
+
* Example Agent for ${workflowName}
|
|
121
|
+
*
|
|
122
|
+
* Agents are async functions that receive a context object and return a result.
|
|
123
|
+
* - Context includes: persisted memory (spread), params, _steering, _config
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
import { llm } from 'agent-state-machine';
|
|
127
|
+
|
|
128
|
+
export default async function handler(context) {
|
|
129
|
+
console.log('[Agent: example] Processing...');
|
|
130
|
+
|
|
131
|
+
// Access global steering prompt if available
|
|
132
|
+
if (context._steering?.global) {
|
|
133
|
+
console.log('[Agent: example] Steering loaded (' + context._steering.global.length + ' chars)');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Example: Call an LLM (configure models in workflow.js)
|
|
137
|
+
// const response = await llm(context, {
|
|
138
|
+
// model: 'smart',
|
|
139
|
+
// prompt: 'Say hello and describe what you can help with.'
|
|
140
|
+
// });
|
|
141
|
+
// console.log('[Agent: example] LLM response:', response.text);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
received: Object.keys(context).filter((k) => !String(k).startsWith('_')),
|
|
146
|
+
processedAt: new Date().toISOString()
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const meta = {
|
|
151
|
+
name: 'example',
|
|
152
|
+
description: 'An example agent to get you started',
|
|
153
|
+
version: '1.0.0'
|
|
154
|
+
};
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
const agentFile = path.join(workflowDir, 'agents', 'example.js');
|
|
158
|
+
fs.writeFileSync(agentFile, exampleAgent);
|
|
159
|
+
console.log(` Created: ${path.relative(process.cwd(), agentFile)}`);
|
|
160
|
+
|
|
161
|
+
// Create example markdown agent
|
|
162
|
+
const yodaGreeterAgent = `---
|
|
163
|
+
model: low
|
|
164
|
+
output: greeting
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
# Greeting Task
|
|
168
|
+
|
|
169
|
+
Generate a friendly greeting for {{name}} in a yoda style. Prompt user for their actual {{name}} if you dont have it.
|
|
170
|
+
|
|
171
|
+
Once you have it create a yoda-greeting.md file in root dir with the greeting.
|
|
172
|
+
|
|
173
|
+
You are a fast, direct worker. Do NOT investigate the codebase or read files unless strictly necessary. Perform the requested action immediately using the provided context. Avoid "thinking" steps or creating plans if the task is simple.
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
const yodaNameCollectorAgent = `---
|
|
177
|
+
model: low
|
|
178
|
+
output: name
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
# Name Collection Task
|
|
182
|
+
|
|
183
|
+
Ask for users name in a yoda style. Unless you have it already.
|
|
184
|
+
|
|
185
|
+
Keep it brief and warm.
|
|
186
|
+
|
|
187
|
+
You are a fast, direct worker. Do NOT investigate the codebase or read files unless strictly necessary. Perform the requested action immediately using the provided context. Avoid "thinking" steps or creating plans if the task is simple.
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
const yodaNameCollectorAgentFile = path.join(workflowDir, 'agents', 'yoda-name-collector.md');
|
|
191
|
+
fs.writeFileSync(yodaNameCollectorAgentFile, yodaNameCollectorAgent);
|
|
192
|
+
|
|
193
|
+
const yodaGreeterFile = path.join(workflowDir, 'agents', 'yoda-greeter.md');
|
|
194
|
+
fs.writeFileSync(yodaGreeterFile, yodaGreeterAgent);
|
|
195
|
+
|
|
196
|
+
console.log(` Created: ${path.relative(process.cwd(), yodaGreeterFile)}`);
|
|
197
|
+
console.log(` Created: ${path.relative(process.cwd(), yodaNameCollectorAgentFile)}`);
|
|
198
|
+
|
|
199
|
+
// Create initial state (native format)
|
|
200
|
+
const initialState = {
|
|
201
|
+
format: 'native',
|
|
202
|
+
status: 'IDLE',
|
|
203
|
+
memory: {},
|
|
204
|
+
_pendingInteraction: null,
|
|
205
|
+
_error: null,
|
|
206
|
+
startedAt: null,
|
|
207
|
+
lastUpdatedAt: new Date().toISOString()
|
|
208
|
+
};
|
|
209
|
+
const stateFile = path.join(workflowDir, 'state', 'current.json');
|
|
210
|
+
fs.writeFileSync(stateFile, JSON.stringify(initialState, null, 2));
|
|
211
|
+
console.log(` Created: ${path.relative(process.cwd(), stateFile)}`);
|
|
212
|
+
|
|
213
|
+
// Create empty history file
|
|
214
|
+
const historyFile = path.join(workflowDir, 'state', 'history.jsonl');
|
|
215
|
+
fs.writeFileSync(historyFile, '');
|
|
216
|
+
console.log(` Created: ${path.relative(process.cwd(), historyFile)}`);
|
|
217
|
+
|
|
218
|
+
// Create steering config
|
|
219
|
+
const steeringConfig = {
|
|
220
|
+
_comment: 'Steering configuration',
|
|
221
|
+
enabled: true,
|
|
222
|
+
globalPrompt: 'global.md'
|
|
223
|
+
};
|
|
224
|
+
const steeringFile = path.join(workflowDir, 'steering', 'config.json');
|
|
225
|
+
fs.writeFileSync(steeringFile, JSON.stringify(steeringConfig, null, 2));
|
|
226
|
+
console.log(` Created: ${path.relative(process.cwd(), steeringFile)}`);
|
|
227
|
+
|
|
228
|
+
// Create global.md steering prompt
|
|
229
|
+
const globalMd = `# Global Steering Prompt
|
|
230
|
+
|
|
231
|
+
This content is included with every agent execution in the ${workflowName} workflow.
|
|
232
|
+
|
|
233
|
+
## Guidelines
|
|
234
|
+
|
|
235
|
+
- Process data carefully and validate inputs
|
|
236
|
+
- Return well-structured JSON when applicable
|
|
237
|
+
- Log meaningful progress messages
|
|
238
|
+
- Handle errors gracefully
|
|
239
|
+
|
|
240
|
+
## Notes
|
|
241
|
+
|
|
242
|
+
This file is automatically loaded and passed to every agent in the workflow via \`context._steering.global\`.
|
|
243
|
+
`;
|
|
244
|
+
const globalMdFile = path.join(workflowDir, 'steering', 'global.md');
|
|
245
|
+
fs.writeFileSync(globalMdFile, globalMd);
|
|
246
|
+
console.log(` Created: ${path.relative(process.cwd(), globalMdFile)}`);
|
|
247
|
+
|
|
248
|
+
// Create mac-notification.js script
|
|
249
|
+
const macNotificationScript = `"use strict";
|
|
250
|
+
|
|
251
|
+
import { spawnSync } from "node:child_process";
|
|
252
|
+
import { existsSync } from "node:fs";
|
|
253
|
+
|
|
254
|
+
function escAppleScript(s) {
|
|
255
|
+
return String(s).replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function notify(title = "Notification", message = "Everything finished!") {
|
|
259
|
+
const script = \`display notification "\${escAppleScript(message)}" with title "\${escAppleScript(title)}"\`;
|
|
260
|
+
spawnSync("osascript", ["-e", script], { stdio: "ignore" });
|
|
261
|
+
|
|
262
|
+
const soundPath = "/System/Library/Sounds/Glass.aiff";
|
|
263
|
+
const fallbackPath = "/System/Library/Sounds/Ping.aiff";
|
|
264
|
+
|
|
265
|
+
if (existsSync(soundPath)) {
|
|
266
|
+
spawnSync("afplay", [soundPath], { stdio: "ignore" });
|
|
267
|
+
} else if (existsSync(fallbackPath)) {
|
|
268
|
+
spawnSync("afplay", [fallbackPath], { stdio: "ignore" });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { notify };
|
|
273
|
+
`;
|
|
274
|
+
const notificationFile = path.join(workflowDir, 'scripts', 'mac-notification.js');
|
|
275
|
+
fs.writeFileSync(notificationFile, macNotificationScript);
|
|
276
|
+
console.log(` Created: ${path.relative(process.cwd(), notificationFile)}`);
|
|
277
|
+
|
|
278
|
+
// Create README
|
|
279
|
+
const readme = `# ${workflowName}
|
|
280
|
+
|
|
281
|
+
A workflow created with agent-state-machine (native JS format).
|
|
282
|
+
|
|
283
|
+
## Structure
|
|
284
|
+
|
|
285
|
+
\\\`\\\`\\\`
|
|
286
|
+
${workflowName}/
|
|
287
|
+
├── workflow.js # Native JS workflow (async/await)
|
|
288
|
+
├── package.json # Sets "type": "module" for this workflow folder
|
|
289
|
+
├── agents/ # Custom agents (.js/.mjs/.cjs or .md)
|
|
290
|
+
├── interactions/ # Human-in-the-loop inputs (created at runtime)
|
|
291
|
+
├── state/ # Runtime state (current.json, history.jsonl)
|
|
292
|
+
└── steering/ # Steering configuration
|
|
293
|
+
\\\`\\\`\\\`
|
|
294
|
+
|
|
295
|
+
## Usage
|
|
296
|
+
|
|
297
|
+
Run the workflow:
|
|
298
|
+
\\\`\\\`\\\`bash
|
|
299
|
+
state-machine run ${workflowName}
|
|
300
|
+
\\\`\\\`\\\`
|
|
301
|
+
|
|
302
|
+
Resume a paused workflow:
|
|
303
|
+
\\\`\\\`\\\`bash
|
|
304
|
+
state-machine resume ${workflowName}
|
|
305
|
+
\\\`\\\`\\\`
|
|
306
|
+
|
|
307
|
+
Check status:
|
|
308
|
+
\\\`\\\`\\\`bash
|
|
309
|
+
state-machine status ${workflowName}
|
|
310
|
+
\\\`\\\`\\\`
|
|
311
|
+
|
|
312
|
+
View history:
|
|
313
|
+
\\\`\\\`\\\`bash
|
|
314
|
+
state-machine history ${workflowName}
|
|
315
|
+
\\\`\\\`\\\`
|
|
316
|
+
|
|
317
|
+
Reset state:
|
|
318
|
+
\\\`\\\`\\\`bash
|
|
319
|
+
state-machine reset ${workflowName}
|
|
320
|
+
\\\`\\\`\\\`
|
|
321
|
+
|
|
322
|
+
## Writing Workflows
|
|
323
|
+
|
|
324
|
+
Edit \`workflow.js\` - write normal async JavaScript:
|
|
325
|
+
|
|
326
|
+
\\\`\\\`\\\`js
|
|
327
|
+
import { agent, memory, initialPrompt, parallel } from 'agent-state-machine';
|
|
328
|
+
|
|
329
|
+
export default async function() {
|
|
330
|
+
console.log('Starting project-builder workflow...');
|
|
331
|
+
|
|
332
|
+
// Example: Get user input (saved to memory)
|
|
333
|
+
const answer = await initialPrompt('Where do you live?');
|
|
334
|
+
console.log('Example prompt answer:', answer);
|
|
335
|
+
|
|
336
|
+
const userInfo = await agent('yoda-name-collector');
|
|
337
|
+
memory.userInfo = userInfo;
|
|
338
|
+
|
|
339
|
+
// Provide context
|
|
340
|
+
// const userInfo = await agent('yoda-name-collector', { name: 'Luke' });
|
|
341
|
+
|
|
342
|
+
console.log('Example agent memory.userInfo:', memory.userInfo || userInfo);
|
|
343
|
+
|
|
344
|
+
// Context is provided automatically
|
|
345
|
+
const { greeting } = await agent('yoda-greeter');
|
|
346
|
+
console.log('Example agent greeting:', greeting);
|
|
347
|
+
|
|
348
|
+
// Or you can provide context manually
|
|
349
|
+
// await agent('yoda-greeter', userInfo);
|
|
350
|
+
|
|
351
|
+
// Example: Parallel execution
|
|
352
|
+
// const [a, b] = await parallel([
|
|
353
|
+
// agent('example', { which: 'a' }),
|
|
354
|
+
// agent('example', { which: 'b' })
|
|
355
|
+
// ]);
|
|
356
|
+
|
|
357
|
+
notify(['project-builder', userInfo.name || userInfo + ' has been greeted!']);
|
|
358
|
+
|
|
359
|
+
console.log('Workflow completed!');
|
|
360
|
+
}
|
|
361
|
+
\\\`\\\`\\\`
|
|
362
|
+
|
|
363
|
+
## Creating Agents
|
|
364
|
+
|
|
365
|
+
**JavaScript agent** (\`agents/my-agent.js\`):
|
|
366
|
+
|
|
367
|
+
\\\`\\\`\\\`js
|
|
368
|
+
import { llm } from 'agent-state-machine';
|
|
369
|
+
|
|
370
|
+
export default async function handler(context) {
|
|
371
|
+
const response = await llm(context, { model: 'smart', prompt: 'Hello!' });
|
|
372
|
+
return { greeting: response.text };
|
|
373
|
+
}
|
|
374
|
+
\\\`\\\`\\\`
|
|
375
|
+
|
|
376
|
+
**Markdown agent** (\`agents/greeter.md\`):
|
|
377
|
+
|
|
378
|
+
\\\`\\\`\\\`md
|
|
379
|
+
---
|
|
380
|
+
model: fast
|
|
381
|
+
output: greeting
|
|
382
|
+
---
|
|
383
|
+
Generate a greeting for {{name}}.
|
|
384
|
+
\\\`\\\`\\\`
|
|
385
|
+
`;
|
|
386
|
+
const readmeFile = path.join(workflowDir, 'README.md');
|
|
387
|
+
fs.writeFileSync(readmeFile, readme);
|
|
388
|
+
console.log(` Created: ${path.relative(process.cwd(), readmeFile)}`);
|
|
389
|
+
|
|
390
|
+
console.log('─'.repeat(40));
|
|
391
|
+
console.log(`\n✓ Workflow '${workflowName}' created successfully!\n`);
|
|
392
|
+
console.log('Next steps:');
|
|
393
|
+
console.log(` 1. Edit workflows/${workflowName}/workflow.js to implement your flow`);
|
|
394
|
+
console.log(` 2. Add custom agents in workflows/${workflowName}/agents/`);
|
|
395
|
+
console.log(` 3. Run: state-machine run ${workflowName}\n`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export { setup };
|