Tidebase Tidebase GitHub Start self-hosting
v0.5The checkpoint layer for AI agents

Your agent crashed at step 7. Resume it.

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.

$ docker compose up -d postgres && pnpm dev

run_8fa3c1d0 · generate-report Running
plan checkpointed0.4s
fetch-sources replayed1.2s
write-report state: writing · 0.7running…
gate approve-report → approved webhook ops-agents cost $0.04 · 4.2k tok

Your agent works. Until a step flakes.

A demo only has to work once. Real runs have ten steps, flaky tools, and restarts. Tidebase makes every run durable, so the question “is it safe to rerun?” always has an answer.

The playable proof

Kill the process. Watch it resume.

A live run, checkpointing each step to Postgres. Kill it mid-flight, re-invoke with the same run id, and completed steps replay in 0ms.

run_8fa3c1d0· generate-reportReady
planllm
fetch-sourcestool
summarizellm
write-reporttool
steps re-executed after crash00 / 4 checkpointed
// event stream — press Run
the same workflow, two ways

Delete the glue.

without-tidebase.ts~180 lines
// dedupe so a retry can't double-charge
const seen = await redis.get(`${id}:charged`)
if (seen) return JSON.parse(seen)
await db.runs.update(id, { status: 'charging' })
try {
const res = await charge(card, amount)
await redis.set(`${id}:charged`, res)
await db.runs.update(id, { status: 'charged' })
} catch (e) {
// retry with backoff
await queue.enqueue('retry-charge', { id })
await db.runs.update(id, { status: 'failed' })
throw e
}
// on restart, figure out where we stopped
const done = await db.steps.where({ run: id })
with-tidebase.ts9 lines
const res = await ctx.step('charge-card', {
sideEffects: ['stripe.charge'],
replay: 'auto',
retries: 3,
}, () => charge(card, amount))
// state + resume: handled for you
manual idempotency & dedup keysreplay: 'auto'
The whole API

Five calls. That's the surface area.

generate-report.ts TypeScript
await tide.run('generate-report', { runId }, async (run) => {  const plan = await run.step('plan', makePlan)   const sources = await run.step('fetch-sources',    () => fetchSources(plan))   await run.state.set({ status: 'writing', progress: 0.7 })   const ok = await run.gate('approve-report', {    prompt: 'Send the generated report?'  })   return run.step('write-report',    () => writeReport(sources))})
tide.run() · run.step()

Wrap and checkpoint

Create or resume a run, then checkpoint each unit of work. Completed steps return from Postgres on replay.

run.state.set()

Stream live state

Push status and progress your product UI can subscribe to, in real time.

run.gate()

Pause for a human

Open a durable gate and wait. The run resumes only once a decision exists, and it resolves exactly once.

return run.step()

Finish the run

The last step runs once the gate clears, and its result is checkpointed too.

Human-in-the-loop

An approval queue, in the run.

The run pauses at a durable gate and waits for a decision — in Studio, your own app, or any webhook surface. It resolves exactly once, and every decision is audited.

run_8fa3c1d0· refund-agentRunning
load-accounttool
check-policyllm
approve-refundgate
issue-refundtool
send-receipttool
// event stream
Time travel

Scrub back to the step the bug entered.

Every step's state is versioned in Postgres. When a run produces a wrong answer, scrub the timeline to the exact step that poisoned it — no print statements, no rerunning.

run_8fa3c1d0· refund-agentWrong result

the run emitted a $2400 refund — scrub back to find where

emit-result
step 5 · emit-resultINHERITED
ctx.state @ emit-result
{
  "refund": 2400.00 ← wrong,
  "status": "awaiting-gate",
}
✕ wrong refund queued — 10× too high
// every step's state is checkpointed —
// scrub back to the exact step the bug entered.
✕ emit-result · refund 2400.00 (10× too high)
↳ extract-amount · misread $240.00 → $2400.00
✓ root cause found by replaying stored state
Subagents

Fan out. Join durably.

run.fanout() runs subagents as child runs, each checkpointed independently. If the parent resumes, existing children are reused — no duplicate subagents, no double spend.

run_8fa3c1d0· researcherRunning
planllm
4 parallel sub-agents
search-web
search-docs
search-code
search-issues
synthesizellm

run.fanout() runs 4 sub-agents in parallel · each checkpointed independently · joined on completion

// event stream
Cost & usage

Catch the runaway loop before the bill does.

Token and tool spend is attributed per run and per step — without proxying your LLM calls — so a scraping loop gone wrong shows up on a graph, not on an invoice.

run_8fa3c1d0· scraper-agentRunning
$0.00/ $5.00 cap · 0 tool calls
0% of budgetattributed per run · per step
// event stream
Every run comes with

Batteries included.

The run is the core object. Checkpoints, state and gates are just the start — these services come wired in, so there's no extra infrastructure to build.

Gates & events on your surfaces

Run events stream to any webhook you own — Slack via your bot, an internal tool, or your product — and a pending gate becomes a message your team can approve right there. Decisions happen where people already work, not in yet another dashboard.

Tidebase#ops-agents
generate-report is waiting on approve-report. Send the report to the customer?
Approve Reject

Leases & fencing

Only one worker ever owns a run at a time, and zombie workers are fenced from writing stale results — so a retry can't double-charge a customer or fire the same tool twice.

Recovery webhooks

When a run fails, Tidebase calls a signed endpoint in your app so it can re-invoke the workflow. Every attempt is recorded with its delivery status.

Fanout & forking

Subagents run as child runs, idempotent by name on resume. State is versioned, so snapshots, time travel, and forks fall out of the same model.

Cost & usage

Token spend attributed per run and per step — without proxying your LLM calls — so a runaway agent shows up on a graph before it shows up on the bill.

Capability audit

Every scope an agent asks for is recorded with who approved it and why. Audit metadata only — Tidebase never stores your keys.

Tidebase Studio

Agent observability, built in.

Every run is a full trace: step timeline, live state, pending gates, costs, and the event stream. Self-hosted, in your own Postgres, no secret custody.

0
invariant tests against real Postgres
0
calls in the core API
0
database — your Postgres
0
secrets stored, runtimes adopted
localhost:5173 / studio
Tidebase Studio — runs list and trace view
Ask your model

Don't take our word for it.

The docs ship as one file your assistant can read. Open Claude or ChatGPT with it preloaded and have it evaluate Tidebase against your stack — critically.

Read https://tidebase.dev/llms-full.txt. I build multi-step AI agent workflows. Evaluate whether Tidebase would help me, how it compares to Temporal, Inngest, LangGraph checkpointers, or hand-rolling status columns — and be critical: tell me when I should NOT use it.
Ask Claude Ask ChatGPT

llms-full.txt · every docs page also serves a raw .md twin

Make your runs durable.

Self-hosted from day one. Point the SDK at Postgres and wrap a workflow.

$ git clone https://github.com/BlueprintLabIO/tidebase