Decisions

ADR-103: PM stays in warp; the factory is the operations cockpit

ADR-097 (accepted earlier today) committed to absorbing the operator-facing PM kanban into the factory at `gyrum.ai/pm/*` and shrinking `warp.gyrum.ai` to API + redirect-stub. The plan was five phases; only Phase 1…

#103

ADR-103: PM stays in warp; the factory is the operations cockpit

Status: Accepted Date: 2026-04-25 Supersedes: ADR-097 (project management lives in the factory; warp is the system of record) Related: ADR-077 (warp as session-independent agent coordination layer — warp's API and inbox role this ADR re-affirms), ADR-081 (UI shell consolidation grammar — names the factory as the operator's everything-pane and is the principle this ADR refines), ADR-096 (warp as fleet PM via one-way GitHub-issue mirror — the data substrate; surface ownership shifts but the data shape does not), ADR-098 (epics with structured requirements, agent-driven decomposition, live progress rollup — every affordance lands in warp's UI under this ADR), ADR-100 (factory growth boundaries — monolith UI, federated backends, audience-driven splits — THE structural backing for this ADR's "audience boundary justifies a separate shell" claim), and the operator memory note project_pm_in_factory.md whose "working system bar" (five concrete signals) is the empirical lens this ADR uses to read tonight's evidence.

Context

