Skip to content

Quota recorder: record on sandbox, ship via fleet proxy, ingest at a laptop broker (retire the recycle-vanishing sandbox units) #5703

Open

Description

@dezren39

Quota recorder: move the Supabase write to the laptop (design + recommendation)

Current state (verified 2026-11-04)

  • The sandbox quota recorder runs from systemd units installed by ~/tools/quota-recorder/ (its timer + service). Like every /etc/systemd/system unit, they vanish on every sandbox recycle and must be reinstalled — today that reinstall is a manual/cron-assisted step (the 2026-11-04 15:08 boot check found them missing again post-recycle and reinstalled them from ~/tools/quota-recorder/, after which ticks resumed with Supabase acks).
  • The recorder writes usage snapshots to Supabase (muse-dev2) from the sandbox, which means the sandbox side holds/uses the Supabase write path directly.
  • Recycles are frequent (six boots on 2026-11-04 alone), so "reinstall after recycle" is a recurring failure surface, and a missed reinstall = a silent gap in the quota record the burn plan depends on.

Proposal (Drew's question, 2026-11-04)

Split the recorder the same way the queue and secrets already work:

  • Sandbox side: record-only. A small local writer appends quota observations to a local spool (JSONL under ~/tools/quota-recorder/ state or ~/.local/state/quota-recorder/). No Supabase credentials, no network write on the sandbox. Survives recycles by construction (the spool lives in /home/hatch, which persists).
  • Ship via the fleet proxy. Batches move sandbox → laptop through the existing fleet session proxy, the same transport every other broker call uses.
  • Laptop side: broker-owned ingest. A laptop endpoint (a fleet serve role alongside queue-broker :4801 / secrets-broker :4800, or an ingest route on an existing broker) owns the Supabase connection and performs the insert — credentials live only on the laptop, exactly like the queue-broker holding the Supabase credentials for queue operations today.
  • Ack + trim: the laptop acks batch ids; the sandbox trims its spool only on ack. A laptop-off window (e.g. 2026-11-04 evening, laptop powered down) becomes a growing local spool, not lost data — recording continues and ships when the laptop returns.

Evaluation vs. status quo

Status quo (sandbox units → Supabase direct) Proposed (spool → proxy → laptop broker)
Survives recycle No — units vanish; reinstall is a step that can be missed Yes — no unit needed for recording; shipper can be an init-ensure-style prelude check or a tiny timer reinstalled by the same mechanism as other fleet units
Credentials on sandbox Yes (Supabase write path) No — laptop-only, matching the queue/secrets pattern
Laptop off Recorder still writes (Supabase is remote) Recording continues to spool; ingest pauses; no loss
Failure visibility Missing ticks noticed by the boot check Spool depth + last-ack age are directly measurable; alert on spool age
New machinery — One laptop ingest endpoint + batch/ack protocol (small; mirrors trip-log ingest follow-on on #5343)

Recommendation

Adopt the proposal. The deciding factors: the recycle-vanish reinstall already failed once today and will keep failing on the same cadence; the credential placement matches the fleet's established broker pattern; and the spool design converts the laptop-off case from data loss risk into mere delay. The one real cost — ingest pauses while the laptop is off — is acceptable because the quota data is consumed on planning timescales (the burn plan reads snapshots, and the gatherer already tolerates staleness by design: a stale read never idles a pool).

Acceptance / next steps

  1. Local spool writer on the sandbox replacing the direct-write recorder; format = the existing snapshot payload, one JSON object per line, batch id per ship.
  2. Laptop ingest endpoint with ack; idempotent on batch id (replays after a crash are expected).
  3. Shipper on the sandbox (fleet-proxy transport) with spool trim-on-ack and a last-ack watermark.
  4. Boot check updated: instead of "reinstall the units," it verifies spool freshness + last ack; a stale ack surfaces like today's missing-timer finding.
  5. Old sandbox units retired once the new path has produced a week of clean acks.

Related: #2072 (unified quota attribution/observability) covers the read/attribution side; this issue is only about the recorder write path and where the Supabase connection lives.

Acceptance criteria

  • The quota recorder runs on the sandbox and ships records via the fleet proxy to ingestion at a laptop broker, per the design in this issue.
  • The retired Supabase path is removed or explicitly disabled, with no silent dual-writing.
  • Recorded quota data matches the previous recorder's output for the same inputs (parity spot-check recorded on the issue).

Activity

  1. added
    priority/normalOrdinary priority (the default)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 5, 2026
  2. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Intake review

    Related: part of the quota-pipeline batch filed 2026-11-04 23:12–23:18Z (see also #5704). Notes: this retires a Supabase dependency for quota records in favor of sandbox-record + fleet-proxy ship + laptop-broker ingest. Risk: dual-running or a silent gap during cutover would corrupt the quota history the dispatch gates read. Suggested approach: build the ship/ingest path behind the current recorder, parity-check outputs, then retire the old path in the same change.

    Validation: filed within the last hour and reviewed against its own body this run; no merged PR resolves it (the work it describes has not been implemented). Acceptance criteria appended to the body; labels normalized by the fleet monitor.

    🤖 Agent note · intake enrichment

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: monitor run 2026-11-04 18:12 CST
    • Found via: monitor reconciliation
    • Task: Duty 2 intake enrichment (protocol §5)
  3. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 5, 2026
  4. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  5. 145 remaining items

  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 6, 2026
  7. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  8. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. added
    queue/claimedQueue worker lifecycle state
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    queue/claimedQueue worker lifecycle state
    on Nov 6, 2026
  11. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/lLarge: multi-day or open-ended; expect staged check-inskind/enhancementmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)queue/parkedQueue task is parked (waiting on CI, time, or an event)status/parkedDeliberately paused with a recorded resume conditiontask/triageQueue packet kind: verify, reproduce, and route this itemwork/assignedQueue worker lifecycle state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions