How Tinyflows-Powered Workflow Graphs Enable Durable Checkpointed Automation in OpenHuman

OpenHuman leverages the tinyflows engine to execute automation workflows as immutable directed graphs, persisting execution state after every node via SQLite-backed checkpoints that enable exact resumption after crashes, pauses, or system restarts.

OpenHuman’s automation layer is built on the open-source tinyflows engine, which transforms visual workflow designs into durable, resumable execution graphs. By integrating tinyflows-powered workflow graphs with a persistent checkpointing layer, OpenHuman ensures that long-running automations survive interruptions and can be audited step-by-step.

The Typed Directed Graph Architecture

At the core of OpenHuman’s workflow system is the immutable directed graph defined in tinyflows::model::WorkflowGraph. This structure describes nodes (triggers, agents, tools, and conditional branches) and their connections as a type-safe graph that can be validated before execution begins.

When a flow is saved, OpenHuman wraps the raw tinyflows graph in domain-specific types defined in src/openhuman/flows/types.rs. This wrapper adds essential metadata including a stable UUID, creation timestamps, and a reference to the checkpoint that tinyflows creates on each run. This separation between the pure graph model and OpenHuman’s persistence layer ensures that workflow definitions remain immutable while execution state evolves independently.

The Three-Stage Execution Pipeline

The tinyflows engine processes every workflow through three well-defined stages that enforce safety before any side-effects occur.

Validation via validate_all

Before compilation, the graph undergoes structural validation using tinyflows::validate::validate_all. This function guarantees structural correctness—such as ensuring exactly one trigger node exists—before any side-effects are allowed. Validation failures are caught early, preventing invalid workflows from ever reaching the execution phase.

Compilation and Checkpoint Storage

Once validated, tinyflows::compiler::compile transforms the graph into an executable representation. The compiled form is immediately stored in a checkpointer, which can be either the default tinyflows::graph::checkpoint::FileCheckpointer or OpenHuman’s custom SQLite-backed implementation in src/openhuman/flows/tinyflows/checkpoint_sqlite.rs. This compilation step ensures that the execution plan is serialized and ready for resumption at any point.

Execution with Capability Adapters

The tinyflows::engine::run function walks the compiled graph, invoking capability adapters defined in src/openhuman/flows/tinyflows/caps.rs. Each adapter implements a tinyflows trait—such as HttpClient, MemoryProvider, or ToolInvoker—by delegating to OpenHuman services. This architecture keeps the execution engine side-effect-free; actual HTTP requests, memory calls, and tool executions only occur when the adapters are invoked.

Checkpointing for Durable Automation

The checkpointer is the foundation of durability in OpenHuman’s workflow engine. After every node finishes execution, tinyflows writes a checkpoint record containing the node ID, current execution state, and any output required for later resumption to the SQLite database via tinyflows::graph::checkpoint.

If a process crashes or a user pauses the flow at an approval gate, the stored checkpoint allows the engine to resume exactly where it left off using tinyflows::graph::checkpoint::RunObserver::resume. Because checkpoints are persisted on disk, runs survive restarts, system failures, or user-initiated pauses. The run history is later exposed via the RPC surface (flows::ops::reconstruct_steps) and the UI’s workflow canvas, providing a step-by-step replay of execution.

Practical Implementation Examples

Defining a Tinyflows Workflow Graph

use tinyflows::model::{Node, NodeKind, WorkflowGraph};

let nodes = vec![
    Node { 
        id: "trigger".into(), 
        kind: NodeKind::Trigger, 
        name: "Schedule".into(), 
        config: json!({"cron":"0 9 * * MON"}) 
    },
    Node { 
        id: "http".into(),    
        kind: NodeKind::Tool,    
        name: "HttpRequest".into(), 
        config: json!({"method":"GET","url":"https://api.example.com/report"}) 
    },
];
let edges = vec![ ("trigger".into(), "http".into()) ];