ADR-097 (accepted earlier today) committed to absorbing the operator-facing PM kanban into the factory at gyrum.ai/pm/* and shrinking warp.gyrum.ai to API + redirect-stub. The plan was five phases; only Phase 1 (read-only /pm dashboard at PR ai-frontend#235) ever shipped on the factory side, and that dashboard was unreachable for the operator tonight because it required a localStorage['warp.token'] minted at warp.gyrum.ai and the empty-state CTA didn't make the cross-origin token mint cheap. Meanwhile a different thing happened: roughly thirty PRs landed against gyrum-labs/warp/web/ tonight — epic UI, sort, filter persistence, audit timeline, markdown editor, bulk operations, mobile-responsive layout, claim flow, key management, a host of smaller affordances. The factory's /pm got SWARM-AH's read-only dashboard and SWARM-AV's epic UI; the warp web kanban got everything else. The reversal of intent was already de facto complete by where the work flowed; this ADR makes it explicit so future Claudes and future operators don't re-litigate it.

The structural backing is ADR-100, accepted earlier the same day, which named the audience-driven split rule: an audience that shares the operator's chrome belongs in the same shell; an audience with a different cadence, different interaction shape, or different interaction frequency earns a separate shell. Tonight's evidence reads through that lens cleanly. Warp's audience — the operator triaging items many times an hour, plus every AI agent on every machine claiming and heartbeating against the API — has a different interaction shape (high-frequency, kanban-and-keyboard, item-centric) from the factory's audience — the operator inspecting fleet state a few times a day across /operate, /infra, /ship, /experiment, /platform/*. ADR-097 read this as one audience because the operator was the same person; ADR-100's rule is sharper: same person wearing the same hat at the same cadence is one audience; same person wearing a different hat at a different cadence is two audiences. PM is the high-cadence, hat-on, keyboard-driven view; operations is the low-cadence, hat-off, scan-and-drill view. Two audiences, two shells.

The "working system bar" memory note (project_pm_in_factory.md) had already deferred Phase 2 (/pm/board port) tonight at 18:00 UTC pending five concrete signals (operators stop opening warp directly; agent fleet autonomous so warp UI rarely human-touched; cross-system actions in /operate need deep-link frequency; multiple operators share the system; design drift visible). None of the five are true today, and the work that landed in warp's web tonight pushes signal 1 the wrong way — operators have more reason to open warp directly because warp's UI shipped real new affordances. The deferral was the right call; this ADR upgrades it from "deferred until conditions are met" to "reversed because the conditions were the wrong question, the audience boundary is."

Decision

The operator-facing project-management UI lives at warp.gyrum.ai and stays there. The factory at gyrum.ai becomes the operations cockpit only: /infra, /operate, /ship, /experiment, /platform/[project], /playbooks, /sitemap, /explore, plus any future operator surface whose audience and cadence match the cockpit shape. The factory's /pm route (PR ai-frontend#235) is retired or repurposed as a thin /pm-summary widget showing top-N items + claim count for cross-context awareness — never a full board, never a substitute for opening warp.

The shape of the cut:

  • Warp owns every PM affordance, end to end. Kanban, epics + decomposition (per ADR-098), audit timeline, markdown editor, bulk operations, claim flow, key management, future PM features (burndown, capacity, calendar, anything else). New PM UI lands in gyrum-labs/warp/web/, never in gyrum-labs/ai-frontend/src/routes/pm/. Warp's web kanban is no longer "deprecated in favour of factory routes" — that ADR-097 framing is reversed in full; warp's UI is the canonical operator PM surface.

  • Warp keeps its system-of-record role from ADR-077 + ADR-097 unchanged. Postgres on warp-01 owns warp_items, warp_keys, external_id/source (ADR-096), parent_id/size/target_date (SWARM-AA), kind/requirements (ADR-098). The HTTP API at warp.gyrum.ai/api/v1/* keeps serving every client (factory included) the same way it does today. The cut ADR-097 made between "system of record" and "operator UI" still holds; what reverses is which surface owns the operator UI half.

  • The factory becomes the operations cockpit. Routes that stay: /infra, /operate, /ship, /experiment, /platform/[project], /playbooks, /sitemap, /explore, plus the existing chrome (command palette, mode pill, sidebar, design system, persistent auth). Routes that go: /pm retires (or shrinks to /pm-summary per below). Audience: operator inspecting fleet state at the few-times-a-day cadence ADR-100 named.

  • Cross-link affordances bridge the two shells without merging them. Three concrete affordances make the split cheap to live with rather than friction-laden:

    • Factory top nav: external link to warp.gyrum.ai labelled "PM". One click from anywhere in the factory to the canonical PM surface. Opens in same tab (preserves operator's "I went to PM" flow) or new tab per operator preference.
    • Factory /[project] tabs: "Tickets" tab embedding warp items filtered by repo. Calls GET /api/v1/items?tag=repo:<slug> and renders the queryable subset inline; "open in warp" link in the tab header for the full kanban. Same localStorage['warp.token'] already used by the existing /pm dashboard — no new auth.
    • Factory /operate: "PM activity" card — last 10 events from warp via SSE or poll. A one-way mirror surfacing claim/complete/release/block events in the operate-mode dashboard so the operator scanning fleet state sees PM activity in their peripheral vision without opening warp. Read-only; clicking through opens the warp item in warp.
  • Optional thin /pm-summary widget on the factory. If the cross-context awareness need persists after the three cross-link affordances ship, a thin widget at gyrum.ai/pm-summary (top-N items by recency + claim count by status) replaces the retired /pm dashboard. Not a board, not a kanban, not a substitute for warp — a glance-card. Decision deferred to the follow-up that retires /pm; either it ships as /pm-summary or /pm redirects to warp.gyrum.ai and the operate-mode PM activity card is the only in-factory PM affordance.

  • Auth: single sign-in at warp.gyrum.ai; factory accepts the same token via header or cross-origin allow-list. The token model is unchanged from ADR-097 in shape (localStorage['warp.token']) but reversed in origin: the operator's primary mint surface is warp.gyrum.ai/keys (where it already lives), and the factory's three cross-link affordances read the same token from the same storage key for cross-origin calls back to warp.gyrum.ai/api/v1/*. CORS allows https://gyrum.ai on warp's API the same way it allows https://warp.gyrum.ai; the existing contract holds. The "Connect to warp first" empty-state friction the operator hit tonight on localhost:5274/pm is by construction: the factory's PM-adjacent affordances are read-only mirrors of warp data, and a missing token means "go mint one in warp" — same cross-origin shape as today, just no longer pretending the factory is the home.

Consequences

What becomes easier:

  • Operator friction down. The operator's daily PM workflow stays in the surface that already has the design grammar, the keyboard discipline, and the affordances they want. Tonight's evidence is unambiguous — warp shipped epic UI, sort, filter persistence, audit timeline, markdown editor, bulk ops, mobile responsive in one session because warp is where PM actually lives. The factory's two PM PRs (read-only dashboard + epic UI) shipped slower and had reachability bugs (no GHCR image, no /pm route, no localStorage token cross-origin). Reversing the direction lets the work flow where it already wants to go.
  • Design consistency preserved. Warp's UI has its own design grammar (consistent across kanban, item drawer, keys, audit) that the factory would have to either rebuild (duplication) or migrate toward (cost). Refusing to fight the migration preserves both shells' internal consistency. The shared gyrum-ui component library (ADR-100's stable-widgets layer) keeps tokens and primitives aligned without forcing chrome alignment.
  • Auth simpler. The cross-origin shenanigans tonight (operator hit "Connect to warp first" on localhost:5274/pm because the token lives at warp.gyrum.ai) become non-issues once warp.gyrum.ai is the canonical PM surface. The factory's three cross-link affordances inherit the same localStorage['warp.token'] for their read-only mirrors; no change to the auth shape, just a re-statement of which origin is primary.
  • Warp remains its own product. ADR-097's dogfood angle ("we use our own SaaS shell to run our own PM") and the operator's "future product angle" (warp's kanban could later carve out as its own product offering) both stay open. Warp is already a candidate for SaaS extraction; keeping its UI standalone keeps that path cheap. Folding it into the factory as ADR-097 planned would have made extraction an UN-merge — a strictly more expensive operation than today's "warp is its own thing."
  • Future Claudes don't re-litigate. The audience-driven split rule from ADR-100 is the lens that resolves the question; this ADR cites it explicitly so a future "should we move PM into the factory?" question has a captured answer.

What becomes harder:

  • Cross-system context-switch when going from /operate to a warp item. The same context-switch existed before (warp → factory under ADR-097); the direction is now operate → warp instead of warp → factory. The three cross-link affordances (top-nav PM link, project Tickets tab, operate PM activity card) make the switch cheap, but it's a switch nonetheless — two URLs, two browser tabs in the operator's stack at any given moment. Mitigated but not eliminated.
  • Two repos to coordinate for any cross-cutting PM-shape change. Same two-repo dance ADR-097 named, just with the canonical UI now in gyrum-labs/warp instead of gyrum-labs/ai-frontend. New warp_items columns require schema migration in warp + API change in same PR + UI rendering in warp's web. The factory's three cross-link affordances pick up new fields opportunistically (read-only mirrors don't have to render every field).
  • The /pm-summary widget is a small ongoing maintenance surface. If we ship it (rather than full retirement of /pm), it's a thin SvelteKit page that has to keep working as warp's API evolves. Mitigated by the read-only-mirror shape — the widget doesn't have to handle write paths or schema migrations, only display top-N + counts.
  • The reversal itself is a cost paid now. ADR-097 was accepted earlier today, work was started against it, the operator memory note already reflected its direction. Reversing within 12 hours has a real "what changed?" tax that this ADR pays by being explicit about the audience-driven backing (ADR-100), the de facto evidence (where work flowed tonight), and the rebut-the-steelman section below.

What we have signed up to operate:

  • The warp web kanban at warp.gyrum.ai/ as the canonical operator PM surface, owned by gyrum-labs/warp/web/. Every new PM affordance lands here.
  • The factory's three cross-link affordances: top-nav "PM" external link, /[project] "Tickets" tab embedding warp items filtered by tag=repo:<slug>, /operate "PM activity" card showing last-10 events via SSE or poll. Each is a follow-up warp item filed at the bottom of this ADR.
  • Either retirement of factory /pm (302 to warp.gyrum.ai) or a thin /pm-summary widget — decision lands in the retire-or-repurpose follow-up.
  • The CORS contract on warp.gyrum.ai/api/v1/* allowing https://gyrum.ai origins (already shipped per ADR-097's Phase 1; this ADR keeps it).
  • The "new PM UI lands in warp, not in ai-frontend" rule documented at the top of gyrum-labs/ai-frontend/src/routes/pm/README.md (or its equivalent) and in dark-factory/docs/ARCHITECTURE.md next to the existing factory and warp paragraphs.

What we revisit:

  • If at least three of the five "working system bar" signals from project_pm_in_factory.md go true (operators stop opening warp directly; agent fleet autonomous; cross-system action frequency in operate; multi-operator onboarding tax; design drift visible), this ADR comes back to the table. The bar was the right empirical lens; tonight's evidence didn't trip it; if a future state does, the question genuinely re-opens.
  • If warp graduates to a SaaS product (the operator's "future product angle"), the cross-link affordances on the factory may need to switch from same-tenancy direct-API calls to a tenancy-aware shape. Until then, single-tenant direct calls are fine.
  • If the /pm-summary widget proves to be more useful than expected (operators using it as a primary glance-card and rarely clicking through to warp), revisit whether it should grow. The audience rule still holds — it doesn't grow into a kanban; it might grow into a richer summary card.

Alternatives considered

  • Keep ADR-097 as-is and ship the missing pieces (Phase 2-4). Build out /pm/board, /pm/keys, the warp.gyrum.ai → gyrum.ai/pm redirects, and accept tonight's reachability bugs as ordinary delivery cost. Lost on the empirical evidence: the work didn't ship that way tonight even when it was the named direction, and the friction points that emerged (no GHCR image, no /pm route, cross-origin token mint) were not the kind of bugs you fix once — they're shape-of-the-problem bugs that recur every release window. Also lost on ADR-100: the audience-driven rule reads warp's audience as distinct (high-cadence, hat-on, keyboard-driven) from the factory's (low-cadence, scan-and-drill), and ADR-097 was applying a same-audience consolidation principle to a two-audience boundary. Rejected — the right correction is structural, not phased.

  • Full merge of warp into factory: move the database, retire warp.gyrum.ai entirely, factory owns API + UI. Maximum consolidation. Lost on ADR-097's own dogfood angle ("warp is its own product, kanban could later carve out as a SaaS offering"). Folding warp's database, API, and UI into the factory makes the SaaS extraction path a UN-merge — strictly more expensive than today. Also lost on ADR-077's AI-worker inbox role: the warp protocol (claim/heartbeat/complete with SELECT … FOR UPDATE SKIP LOCKED) is a real concurrency contract that lives at the API layer, and consolidating it into the factory's backend is either a re-implementation or a rename, neither of which buys anything. Rejected — warp is its own product and should look like one.

  • Keep both UIs forever; let the operator pick. No deprecation either direction. Lost on the maintenance-bill argument and on the first-time-operator question (which URL is canonical?). Also lost on the "where do new PM features land?" question — without a captured rule, every PM feature gets re-litigated. Rejected — the audience rule lets us name a canonical surface (warp) without forcing the other surface (factory) to mirror it.

  • Move only the read view (factory /pm dashboard); keep all writes in warp. This is roughly what ADR-097's Phase 1 shipped. Lost on the same consequence-side argument ADR-097 itself made: as soon as the operator wants to claim, edit, or decompose from the factory dashboard, they have to context-switch to warp anyway. Either the read view earns its keep as a glance-card (which is the /pm-summary shape this ADR allows) or it grows into a write surface (which is what ADR-097 planned and this ADR reverses). Rejected as a permanent shape — accepted as a transitional shape until the cross-link affordances ship.

  • Steelman of ADR-097: "factory is the everything pane; one shell, one palette, one auth." The original argument was sound on its own terms — the factory's investment in command palette, design system, and chrome is real, and concentrating PM in the same shell would compound that investment. The rebut: the audience boundary that ADR-100 names is older and stronger than the everything-pane principle — the everything-pane principle applies within an audience, not across audiences. Warp.gyrum.ai already exists, has its own design grammar, operators visit it many times daily. We're not creating a new split; we're refusing to fight the one that already won.


Supersedes: ADR-097. The system-of-record cut from ADR-097 (warp owns the database and API; the factory consumes via localStorage['warp.token'] + cross-origin calls) survives unchanged. What this ADR reverses is the operator-UI cut: the operator-facing PM kanban stays at warp.gyrum.ai instead of migrating to gyrum.ai/pm, the factory's /pm route retires (or shrinks to /pm-summary), and warp's web UI is no longer deprecated. ADR-097 is marked Status: Superseded by ADR-103 with a header note pointing at this file. Related (composes with): ADR-077, ADR-081, ADR-096, ADR-098, ADR-100. ADR-100 is the structural backing — the audience-driven split rule is the lens through which tonight's evidence resolves the PM-surface question, and any future challenge to this ADR has to argue against ADR-100's rule rather than against the empirical evidence alone. Superseded by: