Vibes DIY
Vibes DIY / Docs
creator docs · pattern

Doc-status state machines

The problem: work that spans multiple actors — a user requests something, a server process picks it up, an external worker does a slow step, something posts the result. Traditionally: a queue, a consumer, a status API, and a dashboard to see what's stuck.

The pattern: one document per unit of work, with a status field walking a small state machine. Every actor reads the statuses it owns, does its step, and writes the doc forward.

The platform's own mention-builds bot (the story) coordinates three actors this way, through a mention doc in its requests database:

pending-build → building → built → replied
       with terminals: skipped (guardrails), build-failed, error
  • The vibe's scheduled handler ingests Bluesky mentions and writes pending-build docs (after guardrails — dedupe, caps, moderation).
  • A GitHub Actions cron picks up pending-build via the CLI, flips it to building, runs codegen, writes built (or build-failed).
  • The next scheduled tick sees built, posts the in-thread reply, writes replied with the permalink.

Same shape in meta-hub's publisher: publish-request docs walk pending → items-created → carousel-created → done | error, and you can inject work from anywhere the CLI runs:

shnpx vibes-diy db put --vibe you/your-hub --db requests '{
  "kind": "publish-request", "platform": "ig", "slug": "my-post",
  "images": ["https://…/slide-1.jpg"], "caption": "…",
  "status": "pending", "attempts": 0, "createdAt": "2026-07-07T00:00:00Z"
}'

The result lands on the same doc a few ticks later — status, permalink, or a lastError you can actually read.

Rules that keep it honest

  • One writer per status. Decide which actor may write each transition (the builder lane writes building/built/build-failed; the vibe writes everything else). Enforce the coarse boundary in access.js; keep the fine-grained transition table in code and in the doc's comment.
  • Statuses are terminal or owned — never ambient. A doc sitting in building for 45 minutes means its worker died; the next worker run retries it, bounded by an attempts counter on the doc. Timeouts and retry budgets live on the document, visible to the dashboard.
  • Failures are states, not exceptions. build-failed, error, skipped are first-class: nothing disappears, and the ledger doubles as the audit log.
  • Idempotent ids. Derive _id from the upstream event (the Bluesky record key, the request slug) so re-ingesting is a no-op instead of a duplicate.

What you get for free

Because the "queue" is a Fireproof database: the dashboard is a useLiveQuery over the same docs (see the operator console pattern), every transition syncs live to whoever's watching, the access function gates who can inject work, and the CLI gives any script or CI runner authenticated access with no extra API (the CI lane pattern).

Two actors never need to share a secret — they only share document shapes. The Bluesky credential in mention-hub never leaves the vibe's vault; the CI runner holds only a device cert. The database is the interface.