Decisions

ADR-173: Player Queue as Unattended-Execution Substrate; Relationship to Warp

Same gating shape as ADRs 171 + 172. Reclassify to Accepted when warp#1762 Phase 1.1 (`type:deploy_request` schema) ships, demonstrating the *typed-ticket-on-Warp* pattern works in production.

#173

ADR-173: Player Queue as Unattended-Execution Substrate; Relationship to Warp

Status: Proposed Date: 2026-05-07 Tracker: warp#1881 (Phase 0 of EPIC warp#1838) Cross-refs: ADR-170, ADR-171, ADR-172, ADR-166

Status note — Proposed not Accepted

Same gating shape as ADRs 171 + 172. Reclassify to Accepted when warp#1762 Phase 1.1 (type:deploy_request schema) ships, demonstrating the typed-ticket-on-Warp pattern works in production.

Context

ADRs 170-172 commit to the substrate's threat model, execution model, and wire contract. They do not commit to where the queue lives. Two options were considered:

  1. A separate "factory queue" service — its own Postgres database, its own HTTP API, its own ops surface.
  2. Ride on Warp — the existing fleet work-item queue, with new typed-ticket discriminators.

Option 1 is the substrate paper's earlier draft framing (the paper used the term player queue and named a target home factory-cli/pkg/playerqueue/). Option 2 emerged as the Fleet Runtime paper (warp#1859) walked through the architectural overlap with ADR-166 and warp#1763 — both of which are already riding on Warp's atomic-claim primitive for type:deploy_request and type:ci_run tickets.

The risk of option 1 is operational sprawl — two queue services with similar lifecycle semantics (claim, heartbeat, lease, complete) but separate storage, separate UIs, separate audit trails, separate maintenance.

The risk of option 2 is conflation — substrate-shaped jobs (large inputs, long traces, structured outputs) sitting in the same table as agent work-items (a few hundred bytes of human-readable prose).

This ADR commits to option 2 with explicit boundaries.

Decision

The substrate's queue rides on Warp's existing atomic-claim primitive. Pipeline jobs become typed Warp items with kind: pipeline_job (or a similar discriminator); the inputs/outputs/scope/trace are stored in fields that the existing item-table accommodates.

What this means concretely

  • Storage. Same Postgres backend, same items table, same SELECT … FOR UPDATE SKIP LOCKED claim semantics. New columns or sidecar tables for the substrate-specific fields (inputs_manifest, scope, outputs_manifest, trace_bundle_uri).
  • API. Substrate jobs expose /api/v1/jobs/* endpoints (alongside Warp's existing /api/v1/items/*). The endpoints share the same auth and the same identity gates but expose substrate-shaped operations (reservations, contract validation, output manifests).
  • Identity. Workers authenticate with the same agent-token scheme Warp already uses. Per-claim tokens (substrate paper §6.5 Layer 1) are issued by the vault, not by Warp's auth layer.
  • Storage of large artefacts. Folder inputs/outputs go to an object store (per ADR-172), referenced from the Warp item by URI + SHA-256. The Warp items table never carries large blobs directly.
  • Audit retention. Trace bundles ride the substrate's own retention policy (≥ 90 days per ADR-170); the Warp item itself retains its lifecycle metadata indefinitely (claim/heartbeat/complete events).

Naming

The substrate's earlier paper drafts used the term player queue to distinguish it from the existing gyrum-labs/factory-queue repo (a JSON-file backlog, unrelated). This ADR clarifies: player queue and Warp are the same store with separate ticket-type semantics. Throughout downstream ADRs and code, prefer:

  • "Warp queue" for the existing Postgres backend.
  • "type: pipeline_job" (or sister discriminators like type:deploy_request, type:ci_run) for substrate-shaped tickets.
  • Avoid "player queue" as a substantive in new prose — it was scaffold vocabulary while the architectural relationship was unclear; ADR-173 obsoletes it.

Don't conflate with gyrum-labs/factory-queue. That repo exists and is named confusingly close — it's a JSON-file backlog of project ideas, unrelated to the substrate. The substrate's queue is Warp, not factory-queue.

Relationship to ADR-166 and warp#1763

ADR-166's type:deploy_request and warp#1763's planned type:ci_run are sibling ticket types. They share:

  • The same atomic-claim primitive.
  • The same heartbeat/lease shape.
  • The same audit substrate.

They differ in:

  • Schema. Each ticket type has its own structured spec (a deploy carries slug+image-tag+manifest-ref; a CI run carries workflow-path+commit-sha+resolved-inputs).
  • Workers. Receivers and executors (ADR-166) handle deploy-shape work; runner agents (warp#1763) handle CI-shape work; players (this ADR's vocabulary) handle the general substrate-shape work.

The types are separate; the queue is one. Future agents should not conflate them but also should not separate the queue.

Consequences

What becomes easier

  • Single audit trail across all fleet work. A merge fires a webhook → ADR-166 receiver files a type:deploy_request → executor claims → deploy completes → all of that is observable in the same /operate UI as substrate-shaped pipelines and CI runs.
  • Operator's mental model collapses. "Where's that job?" always answers from warp-ready or /operate, regardless of work type.
  • Cross-type orchestration is trivial. A pipeline can file a Warp ticket that triggers a deploy that completes and triggers a CI run. All in one queue, all in one audit log.

What gets harder

  • Schema migrations to the items table touch all consumers. Adding a new column for substrate-specific data needs to be backwards-compatible with existing agent work-items. Migrations must be reviewed across consumer lists.
  • Throughput sharing. Substrate jobs (potentially high-frequency dropped-baton sweeps) and agent work-items share the same atomic-claim infrastructure. Postgres can comfortably handle ~1000 jobs/min on the existing backend (per substrate paper §5.8 capacity planning); we are two orders of magnitude below that ceiling. If substrate work ever approaches that ceiling, the right move is partitioning by priority or type, not horizontal sharding.

What stays out of scope

  • A separate factory-cli/pkg/playerqueue/. The earlier draft of warp#1838 named this; this ADR retracts that target. Player-queue-shaped Go code lives inside warp's existing api codebase, not as a separate package.
  • Multi-region / multi-tenant queues. Single-region Warp Postgres is the substrate. Multi-region is a future concern when fleet scale demands it.
  • Replacing Warp's existing items table with anything else. This ADR assumes Warp's atomic-claim primitive stays; if Warp is ever rebuilt (fleet-internal replatform), the substrate's queue rebuild rides that work, not its own schedule.

Compliance / how reviewers check

A PR adding a new ticket type for substrate-shaped work satisfies this ADR iff:

  • The ticket type extends Warp's existing kind/type discriminators rather than introducing a parallel storage path.
  • Large artefacts (inputs, outputs, trace bundles) are stored in the object store, referenced by URI + SHA-256, not embedded in the items table.
  • The ticket's auth path uses Warp's existing agent-token scheme; per-job credentials come from the vault, not from extra Warp-level tokens.
  • The ticket's lifecycle (claim → heartbeat → complete or fail) maps onto Warp's existing primitives without inventing new ones.

Cross-link

The substrate paper §2.4 walks through this two-surface separation (Warp = coordination; the substrate's queue = runtime); this ADR retracts the separate-store implication of that section in favour of shared-store-with-separate-discriminators. The Fleet Runtime paper (warp#1859, merged) Chapter 3 §3.1 is the up-to-date vocabulary mapping. ADR-166 is the smallest-scope precedent: type:deploy_request is already a Warp ticket type today.