All docs

Quota engine

Quota engine

Module: packages/core/src/quota/ Table: usage_counters (migration 0009_quotas.sql) Period: calendar month, UTC. Encoded as YYYY-MM.

Plans

PLANS in plans.ts is the source of truth for tier limits:

'unlimited' is a sentinel — the gate short-circuits without touching the counter table.

PLANS also carries marketing/display fields (label, priceEurMonthly, comingSoon, historyRetentionDays, supportLevel) so the pricing page and billing UI render against the same object the enforcement reads.

Tables

CREATE TYPE usage_metric AS ENUM (
  'tasks_created','knowledge_stored','embeddings_generated','agents_created'
);

CREATE TABLE usage_counters (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  org_id     uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  period     text NOT NULL,
  metric     usage_metric NOT NULL,
  count      bigint NOT NULL DEFAULT 0,
  updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX usage_counters_org_period_metric_uq
  ON usage_counters (org_id, period, metric);

Period rollover

There is no scheduled reset job. currentPeriod() derives YYYY-MM from Date.now() at call time. When the calendar flips, the next incrementUsage inserts a fresh row for the new period; the old row stays for audit.

nextResetAt() returns the UTC first-of-next-month for Retry-After / reset_at headers.

Race safety

incrementUsage uses a single UPSERT — the count column is updated via count = usage_counters.count + $by, which Postgres serializes per row. 20 parallel increments produce exactly +20 (covered by integration test checkQuota race-free under concurrent increments).

Wiring

Hot-path writers call checkQuota BEFORE the transaction and incrementUsage AFTER commit:

await checkQuota(db, orgId, 'tasks_created');
const result = await db.transaction(async (tx) => {
  /* insert + audit */
});
try {
  await incrementUsage(db, orgId, 'tasks_created');
} catch (err) {
  // Best-effort. Counter under-count is preferable to rolling back a
  // successful business write — the gate may briefly leak one row.
  console.warn(`[quota] increment failed for ${orgId}: ${err}`);
}

assertWithinAgentLimit(db, orgId, currentCount) is used on register_agent (the metric is absolute, not monthly). Sibling helpers assertWithinRepoLimit and assertWithinTeamLimit follow the same shape for resources where the rows themselves are the truth.

History query (billing UI)

usageHistory(db, orgId, metric, months = 6) returns the last N months oldest → newest, zero-filling months where no counter row was recorded so the billing sparkline (task 41) always renders exactly months bars.

Error contract

checkQuota throws QuotaExceededError:

{
  "code": "quota.exceeded",
  "message": "tasks_created over limit (used=250, limit=250)",
  "details": {
    "metric": "tasks_created",
    "used": 250,
    "limit": 250,
    "reset_at": "2026-06-01T00:00:00.000Z",
    "tier": "free"
  }
}

MCP/API layers should map this to HTTP 429 with the reset_at value as Retry-After.