workflows: a host-agnostic Rust engine for agentic workflows (open source) A developer has released Workflows, an open-source Rust engine for agentic workflows that is host-agnostic and never hard-codes vendors. The engine models automations as directed graphs, supports human-in-the-loop approval as a primitive, and provides reliability features like per-node error handling and bounded retry. Workflows is a Rust library crate not a hosted service; the crate name on crates.io/GitHub is tinyflows that models an automation as a WorkflowGraph: a directed graph of typed nodes and edges. You build or generate that graph, it gets structurally validated, compiled into an opaque CompiledWorkflow, and lowered — once per run — onto tinyagents, a state-graph execution engine, via engine::run. model::WorkflowGraph - validate - compiler::compile - engine::run typed graph structural validated handle lowers onto tinyagents, drives to done Run state is a single JSON value shaped like { "run": { "trigger": … }, "nodes": { "": { "items": … } } }. A merge reducer folds each node's output under its own id, so independent branches never collide — which is what keeps parallel fan-out deterministic. The node catalog Kind What it does trigger Entry node that starts the workflow exactly one per graph ; firing mode is host-driven agent Runs an LLM agent turn, with optional chat-model / memory / tool / output-parser sub-ports tool call Invokes one specific integration action deterministically, no LLM involved http request Outbound HTTP request code Sandboxed user code JavaScript or Python output parser Parses/validates an upstream agent's output into a structured shape sub workflow Runs another workflow as a nested sub-graph and returns its output condition Two-way IF, emits on true/false switch Multi-way branch keyed by an expression result merge Fan-in barrier — waits for every wired predecessor before running split out Fan-out — emits one item per element of a list transform Pure, expression-based field mapping over the run state Data flows between nodes as arrays of items shaped { json, binary?, paired item? } — closer to n8n's item-based model than a plain function-composition DAG — and node config can reference the run scope with =-prefixed expressions like =item.name. The part I actually want to talk about: host-agnosticism Every place this engine would normally have to make a decision about who it's talking to — which LLM, which integration provider, how HTTP requests actually go out, where state gets persisted — is instead a Rust trait the embedding application implements: LlmProvider ToolInvoker HttpClient CodeRunner StateStore The crate itself never hard-codes a vendor and never makes a real network call on its own. For testing and for the hello workflow example below , a mock cargo feature ships deterministic in-memory implementations of all five traits. This matters for two reasons beyond the obvious "don't vendor-lock the library" one. First, credentials never touch the crate at all — connections are opaque connection ref values that only the host resolves into real secrets, so a bug in Workflows can't leak a token it never had. Second, it means the exact same compiled workflow can run against mocks in CI and against real capabilities in production, with no branching logic in the workflow definition itself. Human-in-the-loop as a primitive, not a callback you bolt on A node can be marked requires approval. When the engine hits one, the run pauses and shows up in RunOutcome::pending approvals — the host can surface that however it wants a UI diff, a Slack message, whatever , and engine::resume picks the run back up once a human approves it. We built this because "the agent drafts it, a person approves it, then it actually sends" is the whole trust model of the product feature this powers — it needed to be a real pause-and-resume in the execution model, not something the host application fakes by re-running the workflow from the top. Reliability, per node on error policy: stop, continue, or route a failure to a dedicated error output port, so you can build an actual recovery sub-graph instead of just failing the run. Bounded retry. Observability via tracing, plus a RunObserver hook and Run/ExecutionStep records if you want to build your own run inspector on top. Try it dependencies tinyflows = "0.1" use serde json::{Value, json}; use tinyflows::caps::mock::mock capabilities; use tinyflows::compiler::compile; use tinyflows::engine::run; use tinyflows::model::{Edge, Node, NodeKind, WorkflowGraph}; async fn main { js let graph = WorkflowGraph { nodes: vec Node { id: "t".into , kind: NodeKind::Trigger, type version: 1, name: "start".into , config: Value::Null, ports: vec , position: None, }, Node { id: "greet".into , kind: NodeKind::Transform, type version: 1, name: "greet".into , config: json { "set": { "greeting": "=item.name" } } , ports: vec , position: None, }, , edges: vec Edge { from node: "t".into , from port: "main".into , to node: "greet".into , to port: "main".into , } , ..Default::default }; let compiled = compile &graph .expect "compile" ; let outcome = run &compiled, json { "name": "Ada" } , &mock capabilities .await .expect "run" ; println "{}", serde json::to string pretty &outcome.output .unwrap ; } That's the hello workflow example in the repo — cargo run --example hello workflow --features mock. What's done, and what honestly isn't Done Phase A : the full node catalog, structural validation, per-run compilation, item-based data flow with =-expressions, linear/conditional/parallel-fan-out/merge-barrier routing, per-node error handling, human-in-the-loop approval gating, tracing observability, opaque credential references, and a versioned wire format schema version + per-node type version with a migrate framework for upgrades. It's forbid unsafe code , MSRV 1.85, Rust 2024 edition, and runs end-to-end against the mock capabilities behind a reference-workflow e2e suite. Not yet, and we'd rather say so than let you find out the hard way: A real jq/jaq-style expression engine — right now a minimal dotted-path evaluator handles the =item.name style expressions, as an interim. Retry backoff timing and per-node timeouts. Durable, checkpointed super-step replay — resume currently re-executes deterministically rather than resuming from a saved mid-run checkpoint. Visual and agent-first authoring tools that's a host-side concern — it's what the OpenHuman product layer does on top of this . The OpenHuman host integration itself — the piece that actually wires this engine to real LLMs and real integrations — is Phase B and lives in a separate repo that isn't public yet. License GPL-3.0-or-later. Worth knowing up front if you're evaluating it for something you plan to ship closed-source. If you build workflow/automation engines, or you're the kind of person who has opinions about where the "capability trait" boundary should sit in a system like this, I'd genuinely like to hear where you think we got it wrong: Workflows on GitHub.