How to wire Tidebase into a Hono app
Tidebase’s recovery webhook handler is a fetch-style Request → Response function, and Hono hands you exactly that — so the integration is one line per route, no adapters. (The Tidebase server itself is built on Hono, which is why the seam is this clean.)
import { Hono } from 'hono'
import { Tidebase } from '@tidebase/sdk'
import { generateReport } from './workflows.js'
const tide = new Tidebase()
tide.workflow('generate-report', generateReport)
const app = new Hono()
const webhook = tide.webhook()
// Recovery webhook: signed run.resume payloads re-invoke the workflow.
app.post('/tidebase', (c) => webhook(c.req.raw))
// Enqueue a durable run; return immediately.
app.post('/reports', async (c) => {
const { topic } = await c.req.json()
const { run } = await tide.enqueue('generate-report', {
input: { topic },
dedupeKey: `report-${topic}`
})
return c.json({ runId: run.id })
})
// Progress for your UI, straight from your Postgres.
app.get('/reports/:runId', async (c) => {
const detail = await tide.runs.get(c.req.param('runId'))
return c.json({ status: detail.run.status, state: detail.state })
})
export default app
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.
The three roles in this file
/reportsenqueues. The route does no agent work —tide.enqueuewrites a durablequeuedrun (deduped, so a retried request can’t double-create) and returns therunIdinside any runtime’s request budget. This matters doubly on edge/serverless runtimes where Hono often lives: the long-running work must not be in the handler./tidebaseresumes. When a non-queue run stalls or fails, Tidebase POSTs a signedrun.resumepayload here; the SDK verifies the HMAC (TIDEBASE_WEBHOOK_SECRETon both sides — passc.req.rawso the body bytes reach it unparsed) and re-invokes the registered workflow with the samerunId. Completed steps replay from checkpoints per the replay contract./reports/:runIdreads. Live state, step results, and pending gates come fromruns.get— your product UI renders progress and approvals without touching the worker.
The worker
The honest tradeoff, stated plainly: Tidebase does not execute your code. A long-lived worker process claims queued runs and executes them:
import { Tidebase } from '@tidebase/sdk'
import { generateReport } from './workflows.js'
const tide = new Tidebase()
tide.workflow('generate-report', generateReport)
await tide.work({ queues: ['default'] })
If you deploy Hono to an edge runtime, the worker is the one piece that needs a normal Node process (a container, a VM, a long-running service) — tide.work polls and executes, which is not an edge-shaped job. Retries, backoff, lease expiry, and requeueing are Tidebase’s problem, not the worker’s.
One caveat for fully-edge deployments: the webhook route executes the remaining workflow inline, so it inherits the runtime’s CPU/time limits. If your unfinished tails can be long, prefer queues — the reconciler requeues a dead worker’s run without any webhook involved.
What Tidebase does not do here
- Alpha, opt-in auth. Set
TIDEBASE_API_KEYbefore exposing the Tidebase server beyond localhost, and the webhook secret before trusting/tidebase.
Repo: https://github.com/BlueprintLabIO/tidebase · See also: How to wire Tidebase into an Express app