Authoritative per-domain and cross-domain performance targets, maintained alongside the benchmarks that verify them. Update this file in the same PR as any change to the related SLO threshold in code.
Canonical benchmark sources (do not duplicate budgets in docs alone — a budget that lives only here and not in a test is not enforced):
| Benchmark | Scope |
|---|---|
testing/performance/benchmarks/api-benchmark.test.ts |
Per-domain public REST SLOs (health + concurrent fan-out) |
testing/performance/benchmarks/cross-domain-latency.test.ts |
Inter-domain hop latency (Phase 13.6.3) |
testing/performance/k6/load-test.js |
General platform load (ramp to 50 users) |
testing/performance/k6/stress-test.js |
Platform stress (failure discovery) |
testing/performance/k6/aphrodite/*.js |
Aphrodite live-stream load suites |
testing/performance/k6/isis/*.js |
Isis enqueue throughput & worker drain |
testing/performance/k6/veritas/*.js |
Veritas CDN-backed latency |
testing/performance/k6/sophia/*.js |
Sophia retrieval (search + RAG) and knowledge-graph load |
testing/performance/k6/hathor/*.js |
Hathor world CRUD, narrative, simulation first-frame |
testing/performance/k6/bellona/*.js |
Bellona sessions, builds, exports, hot-path polling |
testing/performance/k6/yemaya/*.js |
Yemaya studio edit, asset upload commit, collab fan-out |
0. Measurement provenance#
These numbers are test-enforced budgets. They originate from (a) product
latency requirements for each interactive loop, (b) a floor analysis of the
minimum work each endpoint must do (bcrypt, pgvector search, LLM call, etc.),
and (c) measured percentiles in the reference environment below. Every cell in
the §2 table is the target.p{50,95,99} constant of the corresponding scenario
in testing/performance/benchmarks/cross-domain-latency.test.ts — changing a
number here without changing the test is a type-checked impossibility, so the
doc cannot drift from the gate.
Where the gate runs:
| Harness | Run by |
|---|---|
cross-domain-latency.test.ts (harness + budgets) |
pnpm test:performance:cross-domain (CI step) |
| Per-domain k6 scripts | .github/workflows/benchmarks.yml (schedule + dispatch) |
| Consumer-driven contracts | pnpm test:integration:contracts (CI step) |
Harness integrity is itself tested: the percentile math, empty-sample handling,
and report shape are asserted by the cross-domain latency harness describe
block at the bottom of the benchmark file (tests run in every CI pass regardless
of whether endpoints are reachable). On CI's default path, cross-domain budget
assertions run against in-cluster staging endpoints when BENCH_*_URL env vars
are set, and with BENCH_ALLOW_SKIP=1 otherwise — skipped scenarios are
explicit and loggable, never silent passes.
| Field | Value |
|---|---|
| Last reviewed | 2026-04-18 (cross-domain harness + k6 scripts wired into CI) |
| Reference environment | Kubernetes prod-staging, us-east-1, single region |
| Reference hardware (API) | c7i.2xlarge (8 vCPU / 16 GiB), 2 replicas per service |
| Reference hardware (DB) | Aurora PostgreSQL db.r6i.2xlarge, pgvector HNSW ef=64 |
| Reference hardware (GPU) | A100 40 GB for Isis TTS/IMG paths, CPU-only warm for retrieval |
| Network | In-cluster service mesh, no cross-AZ steady-state traffic |
| Warmup | 3 iterations per scenario discarded |
| Sample size | 20–25 iterations per scenario (see benchmark source) |
| Percentile method | Linear-interpolated quantile — unit-tested in the bench file |
| Full-response body consumed | Yes — latency includes body drain, not just TTFB |
| Concurrency | Sequential per scenario (k6 scripts cover concurrent load) |
How to refresh this table after a measurement run:
- Stand up the reference stack
(
docker compose -f docker/docker-compose.dev.yml up -d- the capability-domain services).
- Run the harness with endpoint env vars set:bash
BENCH_ISIS_GENERATION_URL=http://localhost:8120 \ BENCH_SOPHIA_URL=http://localhost:8130 \ BENCH_BELLONA_URL=http://localhost:8140 \ BENCH_HATHOR_URL=http://localhost:8150 \ pnpm test:performance:cross-domain - For each scenario whose measured p50/p95/p99 drifts more than 10% from the
current
targetconstant, update the constant incross-domain-latency.test.tsand the matching row in §2 together in one PR. - Bump "Last reviewed" to the date of the run.
1. Public REST endpoint SLOs#
Source of truth: testing/performance/benchmarks/api-benchmark.test.ts.
| Endpoint class | P50 | P95 | P99 | Scope |
|---|---|---|---|---|
| Health check (per domain) | 50 ms | 100 ms | 200 ms | /health — yemaya, lilith, isis, sophia, hathor, bellona |
10× concurrent /health |
n/a | 1,000 ms | n/a | Aggregate wall-clock budget (one representative domain) |
50× concurrent /health |
n/a | 3,000 ms | n/a | Aggregate wall-clock budget (one representative domain) |
Per-domain authenticated read/write budgets (search, entity resolution, world
create, session create, build fetch, etc.) are documented under §3 and gated by
the domain-specific harnesses listed there. Authentication round-trip is not
covered here — Oshun has no monolithic /api/auth/login; each domain validates
JWTs issued upstream and the token-issuance path is gated by
tests/security/cross-domain-auth.security.test.ts.
Review cadence: quarterly. The benchmark asserts against these exact numbers.
2. Cross-domain call latency (Phase 13.6.3)#
Source of truth: testing/performance/benchmarks/cross-domain-latency.test.ts.
All hops are measured as a single outbound HTTP call from the consuming domain's BFF/worker to the provider domain's public API, not through the end-to-end gateway. Measurements include full response body consumption.
| From | To | Scenario | P50 | P95 | P99 |
|---|---|---|---|---|---|
| Lilith | Isis | BFF → generation enqueue | 150 ms | 400 ms | 800 ms |
| Lilith | Sophia | BFF → RAG question | 300 ms | 800 ms | 1,500 ms |
| Hathor | Sophia | World-builder → entity resolution | 120 ms | 300 ms | 600 ms |
| Hathor | Bellona | World publication → build trigger | 200 ms | 500 ms | 1,000 ms |
| Isis | Bellona | Generated asset → engine export | 150 ms | 400 ms | 900 ms |
| Yemaya | Bellona | Creative studio → engine session live-link sync | 100 ms | 250 ms | 500 ms |
Budget rationale per scenario:
- P50 reflects a warm-cache happy path with both domains co-located in the same Kubernetes cluster. If P50 regresses, suspect a code-path or serialisation change in the consumer or provider.
- P95 reflects one in-flight dependency slow-down (database failover, Redis eviction, cold model load). Sustained P95 regressions indicate a capacity or autoscaler issue.
- P99 allows for one transient retry. A P99 regression that is not matched by a P95 regression points at transient cross-AZ network or noisy-neighbour effects rather than a code defect.
Running the benchmark against a live stack#
export BENCH_ISIS_GENERATION_URL=http://localhost:3400
export BENCH_SOPHIA_URL=http://localhost:3500
export BENCH_BELLONA_URL=http://localhost:3006
cd testing/performance/benchmarks
npx vitest run --config vitest.config.ts cross-domain-latency.test.ts
By default, when a BENCH_*_URL env var is missing the benchmark falls back to
the local docker-compose port for that domain. If that service is not running
the scenario hard-fails rather than passing silently. PR jobs that cannot stand
up every domain can opt into skipping by exporting BENCH_ALLOW_SKIP=1, which
converts "no endpoint reachable" into a logged skip for that scenario only.
Main-branch CI must not set that flag.
3. Per-domain baselines#
Lilith (consciousness / conversation BFF)#
| Metric | Target | Source |
|---|---|---|
| Chat-turn round-trip (P95) | ≤ 800 ms | docs/domains/lilith/extras/performance/CHAT_TTS_LOAD_TESTING.md |
| Chat-TTS first-byte latency (P95) | ≤ 1,200 ms | same |
| Regression gate script | scripts/lilith/performance/check-regression.cjs |
|
| SLA validator | scripts/lilith/performance/validate-sla.cjs |
Isis (generative factory)#
| Metric | Target | Source |
|---|---|---|
| Job enqueue (P95) | ≤ 250 ms | testing/performance/k6/isis/enqueue-throughput.js |
| Worker consumption latency (P95) | ≤ 500 ms queue-to-dequeue | testing/performance/k6/isis/worker-consumption.js |
| Flagship model TTFT | ≤ 1,500 ms | libs/iris/testing/src/performance/latency-tests.ts |
| Standard model TTFT | ≤ 800 ms | same |
Sophia (knowledge / RAG)#
| Metric | Target |
|---|---|
/v1/search (P95) |
≤ 500 ms |
/v1/ask (P95, 3 retrieved docs) |
≤ 800 ms |
/v1/entities/resolve (P95) |
≤ 300 ms |
/v1/graph/subgraph (P95) |
≤ 600 ms |
Baseline rationale: Sophia's retrieval path hits pgvector + a reranker; the 800
ms P95 for /v1/ask assumes the reranker is on and reads 3 documents.
Hathor (worldbuilding)#
| Metric | Target |
|---|---|
POST /v1/worlds (P95) |
≤ 400 ms |
POST /v1/worlds/{id}/export (P95) |
≤ 2,000 ms |
POST /v1/worlds/{id}/quests/generate |
≤ 3,500 ms |
| Simulation stream first frame (P95) | ≤ 1,500 ms |
World exports are large (the P95 budget allows 2 s to accommodate packaging a small-to-medium world). Quest generation includes an LLM call.
Bellona (engine bridge)#
| Metric | Target |
|---|---|
POST /v1/sessions (P95) |
≤ 250 ms |
POST /v1/builds (P95) |
≤ 500 ms |
POST /v1/exports (P95) |
≤ 400 ms |
GET /v1/builds/{id} (P95) |
≤ 150 ms |
| Engine health ping round-trip (P95) | ≤ 100 ms |
Yemaya (creative studio)#
| Metric | Target | Source |
|---|---|---|
| Studio edit round-trip (P95) | ≤ 150 ms | Yemaya UI interactivity budget |
| Asset upload commit (P95, ≤ 100 MB) | ≤ 2 s | Storage pipeline |
| Multi-user sync fan-out (P95) | ≤ 250 ms | Yemaya live-link session |
Aphrodite (talent / live stream)#
Source: testing/performance/k6/aphrodite/README.md and per-scenario scripts.
| Scenario | P95 budget |
|---|---|
| Viewer simulation — 1k peak | ≤ 300 ms |
| Device flooding — 50 devices | ≤ 800 ms |
| Tip storm — 100 tips/sec | ≤ 500 ms |
Veritas (news / citations)#
Source: testing/performance/k6/veritas/config.js.
| Scenario | P95 budget |
|---|---|
| Combined load (read-heavy) | ≤ 350 ms |
| Stress (peak burst) | ≤ 700 ms |
Iris (AI assistant framework)#
Source: docs/domains/iris/extras/performance-validation-report.md. Summary:
| Target | Number |
|---|---|
| Response latency | P95 ≤ 500 ms |
| Memory retrieval | P95 ≤ 100 ms |
| Voice first-byte | ≤ 200 ms |
| Concurrent capacity | Adaptive rate limiting gated |
4. Regression policy#
A PR is considered to regress performance if any of the following occur:
- A benchmark in the table above fails its assertion and the assertion is not concurrently relaxed in the same PR with a written justification.
- The cross-domain latency test reports an unexpected
SKIPfor a scenario whose env vars CI has always provided. - A new cross-domain hop is added without a corresponding row in §2.
Regressions are blockers. The PR author is expected to either fix the regression, attribute it to a concurrent migration with a tracking issue, or revise the budget with sign-off from the affected domain owners.
5. Update procedure#
When changing a budget:
- Update the SLO threshold in the canonical benchmark source (§2 lists the files).
- Update the corresponding row in this document.
- Re-run the benchmark locally to confirm the new threshold passes against current production numbers. Attach the percentile output to the PR.
- Reviewers from every affected domain sign off.