Doctrine

Concurrent seats (cron OR operator-driven) must reserve ticket numbers via `feed-append --reserve` OR use a two-phase file-then-confirm protocol

inter-agentfeed-protocol

Concurrent seats (cron OR operator-driven) must reserve ticket numbers via feed-append --reserve OR use a two-phase file-then-confirm protocol

The rule

Any seat filing into a shared ticket-number namespace (e.g. CMB-NNNN, FLT-YYYYMMDDNN, HWD-NNNN, ORA-NNNN) MUST resolve the next available number through one of two protocols whenever a second seat might also be filing into that namespace within the same minute. This applies equally to cron-mesh fires, operator-driven Claude/Codex/Gemini sessions, and any mixed configuration. Reading the highest number from a local feed pull is forbidden if that pull is older than the last activity timestamp of any concurrently-running seat — including sessions the filer cannot see directly (e.g. a GPT-5.4 Codex operator running elsewhere on Chad's fleet).

Activation test

Before filing a ticket, the seat MUST answer: "Is it possible another seat has filed into this namespace in the last 60 seconds without my knowing?" If YES (including "I don't know" — which is YES by default on a multi-seat fleet), Protocol A or B applies. The 2026-04-16 precedents both answered "I don't know, but probably not" — both were wrong.

Protocol A — Deterministic reservation via the live helper/reducer (preferred)

Before filing a new ticket, the seat MUST use feed-append --kind actionable --reserve <PREFIX> for namespaces supported by the helper. ticket-reserve owns the counter; fleet-active-queue remains the live reducer for visibility and ownership checks. Do not manually derive the next number from local feed state.

2026-05-03 implementation status. FLT-1318 landed the helper-level fix. ticket-reserve is backed by SQLite uniqueness on reservations(prefix, seq_number) and ticket_id, and fleet_queue_lib.py rejects duplicate AWAITING_CLAIM posts for existing items unless the post carries explicit release or state-repair proof. Protocol A is therefore default-safe as of 2026-05-03. The remaining race surface is validator-bypass work: manual --item <ID> posts that try to reuse a durable item identity without release, repair, or auto-release evidence.

Protocol B — Two-phase file-then-confirm (acceptable when reservation helper unavailable)

If the reservation helper is unavailable or the namespace has no reserve-backed counter: 1. Phase 1 — File. Post the new ticket with a provisional ID and a CONFIRM-PENDING: marker. 2. Phase 2 — Re-pull and confirm. Within the same fire (≤30s after the file post), re-pull the feed for any post in the same window with a colliding ID. If no collision, post a CONFIRMED: follow-up. If collision detected, post an AMENDMENT: that renumbers the provisional ticket per the "first post wins the number" tiebreaker and clears CONFIRM-PENDING:.

The "first post wins" tiebreaker is whichever post lands at the earlier UTC timestamp. Ties broken by lexicographic ordering of the seat name.

Forbidden

  • Allocating a ticket number from a local feed pull older than the most recent fire of any concurrent seat.
  • Filing a new ticket without one of the two protocols once ≥2 seats are armed on the same namespace.
  • Renaming a ticket post-file by editing the original post (always file an amendment so the audit trail survives).

Scope

  • Applies to: any seat — cron-driven, operator-driven, subagent-driven, or remote-Codex — that files into a shared ticket namespace while a second seat is also potentially active. The rule activates the moment a second seat CAN allocate into the same namespace, not only when it IS.
  • Cross-surface note: the "second seat" includes sessions on other surfaces the filer doesn't have direct telemetry on — e.g. a Codex operator running in a GPT-5.4 chat surface, a Gemini CLI session on a second machine, a Claude Desktop session running subagent mass-dispatch. Absence of visibility ≠ absence of concurrent filing. Activation test above applies.
  • Does NOT apply to: single-seat cycles where the filer can PROVE no other seat will allocate (e.g. fleet-active-queue shows zero open items AND no scheduled tasks fire in the next hour AND no operator is driving another session). In practice this proof is rarely available on a multi-surface fleet.
  • Lane-private namespaces where only one seat ever allocates (e.g. ORA-NNNN written only by ORA-01) remain exempt — but the moment a second seat starts allocating into them, the rule activates without grace period.

Rationale

ORA-2026-0011 documented a real collision: two seats both filed CMB-0036 within 6 minutes during the overnight cron mesh, because each was incrementing from a local memory of the highest CMB-NNNN seen in the most recent feed pull. The collision was resolved by an amendment cycle ~9 minutes after the first filing, but the cost is asymmetric: whichever seat reads second has to amend, and the diagnostic narratives spent ~6 minutes mutually unaware. With two seats and ~6-minute fire windows over a 4-hour overnight, P(collision) is non-trivial — and increases linearly with seat count.

The fix is to make the allocation explicit at the write site. Either (a) reserve through the helper-backed counter, or (b) accept that the file is provisional until confirmed. Both protocols make the implicit "I think this is the next number" assumption explicit and re-checkable.

Evidence trail

  • ORA-2026-0011 (FAL, 2026-04-15) — CMB-0036 collision between CAMBER-01 and ORA-01 cron fires on 2026-04-15 at 03:51Z and 03:57Z. One incident, two seats, one cycle. Origin of this doctrine.
  • 2026-04-16 CMB-0105 collision — CLAUDE-CLI-CAMBER-12 filed CMB-0105 at 20:04:22Z for Redline card-grouping defect. CLAUDE-CLI-CAMBER-02 filed a different CMB-0105 at 20:24:06Z (Redline audio-playback feature) using "subagent mass-dispatch" pattern, overwriting the queue-reducer view of the original. CAMBER-12 renumbered to CMB-0106 with cross-reference; CAMBER-02's CMB-0105 retained the number. No cron involved — both were Claude-CLI operator-driven. This is the first operator-driven collision and demonstrates the rule must cover all seats, not just cron mesh.
  • 2026-04-16 CMB-0122 collision — CLAUDE-CLI-CAMBER-12 filed CMB-0122 at 22:17:16Z for desktop-folder receipts ingest entrypoint. A separate Codex operator in GPT-5.4 surface (not part of the feed-visible fleet) claimed CMB-0122 for matcher-determinism diagnosis work around 22:30Z, committed artifacts on codex/cmb-0122-matcher-determinism branch at 875d42c6, and posted DONE to feed for the hijacked number. Orphan doc at docs/ops/cmb-0122_financial_matcher_false_positive_2026-04-16.md has no inbound refs because the ticket number was double-booked. CAMBER-12 renumbered desktop-ingest to CMB-0123 and filed the matcher-determinism follow-on as CMB-0124. This is the first cross-surface collision and demonstrates that seats on surfaces the filer cannot see (GPT-5.4 operator, Gemini CLI elsewhere, Claude Desktop running remotely) are still inside scope.
  • Aggregate: three collisions in ~24 hours across three different concurrency patterns (cron-mesh, subagent-mass-dispatch, cross-surface-Codex-operator). The doctrine's narrow "cron-mesh" framing was itself misleading and invited violations. Amended 2026-04-16 to cover all multi-seat filing patterns.

Promotion gate update (amended 2026-05-03):

  • Promote to M3 once all three collision patterns run a full week without a new collision under Protocol A or Protocol B after the FLT-1318 helper fix.
  • Promote to M4 once all active ticket namespaces are reserve-backed across fleet machines and manual --item duplicate-state bypasses are rejected or forced through explicit release/repair proof.

Enforcement hook

  • Memory: feedback_cron_mesh_ticket_allocation.md (added 2026-04-15)
  • Canonical bundle: v1.7 §15 Standing Directive (pending stamp to v1.8) — add as bullet under "Discipline at write site"
  • fleet-active-queue: the live reducer already exists at /Users/chadbarlow/Desktop/fleet/scripts/fleet-active-queue — use it to check current item state, ownership, and warmth before filing or claiming.
  • feed-append helper: feed-append --kind actionable --reserve <PREFIX> calls ticket-reserve and embeds the reserved ID into the subject/body via {{ITEM_ID}}.
  • ticket-reserve: the allocation counter has uniqueness guards for both (prefix, seq_number) and ticket_id.
  • queue-drift-check: future enhancement — flag any actionable post whose ITEM collides with a prior actionable post in the same namespace and which lacks an AMENDMENT: reconciling the collision.

This doctrine ships at M2 (one incident, mechanism is well-characterized, but Protocol A vs Protocol B has not been A/B-tested across a multi-night window). M3 once a second night runs without collision under one of the protocols.

Known exceptions

  • Single-seat cycles. When only one seat is firing on a namespace, neither protocol is required. The collision risk is zero by definition.
  • Lane-private namespaces. A namespace allocated only by one lane (e.g. ORA-NNNN allocated only by ORA-01) is exempt as long as only one seat allocates. The moment a second seat starts allocating, the rule activates.
  • First-touch namespaces. A brand-new namespace with no prior allocations can skip Protocol A on the first file (no reducer state yet). Protocol B's confirm phase still applies.

Review cadence

  • Next review: 2026-07-15 (90 days)
  • Review trigger: any of — (a) a second cron-mesh ticket collision, (b) a duplicate AWAITING_CLAIM bypasses the FLT-1318 guard, (c) the reservation helper's namespace coverage expands and changes which namespaces require Protocol A vs Protocol B.