Architecture
Dual‑store pattern¶
- Convex (tables:
runs_live,kpis_1m,projects,apiKeys,entitlements) for real‑time reads and live subscriptions. - Postgres (Prisma models) for durable analytics and joins.
- A lightweight internal sync bridges Convex→Postgres.
Ingestion path¶
flowchart LR
App -->|LLM call (BYOK)| Provider
App ==> |RunForge.track metrics| RunForge[/api/ingest/]
RunForge --> Convex[(Realtime)]
RunForge --> Postgres[(History)]
Convex --> Dashboard
/api/ingestvalidates payloads with Zod and authenticates viaINGEST_API_KEY.api.runs.ingestRun/ingestRunsinsert intoruns_liveand schedulerunsActions.internalSyncToPg.- Internal HMAC‑signed POST to
/api/internal/pg-syncmarkssyncedToPgon success.
Cost resolution¶
flowchart TB
Response -->|usage.total_cost?| Decision{OpenRouter cost?}
Decision -->|Yes| UseProviderCost[Use provider cost\ncostSource=provider]
Decision -->|No| Registry[Compute from pricing registry\ncostSource=catalog]
Registry --> Flags[costEstimated?]
Streaming timing¶
sequenceDiagram
participant App
participant Provider
App->>Provider: stream=true (+include_usage)
Provider-->>App: chunks...
Provider-->>App: final chunk (usage)
App->>RunForge: POST /ingest (tokens, latency, model)
Note over App: No prompts/outputs sent
Dashboard read path¶
flowchart LR
Convex[(runs_live)] -->|query listRuns/listRunsByOrganization| UI
Convex[(kpis_1m)] -->|rollup-1m cron| UI
- UI subscribes to
api.runs.listRunsandapi.runs.listRunsByOrganization. kpis.rollup1mcomputes per‑minute aggregates for projects.
Experiment run matrix (concept)¶
sequenceDiagram
participant User
participant UI
participant Convex
participant API
participant Postgres
User->>UI: Start experiment matrix
UI->>Convex: create Experiment doc (future)
UI->>API: enqueue runs per model/temp
API->>Convex: write runs_live
Convex-->>UI: stream live results
Convex->>API: internalSyncToPg
API->>Postgres: upsert runs
UI->>API: fetch metrics/export
Idempotency, IDs, retention¶
- Idempotency by
idcheck onruns_live(by_run_idindex), using providedrunIdwhen set. generateRunId()uses UUIDv4 fallback; ULIDs optional.- Retention policy for
runs_liveTBD. TODO: Add TTL cleanup forruns_live(seeconvex/runs.ts).
See also: 05-apis.md, 08-convex-realtime.md