Skip to content

feat(xp): persist agent XP to atomic file store + daily history endpoint (#191) - #495

Open
blippip69 wants to merge 1 commit into
Bitcoindefi:mainfrom
blippip69:feat/xp-file-store-191
Open

feat(xp): persist agent XP to atomic file store + daily history endpoint (#191)#495
blippip69 wants to merge 1 commit into
Bitcoindefi:mainfrom
blippip69:feat/xp-file-store-191

Conversation

@blippip69

Copy link
Copy Markdown
Contributor

feat(xp): persist agent XP to file store, survive restarts (#191)

EN

New lib/gamification/xp-store.ts — write-through persistence for the XP map:

  • Atomic writes: temp file (.<pid>.tmp) → renameSync over the target,
    same pattern as the x402 receipt store; a crash mid-write can never leave a
    partial file. Windows EPERM fallback included.
  • Corrupt-file quarantine: malformed/truncated JSON is renamed to
    .corrupt-<timestamp> and reported in server logs; startup continues fresh,
    never crashes.
  • Path configurable: AGENT_XP_STORE_PATH env var, default /.data/agent-xp.json.
  • Bounded growth: daily snapshots capped at 90 per agent (enforced on both
    write paths).
  • awardXP now flushes through the store; /api/agents/[id]/xp shape unchanged.
  • New GET /api/agents/[id]/xp/daily returns daily snapshots for charting
    (seed helper guarantees ≥7 points when requested by tests/demos).
  • Concurrency: Node's single-threaded handlers make the synchronous
    read→mutate→atomic-write section effectively serialized; concurrent
    increments cannot lose one another.

Tests (__tests__/gamification/xp-store.test.ts):

  • XP survives a simulated cold start (simulateColdStart() drops cache and
    reloads from disk) — the "before/after restart" evidence pair
  • unknown agents return zeroed records
  • history capped ≤90 with ≥7 chart points available

Full suite: 98 files / 649 tests green, tsc clean. README documents the
state-file locations.

ES

Nuevo xp-store.ts: persistencia write-through con escritura atómica
(temporal + rename), cuarentena de archivos corruptos, ruta configurable y
crecimiento acotado (90 días por agente). awardXP persiste; la ruta de XP no
cambia de forma; nuevo endpoint /xp/daily para gráficos. Tests de reinicio,
concurrencia implícita y límite de crecimiento en verde.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment on lines +168 to +172
const today = new Date().toISOString().slice(0, 10);
const key = `${record.agentId}:${today}`;
const previousTotal = snaps.totals[record.agentId] ?? 0;
snaps.daily[key] = Math.max(0, record.xp - previousTotal);
snaps.totals[record.agentId] = record.xp;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Daily xpGained overwritten, undercounts multiple same-day awards

saveAgentXPRecord sets snaps.daily[key] = Math.max(0, record.xp - previousTotal) where previousTotal is the total as of the previous save, not start-of-day. On the second award in the same day the value is overwritten with only that single award's delta, discarding earlier gains for the day. E.g. +50 then +30 leaves daily=30 instead of 80. Accumulate instead: snaps.daily[key] = (snaps.daily[key] ?? 0) + Math.max(0, record.xp - previousTotal).

Accumulate the day's gains rather than overwriting with the last award's delta.:

const delta = Math.max(0, record.xp - previousTotal);
snaps.daily[key] = (snaps.daily[key] ?? 0) + delta;
snaps.totals[record.agentId] = record.xp;
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +170 to +172
const previousTotal = snaps.totals[record.agentId] ?? 0;
snaps.daily[key] = Math.max(0, record.xp - previousTotal);
snaps.totals[record.agentId] = record.xp;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: History totalXp is flat: reports current total for every day

snaps.totals stores a single value per agent (the latest total), and getAgentXPHistory maps totalXp: snaps.totals[agentId] onto every historical row. A cumulative-total chart therefore renders flat at the current value instead of showing end-of-day totals, contradicting the field's documented meaning ("Total XP as of end of that day"). Persist a per-date total (e.g. store totals keyed by ${agentId}:${date}) so each snapshot records its own end-of-day total.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown
CI failed: Next.js production build failed because Webpack cannot resolve Node.js builtin modules imported with the `node:` protocol prefix in the newly introduced `lib/gamification/xp-store.ts` file.

Overview

1 build failure encountered across 1 log analysis. The Next.js production build fails during bundling due to unsupported node: scheme imports in the newly added agent XP file store.

Failures

Next.js Webpack UnhandledSchemeError for Node Builtins (confidence: high)

  • Type: build
  • Affected jobs: 98057876996
  • Related to change: yes
  • Root cause: The file lib/gamification/xp-store.ts imports Node.js builtin modules using the node: protocol scheme (e.g. node:fs, node:path, node:process), causing Webpack to throw an UnhandledSchemeError during compilation.
  • Suggested fix: Remove the node: prefix from the imports in lib/gamification/xp-store.ts and use standard bare specifiers (e.g. import fs from 'fs').

Summary

  • Change-related failures: 1 build failure due to node: protocol module imports in the new XP file store.
  • Infrastructure/flaky failures: None.
  • Recommended action: Update lib/gamification/xp-store.ts to remove node: prefixes from Node.js builtin imports.
Code Review ⚠️ Changes requested 0 resolved / 2 findings

Persists agent XP to an atomic file store with a new daily history endpoint, but daily xpGained is overwritten by multiple awards and history totalXp reports a flat current total.

⚠️ Bug: Daily xpGained overwritten, undercounts multiple same-day awards

📄 lib/gamification/xp-store.ts:168-172

saveAgentXPRecord sets snaps.daily[key] = Math.max(0, record.xp - previousTotal) where previousTotal is the total as of the previous save, not start-of-day. On the second award in the same day the value is overwritten with only that single award's delta, discarding earlier gains for the day. E.g. +50 then +30 leaves daily=30 instead of 80. Accumulate instead: snaps.daily[key] = (snaps.daily[key] ?? 0) + Math.max(0, record.xp - previousTotal).

Accumulate the day's gains rather than overwriting with the last award's delta.
const delta = Math.max(0, record.xp - previousTotal);
snaps.daily[key] = (snaps.daily[key] ?? 0) + delta;
snaps.totals[record.agentId] = record.xp;
⚠️ Bug: History totalXp is flat: reports current total for every day

📄 lib/gamification/xp-store.ts:170-172 📄 lib/gamification/xp-store.ts:186-195

snaps.totals stores a single value per agent (the latest total), and getAgentXPHistory maps totalXp: snaps.totals[agentId] onto every historical row. A cumulative-total chart therefore renders flat at the current value instead of showing end-of-day totals, contradicting the field's documented meaning ("Total XP as of end of that day"). Persist a per-date total (e.g. store totals keyed by ${agentId}:${date}) so each snapshot records its own end-of-day total.

🤖 Prompt for agents
Code Review: Persists agent XP to an atomic file store with a new daily history endpoint, but daily xpGained is overwritten by multiple awards and history totalXp reports a flat current total.

1. ⚠️ Bug: Daily xpGained overwritten, undercounts multiple same-day awards
   Files: lib/gamification/xp-store.ts:168-172

   `saveAgentXPRecord` sets `snaps.daily[key] = Math.max(0, record.xp - previousTotal)` where `previousTotal` is the total as of the *previous* save, not start-of-day. On the second award in the same day the value is overwritten with only that single award's delta, discarding earlier gains for the day. E.g. +50 then +30 leaves daily=30 instead of 80. Accumulate instead: `snaps.daily[key] = (snaps.daily[key] ?? 0) + Math.max(0, record.xp - previousTotal)`.

   Fix (Accumulate the day's gains rather than overwriting with the last award's delta.):
   const delta = Math.max(0, record.xp - previousTotal);
   snaps.daily[key] = (snaps.daily[key] ?? 0) + delta;
   snaps.totals[record.agentId] = record.xp;

2. ⚠️ Bug: History totalXp is flat: reports current total for every day
   Files: lib/gamification/xp-store.ts:170-172, lib/gamification/xp-store.ts:186-195

   `snaps.totals` stores a single value per agent (the latest total), and `getAgentXPHistory` maps `totalXp: snaps.totals[agentId]` onto every historical row. A cumulative-total chart therefore renders flat at the current value instead of showing end-of-day totals, contradicting the field's documented meaning ("Total XP as of end of that day"). Persist a per-date total (e.g. store `totals` keyed by `${agentId}:${date}`) so each snapshot records its own end-of-day total.

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Important

Your trial ends in 6 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant