Tidebase Tidebase GitHub Start self-hosting
Docs

How to use Tidebase with LangGraph

Use LangGraph’s own checkpointer for graph state — that’s what it’s for — and use Tidebase for what the checkpointer doesn’t give you: a queryable run record in your own Postgres, durable human approval gates, a side-effect replay contract, and a per-run cost ledger. The integration point is the node function: pass the Tidebase run context through the graph’s configurable, and wrap external writes and approvals inside nodes.

from langgraph.graph import StateGraph, START, END
from tidebase import Tidebase

tide = Tidebase()

def publish_node(state, config):
    run = config["configurable"]["tide_run"]

    decision = run.gate(
        "approve-publish",
        "Publish this draft?",
        data={"preview": state["draft"][:200]},
    )
    if not decision.approved:
        return {"published": False}

    run.step(
        "publish",
        lambda: cms.publish(state["draft"]),
        input={"draft": state["draft"]},
        side_effects=["cms"],
        idempotency_key=f"publish-{run.run_id}",
    )
    return {"published": True}

def workflow(run, input):
    graph = build_graph()  # your StateGraph with publish_node compiled in
    result = graph.invoke(
        {"topic": input["topic"]},
        config={"configurable": {"tide_run": run, "thread_id": run.run_id}},
    )
    return result

tide.run("research-and-publish", workflow, run_id=run_id, input={"topic": topic})

Tidebase is an open-source checkpoint layer for AI agents: wrap your steps, and failed runs resume from the last safe point — in your own Postgres, without moving execution into a new runtime.

Two layers, two jobs

This guide deliberately does not wrap graph.invoke() in one giant step. The two layers do different jobs:

  • LangGraph’s checkpointer persists graph state per super-step so an interrupted graph resumes at the right node with the right state. Keep it — use thread_id = run.run_id so the two layers share an identity.
  • Tidebase records the run as queryable rows in your Postgres (state, events, gates, usage), enforces the replay contract on side effects, and parks approvals durably.

If all you need is graph-state persistence, the checkpointer alone may be enough — the structural differences are laid out in Tidebase vs LangGraph checkpointers. Reach for both when your graph touches the outside world or needs humans in the loop.

Side effects inside nodes

A LangGraph resume re-executes the node that was interrupted. If that node had already half-fired an external write, you get a double-fire — unless the write sits behind a Tidebase step. The publish step above is checkpointed by name and input hash: when the node re-runs, a completed publish replays from Postgres instead of hitting the CMS again. A failed side-effecting step without an idempotency key parks as manual_review rather than being blindly retried.

Gates instead of interrupt

LangGraph’s interrupt pauses a graph for input, but the pending question lives in graph state. A Tidebase gate is a durable, exactly-once decision in Postgres: it survives restarts, is resolvable from Studio or a webhook channel (Slack, your product UI), and records who decided. The blocking run.gate(...) call inside a node works because the graph is already executing inside your own process — Tidebase isn’t running anything.

Recording model usage

LangGraph doesn’t meter your LLM calls; wherever a node calls a model, record it:

run.usage.record(
    kind="llm", provider="openai", model="gpt-4.1-mini",
    input_tokens=cb.input_tokens, output_tokens=cb.output_tokens,
)

The honest tradeoffs

  • Tidebase does not execute your code. Something must re-invoke the workflow after a crash — a Tidebase queue, a recovery webhook, or your own infrastructure. Re-invocation with the same run_id is always safe.
  • Two persistence layers means two things to operate. That’s real. The split earns its keep when you need gates, the side-effect contract, or the run record outside the framework; skip it when you don’t.
  • Alpha, opt-in auth. Self-hosted alpha — set TIDEBASE_API_KEY before exposing the server beyond localhost.

Repo: https://github.com/BlueprintLabIO/tidebase · See also: Tidebase vs LangGraph checkpointers