English | 中文
A Rust implementation of LangGraph's state machine framework for building LLM agent applications.
Juncture preserves the core programming model -- StateGraph + Pregel execution engine -- while leveraging Rust's type system for compile-time safety and true multi-core parallelism. The API design stays close to LangGraph Python so that developers familiar with the original can transfer their knowledge directly.
This project would not exist without LangGraph. The programming model, execution semantics, and API design are derived from LangGraph's architecture. We are deeply grateful to the LangChain team for their pioneering work on graph-based agent orchestration.
During development, we also studied and learned from several Rust community efforts in the same space:
| Project | What We Learned |
|---|---|
| rust-langgraph | Early proof that LangGraph's model maps well to Rust traits |
| oxidizedgraph | Patterns for async graph execution with tokio |
| cognis | Multi-crate workspace organization for agent frameworks |
Each of these projects explored different trade-offs in adapting LangGraph to Rust. Juncture builds on their insights while taking a different approach: prioritizing semantic equivalence with LangGraph Python over novel abstractions.
The performance advantage comes primarily from Rust's runtime characteristics, not from clever engineering. When your nodes execute in a tokio::spawn work-stealing scheduler instead of Python's single-threaded asyncio, parallelism happens naturally.
That said, the benchmarks do show meaningful differences. Here is a summary (full methodology and caveats in benchmarks/README.md):
| Scenario | Juncture (Rust) | LangGraph (Python) | Speedup |
|---|---|---|---|
| Sequential 3000 nodes | 16.9 ms | 7,652 ms | 452x |
| Streaming 10000 nodes | 142.7 ms | 78,085 ms | 547x |
| Fanout 100 subjects | 1.35 ms | 566 ms | 420x |
| Wide State 1200 iter | 95.4 ms | 3,593 ms | 38x |
| Conditional Routing 50 | 0.7 ms | 3.9 ms | 5.6x |
Important: These numbers reflect framework overhead on no-op nodes. Real-world LLM calls dominate execution time, making the framework overhead difference negligible in practice. The value of Rust here is more about type safety, memory efficiency, and deployment flexibility than raw speed.
#[derive(State)]-- compile-time typed state with per-field reducers (replace,append,ephemeral,last_write_wins,custom)StateGraphbuilder withadd_node,add_edge,add_conditional_edges- Pregel execution engine with
tokio::spawn+JoinSetfor true parallel execution CowState<S>(Arc-based copy-on-write) to avoid expensive state clonesCommand<S>for node return routing (goto, resume, parent navigation)Sendfor dynamic fan-out to parallel subgraphsinterrupt!macro for human-in-the-loop workflows- Streaming with 9 modes (Values, Updates, Messages, Custom, Debug, Tools, Checkpoints, Tasks, Multi)
Storetrait for cross-thread persistent key-value storageRetryPolicyandTimeoutPolicyper nodeDurabilitymodes (Sync, Async, Exit)Runtime<C>for context injection, heartbeat, and execution info
MemorySaverfor developmentSqliteSaverandPostgresSaverfor production- Serialization backends: JSON, MessagePack, JSON+, AES-256-GCM encrypted
- Incremental write persistence (
put_writesper task completion)
ChatModeltrait with providers: OpenAI, Anthropic, OllamaTooltrait withToolNode, interceptors, and transformerscreate_react_agent()factory for ReAct-style agentsAgentMiddlewarechain (loop detection, error handling)SubagentToolandAgentRegistryfor multi-agent delegationRetryingModelwrapper with configurable retry policy- Structured output extraction via
schemars CircuitBreakerfor provider health tracking
juncture-tracing -- OpenTelemetry integration:
- Node-level spans with token usage metrics
GraphCallbackHandlerlifecycle callbacks- Cross-service trace context propagation
juncture-telemetry -- Langfuse-compatible embedded observability:
- One-line setup:
init().with_store("db").with_langfuse_from_env().with_dashboard(8123).install().await? - SQLite-backed trace/observation/session storage
- Embedded web dashboard with trace tree, observation detail, cost/token charts
- Langfuse cloud export (auto-reads
LANGFUSE_*env vars) - Langfuse-compatible REST API (traces, sessions, stats, ingestion)
- OTLP HTTP ingest
- Multi-agent tracing via nested observation trees
- RAII auto-flush on drop + signal handler for graceful shutdown
| Local Dashboard | Langfuse Cloud Sync |
|---|---|
![]() |
![]() |
![]() |
- Browser (
wasm32-unknown-unknown) viawasm-bindgen - Edge CLI (
wasm32-wasip1) via WASI - Edge HTTP server via Fermyon Spin
- Feature-gated:
wasmfeature flag,web-timeforInstant,getrandomwithwasm_js
Add to your Cargo.toml:
[dependencies]
juncture = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }use juncture::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(State, Clone, Debug, Serialize, Deserialize)]
struct MyState {
#[reducer(replace)]
count: i32,
#[reducer(append)]
history: Vec<String>,
}
async fn increment(state: &MyState) -> Result<MyState::Update> {
Ok(MyStateUpdate {
count: Some(state.count + 1),
history: Some(vec![format!("count -> {}", state.count + 1)]),
})
}
#[tokio::main]
async fn main() -> Result<()> {
let mut graph = StateGraph::<MyState>::new();
graph.add_node("increment", increment);
graph.add_edge(START, "increment");
graph.add_edge("increment", END);
let compiled = graph.compile()?;
let result = compiled.invoke(MyState { count: 0, history: vec![] }, &RunnableConfig::default()).await?;
println!("Result: {:?}", result);
Ok(())
}use juncture::prelude::*;
use juncture::tools::Tool;
use juncture::llm::ChatOpenAI;
// Define tools, create agent
let model = ChatOpenAI::new("gpt-4o")?;
let agent = create_react_agent(model, vec![my_tool])?;
let result = agent.invoke(input, &config).await?;See the examples/ directory for 15 progressive examples, from basic state machines to production LLM pipelines.
| # | Example | Concepts |
|---|---|---|
| 01 | State Machine | #[derive(State)], linear graph, invoke() |
| 02 | Counter Reducers | #[reducer(append)], #[reducer(last_write_wins)] |
| 03 | Conditional Routing | Router trait, PathMap, add_conditional_edges |
| 04 | Chat Basic | MessagesState, Message, MockChatModel |
| 05 | Tool Calling | Tool trait, ToolNode, manual agent graph |
| 06 | Streaming | stream(), StreamMode, StreamEvent |
| 07 | Human-in-the-Loop | CompileConfig interrupts, interrupt_before |
| 08 | Checkpoint Resume | MemorySaver, compile_with_checkpointer(), thread_id |
| 09 | Error Recovery | Result propagation, error handling with ? |
| 10 | Basic Chat | ChatOpenAI, single/multi-turn with real LLM |
| 11 | Streaming Chat | ChatModel::stream, token-by-token display |
| 12 | Tool Execution | bind_tools, tool execution loop with real LLM |
| 13 | ReAct Agent | create_react_agent, weather + math tools |
| 14 | Multi-turn | Conversation history accumulation, system prompts |
| 15 | Structured Output | ToolChoice::Required, JSON entity extraction |
| 16 | Telemetry | init() builder, Langfuse dashboard, cloud export, real LLM + tools |
| -- | Deep Research | Multi-agent research assistant (separate package) |
| -- | WASM Example | Browser-based graph execution via wasm-bindgen |
| -- | WASM Edge CLI | WASI standalone binary |
| -- | WASM Edge Server | Fermyon Spin HTTP edge service |
# Run any mock example (no API key needed)
cargo run -p juncture-simple-example --bin 01_state_machine
# Run a real LLM example (requires .env with OPENAI_API_KEY)
cargo run -p juncture-simple-example --bin 13_react_agentjuncture/
crates/
juncture/ # Facade crate -- LLM providers, tools, prebuilt agents
juncture-core/ # Channel system, StateGraph, Pregel engine, Node/Edge
juncture-derive/ # #[derive(State)] proc-macro
juncture-checkpoint/ # MemorySaver, SqliteSaver, PostgresSaver
juncture-tracing/ # OpenTelemetry integration
juncture-telemetry/ # Langfuse-compatible embedded observability
juncture-store/ # Cross-thread persistent key-value storage
benchmarks/ # Juncture vs LangGraph performance comparison
examples/ # 16 examples + deep-research + WASM demos
design/ # Architecture design documents (11 modules)
# Build everything
cargo build --workspace --all-features
# Run all tests
cargo test --workspace --all-targets --all-features
# Lint (zero warnings policy)
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Format check
cargo fmt --all -- --check
# Run benchmarks
cargo bench -p juncture-benchmarksJuncture takes a specific position among Rust LangGraph implementations:
| Aspect | Juncture | Other Rust Ports |
|---|---|---|
| Goal | Semantic equivalence with LangGraph Python | Novel abstractions or subsets |
| State system | #[derive(State)] proc-macro with per-field reducers |
Manual trait impls or dynamic maps |
| Channel model | Static, compile-time verified | Dynamic or simplified |
| Execution | Full Pregel with field-version scheduling | Simplified sequential or basic parallel |
| Feature coverage | HITL, subgraphs, Send, streaming, checkpoints, Store | Partial coverage |
| Observability | Langfuse-compatible embedded dashboard + cloud export + OTLP | Not typically supported |
| WASM | Browser + WASI + Spin edge | Not typically supported |
| Maturity | Early stage, design-driven | Varies |
The trade-off is clear: Juncture prioritizes completeness and compatibility over simplicity. If you need a lightweight graph executor, other options may be more suitable. If you want to port a LangGraph application to Rust with minimal semantic changes, Juncture is designed for that.
- Production hardening (error recovery, resource limits)
- Performance optimization for wide-state scenarios
- Additional LLM providers
- Graph visualization tooling
- LangGraph Platform API compatibility
Contributions are welcome. Please ensure:
cargo clippy --workspace --all-targets --all-features -- -D warningspasses with zero warningscargo fmt --all -- --checkpasses- All tests pass
- No
unwrap(),todo!(), orunimplemented!()in committed code
Licensed under either of Apache License, Version 2.0


