Every public item of the three packages, read from each package's interface file and
from the doc comments beside the code. Nothing here is written by hand, and a stale page
fails the build. Press / to filter.
moonstate
The graph, its builder, the steps a node returns, the two contracts a store or a saver implements, and the in-memory implementations that ship with them.
suberror GraphError
Why a run could not finish.
pub(all) suberror GraphError {
StepLimit(limit~ : Int32)
NodeFailed(node~ : String, message~ : String)
Interrupted(thread~ : String, value~ : @json.Json)
NotInterrupted(thread~ : String)
MissingThread(thread~ : String)
MissingNode(node~ : String)
Unreadable(reason~ : String)
Cancelled(thread~ : String)
Backend(reason~ : String)
}
StepLimit The run took limit supersteps without reaching an end.
NodeFailed A node raised, after its retries if it had any; message is the error text.
Interrupted A node asked to stop and wait for an answer. resume continues the thread.
NotInterrupted resume was called on a thread with nothing to continue.
MissingThread The thread has no checkpoint the saver knows about.
MissingNode A node routed the run to a name the graph does not define. The compile-time check cannot see the names a Goto, a Send or a branch picks, so the run reports them the first time one is taken.
Unreadable A checkpoint could not be decoded back into the graph's state type.
Cancelled A node cancelled the run, or the caller did before the next superstep.
Backend A saver, a store, an executor or a stream callback raised. The engine keeps going only where it can tell what went wrong, so this is where an outside failure ends up.
suberror Refused
Why a graph definition was refused when it was compiled. Every check here is structural: the nodes and edges are known then, the runs are not.
pub(all) suberror Refused {
NoNodes
NoEntry
UnknownEntry(node~ : String)
DuplicateNode(node~ : String)
DanglingEdge(from~ : String, to~ : String)
Unreachable(node~ : String)
NoExit
}
struct Builder
Accumulates nodes and edges, then checks them once in compile. Every method returns the builder, so a graph reads as one chain.
pub struct Builder[S, U] {
merge : (S, U) -> S
initial : S
mut entry : String?
nodes : Map[String, NodeSpec[S, U]]
edges : Array[(String, String)]
joins : Array[(Array[String], String)]
branches : Array[(String, async (S) -> Array[String])]
routed : Array[String]
duplicates : Array[String]
}
pub fn[S, U] Builder::branch(Self[S, U], String, async (S) -> Array[String]) -> Self[S, U]
Let route decide where to go after from. It is called with the state as merged at the end of the superstep, so it sees what the nodes wrote.
pub fn[S, U] Builder::compile(Self[S, U], String, saver? : &Saver, store? : &Store, executor? : &Executor, limit? : Int32) -> Graph[S, U] raise Refused
Fold the collected structure into a graph, refusing a definition that cannot run: an edge to nowhere, a node nothing reaches, a graph with no end.
pub fn[S, U] Builder::edge(Self[S, U], String, String) -> Self[S, U]
Run to after from, unconditionally.
pub fn[S, U] Builder::entry(Self[S, U], String) -> Self[S, U]
Make name the node a run starts at. Without it a graph of exactly one node starts there, and anything larger is refused.
pub fn[S, U] Builder::join(Self[S, U], Array[String], String) -> Self[S, U]
Run to once every node in from has run in the same superstep, and not before: a join is a gate, not just an edge.
pub fn[S, U] Builder::new(merge~ : (S, U) -> S, initial~ : S) -> Self[S, U]
A builder whose state starts at initial and whose every merged write goes through merge. initial is what a run starts from before the input is merged in, so it is the empty state: zeroed counters, empty lists.
pub fn[S, U] Builder::node(Self[S, U], String, async (S, Run) -> Array[Step[S, U]], retry? : Retry) -> Self[S, U]
Add node name. run gets the state and a Run handle and returns the steps it wants applied; retry gives it more than one try.
pub fn[S, U] Builder::routed(Self[S, U], String) -> Self[S, U]
Declare that name is entered at run time — by a Goto, a Send or a route — rather than by an edge. Without this the compile-time reachability check has no way to tell such a node from dead code, and refuses the graph.
pub fn[T : @json.ToJson + @json.FromJson, V : @json.ToJson + @json.FromJson, S, U] Builder::subgraph(Self[S, U], String, Graph[T, V], enter~ : (S) -> T, leave~ : (T) -> U) -> Self[S, U]
struct Checkpoint
One durable point in a thread's run: the state after step supersteps, the tasks the next superstep will run, and the writes already collected for them. A Saver stores these verbatim; the engine is the only thing that reads the state back into its own type.
pub(all) struct Checkpoint {
thread : String
id : String
parent : String?
seq : Int32
step : Int32
state : @json.Json
pending : Array[Pending]
interrupt : @json.Json?
created_at : Int64
writes : Array[TaskWrite]?
}
id Unique within the thread, and ordered by the saver.
parent The checkpoint this one was written on top of, or None for the first.
seq How many checkpoints the thread had written before this one. id is derived from it, so an update that forks the thread does not collide with the checkpoints it forked away from.
step The superstep this checkpoint is about to run.
state The whole state, encoded.
pending The tasks of the next superstep, in the order their writes will be merged.
interrupt Set when the superstep was cut short by Step::Interrupt.
writes The writes recorded for this checkpoint's tasks. A saver fills this in when it reads the checkpoint back; it is never part of what put stores.
pub fn Checkpoint::from_json(@json.Json, @json.JsonPath) -> Self raise @json.JsonDecodeError
pub fn Checkpoint::to_json(Self) -> @json.Json
struct Context
What an entrypoint hands its body: the run it belongs to, and the machinery a task call needs. One context per attempt; call advances it, which is how a replayed run lines its calls up with the recorded ones.
pub struct Context {
run : Run
saver : &Saver?
executor : &Executor
graph : String
mut index : Int32
recorded : Map[String, @json.Json]
}
pub fn Context::index(Self) -> Int32
How many task calls this attempt has made so far.
pub fn Context::run(Self) -> Run
The run this entrypoint belongs to.
struct Done
What an executor reports back for one work item. Exactly one of values and error is set: a failed node's error travels as text, because the executor may not share a process with the graph that would have to match on it.
pub(all) struct Done {
values : Array[@json.Json]?
error : String?
}
struct Entrypoint
A graph built from one function: the function is the only node, and every Task::call inside it is a durable step of that node.
pub struct Entrypoint[I, O] {
graph : Graph[Frame, Frame]
encode : (I) -> @json.Json
decode : (@json.Json) -> O raise GraphError
}
enum Event
What a run reports while it happens. The stream modes of a graph engine are one type here: the caller takes the variants it needs and ignores the rest.
pub(all) enum Event[S, U] {
Values(S)
Updates(node~ : String, U)
Custom(node~ : String, @json.Json)
Started(node~ : String)
Finished(node~ : String)
Interrupted(@json.Json)
}
Values The whole state after a superstep.
Updates One node's write, as it is folded in. Several arrive per superstep.
Custom Whatever a node passed to Run::emit.
Started A node has been handed to the executor.
Finished A node finished and its steps are about to be applied.
Interrupted The run stopped and is waiting for resume with this value.
struct Frame
The state a functional graph runs on: the input the entrypoint was called with, and the value it returned. Keeping both as JSON is what makes a half-finished entrypoint resumable — the input is still there to replay from.
pub(all) struct Frame {
input : @json.Json
output : @json.Json?
}
pub fn Frame::from_json(@json.Json, @json.JsonPath) -> Self raise @json.JsonDecodeError
pub fn Frame::to_json(Self) -> @json.Json
struct Graph
A compiled graph: the nodes, the edges between them, and the three services a run leans on — a saver for checkpoints, a store for long-term memory, and an executor for where the nodes run. Everything structural was checked in compile, so a run only fails for reasons the run itself discovers.
pub struct Graph[S, U] {
name : String
merge : (S, U) -> S
initial : S
entry : String
nodes : Map[String, NodeSpec[S, U]]
edges : Map[String, Array[String]]
joins : Array[(Array[String], String)]
branches : Map[String, Array[async (S) -> Array[String]]]
saver : &Saver?
store : &Store?
executor : &Executor
limit : Int32
}
pub fn[S, U] Graph::limit(Self[S, U]) -> Int32
The superstep bound this graph was compiled with.
pub fn[S, U] Graph::name(Self[S, U]) -> String
struct Item
One remembered value: a space, a key inside it, and the value itself.
pub(all) struct Item {
space : Array[String]
key : String
value : @json.Json
}
struct MemorySaver
A saver that keeps everything in memory: enough for a run that has to survive a failed node, not the process it runs in.
pub struct MemorySaver {
threads : Map[String, Array[Checkpoint]]
writes : Map[String, Array[TaskWrite]]
}
pub fn MemorySaver::new() -> Self
struct MemoryStore
A store that keeps everything in memory, for tests and for short-lived processes.
pub struct MemoryStore {
items : Map[String, Item]
}
pub fn MemoryStore::new() -> Self
struct Pending
A node queued for a superstep, as a checkpoint records it. input is the state the node is handed: absent means the state as merged at that point, present means a Send gave it its own.
pub(all) struct Pending {
node : String
input : @json.Json?
}
pub fn Pending::from_json(@json.Json, @json.JsonPath) -> Self raise @json.JsonDecodeError
pub fn Pending::to_json(Self) -> @json.Json
struct Retry
How often a node is tried before its failure ends the run, and how long to wait between tries. attempts counts the first try: attempts of 1 means no retry at all.
pub(all) struct Retry {
attempts : Int32
delay : Int32
}
struct Run
What a node is handed besides the state: which thread it belongs to, which superstep it is running in, where to put long-term memory, how to report progress, and how to stop. A run handle belongs to one task. Calling cancel on it stops the thread at the next superstep boundary — never in the middle of a node, so what a node wrote before asking to stop is kept.
pub struct Run {
thread : String
step : Int32
checkpoint : String
answer : @json.Json?
store : &Store?
saver : &Saver?
cancelled : @ref.Ref[Bool]
emit : (@json.Json) -> Unit
writes : Array[TaskWrite]
}
pub fn Run::answer(Self) -> @json.Json?
The answer resume was called with, for the node that asked for it.
pub fn Run::cancel(Self) -> Unit
Ask the thread to stop at the next superstep boundary.
pub fn Run::checkpoint(Self) -> String
The checkpoint this superstep's writes are filed under. A node that keeps its own progress in Saver::put_writes needs it.
pub fn Run::emit(Self, @json.Json) -> Unit
Report progress to whoever is streaming this run. Does nothing when nobody is.
pub fn Run::is_cancelled(Self) -> Bool
pub fn Run::saver(Self) -> &Saver?
The saver the graph was compiled with, if any. A node that runs another graph hands it on, so the child's checkpoints land next to the parent's.
pub fn Run::step(Self) -> Int32
The superstep this task belongs to, counting from zero.
pub fn Run::store(Self) -> &Store?
The long-term store the graph was compiled with, if any.
pub fn Run::thread(Self) -> String
pub fn Run::writes(Self) -> Array[TaskWrite]
The writes already recorded for this checkpoint's tasks. A node that runs other durable steps itself — the functional form does — reads them back here to skip work a crashed run had already finished.
struct Snapshot
A checkpoint seen through the graph's own state type: what a caller asks for when it wants to look at a thread, not to resume it.
pub(all) struct Snapshot[S] {
thread : String
id : String
parent : String?
step : Int32
state : S
next : Array[String]
interrupt : @json.Json?
created_at : Int64
}
next The nodes the next superstep would run. Empty when the run finished.
enum Step
What one node contributes to the superstep it took part in. A node returns a list of these, applied in the order it wrote them: writes fold into the state, Goto and Send decide who runs next, and Interrupt stops the run there.
pub(all) enum Step[S, U] {
Write(U)
Goto(String)
Send(String, S)
Interrupt(@json.Json)
}
Write Fold U into the state with the graph's merge function.
Goto Leave the node's declared successors behind and run this node next.
Send Queue this node for the next superstep with its own input state, which is how a fan-out hands each branch a different slice of the work.
Interrupt Stop before the next superstep and wait for resume. The node that returned this runs again from the same input when the thread resumes, and reads the answer through Run::answer.
pub fn[S : @json.FromJson, U : @json.FromJson] Step::decode(@json.Json) -> Self[S, U] raise GraphError
Read back a step written by encode. A step that cannot be decoded cannot be applied, and guessing at it would corrupt the state silently.
pub fn[S : @json.ToJson, U : @json.ToJson] Step::encode(Self[S, U]) -> @json.Json
The wire form of a step: what a Work carries back from wherever the node ran. Executors only ever see this, which is what lets a node run in another process without the graph itself crossing the boundary.
struct Task
A step that is recorded when it finishes. Calling one through Task::call writes its result to the checkpoint before the entrypoint moves on, so a run that replays after a crash reuses the result instead of doing the work twice.
pub struct Task[I, O] {
name : String
run : async (I) -> O
retry : Retry?
}
struct TaskWrite
The steps one task produced, kept apart from the checkpoint record so that a finished task is never run twice after a crash.
pub(all) struct TaskWrite {
task : String
values : Array[@json.Json]
}
pub fn TaskWrite::from_json(@json.Json, @json.JsonPath) -> Self raise @json.JsonDecodeError
pub fn TaskWrite::to_json(Self) -> @json.Json
struct Work
One node's turn to run, as the executor sees it. input is a function so the state is encoded only when it actually crosses a process boundary; run is what an in-process executor calls, and what an executor that ships the task elsewhere ignores — it finds the node by graph and node on the far side.
pub(all) struct Work {
graph : String
node : String
thread : String
step : Int32
attempt : Int32
input : () -> @json.Json
run : async () -> Array[@json.Json]
}
trait Executor
Where a superstep's tasks run. The engine hands over the whole superstep at once, which is what makes a fan-out parallel; the results come back in the order the work was given, so merging stays deterministic.
pub(open) trait Executor {
async fn each(Self, Array[Work]) -> Array[Done]
}
trait Saver
Where a thread's checkpoints live. The engine writes one per superstep and reads the last one back to resume, so an implementation only has to store records and hand them back in order.
pub(open) trait Saver {
async fn get(Self, String, id? : String) -> Checkpoint?
async fn put(Self, Checkpoint) -> Unit
async fn put_writes(Self, String, String, String, Array[@json.Json]) -> Unit
async fn list(Self, String, limit? : Int32, before? : String) -> Array[Checkpoint]
async fn delete(Self, String) -> Unit
}
asyncDrop a thread entirely: checkpoints and writes.
trait Store
Long-term memory that outlives a thread: spaced key-value pairs with a search over them. A node reaches the store through Run::store.
pub(open) trait Store {
async fn get(Self, Array[String], String) -> Item?
async fn put(Self, Array[String], String, @json.Json) -> Unit
async fn delete(Self, Array[String], String) -> Unit
async fn search(Self, Array[String], query? : String, filter? : Map[String, @json.Json], limit? : Int32) -> Array[Item]
}
asyncItems under space (or below it), newest first. query is matched against the item's text, filter against its top-level object fields.
Functions
pub fn[I : @json.ToJson + @json.FromJson, O : @json.ToJson + @json.FromJson] entrypoint(String, async (I, Context) -> O, saver? : &Saver, store? : &Store, executor? : &Executor, limit? : Int32) -> Entrypoint[I, O] raise Refused
Wrap body as a graph with one node. The body may call tasks; each call is recorded, and a resumed run replays the body from the start and reuses the results. The body itself has to be deterministic apart from those calls — replaying it is how the run gets back to where it stopped.
pub fn[I, O] task(String, async (I) -> O, retry? : Retry) -> Task[I, O]
Values
pub let default_limit : Int32
How many supersteps a run may take before it is stopped. This is the bound a graph made of cycles relies on; it is LangGraph 1.2's default recursion limit of 25 (libs/langgraph/langgraph/constants.py), kept the same so a graph ported from there needs no new number.
pub let inline : &Executor
The default executor: every task in this process, at the same time.
pub let retry : Retry
Three tries with no pause, the default a durable workflow wants for a step that talks to something outside the process.
moonstate/sql
Checkpoints and long-term memory on a SQL backend: one implementation, written against the dialect the driver reports.
suberror Failed
A row that could not be read back into the record it was written from: a checkpoint's state, a task's steps, a store item, or a column that is null when the record needs it.
pub(all) suberror Failed {
Unreadable(column~ : String, reason~ : String)
}
pub fn Failed::report(Self) -> String
The text an error carries, for the Backend a caller ends up matching on.
struct Names
The three tables this package keeps, so that two savers can share one database without sharing one set of names.
pub(all) struct Names {
checkpoint : String
write : String
item : String
}
checkpoint One row per checkpoint.
write One row per task that finished inside a superstep.
item One row per key of the long-term store.
struct SqlSaver
Checkpoints in a SQL table. One connection, one mutex: the driver's connection carries one exchange at a time, and two runs sharing a saver would otherwise interleave their statements on it. Compile a graph with one saver per connection.
pub struct SqlSaver[T] {
driver : T
names : Names
statements : Statements
lock : @async.Mutex
dialect : @moondb.Dialect
}
pub fn[T] SqlSaver::dialect(Self[T]) -> @moondb.Dialect
The dialect the driver reported, which is the one the statements were written for.
struct SqlStore
Long-term memory in a SQL table: spaced keys, each holding one JSON value. The store is meant to outlive a thread, so it shares the connection discipline with the saver: one connection, one mutex, one saver and one store per connection.
pub struct SqlStore[T] {
driver : T
names : Names
statements : Statements
lock : @async.Mutex
dialect : @moondb.Dialect
}
Functions
pub fn[T : @moondb.AsyncDriver] saver(T, names? : Names, dialect? : @moondb.Dialect) -> SqlSaver[T]
A saver over driver, using the tables in names. Nothing is written until setup has run: a saver does not create tables behind the caller's back, because a service account is often not allowed to. ``moonbit nocheck let saver = @sql.saver(connection) saver.setup() let graph = builder.compile("job", saver=saver) ` dialect` overrides what the driver reports. A driver that numbers its parameters has to report its own dialect for this to be unnecessary; until it does, naming it here is the difference between one statement working and the first one being rejected by the server.
pub fn[T : @moondb.AsyncDriver] store(T, names? : Names, dialect? : @moondb.Dialect) -> SqlStore[T]
A store over driver, using the tables in names. setup on a saver of the same tables creates what this needs. dialect overrides what the driver reports, the way saver's does.
Values
pub let names : Names
The names a saver and a store take when nothing else is asked for.