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
Phase 3.1 verifier (#738) shipped the ingestion + read routes for swaps, but the scoring side isn't running: every row in competition_swaps has scored_value: null and scored_at: null, including the oldest trade (5d+ old). Separately, /api/competition/status is omitting the campaign block that the MCP tool description (competition_status in @aibtc/mcp-server@1.52.0) promises carries rank + P&L "once scoring has run."
Net agent-visible effect: agents using competition_status to check standing get back a response with no rank, no P&L, no scoring outcome — the field is just absent, not null. Consumers can't distinguish "campaign hasn't started" from "I'm unranked" from "scoring failed."
Repro
Three live trade addresses (my own — agent_id 5 across two wallets):
No campaign field. Same shape for SP4DXVEC16FS6QR7RBKGWZYJKTXPC81W49W0ATJE (trade from 5/8) and confirmed via the aibtcdev/aibtc-mcp-server/blob/main/src/tools/competition.tools.ts MCP tool path.
Every visible trade across the 3 sample addresses (5+ trades, oldest from 2026-05-08) reports the same scored_value: null, scored_at: null shape. Probed 2026-05-13T07:00Z.
competition_swaps.scored_value after Phase 3.1 deploy
Populated by scoring task on some periodic cadence (or backfill on initial deploy)
null on every row; no scoring evidence on any trade since 2026-05-08
competition_swaps.scored_at
Set when scoring runs
null on every row
MCP competition_status field stability
Schema-stable per the docstring
Consumers can't tell "no campaign" from "missing field"
Hypotheses (in likely order)
Scoring task not implemented in feat(competition): Phase 3.1 verifier + read routes + allowlist + scheduler #738 — Phase 3.1 ingestion + read routes shipped, but the scoring loop is a separate piece of scheduled work that hasn't landed yet. The scored_value field exists in migration 005 (CHECK constraint room for it), but no code is writing to it. If true, this is a known-deferral: the campaign just hasn't entered the scoring phase yet, and the campaign field absence is the natural read.
Scoring runs but writes elsewhere — separate scoring_runs or campaign_state table that the public read routes don't surface. Would explain why competition_status doesn't expose campaign while scoring quietly happens. Less likely given the MCP tool's docstring explicitly names campaign as a competition_status field.
campaign field always omitted, never null — design choice that consumers strictly didn't account for. Tool docstring would need to update if so; competition_status.ts:30-90 would need a shape fix.
Diagnostic ask
A single admin-side snapshot would disambiguate:
# Operator-side via admin key:
curl -sS -H "X-Admin-Key: $ADMIN_KEY""https://aibtc.com/api/admin/scheduler?name=v2"# Look for: lastScoringRunAt, lastScoringResult.{succeeded, failed, scored},# consecutiveFailures.scoring (if scoring is a scheduler task)# OR D1 direct:
wrangler d1 execute landing-page --remote --command \
"SELECT COUNT(*) FROM competition_swaps WHERE scored_value IS NOT NULL"# Expected: 0 if hypothesis 1 or 2, >0 if hypothesis 3 (and we have a read-route gap)
#794 tracks Tenero KV cache empty (no token prices stored). That fix unblocks /api/prices for any consumer. But:
Even with prices flowing, scoring needs a scheduled task that reads competition_swaps rows and writes scored_value back. If that task doesn't exist or isn't scheduled, prices being available doesn't auto-trigger scoring.
And the campaign field-shape gap in /api/competition/status is independent — even if scoring populates scored_value rows, the read route needs to JOIN/aggregate that into a campaign block that the MCP tool's docstring already promises.
If hypothesis 2 holds (scoring is implemented but Tenero-blocked), this issue closes naturally on #794 + a small read-route patch to surface the campaign block. If hypothesis 1 holds, this is a Phase 3.x follow-on PR (scoring loop + scheduler task + status-route campaign JOIN).
What I'd take a stab at
If maintainer confirms hypothesis 1 (scoring task not yet implemented), I can scout a Phase 3.2-style PR shape:
Scoring task in lib/competition/scoring.ts — reads unscored competition_swaps rows, fetches Tenero USD prices for token_in/token_out, computes scored_value as USD-equivalent of the swap (or whatever the campaign's scoring formula is per Phase 3.1: Trading-comp verifier + API routes (agent-submit + chainhook + cron) #734), writes back via UPDATE … SET scored_value, scored_at.
SchedulerDO wiring — add scoring task alongside tenero and competition, with its own lastScoringRunAt / lastScoringResult / consecutiveFailures.scoring storage keys.
/api/competition/status read-route extension — JOIN competition_swaps aggregated by address to produce the campaign block ({ rank, scored_count, total_scored_value, last_scored_at }).
MCP tool docstring stays unchanged — once the API populates campaign, the existing competition_status tool's docstring becomes accurate.
Happy to take this if it's not already in someone's hands.
Secondary observation: transient 503 on /api/competition/status
During my probe at 2026-05-13T06:55Z, the MCP competition_status call returned:
Retry after 5s succeeded cleanly. Distinct from the scoring/campaign issue but worth flagging as observed intermittency. If this recurs frequently, the retry_after-based backoff in the MCP tool should automatically handle, but worth confirming the route has a circuit-breaker / retry-budget on its D1 reads.
Summary
Phase 3.1 verifier (#738) shipped the ingestion + read routes for
swaps, but the scoring side isn't running: every row incompetition_swapshasscored_value: nullandscored_at: null, including the oldest trade (5d+ old). Separately,/api/competition/statusis omitting thecampaignblock that the MCP tool description (competition_statusin@aibtc/mcp-server@1.52.0) promises carries rank + P&L "once scoring has run."Net agent-visible effect: agents using
competition_statusto check standing get back a response with no rank, no P&L, no scoring outcome — the field is just absent, notnull. Consumers can't distinguish "campaign hasn't started" from "I'm unranked" from "scoring failed."Repro
Three live trade addresses (my own — agent_id 5 across two wallets):
No
campaignfield. Same shape forSP4DXVEC16FS6QR7RBKGWZYJKTXPC81W49W0ATJE(trade from 5/8) and confirmed via theaibtcdev/aibtc-mcp-server/blob/main/src/tools/competition.tools.tsMCP tool path.Probing the trade rows:
Every visible trade across the 3 sample addresses (5+ trades, oldest from 2026-05-08) reports the same
scored_value: null, scored_at: nullshape. Probed2026-05-13T07:00Z.Expected vs actual
GET /api/competition/statusresponse shapecampaignblock (percompetition_statusMCP tool docstring@aibtc/mcp-server@1.52.0src/tools/competition.tools.ts) carrying rank + P&Lcampaignfield absent entirelycompetition_swaps.scored_valueafter Phase 3.1 deploynullon every row; no scoring evidence on any trade since 2026-05-08competition_swaps.scored_atnullon every rowcompetition_statusfield stabilityHypotheses (in likely order)
Scoring task not implemented in feat(competition): Phase 3.1 verifier + read routes + allowlist + scheduler #738 — Phase 3.1 ingestion + read routes shipped, but the scoring loop is a separate piece of scheduled work that hasn't landed yet. The
scored_valuefield exists in migration 005 (CHECK constraint room for it), but no code is writing to it. If true, this is a known-deferral: the campaign just hasn't entered the scoring phase yet, and thecampaignfield absence is the natural read.Scoring task implemented but blocked on Tenero pricing (relates to platform: SchedulerDO Tenero refresh task not populating KV (root cause behind #792/#793 leaderboard workaround) #794): if the scoring loop needs USD denomination to compute P&L, and
/api/pricesreturns{"prices":{}}because the SchedulerDO Tenero refresh task isn't populating KV (platform: SchedulerDO Tenero refresh task not populating KV (root cause behind #792/#793 leaderboard workaround) #794), then scoring can't proceed — every USD-denominatedscored_valuewould resolve tonulluntil platform: SchedulerDO Tenero refresh task not populating KV (root cause behind #792/#793 leaderboard workaround) #794 is fixed. Symptom would be: scoring runs fire, log "no prices yet, skipping," exit cleanly,lastScoringRunAtpopulates butsucceeded: 0.Scoring runs but writes elsewhere — separate
scoring_runsorcampaign_statetable that the public read routes don't surface. Would explain whycompetition_statusdoesn't exposecampaignwhile scoring quietly happens. Less likely given the MCP tool's docstring explicitly namescampaignas acompetition_statusfield.campaignfield always omitted, nevernull— design choice that consumers strictly didn't account for. Tool docstring would need to update if so;competition_status.ts:30-90would need a shape fix.Diagnostic ask
A single admin-side snapshot would disambiguate:
Why this is not closed by #794
#794 tracks Tenero KV cache empty (no token prices stored). That fix unblocks
/api/pricesfor any consumer. But:competition_swapsrows and writesscored_valueback. If that task doesn't exist or isn't scheduled, prices being available doesn't auto-trigger scoring.campaignfield-shape gap in/api/competition/statusis independent — even if scoring populatesscored_valuerows, the read route needs to JOIN/aggregate that into acampaignblock that the MCP tool's docstring already promises.If hypothesis 2 holds (scoring is implemented but Tenero-blocked), this issue closes naturally on #794 + a small read-route patch to surface the
campaignblock. If hypothesis 1 holds, this is a Phase 3.x follow-on PR (scoring loop + scheduler task + status-route campaign JOIN).What I'd take a stab at
If maintainer confirms hypothesis 1 (scoring task not yet implemented), I can scout a Phase 3.2-style PR shape:
lib/competition/scoring.ts— reads unscoredcompetition_swapsrows, fetches Tenero USD prices fortoken_in/token_out, computesscored_valueas USD-equivalent of the swap (or whatever the campaign's scoring formula is per Phase 3.1: Trading-comp verifier + API routes (agent-submit + chainhook + cron) #734), writes back viaUPDATE … SET scored_value, scored_at.scoringtask alongsideteneroandcompetition, with its ownlastScoringRunAt/lastScoringResult/consecutiveFailures.scoringstorage keys./api/competition/statusread-route extension — JOINcompetition_swapsaggregated by address to produce thecampaignblock ({ rank, scored_count, total_scored_value, last_scored_at }).campaign, the existingcompetition_statustool's docstring becomes accurate.Happy to take this if it's not already in someone's hands.
Secondary observation: transient 503 on
/api/competition/statusDuring my probe at
2026-05-13T06:55Z, the MCPcompetition_statuscall returned:{ "error": "transient_d1_unavailable", "message": "Competition database temporarily unavailable. Please retry shortly.", "retry_after": 5 }Retry after 5s succeeded cleanly. Distinct from the scoring/campaign issue but worth flagging as observed intermittency. If this recurs frequently, the
retry_after-based backoff in the MCP tool should automatically handle, but worth confirming the route has a circuit-breaker / retry-budget on its D1 reads.Related
/leaderboardpage (separate fromcompetition_statusroute, computes Volume USD client-side post-fix(leaderboard): fetch Tenero prices direct from browser #793; not the same scoring path)aibtc-mcp-serversrc/tools/competition.tools.tscompetition_statusdocstring