Run the engine in Node.js
In this tutorial you run smllm’s engine inside your own program. You compile a state machine to JSON, load it in Node.js with @six5536/smllm-wasm, and fire events as an agent does, with no smllm process and no harness. At the end you can build a host of your own that gives an agent the smllm tool.
Before you begin
- You have written and run a machine, as Your first state machine shows.
- smllm is installed, as the README shows.
- A current long-term support (LTS) release of Node.js is installed.
Step 1: write the machine
In an empty folder, install the package:
npm install @six5536/smllm-wasm
Write a machine in the folder. It guides an agent through the review of a change: in READ the agent reads the change, in REPORT it writes its findings, and the event reported takes the agent’s verdict.
review.smllm.yaml
id: review
description: Review a change and give a verdict.
initial: READ
meta:
smllm: 1
instance:
kind: review
events:
read:
description: Fire after you have read the whole change.
reported:
description: Fire after you write your findings, with your verdict.
params:
type: object
properties:
verdict: { enum: [approve, reject], description: "Your verdict on the change." }
required: [verdict]
states:
READ:
meta: { entryPoint: true }
entry: { type: prompt, params: { text: "Read the change and the code around it." } }
on:
read: REPORT
REPORT:
entry: { type: prompt, params: { text: "Write your findings, then fire reported with your verdict." } }
on:
reported: DONE
DONE:
type: final
entry: { type: prompt, params: { text: "Post your findings for the author." } }
Check the machine:
$ smllm validate review.smllm.yaml
0 errors, 0 warnings, 0 info
Step 2: compile the machine
The engine reads compiled machines, not YAML. smllm compile checks a machine file, or a config with all its machines, and writes it as JSON. It prints nothing when it succeeds:
$ smllm compile review.smllm.yaml -o review.json
Compile the machine again after each change to it, because the engine reads only review.json.
Step 3: drive the machine from Node.js
Write a script that loads review.json into an engine, binds a session, and makes the calls that an agent makes through the smllm tool:
review.mjs
import { readFileSync } from "node:fs";
import init, { Engine } from "@six5536/smllm-wasm";
// Load the engine's WebAssembly once.
await init();
let n = 0;
const engine = new Engine(readFileSync("review.json", "utf8"), {
// Fixed random numbers give the same keys on every run.
random: () => ++n,
});
const { session, text } = engine.bind("node");
console.log(text);
console.log(engine.callTool({ session, event: "enter", params: { stateMachine: "review" } }).text);
const stop = engine.stop(session);
console.log(stop.decision);
console.log(stop.text);
console.log(engine.callTool({ session, event: "read" }).text);
console.log(engine.callTool({ session, event: "reported", params: { verdict: "approve" } }).text);
The script makes these calls:
initloads the engine’s WebAssembly, once for each process.bindstarts a session for the harnessnode, and returns its key and the text that a session start hook gives the agent.callTooltakes the same arguments as the smllm tool, and returns the tool’s reply.stopdecides whether the agent can stop, as the stop hook does.
Run the script:
$ node review.mjs
<smllm>
session sm-200000 · idle
State machines:
- review: Review a change and give a verdict.
review id param ref
starts at: READ; entry points: READ
Fire one event: smllm({ session: "sm-200000", event, params })
<events>
- enter: Start a new instance of a state machine, or continue one.
stateMachine (required, one of: review): The state machine to enter.
ref (optional): The id param that the list of state machines names for the machine: an existing id or ref continues that instance, and a new ref starts one with it. Omit it to start a new instance.
state (optional): An entry point to enter instead, or any state for an instance whose saved state no longer exists. Omit it to enter a new instance at the initial state, or an existing one at its saved state.
</events>
</smllm>
<smllm>
session sm-200000 · review › READ · review i-400000
Arrived by: enter (new review)
Params: stateMachine = "review"
<instructions>
Read the change and the code around it.
</instructions>
</smllm>
block
<smllm>
session sm-200000 · review › READ · review i-400000
Fire one event: smllm({ session: "sm-200000", event, params })
<events>
- read: Fire after you have read the whole change.
- yield: Stop for now and stay in READ.
note (optional): What you are waiting for.
- pause: Put review i-400000 aside and return to idle.
- unmatched: The request fits none of these; handle it from idle, then resume.
</events>
</smllm>
<smllm>
session sm-200000 · review › REPORT · review i-400000
Arrived by: read from READ
<instructions>
Write your findings, then fire reported with your verdict.
</instructions>
</smllm>
<smllm>
session sm-200000 · review › DONE · review i-400000
Arrived by: reported from REPORT
Params: verdict = "approve"
<instructions>
Post your findings for the author.
</instructions>
Completed review i-400000. You are now in idle.
State machines:
- review: Review a change and give a verdict.
review id param ref
starts at: READ; entry points: READ
Fire one event: smllm({ session: "sm-200000", event, params })
<events>
- enter: Start a new instance of a state machine, or continue one.
stateMachine (required, one of: review): The state machine to enter.
ref (optional): The id param that the list of state machines names for the machine: an existing id or ref continues that instance, and a new ref starts one with it. Omit it to start a new instance.
state (optional): An entry point to enter instead, or any state for an instance whose saved state no longer exists. Omit it to enter a new instance at the initial state, or an existing one at its saved state.
</events>
</smllm>
The output shows three things about the engine:
- The texts are the same as those of the
smllmcommand: the engine is the same. - The stop is
block, because the agent is inREADand fired no event, so a host shows the agent the events and keeps it working. - Without
random, the engine draws random keys, so your keys differ from the page’s.
The engine keeps its state in memory. To keep it between runs, pass a storage as the third argument of Engine, such as nodeFileStorage from @six5536/smllm-wasm/node. The README of @six5536/smllm-wasm describes the whole API: storage, hosts that run command guards, and hosts that drive a model without tools.