let graph = WorkflowGraph { nodes, edges };

Source: Tinyflows graph definition in the vendored tinyflows crate.

Importing and Persisting in OpenHuman

use openhuman::flows::ops::{import_flow, persist_flow};

let import = import_flow(&graph).expect("validation ok");
let flow = persist_flow(import, workspace_id).expect("saved");
println!("Saved flow {} with id {}", flow.name, flow.id);

Source: src/openhuman/flows/ops.rs handles validation wrapping and persistence logic.

Running with Checkpointing

use openhuman::flows::tinyflows::caps::build_capabilities;
use tinyflows::engine::run;
use tinyflows::graph::checkpoint::FileCheckpointer;

// Build adapters that talk to OpenHuman services
let caps = build_capabilities(&core_context);

// Load the persisted checkpoint (or start fresh)
let checkpointer = FileCheckpointer::new("/tmp/flow_checkpoints.db");

// Compile the stored graph
let compiled = tinyflows::compiler::compile(&flow.graph).unwrap();

// Execute – each step writes a checkpoint
let outcome = run(&compiled, tinyflows::json::Value::Null, &caps)
    .with_checkpointer(&checkpointer)
    .execute()
    .await?;

Source: src/openhuman/flows/tinyflows/caps.rs provides the adapter builder, while src/openhuman/flows/tinyflows/checkpoint_sqlite.rs implements the durable storage backend.

Resuming a Paused Flow

use tinyflows::graph::checkpoint::RunObserver;

// Retrieve the last checkpoint for this flow run
let observer = RunObserver::load(&checkpointer, run_id).await?;
let resumed_outcome = observer.resume(&compiled, &caps).await?;

Source: tinyflows::graph::checkpoint APIs and OpenHuman’s wrapper integration.

Summary

  • Immutable graph model: Workflows are stored as typed tinyflows::model::WorkflowGraph instances that ensure structural correctness before execution.
  • Three-stage safety: Validation (validate_all), compilation (compile), and execution (run) occur sequentially to prevent invalid or unsafe workflows from running.
  • SQLite-backed durability: The custom checkpointer in src/openhuman/flows/tinyflows/checkpoint_sqlite.rs persists state after every node, enabling exact resumption via RunObserver::resume.
  • Side-effect isolation: Capability adapters in src/openhuman/flows/tinyflows/caps.rs map pure engine operations to OpenHuman services, keeping the core execution logic side-effect-free until adapters are invoked.

Frequently Asked Questions

What happens if a node fails during execution?

When a node fails, the checkpoint recorded immediately after the previous successful node remains intact. The workflow can be resumed from that last valid checkpoint after the underlying issue is resolved, without re-executing earlier steps. This behavior is managed by the RunObserver interface in tinyflows::graph::checkpoint and exposed through OpenHuman’s flows::ops::reconstruct_steps RPC surface.

How does checkpointing differ from simple execution logging?

Checkpointing captures the complete serializable state required to resume execution—including node outputs and execution context—whereas logging merely records events. OpenHuman’s SQLite checkpointer stores this state durably in checkpoint_sqlite.rs, allowing the engine to reconstruct the exact runtime environment and continue from any point, even after a system restart.

Can workflows survive a complete system restart?

Yes. Because checkpoints are persisted to disk via FileCheckpointer or the SQLite-backed implementation, workflow runs survive process crashes, container restarts, and system reboots. The RunObserver::load method retrieves the last checkpoint state from disk, and resume continues execution exactly where it stopped.

Why are capability adapters necessary in the OpenHuman integration?

Capability adapters in src/openhuman/flows/tinyflows/caps.rs implement tinyflows traits like HttpClient and MemoryProvider, bridging the pure, side-effect-free tinyflows engine with OpenHuman’s actual services. This separation ensures that the workflow graph itself remains immutable and testable, while side effects (HTTP calls, database writes) are isolated in adapter code that the engine invokes only during execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →