Claudium
Concepts

Per-org learning loop

The intelligence layer learns from your team's activity and nobody else's. Schema, RLS, embedding pipeline, cost guardrails.

This is the layer that turns Claudium from a visualization into a recommendation engine. It runs per-org, gated by Postgres RLS so a wrong query in application code cannot leak rows across orgs.

The pipeline

sender → hub

  validate + broadcast (live to viewers)

  event-store INSERT (per-org RLS, monthly quota gated)

  turn-stitcher (every 30 s) → turns + summaries

  embedding-worker (every 60 s) → 1536-dim vector in turn_embeddings

  [Phase 3 — pattern detection]   ← roadmap

  [Phase 4 — Admin Patterns tab]  ← roadmap

Three stages of data flow:

1. Events

Every tool call from every contributor becomes a row in events. High-volume. Columns:

  • org_id (hard-partitioned, RLS-enforced)
  • sender_id (which contributor)
  • session_id (Claude Code session id)
  • ts, agent_id, region, tool_name, token_count, target
  • turn_id (nullable; filled by the stitcher)

2. Turns

A turn is the natural unit of one assistant reply between two user messages. The turn-stitcher groups consecutive events from the same (session_id, sender_id) within a 60-second gap into one turn, writes a compact summary ("Read auth.ts; Edited auth.ts; ran tests"), and updates events.turn_id for the assigned rows.

Turns are the unit the embedding pipeline operates on. They give the model enough context to capture intent without being so large that embedding cost explodes.

3. Embeddings

Each turn's summary is sent to OpenAI's text-embedding-3-small (1536 dims, $0.02/M tokens) and the resulting vector lands in turn_embeddings with the same org_id for direct RLS matching. The HNSW cosine index on the embedding column makes nearest-neighbour queries fast inside an org.

The threat model: "the next bug we write"

The hardest sentence to keep honest in a multi-tenant product is "your data only helps your team." It's true in most products only because application code happens to write the right WHERE clause every time. One refactor, one query handed to a model and committed without review, and the promise breaks.

So we built the per-org learning loop around the assumption that we will write that bug, and the database should refuse to return foreign rows when we do.

Every tenant table — events, turns, turn_embeddings, subscriptions, plus the pre-existing senders / memberships / orgs — carries an org_id column and the same RLS policy:

CREATE POLICY turn_embeddings_tenant ON turn_embeddings
  FOR ALL
  USING (
    current_setting('app.bypass_rls', true) = 'on'
    OR org_id = NULLIF(current_setting('app.org_id', true), '')::bigint
  )
  WITH CHECK (...same...);

Two GUCs control visibility:

  • app.org_id — set at the start of every per-org transaction. Rows are visible only when matched.
  • app.bypass_rls = 'on' — exclusively for legitimate cross-org work (token-hash lookup, cron discovery, admin). Used via withServiceClient and audited at the call site.

Both are wrapped in withOrgClient(orgId, fn) and withServiceClient(fn). The application rule is: a pool.connect() outside these wrappers fails code review.

Cost guardrail

Embedding isn't free. We agreed on "global daily cap, per-org pro-rated by tier." Defaults:

ScopeDefaultEnv override
Global$5/day (~2.5M turns)OPENAI_DAILY_BUDGET_USD
Free org$0.05/day (~25k turns)OPENAI_ORG_DAILY_BUDGET_FREE_USD
Team org$0.30/day (~150k turns)OPENAI_ORG_DAILY_BUDGET_TEAM_USD
Enterprise orgno per-org cap (global still applies)

Past the cap, the worker leaves the turn unembedded and tries again on a future tick (or tomorrow). Fail-open on lookup error — better to over-count than to silently lose a paying customer's data.

What's NOT happening

A few decisions on the record:

  • No fine-tuning across orgs. Even with strong RLS, sharing a fine-tuned model leaks signal. We never fine-tune.
  • No opt-in cross-org recommendations. It's tempting to offer "compare your team to industry averages." The answer is no until we earn that bit of trust.
  • No persistence of file contents or prompts. Only metadata leaves the contributor's machine. The embedding input is the summary string the stitcher builds, not the contents.

Roadmap

What's not yet shipped on this page:

  • Phase 3 — Pattern detection. Nightly cron that runs sequence mining (PrefixSpan-ish) for "X before Y" patterns plus DBSCAN clustering on the embeddings. Per-org.
  • Phase 4a — Admin Patterns tab. Read-only dashboard surfacing the patterns. First user-facing surface. Read-only by design — we want to see what patterns look like before deciding how aggressively to push them.

Both ship as separate updates once the pipeline up to embeddings has been running long enough to know what the data looks like in real orgs.

On this page