Skip to main content

command — node-directed routing

Normative: MODEL.md §15.

Routing on a graph is decided at plan time from state, before a node runs. A node that only discovers where to go next — an agent choosing its next tool — returns a command instead:

import { graph, channel, START, END, command, run } from "@ilmek/core";
import type { ChannelMap, StateOf } from "@ilmek/core";

// Stand-in model — returns a reply and whether it is finished.
const Model = { next: (messages: string[]) => ({ text: "final answer", done: true }) };

/** Agent state — a growing transcript. */
const AgentState = {
messages: channel.append<string>(),
} satisfies ChannelMap;

type AgentState = StateOf<typeof AgentState>;

const g = graph("agent", AgentState)
.node("agent", (state, ctx) => {
const reply = Model.next(state.messages);
// goto is planned AFTER update reduces — the routing decision sees what we just wrote
return command({ update: { messages: [reply.text] }, goto: reply.done ? END : "tools" });
})
.node("tools", (state, ctx) => ({ messages: ["(ran a tool)"] }))
.edge(START, "agent") // "agent" has no static outgoing edge — it always returns a goto
.edge("tools", "agent") // after tools, loop back to the agent
.compile();

const { status, state } = await run(g, { messages: ["hello"] });
console.log(status, state.messages); // "done" [ "hello", "final answer" ]

Semantics​

  • update is reduced exactly like a normal node return — same channels, same task-order rules.
  • goto (a node name, a list, END, or send(...) values) replaces the node's static outgoing edges for this superstep. A node with no static edges is legal when it always returns a goto.
  • A bare update and command({ update }) are equivalent; command exists only to carry goto alongside.
  • goto is planned after update reduces, so the routing decision sees the state the node just wrote.
  • A command whose goto is omitted falls back to static edges — so you can add a goto to one branch of a node without wiring every path through command.

Purity, preserved​

command keeps the purity rule intact: routers and guards still must not have side effects, because they still run at plan time. command is how a node that has run its journaled side effects then directs the flow — the one place routing and effects legitimately meet.