You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(quota-plane): quota-gatherer service in the PM stack — 15-minute tick, quota-current contract #1151
Build the quota-gatherer as a PM-stack process: 15-minute tick; one adapter per provider with isolated failures; writes (a) the append-only snapshot store (existing) and (b) quota-current — the latest-state contract per pool/service (value, % of limit, resetsAt, measuredAt, source, state). Per-adapter heartbeat + last-error emitted every tick (feeds the self-monitoring issue #1116). check-usage.py snapshots fold in as the local-server adapter. Runs where the fleet-feed collector runs today (sandbox) with the feed carrying its output to the laptop aggregator; placement reviewed if adapter token storage argues for the laptop instead.
Acceptance: two consecutive ticks produce current, correctly-sourced quota-current for every adapter-covered provider; one deliberately broken adapter degrades only its own rows.
REVIEW FINDING (2026-10-26): integration seam for the gatherer. Two options, both real today: (a) shell openchamber quota --json (per-provider or all) from the OpenChamber checkout; (b) import the quota module in-process (ESM, server/lib/quota/index.js exports fetchQuotaForProvider / fetchQuotaForProviderInstances / listConfiguredQuotaProviders).
Caveat found live: the CLI resolves credentials via the OpenCode server API (default http://127.0.0.1:4096) and fails wholesale with "Transport: fetch failed" when no server is listening (verified 2026-10-26 23:1x CDT — no server on 4096 in the sandbox). The gatherer must either guarantee a server, or use the module's direct credential-store read (readOpenCodeCredentials) so a stopped server cannot zero the whole tick. Prefer (b) with the direct read; keep (a) as the diagnostic surface.
DREW RULING (2026-10-27 00:42 CDT, standing): nothing runs in the sandbox — all builds, downloads, test suites, and servers run on the LAPTOP only; the sandbox keeps config/source aligned and does orchestration only. Applied to this issue: the gatherer deploys and ticks ON THE LAPTOP (where the aggregator + snapshot store already live). The in-flight build has been redirected accordingly; sandbox-side timers/services for this system are out of scope.
v1 built + deployed — PR #1309 (branch feat/quota-gatherer, head a7471f0f; not merged, for review).
Deployment host: the LAPTOP (per Drew's 2026-10-27 standing rule — nothing executes on the sandbox). An earlier iteration of this work briefly installed a sandbox timer; it was removed the same night and the sandbox keeps source only. On the laptop the gatherer follows the local PM-stack patterns: systemd user timer quota-gatherer.timer (OnCalendar=*:0/15, Persistent=true, same shape as the Meta exporter timer), units ship in pm-stack/aggregator/systemd/ and are installed by install.sh; linger makes it boot-persistent.
Design decisions
In-process import, direct credential read: the tick imports the OpenChamber quota module from the laptop checkout (~/git/openchamber-fork) and wires its credential source to a read-only projection of OpenCode's own opencode.db credential table (server-identical selection semantics). No OpenCode server needs to be listening — the wholesale transport failure from the review cannot zero a tick.
Outputs in the deployed aggregator dir: quota-current.json (the latest-state contract; heartbeat embedded) + quota-gatherer-heartbeat.json. Snapshot records go directly into the aggregator's snapshot store (SnapshotStore.append), change-only (a record lands when a window's value moves; first tick: 56 records).
Per-provider isolation: 15s cap each; one failure degrades only its rows. Catastrophic failure (module import / credential store) → heartbeat with the error, non-zero exit, previous quota-current untouched.
Mapping is pure + unit-tested (src/quota-gather-map.ts, 11 tests, run on the laptop): provider results → quota-current entries (incl. per-model flattening for Claude/Antigravity shapes) → snapshot records under the hub's existing service ids.
Live proof (laptop): scheduled tick 2026-10-27 01:00:09 CDT fired from the timer — 8 providers, 7 ok in <1s of provider time (cline-pass, codex ×2 rows, deepseek, antigravity with 44 per-model windows, opencode-go, openai.alternate, meta via file export: 5h 1.02% / weekly 8.83% / free credits $148.57). Second consecutive scheduled tick being verified tonight; results in the follow-up comment.
OpenCode Go returns one combined row (the module has no per-credential fetch for it), so records land under opencode-go, not split into opencode-go:personal / :lab.
Verification update — live on the laptop, two consecutive scheduled ticks proven (PR #1309 head now 12e06474; CI green: treefmt, Nix flake eval, test run).
Scheduled ticks of quota-gatherer.timer (all CDT, laptop amd):
01:00:09 — 8 providers, 7 ok, 56 records appended to the aggregator's snapshot store, quota-current.json written.
Change-detection keyed on value only. Tick 2 appended 30 records, most of them noise: several providers recompute the reset instant from a duration on every fetch (sub-second drift; idle 5h windows report "now + 5h"), so keying change on value|resetsAt churned. The deployed state file was re-baselined to the value-only stamps; a smoke tick then appended +3 (genuine moves only) and the next scheduled tick (01:45:07) appended +0.
The unit now runs the script from the laptop's repo clone (~/git/code, tracking this branch), with outputs pinned by env to ~/pm-stack/aggregator. Reason, found live: at 01:28 another workstream's package re-deploy replaced the deployed scripts/ wholesale from a main-based tree and the not-yet-merged gatherer script vanished (the 01:30 tick failed Module not found in the journal; quota-current was NOT touched — fail-soft held). Once feat(aggregator): quota gatherer — 15-minute laptop tick (#1151) #1309 merges, deploys carry the script in-package as well and either path serves it.
Test evidence (run on the laptop, per the execution rule): test/quota-gather-map.test.ts 11/11 pass. Package typecheck shows only the pre-existing grist/seed/roadmap.json artifact error, identical on unmodified main.
Still open (unchanged from the first comment): aggregator Source pickup of quota-current.json (the /quota visibility hop), Claude credential re-login (#1251), OpenCode Go per-workspace split, CNY balance records (#1052).
Final visibility hop DONE (2026-10-27): PR #1329 (head 1a96b5e), deployed on the laptop and verified live. Not merged — parent merges after verification.
What shipped: the aggregator's live quota source now reads quota-current.json from its own data dir (config PM_AGG_QUOTA_CURRENT_FILE, default ./quota-current.json) and overlays it on the quota view via a new pure module (src/quota-current.ts):
Fresh (<=20 min) ok entries win for the pool whose id equals the entry serviceId: percent windows replace the dated-read windows; pool state + choices recomputed. A fresh ok credential row beats an error row for the same service (the Codex two-login case).
Error / aged entries surface as the pool's state (error/stale + reason) via a per-pool freshness stamp — never zeros, older windows never presented as silently current.
/api/quota.json stays additive: pools gain freshness; a top-level gatherer summary (tickAt, ok, per-provider freshness) rides along. /quota cards render a "gatherer · · measured · · " line.
Fail-soft: missing/malformed quota-current.json leaves the base view untouched.
Tests (run on the laptop): new test/quota-current.test.ts 10/10; full aggregator suite 202/202; typecheck clean (exit 0).
Live verification (2026-10-27 ~07:02Z, against the gatherer tick generatedAt 2026-10-27T07:00:20Z):
meta:high-usage — was walled at weekly 100% on the dated read; now healthy with gatherer windows 5-hour 1.36% / weekly 8.94%, freshness ok, source file-export/meta-laptop, measured 07:00:08Z. Meta is on the page from the gatherer.
anthropic:claude-pro — state error, reason "Rate limited. Retrying soon.", dated windows retained under the error badge (not zeroed).
openai:codex — weekly 17% (gatherer) replaces the dated 11%. cline-pass — weekly 29 / monthly 71 replace 26 / 69. deepseek — freshness ok (balance-only read, no invented windows).
Remaining gaps (unchanged, precisely): OpenCode Go still arrives as one combined gatherer row (serviceId "opencode-go") and Antigravity per-model, so neither overlays its split pools yet — both appear in the payload's gatherer summary with their freshness. Both wait on the quota module splitting those providers (noted in the gatherer map when #1309 landed).
Deploy note for the next operator: install.sh ends with systemctl --user enable --now, which does NOT restart an already-running aggregator — after running install.sh, systemctl --user restart pm-aggregator.service is required for new code to serve (hit and handled during this deploy; the live verification above is post-restart).
Activity
developing-today commentedon Oct 27, 2026
CODE-287
dezren39 commentedon Oct 27, 2026
REVIEW FINDING (2026-10-26): integration seam for the gatherer. Two options, both real today: (a) shell
openchamber quota --json(per-provider or all) from the OpenChamber checkout; (b) import the quota module in-process (ESM, server/lib/quota/index.js exports fetchQuotaForProvider / fetchQuotaForProviderInstances / listConfiguredQuotaProviders).Caveat found live: the CLI resolves credentials via the OpenCode server API (default http://127.0.0.1:4096) and fails wholesale with "Transport: fetch failed" when no server is listening (verified 2026-10-26 23:1x CDT — no server on 4096 in the sandbox). The gatherer must either guarantee a server, or use the module's direct credential-store read (readOpenCodeCredentials) so a stopped server cannot zero the whole tick. Prefer (b) with the direct read; keep (a) as the diagnostic surface.
dezren39 commentedon Oct 27, 2026
DREW RULING (2026-10-27 00:42 CDT, standing): nothing runs in the sandbox — all builds, downloads, test suites, and servers run on the LAPTOP only; the sandbox keeps config/source aligned and does orchestration only. Applied to this issue: the gatherer deploys and ticks ON THE LAPTOP (where the aggregator + snapshot store already live). The in-flight build has been redirected accordingly; sandbox-side timers/services for this system are out of scope.
feat(aggregator): quota gatherer — 15-min laptop tick over the OpenCh…
feat(aggregator): flatten per-model quota usage (Claude/Antigravity s…
dezren39 commentedon Oct 27, 2026
v1 built + deployed — PR #1309 (branch
feat/quota-gatherer, heada7471f0f; not merged, for review).Deployment host: the LAPTOP (per Drew's 2026-10-27 standing rule — nothing executes on the sandbox). An earlier iteration of this work briefly installed a sandbox timer; it was removed the same night and the sandbox keeps source only. On the laptop the gatherer follows the local PM-stack patterns: systemd user timer
quota-gatherer.timer(OnCalendar=*:0/15,Persistent=true, same shape as the Meta exporter timer), units ship inpm-stack/aggregator/systemd/and are installed byinstall.sh; linger makes it boot-persistent.Design decisions
~/git/openchamber-fork) and wires its credential source to a read-only projection of OpenCode's ownopencode.dbcredential table (server-identical selection semantics). No OpenCode server needs to be listening — the wholesale transport failure from the review cannot zero a tick.~/atlas/meta-quota-export/latest.json→ servicemeta:high-usage; failure-status documents and files >30 min old become error entries, never zeros.quota-current.json(the latest-state contract; heartbeat embedded) +quota-gatherer-heartbeat.json. Snapshot records go directly into the aggregator's snapshot store (SnapshotStore.append), change-only (a record lands when a window's value moves; first tick: 56 records).src/quota-gather-map.ts, 11 tests, run on the laptop): provider results → quota-current entries (incl. per-model flattening for Claude/Antigravity shapes) → snapshot records under the hub's existing service ids.Live proof (laptop): scheduled tick 2026-10-27 01:00:09 CDT fired from the timer — 8 providers, 7 ok in <1s of provider time (cline-pass, codex ×2 rows, deepseek, antigravity with 44 per-model windows, opencode-go, openai.alternate, meta via file export: 5h 1.02% / weekly 8.83% / free credits $148.57). Second consecutive scheduled tick being verified tonight; results in the follow-up comment.
Known gaps
opencode-go, not split intoopencode-go:personal/:lab.quota-current.jsonyet —Sourcepickup (freshness render + lazy pull, feat(quota-plane): aggregator lazy refresh — pull on page load when data >5 min old #1171/feat(quota-plane): quota-plane status view on /quota — gatherer health + per-provider freshness #1175) is what makes the tick visible on /quota.fix(aggregator): key gatherer snapshot change-detection on value only…
fix(aggregator): run the gatherer from the repo clone so package re-d…
style(aggregator): biome formatting for the quota gatherer files (#1151)
dezren39 commentedon Oct 27, 2026
Verification update — live on the laptop, two consecutive scheduled ticks proven (PR #1309 head now
12e06474; CI green: treefmt, Nix flake eval, test run).Scheduled ticks of
quota-gatherer.timer(all CDT, laptopamd):quota-current.jsonwritten.ok: true. Per-provider: cline-pass / codex (2 credential rows) / deepseek / antigravity / opencode-go / openai.alternate ok; meta ok via the feat(quota-plane): Meta adapter Phase 1 — laptop-side quota exporter (subscription-quota API, 15-min file export) #1301 file export (5h 1.28%, weekly 8.91%, free credits $148.57); claude error (session expired — fix(quota-plane): opencode-serve boot verification + quota CLI per-provider resilience (no wholesale transport failure) #1251).Two fixes landed during verification:
value|resetsAtchurned. The deployed state file was re-baselined to the value-only stamps; a smoke tick then appended +3 (genuine moves only) and the next scheduled tick (01:45:07) appended +0.~/git/code, tracking this branch), with outputs pinned by env to~/pm-stack/aggregator. Reason, found live: at 01:28 another workstream's package re-deploy replaced the deployedscripts/wholesale from a main-based tree and the not-yet-merged gatherer script vanished (the 01:30 tick failedModule not foundin the journal; quota-current was NOT touched — fail-soft held). Once feat(aggregator): quota gatherer — 15-minute laptop tick (#1151) #1309 merges, deploys carry the script in-package as well and either path serves it.Test evidence (run on the laptop, per the execution rule):
test/quota-gather-map.test.ts11/11 pass. Package typecheck shows only the pre-existinggrist/seed/roadmap.jsonartifact error, identical on unmodified main.Still open (unchanged from the first comment): aggregator
Sourcepickup ofquota-current.json(the /quota visibility hop), Claude credential re-login (#1251), OpenCode Go per-workspace split, CNY balance records (#1052).Merge pull request #1309 from developing-today/feat/quota-gatherer
feat(web): serve gatherer quota-current on /quota (#1151)
dezren39 commentedon Oct 27, 2026
Final visibility hop DONE (2026-10-27): PR #1329 (head 1a96b5e), deployed on the laptop and verified live. Not merged — parent merges after verification.
What shipped: the aggregator's live quota source now reads quota-current.json from its own data dir (config PM_AGG_QUOTA_CURRENT_FILE, default ./quota-current.json) and overlays it on the quota view via a new pure module (src/quota-current.ts):
freshness; a top-levelgatherersummary (tickAt, ok, per-provider freshness) rides along. /quota cards render a "gatherer · · measured ·Tests (run on the laptop): new test/quota-current.test.ts 10/10; full aggregator suite 202/202; typecheck clean (exit 0).
Live verification (2026-10-27 ~07:02Z, against the gatherer tick generatedAt 2026-10-27T07:00:20Z):
Remaining gaps (unchanged, precisely): OpenCode Go still arrives as one combined gatherer row (serviceId "opencode-go") and Antigravity per-model, so neither overlays its split pools yet — both appear in the payload's gatherer summary with their freshness. Both wait on the quota module splitting those providers (noted in the gatherer map when #1309 landed).
Deploy note for the next operator: install.sh ends with
systemctl --user enable --now, which does NOT restart an already-running aggregator — after running install.sh,systemctl --user restart pm-aggregator.serviceis required for new code to serve (hit and handled during this deploy; the live verification above is post-restart).Merge pull request #1329 from developing-today/feat/quota-current-vis…