Gamification & Leaderboard System
v3.8.1Last updated: 2026-05-19
Was this page helpful?
Loading OmniRoute...
Last updated: 2026-05-19
Was this page helpful?
Source of truth: , ,
Last updated: 2026-05-19 β v3.8.0
zero-latency on the hot path β gamification events are dispatched fire-and-forget from the request pipeline and never block an LLM response.
calls
after the response is sent; the event module fans
out to XP, streak, badge, leaderboard, and anti-cheat subsystems.
. WAL journaling is inherited from the singleton
in .
from
, exports typed CRUD functions. No raw SQL in route handlers.
File:
follows a polynomial curve:
export async function awardXp(
apiKeyId: string,
action: XpAction,
metadata?: Record<string, unknown>
): Promise<{ xp: number; level: number; title: string; levelUp: boolean }>;
until the cumulative XP
exceeds . Returns the highest level whose threshold is met.
This is O(100) β acceptable since levels cap at 100.
File:
, |
||
, |
||
as JSON :
{
"type": "action_count",
"action": "request",
"count": 1000
}
event-driven β it runs after every gamification event, but
only checks badges whose aligns with the event action. This
keeps evaluation fast (< 5ms for most events).
| entry for this action type | |
File:
table (shared utility table) under namespaced keys:
export async function updateStreak(
apiKeyId: string
): Promise<{ current: number; longest: number; milestone: boolean }>;
, (ISO date string).; update if needed. (streak broken).).
This is intentional β a single canonical timezone prevents gaming via
timezone hopping.
- New users: no streak record exists; first request creates it with
.
- Multiple requests per day: only the first request of the UTC day
increments the streak.
File:
computed at read time, not stored. This avoids stale rank data and eliminates the need for periodic rank recalculation jobs.
export async function getLeaderboard(
scope: LeaderboardScope,
period: string,
limit: number,
offset: number
): Promise<{ entries: LeaderboardEntry[]; total: number }>;
SELECT api_key_id, score,
RANK() OVER (ORDER BY score DESC) as rank
FROM leaderboard
WHERE scope = ? AND period = ?
ORDER BY score DESC
LIMIT ? OFFSET ?
with the period label.
- Reset: delete entries for the expired period.
- Trigger: checked on every
call; the first request
of a new period triggers the rotation.
Endpoint:
File:
:
SELECT COALESCE(SUM(CASE WHEN to_key_id = ? THEN amount ELSE 0 END), 0) - COALESCE(SUM(CASE WHEN from_key_id = ? THEN amount ELSE 0 END), 0) AS balance FROM token_ledger WHERE from_key_id = ? OR to_key_id = ?
export async function transferTokens(
fromKeyId: string,
toKeyId: string,
amount: number,
idempotencyKey: string
): Promise<{ success: boolean; balance: number }>;
, .
- Idempotency: check if
already exists in ledger.
If yes, return cached result.
- Transaction (single SQLite transaction):
a. Compute sender balance.
b. If
, abort (insufficient funds).
c. Insert send row ().
d. Insert receive row ().
- Rate limit: check transfer rate for sender (max 10 transfers/min).
- Event: emit
gamification event for XP + badge evaluation.
- .
File:
), human-readable,
displayed to the user.
- Token: 32-byte random token, stored as SHA-256 hash. Used for
programmatic redemption (e.g., URL links).
| (unique, indexed) | |
File:
overwrite sync, not additive:
row. This avoids transmitting the stored hash.
, , |
|
and sync is paused until a manual health check succeeds.
File:
. Clients never
submit a score β they submit actions, and the server computes XP. The
column is only writable by server-side code.
in ). Falls back to SQLite-backed
counters if the process restarts.
with
and surfaced on the admin dashboard.
with:
.
POST /api/gamification/transfer
// Request
{
"to": "recipient-api-key-id",
"amount": 500,
"idempotencyKey": "uuid-v4"
}
// Response 200
{
"success": true,
"transfer": {
"id": "txn-uuid",
"from": "sender-api-key-id",
"to": "recipient-api-key-id",
"amount": 500,
"createdAt": "2026-05-19T12:00:00.000Z"
},
"balance": 2500
}
// Response 400 (insufficient funds)
{
"error": "Insufficient balance",
"balance": 200,
"requested": 500
}
GET /api/gamification/leaderboard?scope=weekly&limit=10
{
"scope": "weekly",
"period": "2026-W20",
"entries": [
{
"rank": 1,
"apiKeyId": "key-uuid",
"displayName": "User***1234",
"score": 15230,
"level": 42,
"title": "Expert"
}
],
"total": 847,
"updatedAt": "2026-05-19T12:00:00.000Z"
}
alongside existing tools. Scoped under
the permission scope.
| Get leaderboard for a scope/period | |
| | Get caller's rank and neighbors | |
| | Get XP, level, title, streak summary | |
| | List earned badges or all definitions | |
| | Send tokens to another user | |
| | Generate or list invite codes | |
| | List or connect community servers | |
| | View anomaly reports (admin scope) | |
:
// After response is sent to client:
setImmediate(() => {
emitGamificationEvent({
type: "request.completed",
apiKeyId,
metadata: {
provider: selectedProvider,
model: selectedModel,
comboId: resolvedCombo?.id,
compressionUsed: compressionStats?.applied,
skillUsed: skillExecution?.name,
},
}).catch(() => {
// Fire-and-forget: log but never propagate to client
});
});
+ pattern ensures:
| on redemption | |
| for token hash comparison |
, (read-only
leaderboards).
- API key required: all write operations, profile, transfers, invites.
- Admin only: anomaly dashboard, audit log viewer.
- Federation: separate auth path using raw token in
header, validated against stored SHA-256 hash.
).
# All gamification tests node --import tsx/esm --test tests/unit/gamification/*.test.ts # Single test file node --import tsx/esm --test tests/unit/gamification/xp.test.ts
β all new modules must have:
, ..
-
-
-
, .
-
-
.
-
-
-