0051 Lmcp A1 Team Backend Git Queue
ADR-0051: Team backend is git-queue (not Postgres) — LMCP-A1
Amendment (2026-07-03, construct-9oi4.7.11) — the "git-queue vs Postgres backend" framing of this ADR is superseded by a provider-kind contract. Substrate owns the queue/run-store CONTRACT; git-queue is the zero-dependency DEFAULT
kind:'queue'provider (not deprecated or deleted); Postgres is an OPTIONALkind:'queue'provider selected explicitly through the extension registry (ADR-0052 manifest taxonomy). Selection resolves through the registry by kind inlib/intake/queue.mjs; the silentpostgres→gitalias is removed and a git-queue push failure returns a typed non-durable disposition rather than a swallowed success.lib/orchestration/run-store-postgres.mjsis tagged as an UNREGISTEREDkind:'queue'provider scaffold. The decision body below is retained for history.
- Date: 2026-07-03
- Status: accepted
- Deciders: Gerald Dagher (owner), Construct maintainers (cx-architect)
- Relates to: ADR-0020 (local orchestration runtime), ADR-0021 (provider worker backend), ADR-0046 (modular org runtime merge)
- Tracking: LMCP-A1
Problem
The team/enterprise queue declaration is contradictory. Documentation declares Postgres as the queue backend for team and enterprise deployment modes, but the runtime silently routes all queue operations through the git-queue implementation instead:
lib/storage/backend.mjs:9-14—createSqlClient()returnsnullby design; no Postgres client is ever constructedlib/intake/queue.mjs:48-52— resolvesteamandenterprisemodes to'git'at runtimelib/intake/queue.mjs:70-75— aliases'postgres'to'git'so callers requesting Postgres silently get git-queuelib/orchestration/run-store-postgres.mjs— a complete-shaped Postgres store implementation exists but is unreachable through any current dispatch pathlib/deployment-mode.mjs— declaresqueue: 'postgres'for team and enterprise, contradicting the runtime aliases above
The practical consequence is a latent double-claim hazard: two agents can concurrently claim the same work item because the documented correctness guarantee (Postgres row-level locking) is never engaged, and the actual backend (git-queue) provides only best-effort distributed claims with push-failure swallowing:
lib/intake/git-queue.mjs:115— push failure caught and discarded (claim proceeds anyway)lib/intake/git-queue.mjs:141-152— push failure incomplete()similarly swallowed
The mismatch between declaration and runtime is a class of fabrication-adjacent hazard: operators reading the docs build a mental model of Postgres-backed ACID claims, but the system never delivers it.
Decision
Option B — formally designate git-queue as the team backend.
The Postgres path is not adopted at this time. The git-queue implementation already carries all solo and team traffic; it is operationally proven and imposes no external service dependency on operators.
Specifically:
-
Update all deployment-mode declarations — change
queue: 'postgres'toqueue: 'git'for team and enterprise inlib/deployment-mode.mjs. Remove the'postgres'→'git'alias fromlib/intake/queue.mjs(it becomes unnecessary and misleading). -
Fix claim() push-failure swallowing —
lib/intake/git-queue.mjsclaim and complete paths must propagate push failures rather than silently succeeding. A failed push means the claim or completion is not durable; callers must see the error and retry or surface it. -
Document the correctness envelope — the git-queue provides a single-writer guarantee only when backed by dolt's underlying storage (dolt's row-level locking prevents concurrent writes to the same branch). Distributed multi-agent claim correctness is best-effort: a second agent may read a stale queue state before the first agent's push is visible. This limitation is to be documented explicitly in
docs/guides/concepts/queue-backends.mdand in the queue module's JSDoc. -
Gate Postgres behind a future provider-plugin path —
lib/orchestration/run-store-postgres.mjsis retained as-is. Postgres is available as a future queue-backend plugin (see ADR-0052's provider manifestkind: 'queue'); no operator can activate it until that plugin path is complete and migrations are shipped. The implementation file must carry a@unreachableJSDoc tag until then. -
File LMCP-G beads for lease/retry improvements on top of git-queue: optimistic locking via claim-token comparison, exponential backoff on push failure, and a lease-expiry mechanism so crashed agents release claimed items.
Alternatives considered
-
Option A — implement Postgres fully: adopt
run-store-postgres.mjs, add a migration runner, document theDATABASE_URLenv requirement. Provides ACID claim correctness. Rejected because no customer need for full Postgres ops burden is validated yet; it adds a mandatory external service dependency for team users who currently need none; and the correctness gap in git-queue can be narrowed significantly by fixing push-failure propagation and adding the lease model (LMCP-G). -
Option C — make git-queue ACID by introducing a local lock file: a file-system advisory lock prevents concurrent claims on the same machine. Rejected because it does not help multi-machine distributed agents, and dolt's storage already provides single-machine serialization; the real problem is multi-agent distributed concurrency that needs the lease model (LMCP-G), not a local lock.
Consequences
lib/deployment-mode.mjsteam/enterprisequeuefield changes from'postgres'to'git'; the alias inlib/intake/queue.mjsis removed.lib/intake/git-queue.mjsclaim and complete paths throw (or return a typed error) on push failure instead of swallowing it; callers that currently rely on silent success must be updated.lib/orchestration/run-store-postgres.mjsis tagged@unreachableand remains in-tree as a scaffold for the future plugin path.- Operators reading documentation will see an accurate description of the git-queue backend with its correctness envelope and known limitations.
- LMCP-G1..G10 (lease/retry/backoff improvements), LMCP-O2 (queue observability), and LMCP-O4 (claim-state reconciliation) are unblocked by this decision.