Skip to main content

Documentation Index

Fetch the complete documentation index at: https://cognis.vasanth.xyz/llms.txt

Use this file to discover all available pages before exploring further.

Linear chains break down once you need branching, loops, or human-in-the-loop. Graph<S> is Cognis’ answer: a stateful workflow where each step is a node that reads typed state and decides what happens next. Pregel-style execution, generic over your state struct.

What it is

A graph has three parts:
  • State — a struct of yours that travels between nodes. You implement GraphState, which says how an Update is applied to the state.
  • Nodes — async functions that read the state and emit NodeOut { update, goto }.
  • A start node — declared with .start_at("name"). The engine walks edges from there until a node returns Goto::end().
The compiled graph is itself a Runnable<S, S>, so it composes with everything else.

When to reach for a graph

  • You need a tool-calling loop with custom termination logic.
  • You want time travel — checkpoint after every step, rewind, branch.
  • You need human-in-the-loop pauses at specific points.
  • Branches need to run in parallel and rejoin.
  • You want to subgraph and namespace state.
For a plain “model + tools + loop” agent, AgentBuilder is faster — it builds the graph for you. Drop down to graphs when the agent shape isn’t enough.

Quick example

A graph that increments a counter until it hits 5:
use cognis::prelude::*;

#[derive(Default, Clone, Debug)]
struct State { count: u32 }

#[derive(Default, Clone)]
struct Update { count: u32 }

impl GraphState for State {
    type Update = Update;
    fn apply(&mut self, u: Update) { self.count += u.count; }
}

#[tokio::main]
async fn main() -> Result<()> {
    let tick = node_fn::<State, _, _>("tick", |s, _ctx| {
        let cur = s.count;
        async move {
            if cur >= 5 {
                Ok(NodeOut { update: Update { count: 0 }, goto: Goto::end() })
            } else {
                Ok(NodeOut { update: Update { count: 1 }, goto: Goto::node("tick") })
            }
        }
    });

    let graph = Graph::<State>::new()
        .node("tick", tick)
        .start_at("tick")
        .compile()?;

    let final_state = graph.invoke(State::default(), RunnableConfig::default()).await?;
    println!("count: {}", final_state.count);
    Ok(())
}
node_fn::<S, _, _>(name, async closure) wraps an async closure into a Node<S>. The closure receives (&S, &NodeCtx) and returns Result<NodeOut<S>>.

State and reducers

Two ways to define state.
Spell out how updates apply. Most explicit; works everywhere.
#[derive(Default, Clone, Debug)]
struct State {
    messages: Vec<Message>,
    counter: u32,
}
#[derive(Default, Clone)]
struct Update {
    messages: Vec<Message>,
    counter: u32,
}
impl GraphState for State {
    type Update = Update;
    fn apply(&mut self, u: Update) {
        self.messages.extend(u.messages);
        self.counter += u.counter;
    }
}

Control flow

Nodes pick the next step by returning a Goto:
ConstructorEffect
Goto::node("name")Single successor.
Goto::end()Terminate.
Goto::halt()Pause without ending.
Goto::Multiple(vec!["a", "b"])Parallel fan-out.
Goto::Send(vec![("worker", payload)])Cross-graph dispatch.
You can also declare static edges and skip the routing logic in the node:
let g = Graph::<S>::new()
    .node("plan", plan)
    .node("execute", execute)
    .node("review", review)
    .edge("plan", "execute")
    .edge("execute", "review")
    .start_at("plan")
    .compile()?;

How it works

  • Each invoke is a sequence of supersteps. The engine runs all eligible nodes in a step, applies their updates atomically, then advances.
  • Goto::Multiple triggers parallel fan-out. All targets run in the same superstep.
  • State is applied, not replaced. Your apply(&mut self, Update) controls how. Reducers are just apply written for one field at a time.
  • Compiled graphs are Runnable<S, S>. Wrappers (with_retry, with_timeout) and observability work the same as anywhere else.

Beyond the basics

See also

Agents and the loop

The shape AgentBuilder compiles into.

Control flow

Fan-out, conditional edges, subgraphs.

Reference → cognis-graph

Full API surface.