Build durable AI agents in Go β a LangGraph-style workflow engine with checkpoints, tools, memory, and local-LLM support. One static binary. Typed state. Production-first.
GraphGo lets you model an AI agent as a graph of nodes and edges β with cycles, conditional routing, retries, tools, and human approvals β and runs it on a durable engine that checkpoints every step. Define workflows in YAML or the typed Go SDK, ship them as a single static binary, and resume, replay or inspect any run.
β· workflow "support-triage" β’ provider=mock β’ store=~/.graphgo/checkpoints.db
βΆ run support-triage (run_369d2a11e978a6fb)
β classify
β done 0s
β checkpoint ckpt_c1057f65
β classify ββΆ technical [technical]
β technical
β done 0s
β checkpoint ckpt_38ae22a7
β technical ββΆ end
β run completed
β status=completed steps=2 run=run_369d2a11e978a6fb
- π§© LangGraph-style, native Go β nodes, edges, conditional routing, and cycles with compile-time typed state (Go generics), not stringly-typed dicts.
- πΎ Durable by default β every super-step is checkpointed to in-memory or SQLite (pure-Go, no CGO). Crash, resume, and continue.
- βΈοΈ Human-in-the-loop β pause a run for approval, persist it, and resume β even in a different process.
- π οΈ Tools & agents β a ReAct-style agent loop with a tool registry whose schema is MCP- and OpenAI-compatible.
- π Any model β OpenAI, Anthropic, Gemini, Ollama / local models, any OpenAI-compatible endpoint, plus a deterministic mock for tests.
- π YAML or Go β prototype in YAML, or drop into the typed SDK. Same engine underneath.
- π¦ One binary β
graphgo run workflow.yaml. No Python, no runtime, no services. Cross-compiles everywhere. - π Observability β streaming execution logs, time-travel replay, run history, and Mermaid/SVG diagram export.
# Go 1.22+
go install github.com/arbazkhan971/graphgo/cmd/graphgo@latest
# Homebrew
brew install arbazkhan971/tap/graphgo
# Docker
docker run --rm ghcr.io/arbazkhan971/graphgo:latest version
# From source
git clone https://github.com/arbazkhan971/graphgo && cd graphgo && make installgraphgo init my-agent # scaffold a starter workflow.yaml
cd my-agent
graphgo run workflow.yaml # runs offline with the mock provider β no API key needed
graphgo visualize workflow.yaml # print a Mermaid diagramPoint it at a real model by exporting a key and selecting a provider:
export OPENAI_API_KEY=sk-...
graphgo run workflow.yaml --provider openai --model gpt-4o-mini
# or fully local, no keys:
graphgo run workflow.yaml --provider ollama --model llama3.2# research-agent.yaml
workflow:
name: research-agent
state:
topic: string
notes: array
answer: string
nodes:
research:
type: llm
system: "You are a meticulous research assistant."
prompt: "Research {{topic}}. Produce 3-5 key facts as short notes."
output: notes
append: true
review:
type: human_approval # pauses for human approval
message: "Approve the notes for '{{topic}}'?"
final:
type: llm
prompt: "Write a clear answer about {{topic}} using: {{notes}}"
output: answer
edges:
- { from: start, to: research }
- { from: research, to: review }
- { from: review, to: final }
- { from: final, to: end }graphgo run research-agent.yaml --set topic="graph databases"package main
import (
"context"
"fmt"
graphgo "github.com/arbazkhan971/graphgo"
)
type State struct {
Topic string `json:"topic"`
Answer string `json:"answer"`
}
func main() {
g := graphgo.NewGraph[State]()
g.AddNode("write", graphgo.LLMNode(graphgo.LLMConfig[State]{
Provider: graphgo.NewMock("Graphs model relationships as nodes and edges."),
System: "You are concise.",
Prompt: func(s State) string { return "Explain " + s.Topic },
Apply: func(s State, r *graphgo.Response) (State, error) { s.Answer = r.Content; return s, nil },
}))
g.SetEntryPoint("write")
g.SetFinishPoint("write")
res, _ := g.Run(context.Background(), State{Topic: "graph theory"})
fmt.Println(res.State.Answer)
}Conditional edges and cycles are first-class:
g.AddConditionalEdge("research", func(_ context.Context, s State) string {
if len(s.Notes) >= 3 {
return "done"
}
return "more"
}, map[string]string{"more": "research", "done": "write"}) // loops back to researchgraphgo visualize code-review.yaml emits a GitHub-renderable Mermaid diagram (or JSON / SVG with -f):
flowchart TD
analyze[["analyze"]]
triage{"triage"}
escalate{{"escalate"}}
report[["report"]]
start(("start"))
end(("end"))
start --> analyze
analyze --> triage
triage -.->|high| escalate
triage -.->|low| report
escalate --> report
report --> end
classDef llm fill:#ede7f6,stroke:#4527a0,color:#311b92;
classDef human fill:#fff3e0,stroke:#e65100,color:#bf360c;
classDef router fill:#fce4ec,stroke:#ad1457,color:#880e4f;
classDef terminal fill:#eceff1,stroke:#455a64,color:#263238;
class analyze llm;
class triage router;
class escalate human;
class report llm;
class start terminal;
class end terminal;
Every step is checkpointed, so runs survive crashes and can be paused/resumed:
# A workflow with a human_approval node pauses and persists:
graphgo run research-agent.yaml --set topic="rust"
# βΈ paused for approval: Approve the notes for 'rust'?
# resume with: graphgo run research-agent.yaml --resume run_ab12 --approve
graphgo run research-agent.yaml --resume run_ab12 --approve # continues to completion
graphgo inspect # list every run and its status
graphgo replay run_ab12 --state # time-travel through each step's stateIn Go, the same durability is one option away:
store, _ := graphgo.OpenSQLite("runs.db")
res, _ := g.Run(ctx, initial, graphgo.WithStore(store), graphgo.WithRunID("job-42"))
if res.Interrupted() {
// ...later, even in another process:
res, _ = g.Resume(ctx, store, "job-42", graphgo.Decision{Approved: true})
}| Type | YAML type
|
SDK builder | Purpose |
|---|---|---|---|
| LLM | llm |
LLMNode |
Prompt a model and write the result to state |
| Agent | (SDK) | AgentNode |
ReAct-style loop that calls tools until done |
| Tool | tool |
ToolNode |
Deterministically invoke one tool |
| Router | router |
LLMRouter / AddConditionalEdge
|
Branch on state or an LLM decision |
| Human approval | human_approval |
HumanApprovalNode |
Pause for human input, then resume |
| Parallel | parallel |
ParallelNode |
Fan out concurrent branches and merge |
| Retryable task |
task (+retry) |
WithRetry |
Any node with an automatic retry policy |
| Provider | Constructor | Env var | Notes |
|---|---|---|---|
| OpenAI | graphgo.OpenAI(...) |
OPENAI_API_KEY |
Also any OpenAI-compatible server via BaseURL (vLLM, LM Studio, Together, Groqβ¦) |
| Anthropic | graphgo.Anthropic(...) |
ANTHROPIC_API_KEY |
Claude Messages API, tool use |
| Gemini | graphgo.Gemini(...) |
GEMINI_API_KEY |
Google Generative Language API |
| Ollama | graphgo.Ollama(...) |
β | Local models, no key, streaming |
| Mock | graphgo.NewMock(...) |
β | Deterministic, offline β powers the test suite |
| Command | Description |
|---|---|
graphgo init [dir] |
Scaffold a starter workflow.yaml
|
graphgo run <file.yaml> |
Run a workflow, streaming and checkpointing each step |
graphgo visualize <file.yaml> |
Export Mermaid (-f mermaid|json|svg) |
graphgo replay <run-id> |
Time-travel through a past run's checkpoints |
graphgo inspect [run-id] |
List runs, or show one run's history and final state |
graphgo new <name> |
Scaffold a Go SDK agent program |
graphgo doctor |
Check environment and provider configuration |
graphgo version |
Print the version |
Global flags: --provider, --model, --base-url, --store (memory or a path), --no-color.
| GraphGo | LangGraph (Python) | Temporal | Hand-rolled Go | |
|---|---|---|---|---|
| Language | Go | Python | Go/Java/β¦ | Go |
| Deployment | single binary | Python runtime | server + workers | your call |
| Typed state | β generics |
|
β | β |
| Graph + cycles | β | β |
|
|
| Checkpoints / resume | β | β | β | β |
| Human-in-the-loop | β | β |
|
β |
| Built-in LLM providers | β | β | β | β |
| YAML workflows | β | β | β | β |
| Local-model friendly | β |
GraphGo borrows LangGraph's excellent mental model (stateful graphs for long-running agents) and Temporal's durability mindset (checkpoint, replay, recover), and delivers them as one dependency-light Go module.
Single core, Intel Xeon @ 2.20GHz, Go 1.22:
| Benchmark | Time/op | Allocs/op |
|---|---|---|
| 3-node linear graph | 4.3 Β΅s | 11 |
| 3-node linear + checkpoint each step | 14.8 Β΅s | 29 |
| 10-iteration conditional cycle | 8.6 Β΅s | 25 |
That's ~230,000 graph runs/sec with zero overhead beyond your node logic. Run them yourself with make bench.
cmd/graphgo the CLI
graphgo.go the ergonomic SDK facade (NewGraph, LLMNode, HumanApprovalNode, β¦)
pkg/graph the execution engine: nodes, edges, cycles, events, retries, interrupts
pkg/state dynamic Dict state + templating for YAML workflows
pkg/checkpoint Store interface + in-memory and SQLite backends
pkg/llm provider interface + OpenAI / Anthropic / Gemini / Ollama / Mock
pkg/tools tool registry + built-ins (calculator, http_get, now)
pkg/runtime streaming console printer + time-travel replay
pkg/visualize Mermaid / JSON / SVG exporters
pkg/workflow YAML workflow parser and graph builder
examples runnable YAML workflows and Go SDK programs
-
examples/go/chatbotβ a minimal single-node bot -
examples/go/researchβ cycles + conditional edges -
examples/go/code-reviewβ a tool-using agent loop -
examples/go/human-approvalβ durable pause & resume with SQLite -
examples/go/ollamaβ a fully-local workflow -
examples/*.yamlβ YAML versions of the above
- Streaming token output surfaced through node events
- First-class MCP client (connect to any MCP tool server)
- Postgres checkpoint store
- Subgraphs / nested graphs as nodes
- Long-term memory store (vector-backed)
- Web UI for run inspection and replay
- State reducers / channels for merge-heavy parallelism
See an issue or open one to shape it.
Contributions are very welcome β see CONTRIBUTING.md. In short:
go test ./... # everything runs offline with the mock provider
make lint # golangci-lint
make bench # benchmarksMIT Β© 2026 Arbaz Khan.