Skip to content

daari — Task tracking

Last updated: 2026-09-23 (config TLS/auth rows — #998) Update this file when phases/tasks complete.
Repo layout and request flow: ARCHITECTURE.md

Legend

  • [x] done [ ] pending [~] in progress [-] deferred

GTM

Launch spine and always-on measure/draft rules: docs/gtm/PLAN.md. Scoreboard lands in #229. Issues #226–#233.

Item Status Notes
Repo listing (description, topics, homepage, Discussions) [x] Set 2026-08-26 via gh repo edit
Honest public copy (#228) [x] #234
Scoreboard (#229) [x] #237
Launch drafts (#230) [x] #236
Comparison SEO (#231) [x] #239
Shipping-note generator (#232) [x] #240
Discussions welcome (#233) [x] templates in docs/gtm/discussions/; welcome is #238
Cloud autodev secret (#226) [ ] HITL — cycle is a 6s no-op
Dual-license (#227) [x] Full Apache 2.0 — ADR-0016

Setup / self-service

Item Status Notes
S1 daari onboard (#256) [x] pip/brew first-run without a git clone
S2 Install CI gate (#257) [x] install job in .github/workflows/ci.yml
S3 Default pull L3+embed (#258) [x] install.sh + doctor require embed when L1 on
S4 Cursor tunnel one-liner (#259) [x] daari setup cursor --tunnel --yes --daemonize
S5 daari service (#260) [x] user systemd / launchd unit files; no CI start; restart added in #323
S6 Windows/WSL (#261) [x] install-windows.md + scripts/install.ps1 (WSL only)
S7 onboard --serve (#268) [x] background daemon after first-run

Phase A — Tracer bullet

Task Status Notes
Scaffold (pyproject.toml, Typer CLI) [x] cf50264
Config (Settings, ~/.daari/config.yaml) [x]
Internal model (InternalRequest / InternalResponse) [x]
L0 exact cache [x] diskcache
ProviderRegistry (cache + Ollama) [x]
OpenAI gateway (POST /v1/chat/completions) [x]
FastAPI server (daari serve) [x] port 11435
Ollama executor (L3) [x]
Router L0 → L3 [x]
Metrics / daari stats [x]
Agent passthrough (exact L0 on identical tools+history) [x] ADR-0004 / G1 #223
X-Daari-No-Cache / tier override headers [x]
Ollama-down → 503 [x]
Eval file GP-01–GP-10 [x]
Routing eval pytest [x] 6768fb8
Live Ollama integration test (optional) [x] skipped without OLLAMA_HOST
Manual Cursor doc [x] setup/cursor.md
Dev pickup docs [x] DEVELOPING.md
Streaming SSE [x] basic OpenAI-style SSE passthrough for stream=true

Exit criteria

Criterion Status Notes
Second identical prompt hits L0 [x]
daari stats shows tier breakdown [x]
Cursor via tunnel setup [x] scripts/tunnel.sh --setup-cursor + daari setup cursor --base-url/--tunnel
GP-01–GP-10 pass MVP criteria [x] tests/test_routing_eval.py

Tests: see Testing below.


Testing

Layer Location CI Notes
Unit tests/unit/ ✅ cache keys, semantic similarity, metrics, settings, internal models, confidence, L6 escalation
Integration (mocked) tests/integration/test_gateway_flow.py, tests/integration/test_l1_semantic_cache.py, tests/integration/test_l6_escalation.py, tests/test_phase_a.py, tests/test_routing_eval.py ✅ gateway + router + L0/L1 cache + L6; Ollama mocked
Integration (live Ollama) tests/integration/test_ollama_live.py, test_sampling_live.py, test_client_path_live.py skipped @pytest.mark.integration; run with OLLAMA_HOST=http://127.0.0.1:11434 pytest -m integration
Benchmark tests/benchmark/ skipped @pytest.mark.benchmark; L0 vs L3 latency
Setup / doctor tests/test_setup.py, tests/test_doctor.py ✅ dry-run, backup, doctor checks

Commands

pytest                              # default: unit + mocked integration (no live Ollama)
pytest -m "not integration and not benchmark"   # same as CI
pytest -m integration               # live Ollama only (needs OLLAMA_HOST + model pulled)
pytest -m benchmark                 # optional latency checks
./scripts/demo.sh                   # one-click smoke (serve + curl + stats)

CI: .github/workflows/ci.yml — Python 3.12, pytest -m "not integration and not benchmark" on push/PR. No secrets.

Gaps (planned): L6 live API integration test (optional, requires frontier key/model); richer streaming metadata.

Count: 1221 passed (pytest -m "not integration and not benchmark", 2026-09-01)


Phase A.1 — Install & setup

Task Status Notes
scripts/install.sh [x] venv + pip + Ollama pull; 13a2345
daari doctor [x] daari/setup/doctor.py
daari setup cursor --dry-run [x]
daari setup cursor (apply + backup) [x] aaf3f06
daari setup --undo cursor [x] daari/setup/backup.py
Interactive daari setup wizard [x] daari/setup/wizard.py — partial vs spec (see gaps below)
daari setup models [x] daari/setup/models.py
JSONC patch helpers [x] daari/setup/jsonc.py
Setup tests [x] tests/test_setup.py
daari install (Typer) [x] wrapper command to scripts/install.sh with --run-doctor; pip/brew falls back to onboard
daari onboard [x] #256 — first-run without a git clone
L6 frontier executor [x] daari/router/frontier.py — OpenAI-compat httpx
Confidence scoring → L6 [x] daari/router/confidence.py — binary heuristic per routing-spec

Exit criteria

Criterion Status Notes
./install.sh && daari doctor passes [x] CI install job: RUN_DOCTOR=0 ./scripts/install.sh + .venv/bin/daari --help; doctor required checks stay in tests/test_doctor.py
daari setup cursor --dry-run shows diff [x] covered by tests
Low-confidence response escalates to L6 [x] when frontier.enabled + API key present

Wizard gaps (A.1 spec vs shipped): single-choice menu (not multi-select); frontier helper writes hints/templates only (no secret capture by design); IntelliJ/Claude deferred to Phase B per setup-spec.

Key commits: 13a2345 (scaffold), aaf3f06 (apply, undo, wizard, models)


Phase B — Full local-first stack

Task Status Notes
L1 semantic cache [x] Ollama embeddings + diskcache; router L0 → L1 → L3
L2 rules engine [x] JSON/YAML deterministic transforms before model tiers
L2-dev developer commands [x] regex rules for git/test/lint + readonly command-context prompts
CCS command context store [x] disk-backed command output reuse with TTL
PolicyEngine B.0 [x] allow/block + unknown deny/ask outcomes for Lt execution
Lt B.0 CLI tools [x] git status, git diff, pytest, eslint command dispatch
L4 medium model [x] second local model tier + L3→L4→L6 escalation path
daari setup openai-compat [x] prints OPENAI_* exports + writes ~/.daari/.env.example
Wizard frontier key helper [x] optional profile hint + env template (no config secret storage)
daari context clear [x] clears L0/L1/CCS caches
daari setup all auto-detect run [x] detects registered clients and runs applicable recipes
daari setup intellij [x] minimal IntelliJ helper config + dry-run + undo path
daari setup vscode [x] VS Code dry-run/apply/undo recipe with marker + docs
daari setup claude-code [x] minimal env helper + config pointer recipe with dry-run/apply
Lt ask/confirm UX [x] daari_meta.confirmation_prompt + X-Daari-Confirm: yes
Lt --yes support [x] --yes in prompt text now confirms unknown-policy commands
Doctor L4 pull hint [x] optional model_l4 hint with pull command
Install optional L4/L5 pull flags [x] daari install --pull-l4 --pull-l5 + scripts/install.sh env knobs
Eval expansion GP-11–GP-20 [x] prompts + regression assertions updated

Exit criteria (Phase B — partial)

Criterion Status Notes
Paraphrased prompt hits L1 [x] mocked embedder tests
L1 metrics in daari stats [x] tier counter L1
L0 → CCS → L1 → L2-dev → L2 → Lt → L3/L4 routing order [x] with tool_calls bypass caches retained
$0-tier rate ≥30% on eval [x] Measured 2026-07-23 on GP-01–GP-20 mocked suite: 55% $0-tier (L0/L1/CCS/L2/Lt) — see scripts/measure_phase_b_metrics.py
Routing accuracy ≥90% on 20-prompt eval [x] Same run: 100% of eval assertions green (20/20)

Tests: see Testing below.


Phase C bootstrap — in progress

Task Status Notes
Gateway adapter protocol (daari/gateway/base.py) [x] OpenAI adapter now implements protocol
Anthropic gateway adapter (/v1/messages) [x] non-stream + SSE event streaming (stream: true)
MCP gateway ingress [x] JSON-RPC 2.0 at POST /mcp; /v1/mcp/query deprecated alias
L5 local tier wiring [~] config + routing/escalation support; large model remains optional
Sourcegraph/GHE provider depth (C3) [x] Sourcegraph GraphQL + GHE repo/issue search with configurable base URLs and token envs
GitLab self-hosted provider depth (C3) [x] REST project/issue search + @gitlab trigger + MCP tool support
L2-live URL fetch [x] simple fetch trigger (fetch/read/summarize/get <url>) + L3 summarization
SSE metadata enrichment [x] stream chunks now include daari_meta tier/provider/model
Browser extension MVP [x] MV3 popup UI in packages/browser-extension/ sends prompts to local daemon (:11435)
Browser extension options + error UX [x] API base URL options page + popup guidance when daemon is unreachable
Browser extension content script [x] #171 — site profiles, intercept + boundary header, transparent fallback, session savings in popup
Router integration prefixes [x] @sourcegraph / @ghe now route to integration providers before L3
Per-project profiles [x] ~/.daari/profiles/<hash|slug>.yaml + DAARI_PROFILE support
Skills loader stub [x] ~/.daari/skills/*.md merged into system prompt prefix
Anthropic stream fallback [x] stream error now emits SSE error and falls back to non-stream response events
Web UI MVP dashboard [x] daari web-ui serve + static dashboard (packages/web-ui/)
Web UI auto-refresh + tier chart [x] dashboard now includes periodic refresh controls and tier distribution bars
Web UI export + theme controls [x] export current stats as JSON, dark/light toggle, and org-learning summary line
Enterprise scaffold [x] daari/enterprise/ added with minimal OrgSettings models
Enterprise E1 runtime scaffold [x] org cache path resolver + daari serve --org + DAARI_ORG_ID + doctor org check
Enterprise E2 org shared cache service [x] daari org-cache serve + org cache client + router L0-org/L1-org lookup + write-through
context clear stale-cache warning [x] prints restart note when daemon is running to avoid stale in-memory cache handles
Hot cache reload endpoint [x] POST /v1/daari/reload-caches reloads cache handles in running daemon; daari context clear invokes it automatically
Enterprise periodic profile sync [x] startup + interval sync via org.learning_sync_seconds, plus force-sync endpoint/CLI
Org cache retry/backoff hardening [x] retries transient org cache failures with exponential backoff
L1 semantic threshold + bench hardening [x] default threshold tuned to 0.88; scripts/bench.sh now deterministically checks L0 and L1
Doctor embedding-model check [x] daari doctor now validates cache.l1.embedding_model (nomic-embed-text)
PyPI publish [x] daari==1.2.0 is on PyPI. Trusted publisher registered 2026-08-13; python scripts/release_pypi.py publish --target pypi reran the failed v1.2.0 job and verify installed from the index. Homebrew tap already pointed at the same tarball. · #160
Cursor setup smoke script [x] scripts/smoke-cursor-dry-run.sh for CI/local setup dry-run validation
Cursor tunnel setup script [x] scripts/tunnel.sh starts local daemon + cloudflared and prints /v1 URL

Cursor E2E BYOK — POC (2026-06-23)

Manual validation: Cursor Ask + daari model via cloudflared tunnel → local Ollama.
Debug log: ~/.daari/cursor-requests.log (request shape, tier attempts, content_chunks).

Issues found & fixed (shipped 2026-06-23)

Issue Symptom Fix Status
Cursor blocks localhost Access to private networks is forbidden HTTPS tunnel (scripts/tunnel.sh) [x]
Array message content ([{"type":"text",...}]) 422 Unprocessable Entity content_to_text() in gateway [x]
Cursor input_text content blocks Empty stream (~20ms), 0 content_chunks content_to_text() handles input_text, output_text, dict blocks [x]
Cursor sends 18 IDE tools on Ask Ollama returns tool_calls, 0 text chunks Strip tools in _prepare_internal_request() + plain-text system hint [x]
tool_calls left in message history Ollama 400, empty stream sanitize_messages_for_ollama() when tools stripped [x]
Stream path missing L4→L3 fallback L4 404 when llama3.1:8b not pulled; empty or slow retry stream_openai_chunks() tier chain + fallback (matches non-stream route()) [x]
Stream error JSON malformed Cursor freeze / parse errors json.dumps() for stream errors; initial role: assistant chunk [x]
Missing /v1/models Cursor model picker issues GET /v1/models, GET /v1/models/{id} [x]
Gateway request logging Hard to debug Cursor payloads ~/.daari/cursor-requests.log via log_gateway_event() [x]
Integration tests for above — tests/integration/test_gateway_flow.py, tests/unit/test_gateway_content.py [x]

Verified E2E: Cursor user_agent: Cursor/1.0 → daari → Ollama llama3.2:3b (L3 fallback) or llama3.1:8b (L4 when pulled); content_chunks > 0. Release notes: RELEASE-v1.1.2-cursor-e2e.md.

Testing summary (2026-06-23)

Layer Result
pytest (default, mocked) 1475 passed (2026-09-03)
Manual Cursor Ask E2E ✅ math question + follow-up
Log verification ✅ tools_stripped, stream_fallback_ok, content_chunks > 0

Next steps (Cursor / BYOK) — migrated to GitHub issues (2026-07-10)

The open rows below moved to the auto-dev backlog worked by the autonomous dev loop (AUTOMATION.md):

Task Issue
~~Commit Cursor compat fixes to main~~ done (commit 1d651c6)
~~Document cursor-requests.log in setup/cursor.md~~ done
Tool hallucination after tools stripped #1 (P1)
Ask vs Agent mode split (ADR-0004) #2 (P1)
Cursor-specific tier policy #3 (P2)
Pull L4 in install by default #4 (P3)
Anthropic stream usage + fallback parity #5 (P2)
Org L1 semantic matching depth #6 (P2)
Browser extension E2E coverage #7 (P3)
Tag v1.1.2 release prep #8 (P3)
Automated Cursor E2E test covered by local watchdog (scripts/autodev-local.sh, Cursor-shaped smoke every 2h)

Autonomous dev loop (2026-07-10)

Piece Status Notes
Backlog seeded as auto-dev issues #1–#8 [x] priorities P1–P3, acceptance criteria per issue
AGENTS.md agent contract [x] repo root
Repo public + auto-merge + branch protection on main (CI test required) [x] via gh api
Local watchdog (scripts/autodev-local.sh + launchd) [x] validated live: filed issue #9 on first cycle (caught real live-test regression), Cursor smoke PASS
Cloud automation drafts (dev-cycle / pr-review / scout) [x] docs/automations/ — create in Agents Window or enable Bugbot
CI fallback dev-cycle workflow [x] .github/workflows/autodev.yml; activates when CURSOR_API_KEY secret is set
Runbook [x] AUTOMATION.md

Demo cycle (2026-07-10) — loop verified end-to-end

The first full autonomous cycle ran the same day the loop was built:

  1. Detect — local watchdog's first run caught a real regression on main (live Ollama test asserting removed daari_meta default + a prompt failing the confidence heuristic) and filed #9 with logs, labels auto-dev,regression.
  2. Fix — agent picked up #9 per AGENTS.md: branch autodev/9-live-test-meta-headers, fix + test runs (live: 1 passed; default: 161 passed), conventional commit.
  3. Gate — PR #10 opened with Closes #9, auto-merge armed; branch protection held it until CI test went green.
  4. Merge — auto-merged as f12889a; branch auto-deleted; issue #9 auto-closed.
  5. Validate — next watchdog cycle on updated main: daemon healthy, integration tests PASS, Cursor smoke PASS (8 content chunks). cycle result: PASS.

Loop cycles 2–3: P1 backlog cleared (2026-07-10)

Issue Fix PR Merge
#1 tool hallucination strengthened NO_TOOLS_HINT leads message list, idempotent #11 4b35cfa
#2 Ask vs Agent split tool-history detection, X-Daari-Tools override, OpenAI tool_calls stream deltas #12 f080150

Feature cycle: v1.2 candidate set (2026-07-10)

Scouted against LiteLLM/RouteLLM feature sets; filed as issues #13–#15, implemented TDD-style, all auto-merged the same evening:

Issue Feature PR Merge
#13 Streaming L0 exact cache read/write + stream metrics (Cursor BYOK is stream-only; the cache never served it before) #16 d4fe1ee
#14 Persistent usage ledger (~/.daari/usage/ledger.sqlite3) + GET /v1/daari/report + daari report CLI with estimated_saved_usd #17 d9736f1
#15 Frontier daily budget guard (frontier.daily_budget_usd, warning frontier_budget_exceeded, spend surfaced in report) #18 2e7ec56

Default suite grew 162 → 180 tests across these five cycles.

Feature cycle: prompt intelligence & transparency (2026-07-10, PRD docs/prd/intelligence.md)

Filed as issues #19–#22 from the new PRD, implemented TDD-style, all auto-merged, then validated end-to-end by a full autodev-local.sh cycle (deploy ae3b01f, live Ollama integration tests PASS, Cursor-shaped streaming smoke PASS) plus a live trace fetch (daari trace showed profile → l0_lookup → l1_lookup → tier_attempt → served for a real daemon request):

Issue Feature PR Merge
#19 Prompt profile (category/complexity/token est) + routing.category_policies action policies; category in daari_meta.task_type, new complexity #23 ed43d21
#20 Per-request decision trace: TraceStore sqlite, daari_meta.trace_id, GET /v1/daari/traces[/id], daari trace CLI (client-facing audit trail) #24 cb80d4a
#21 Cached-draft injection: L1 near-misses (cache.l1.draft_threshold=0.75) seed local and L6 generation as reuse/reformat drafts #25 e4126d4
#22 Context optimizer: system + last-N history trimming and whitespace squeeze for local models (context_optimizer.*), savings traced per prompt #26 ae3b01f

Default suite now at 226 tests (180 → 226).

Loop cycles: original backlog cleared (#3–#6) (2026-07-10)

The remaining seeded issues from the first backlog, implemented TDD-style, auto-merged, and each validated live by a full autodev-local.sh cycle (final deploy a8600cc, live Ollama integration tests PASS, Cursor-shaped streaming smoke PASS):

Issue Feature PR Merge
#3 Tier cap for chat: routing.max_tier_for_chat + X-Daari-Tier-Cap header clamp initial tier, stream chain, and local escalation (latency recipe in docs/setup/cursor.md) #28 9da32c4
#4 daari setup cursor verifies/pulls the L4 model (--yes for non-interactive; tunnel.sh passes it); daari doctor upgrades model_l4 to required when Cursor is configured #29 79b3221
#5 Anthropic stream parity: tier fallback chain, message sanitization, chars/4 usage estimates in message_start/message_delta #30 a956d33
#6 Org L1 semantic matching: embeddings stored with entries, POST /v1/org-cache/similar, client similarity fallback on key miss (paraphrases now hit L1-org) #31 a8600cc

Default suite now at 250 tests (226 → 250). Remaining open backlog: #7 (browser-extension E2E), #8 (v1.1.2 release prep — tagging stays human-approved).

Loop cycles: extension coverage + scouted improvements (#7, #34–#36) (2026-07-10)

After clearing the original backlog, the loop scouted and filed three fresh improvement issues (#34–#36), then implemented all of them plus #7. Each cycle E2E-validated by autodev-local.sh (final deploy 984bc99); markdown export and cache prune also verified live against the running daemon:

Issue Feature PR Merge
#7 Browser extension DOM tests (jsdom + node --test, 12 tests: send flow, error UX, draft persistence, options); new extension CI job #33 aa6602d
#34 Frontier prompt slimming before L6: strip internal hints, collapse duplicate system prompts, trim history; ledger records actual chars sent (frontier.slim_prompts) #37 e27e930
#35 Client-shareable markdown export: daari report/daari trace gain --format markdown + --out FILE #38 78a0066
#36 Cache TTLs (cache.l0/l1.ttl_seconds, category ttl_seconds overrides) + daari cache prune #39 984bc99

Default suite now at 272 pytest tests (250 → 272) + 12 extension tests. Open backlog: #8 (release prep, human-gated).

v1.1.2 released + CI hardening (2026-07-11)

  • CI expanded to four required checks on main: test, extension, lint (ruff), sanity (runtime-deps install, imports, settings load, CLI entrypoint, app build) — #41, 97cb5e1.
  • v1.1.2 shipped (issue #8, user-approved tag): version bump (pyproject + stale daari.__version__), consolidated release notes addendum, python -m build + twine check PASSED (no PyPI upload) — #42, tag v1.1.2.

Loop cycles: streaming L1 + scouted improvements (#43–#46) (2026-07-11)

Scout pass filed four issues; all implemented TDD-style, auto-merged, and E2E-validated by autodev-local.sh on the final deploy 531e407 (live Ollama integration tests PASS, Cursor streaming smoke PASS; daemon restarted on the new build and /v1/daari/report verified live):

Issue Feature PR Merge
#43 Streaming path L1 parity: semantic hits served as SSE, draft-band injection, post-[DONE] L1 write-back — Cursor (all-streaming) finally benefits from the semantic cache #47 1f3d963
#44 Gateway request log rotation: size-based with numbered backups (observability.request_log_max_bytes, default 5 MB / 3 backups) #48 121aa43
#45 Embedding memoization: in-process LRU in OllamaEmbedder keyed by (model, sha256) — repeat L1 lookups skip the Ollama HTTP call (cache.l1.embed_cache_size) #49 b3e4596
#46 Web UI usage & savings dashboard: report totals, per-day tier table, recent traces with step-timeline click-through; jsdom DOM suite added to CI #50 531e407

Default suite now at 291 pytest tests (272 → 291) + 12 extension tests + 7 web-ui tests. Open backlog: empty — scout refills it.

Phase D1 — personal feedback loop (2026-07-11, PRD docs/prd/learning.md)

daari starts learning from outcomes. Everything stays on-device (~/.daari/feedback/feedback.sqlite3) and stores outcome metadata only — never prompt or completion text. PRD merged as #52.

Issue Feature PR Merge
#53 Outcome store + implicit capture (every model-tier response, stream + non-stream, not cache hits) + explicit POST /v1/daari/feedback / daari feedback <trace_id> --accept\|--reject #56 b272153
#54 daari learn stats per-category × tier evidence + daari learn recommend (cheapest tier with escalation ≤ 15%, rejects ≤ 10%, min 20 samples) emitting a Settings-valid routing.category_policies YAML block; GET /v1/daari/learn/stats #57 62dbf24
#55 Routing tuner: per-category confidence thresholds ±0.05 from outcome evidence, bounded [0.5, 0.9], learning.tuner_min_samples gate, tuner trace step; off by default (learning.auto_tune) #58 518e8ae

Live E2E validated 2026-07-11: daemon restarted on 518e8ae; live doc_qa request through the gateway → daari feedback <trace_id> --accept → outcome + accept visible in daari learn stats; daari learn recommend emitted a valid policy block from live evidence. Default suite now at 329 pytest tests (291 → 329). Remaining Phase D scope (D2 local fine-tuning, D3 opt-in collective stats) stays on the roadmap.

Phase D2 — local fine-tuning train (2026-07-12, PRD docs/prd/learning.md)

The models themselves can now improve. Accepted answers — especially L6 frontier answers — become local training data (distillation). Capture is strictly opt-in (learning.capture_examples, default off) because unlike the D1 outcome store it keeps full prompt/completion text; everything stays under ~/.daari/training/ and is wipeable. PRD merged as #60.

Issue Feature PR Merge
#61 ExampleStore + router capture (both paths, L3–L6, never cache hits/tool flows); accept promotes to training data, reject deletes; daari learn examples [--clear] #64 0144b17
#62 daari learn export-dataset: mlx-lm chat-format train/valid JSONL, deterministic trace_id-hash split, min-examples gate, --only-accepted #65 eb7104d
#63 daari learn finetune: plans + runs mlx_lm lora --train (LoRA on mlx-community/Llama-3.2-3B-Instruct-4bit), auditable run.json, --dry-run, clean optional-dep gating #66 1d4cdb5

Live E2E validated 2026-07-12 on an isolated temp instance (capture on, throwaway stores — user config untouched): 10 live Ollama requests captured, 2 accepted + 1 rejected via /v1/daari/feedback (reject confirmed deleted), export-dataset produced 8 train / 1 valid chat-format examples, and plan_finetune emitted the exact mlx_lm lora command + run.json. Default suite now at 359 pytest tests (329 → 359). Actual training runs are user-invoked (daari learn finetune, needs pip install mlx-lm); serving MLX adapters through Ollama (fuse/convert to GGUF) is the documented manual follow-up. Remaining Phase D scope: D3 opt-in collective stats.

Trust & Efficiency trains (2026-07-12/13, PRD docs/prd/trust.md)

Competitive research (Portkey/LiteLLM/OpenRouter/Requesty/RouteLLM, semantic- cache postmortems, local-first routers) distilled into five trains. Headline: daari now measures semantic-cache false-hit rate — the metric none of the compared products ship. PRD merged as #68.

Issue Train PR Merge
#69 Cache trust: embedding-input normalization (fences/JSON scaffolding stripped, cache.l1.normalize_inputs); per-category answer-diversity monitor (/v1/daari/cache/diversity + doctor warning); shadow sampling of L1 hits (cache.l1.shadow_sample_rate, default 5%) → per-category false-hit rate that auto-raises the L1 threshold; report/learn stats/dashboard panels #70 7721b25
#71 Token savings: Anthropic cache_control prompt-cache hint on L6 with byte-stable prefix pinned by test; context_optimizer.compact — over-limit history summarized by L3 into a pinned recap (memoized per prefix); frontier.compress_context — sentence-level relevance pruning before L6 #75 da4fabf
#72 Latency-aware routing: daari profile hardware benchmarks; routing.latency_budget_ms + category override + X-Daari-Latency-Budget with profiled step-down; warm-model preference via TTL-cached /api/ps #79 4c365d9
#73 Learned routing: daari learn train-router centroid classifier over captured prompts; routing.learned_router overrides heuristics when confident (200-sample floor + margin gate); trace learned_route #79 4c365d9
#74 Budget & client UX: monthly + soft budgets (frontier_budget_warning band before hard cap); per-client ledger attribution (X-Daari-Client-Id, Cursor auto-tagged) + daari report --by-client; opt-in pre-L6 PII scrub with typed placeholders #79 4c365d9

Trains 3–5 were consolidated into #79 after the stacked branches went stale post-squash (#76–#78 closed as superseded). Live E2E validated 2026-07-13 on an isolated temp instance (port 11438, live Ollama, throwaway stores): paraphrase served from L1 with normalization on, shadow check ran in background (answer similarity 0.984 → agreed, false-hit rate 0.0 in learn stats/report/diversity endpoints), per-client attribution (e2e-test) and budget state in the report, and benchmark_model + warm tracker measured the live 3B model (349 ms wall, 115 tok/s, warm set detected). Default suite 430 pytest tests (359 → 430); web-ui at 9 DOM tests. All new behaviors default-safe: normalization + shadow sampling on (read-only additions), compaction/compression/learned-router/PII-scrub opt-in.

One-click client setup (2026-07-13, issue #81)

Item PR Merge
Claude Code one-click: daari setup claude-code merges ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/ANTHROPIC_MODEL into the env block of ~/.claude/settings.json (existing keys preserved, backup + undo); Anthropic gateway now honors the top-level system field (string or blocks) that Claude Code sends. Ollama-compatible facade: /api/tags, /api/chat (stream NDJSON + non-stream), /api/version, /api/show, /api/ps — JetBrains AI Assistant, Zed, Continue etc. connect by pasting http://127.0.0.1:11435, full router semantics + per-client attribution (X-Daari-Client-Id). IntelliJ recipe/docs updated with exact AI Assistant steps; native IntelliJ plugin deferred in favor of the facade. #82 bbe233e

Live-verified 2026-07-13 on the running daemon: /api/tags lists daari + tier models, non-stream /api/chat answered from L3 with daari_meta, and the intellij client id landed in the ledger. The tool-passthrough limit noted at the time was closed the same day by issue #84 (below). Suite: 442 pytest tests.

Claude Code agent E2E + tunnel hardening (2026-07-13, issues #84/#86/#88)

Item PR Merge
Anthropic tool passthrough: tools/tool_choice on /v1/messages, tool_use→tool_calls and tool_result→role:tool conversion, agent flows skip sanitization/context-optimizer, streamed tool_use content blocks with stop_reason: "tool_use"; X-Daari-Tools: strip forces plain chat; one-click uninstall fallback strips daari ANTHROPIC_* keys when no backup exists #85 1e2934c
Gateway API-key auth: server.api_key setting + middleware (Bearer or x-api-key, health endpoints exempt); daari setup cursor --tunnel auto-generates + persists the key and configures Cursor with it; docs on named Cloudflare tunnels / Tailscale Funnel #87 b594500
Claude Code live E2E fixes (first real session surfaced 3 gaps): OllamaRequestError preserves Ollama 400 bodies in logs; num_ctx sized from prompt chars (4096..32768) so 10-20k-token Claude Code system prompts stop yielding empty streams; tool-call arguments converted JSON-string→object and content-less non-tool messages dropped (both 400'd Ollama); anthropic_messages_request shape logging #89 8265b21

Loop cycle 2026-07-22 — full live E2E + per-project profiles (#91)

Regression issue #90 closed as transient: the working tree sat on a dev branch Jul 13–22 so the watchdog skipped deploys; its own kickstart recovered the daemon in the same run. Daemon redeployed on main @ 8265b21, then the full live battery passed 11/11 against the running daemon: OpenAI fresh→L3, repeat→L0, stream SSE+DONE, Anthropic non-stream with system, streamed tool_use + stop_reason, facade /api/tags + /api/chat, stats/report/ traces/diversity endpoints. Full suite 474 pytest + ruff + extension/web-ui npm suites green.

Per-project profiles (roadmap Phase C1's last unshipped item): commit a .daari.yaml at the repo root (routing.max_tier_for_chat, no_frontier, latency_budget_ms, client_id); clients opt in with X-Daari-Project: /path/inside/repo on both OpenAI and Anthropic gateways (the Anthropic gateway also gained X-Daari-Client-Id parity). Explicit headers always win; profiles are mtime-cached and malformed files never break a request. New CLI: daari project init / daari project show. Docs: setup/project-profiles.md.

Release v1.2.0 + Claude Code live fix (2026-07-23)

Item PR Merge
Release v1.2.0 — Learning, Trust & Clients: version bump + RELEASE-v1.2.0.md covering the 26 commits since v1.1.2 (Phase D learning, Trust trains, one-click clients, gateway auth, per-project profiles); tagged and published #93 6bfc9e3
Trailing system message fix (issue #94): live claude -p printed nothing — claude-cli 2.1.215 sends messages=[user, system] (SessionStart-hook/plugin content as a 14k-char trailing system-role message) and llama chat templates emit zero tokens in that shape, so L4 and L3 both streamed empty. Captured via a recording proxy; Anthropic gateway now hoists out-of-order system messages ahead of the conversation (stable order, leading-system requests untouched) #95 bab511c

Suite: 493 pytest. The #94 regression test replays the captured Claude Code request shape (top-level system + [user, system] + tools) and asserts Ollama receives [system, system, user]. Live claude -p re-verification runs on the next watchdog deploy cycle.

MLX backend + CI hardening (2026-07-23, issue #97)

Item PR Merge
MLX backend (roadmap C2 optional): MLXExecutor speaks OpenAI wire to mlx_lm.server and duck-types OllamaExecutor (SSE chunks converted to Ollama-style events), so tier loops/caching/escalation/budgets/traces are unchanged; mlx.models tier→model map routes only listed tiers to MLX (mixed setups fine, default off); optional daari doctor mlx check; setup/mlx.md #98 5270dd4
CI ruff pin: lint job installed unpinned ruff>=0.8; a new ruff release flagged 167 pre-existing issues repo-wide and failed every open PR — pinned to ruff==0.15.17 (local version, clean) so lint is deterministic and upgrades become deliberate PRs #99 b1c2385

Suite: 508 pytest (493 → 508). MLX live smoke is hardware/download-gated (pip install mlx-lm + model fetch) — doctor and docs cover the path.

Anthropic observability + Phase D3 + adapter deploy (2026-07-23, issue #101)

Item PR Merge
Anthropic stream observability & routing parity (issue #101): anthropic_stream_attempt_failed logs error_type (timeouts stringify to ""), new anthropic_stream_done event records winning tier/model/latency/chars, and the Anthropic path now builds the prompt profile so category policies, learned routing, and latency-budget step-down apply like the OpenAI path #102 e4628f4
Phase D3 — opt-in collective stats (learning PRD): daari learn export-stats prints the exact metadata-only payload for review (category/tier aggregates, shadow false-hit rates, model IDs — never prompts/IDs); --upload gated on learning.collective_enabled + collective_url with a recursive sensitive-key guard; all defaults off #102 e4628f4
daari learn deploy: bridges D2c fine-tune runs to executors — mlx backend prints the mlx_lm server --adapter-path command + config snippet (long-running, plan-and-print); ollama backend fuses to GGUF and ollama creates a named model with deploy.json status audit #102 e4628f4

Suite: 535 pytest (508 → 535). Live E2E on the redeployed daemon: /v1/messages stream answered via L3 in 2.2s and anthropic_stream_done appeared in cursor-requests.log; learn export-stats rendered real 30-day aggregates (metadata only); learn deploy printed correct mlx and ollama plans against a stub run dir.


Phase E2 — Org shared cache (tracer bullet)

Task Status Notes
Org cache HTTP service (/get, /put, /stats) [x] daari/enterprise/service.py
Router shared-cache lookup order (L0-org, L1-org) [x] local L0 -> org L0 -> local L1 -> org L1
Shared write-through from local model responses [x] pushes L0 + L1 keys to org cache when configured
Config expansion (org.id, shared_cache_url, token, timeout) [x] Settings.load maps org block into enterprise
daari serve --org org-cache client wiring [x] AppContext.from_settings instantiates OrgCacheClient when URL set
Doctor org-cache reachability check [x] optional org_cache check (/v1/org-cache/stats)
Tests (service/client/router/config/cli) [x] no real network required in CI
E3 collective learning [x] metadata-only feedback API + profile sync + CLI stats/export
Web UI serve CLI smoke test [x] tests/test_setup.py::test_web_ui_serve_mounts_static_assets

Deferred / user-owned

  • Cursor smoke test on personal device (daari setup cursor + chat through daari) — Ask E2E verified 2026-06-23; see Cursor E2E BYOK POC
  • L4 model pull/install still user-managed (falls back to L3 when unavailable; pull llama3.1:8b to use L4 without retry)
  • L6 live frontier smoke depends on API key presence
  • Cursor follow-up quality / tool hallucination when tools stripped (tracked in open todos above)
  • PyPI upload remains user-gated (needs PyPI token in repo secrets)

Roadmap v2 (F1–F5) — 2026-07-23/24

Forward plan: ROADMAP-v2.md. Issues labeled auto-dev.

Train Status Notes
F1 OSS launch [x] Docker/compose//ready (#105), MkDocs (#114), PyPI prep (#106, upload user-gated), CHANGELOG + community pack
F2 Gateway parity [x]/[~] Responses API (#108), Prometheus (#107), guardrails (#110), capability catalog (#113); L6 pool (#109), virtual keys (#111) in flight / merged via auto-dev PRs
F3 Ops [x] Prometheus + Grafana (#107); OTel export + structured stdout logs + GET/PATCH /v1/daari/config (#115)
F4 Enterprise scale [x] Redis L0 (#112); Postgres ledger/traces + observability.stateless (#116); Helm + capacity (#117); org pool + daari enterprise bootstrap (#118); SSO/RBAC/audit tracer (#119)
F5 Leftovers [x] Live sources (#120); MCP egress (#121); Phase B exit metrics recorded above + scripts/measure_phase_b_metrics.py (#122); Homebrew formula (#123, sha256 filled after release)

Suite: 669 pytest (default markers).

Auto-mode deepeners (2026-07-24) — reviewed 2026-08-11

See HANDOFF-AUTO-2026-07.md. Built as tracer deepeners with review deferred; the frontier review pass landed as issue #137 (see below).

Item Status Notes
Periodic org policy sync in daemon [x] policy_sync.py + learning sync loop
Config editor persist to disk [x] persist: true on PATCH; atomic 0600 write
D4 propose-defaults [x] YAML proposal only — never auto-promote
Web UI config card [x] Bearer field added in #141
Strong-model review [x] #137 — see review pass below
PyPI upload / Homebrew sha256 [ ] User-gated (needs a release tarball)
Redis L1 semantic (cache.backend=redis) [x] #135 — RedisSemanticCache, tags fable-review/135-*
OIDC JWKS admin SSO [x] #136 — tags fable-review/136-*; HMAC stub retained
Web UI Bearer / API key field [x] #141 — tags fable-review/141-*
Live Redis+Postgres compose E2E [x] #142 — profile backends + scripts/smoke_backends.py / .sh
F6 Product boundaries (scope gate) [x] #145 — ADR-0015; B0–B3 + embed B0 (#172); tags fable-review/boundaries-*

Frontier review pass (2026-08-11, issue #137)

Review of everything built while the deep-review pass was deferred. Defects fixed in tests/unit/test_review_hardening_137.py (15 tests, all previously red):

Defect Severity Fix
Daemon policy sync applied unsigned org config when no signing secret was set (CLI path already failed closed) high sync_policy_once requires a secret unless insecure=True
Policy sync accepted plaintext http:// for config that can disable guardrails and raise budgets high policy_url_is_secure() — https, or http to loopback only
Blocking httpx fetch ran on the event loop, stalling in-flight requests for up to the 10s timeout medium asyncio.to_thread in the sync loop
Sync failures were swallowed by except Exception: pass — no way to see a broken policy feed medium log_gateway_event("policy_sync_failed"/"policy_sync_skipped")
daari learn propose-defaults read a {category: {accept_rate, n}} shape that build_collective_stats never emits, so every run wrote an empty proposal and reported success medium _flatten_category() derives accept rate + sample count from the real nested categories payload; flat shape still supported
~/.daari/config.yaml written non-atomically at umask default, next to provider API keys medium write_config_atomically() — temp file + os.replace, mode 0600 (also used by enterprise bootstrap)
Cached JWKS locked admins out for the full TTL after an IdP key rotation medium refetch once with force=True on unknown kid
PATCH /v1/daari/config returned 500 on malformed values and half-applied out-of-range ones; setattr bypassed pydantic validation medium daari/config/validate.py validates the whole patch first → 400

validate_assignment on Settings closed in #152. EC/ES256 JWKS keys closed in #151. Redis L1 lost-update is #150.

Streaming policy parity (#154, #155) (2026-08-11)

A feature-gap audit found that boundaries, guardrails, and frontier escalation ran only in Router.route(). Both streaming entry points were separate code paths that called none of them — so for every supported IDE client, which streams by default, those features were inert. Reproduced before the fix: the same prompt with boundaries.mode = "block" returned tier='boundary' with zero upstream calls through route(), but streamed model output with no refusal.

Defect Severity Fix
Boundary gate never ran on either stream path — a block-mode refusal was silently skipped for all IDE traffic high _apply_input_policy() shared by route() and both stream paths
Input guardrails never ran on either stream path high same shared gate; refusals emitted as well-formed terminal streams (OpenAI [DONE], Anthropic message_stop)
Output guardrails never ran on streams, so a leaked secret reached the client and was written to L0/L1 high _apply_output_policy() runs over accumulated text before flush and before cache write-back
_stream_tier_chain was local-model-only, so a streaming request could never reach the org pool or L6 high streams call _maybe_escalate(..., local_ladder=False) — org pool and frontier now reachable

local_ladder=False is deliberate: _stream_tier_chain has already chosen and fallen back through the local tiers, so re-running L4/L5 non-streamed would only duplicate work. Non-streaming keeps the full local ladder.

Covered by tests/unit/test_stream_policy_parity.py (11 tests, 5 previously red).

Streaming tier parity (#155) (2026-08-11)

Follow-up that closed the rest of the streaming gap.

Defect Severity Fix
Deterministic tiers (Lt shell tools, L2 rules, live fetch, integrations) were unreachable while streaming — the same prompt got a model answer instead of tool output high _resolve_deterministic_tier() extracted from _route_impl and called from both stream paths
Escalated streams buffered the whole frontier answer before emitting, so first-token latency was non-streaming latency medium FrontierExecutor.stream() relays upstream SSE; the router relays it chunk-by-chunk

_route_impl is now three named steps (deterministic tiers → generation → escalation) rather than one long function, which is what let the stream paths reuse the same logic instead of reimplementing a subset of it.

The relay is skipped, falling back to the buffered path, when the complete text is needed first: output guardrails must see the whole answer to redact it, and the org pool has to be tried before paying for frontier. Both are explicit checks in _can_relay_frontier_stream().

Covered by tests/unit/test_stream_tier_parity.py (7 tests, 4 previously red), including a MockTransport case proving the SSE decoder skips blank, malformed, and [DONE] lines.

Real token accounting and per-model pricing (#156, #157) (2026-08-11)

Every token count in daari was len(chars) // 4, and every cost was that estimate multiplied by one flat frontier.price_per_1k_tokens. Two compounding approximations fed the savings report, the /v1/daari/report endpoint, the Prometheus spend gauges, and the budget cutoff that decides whether to escalate.

Defect Severity Fix
Providers report real usage (Ollama prompt_eval_count/eval_count, OpenAI usage) and none of it was read high daari/observability/tokens.py readers; captured into DaariMeta.input_tokens/output_tokens on both non-streaming and streaming paths
Clients received chars/4 in the usage block with no way to tell it was a guess medium real counts when reported; daari_meta.usage_estimated says which it was
One flat rate priced every model and both directions, so a gpt-4o output token and a gpt-4o-mini input token cost the same high daari/pricing.py with per-model, per-direction USD/1M rates; pricing.models in config
Ledger stored only chars, so spend could not be recomputed per model after the fact medium usage/client_usage gain model, provider, input_tokens, output_tokens

Output tokens cost 4-5x input tokens at every major provider, so pricing both directions at one blended rate was the largest single source of error.

The ledger migration rebuilds usage and client_usage rather than using ALTER TABLE ADD COLUMN: the upsert needs (day, tier, model) as its conflict target and SQLite cannot alter a primary key in place. Existing rows are copied across with model = '', which prices them at the fallback rate — historical rows never had a model recorded, so no precision is lost that existed before.

Unpriced models still work: they fall back to the flat rate and daari doctor prints a warning naming the model, so silently-wrong spend is visible rather than discovered on a bill. The shipped price table is a dated convenience default and pricing.models always overrides it.

Covered by tests/unit/test_token_accounting.py (15 tests) plus an integration test asserting reported counts survive from executor to both the API usage block and the ledger columns.

L1 hit verification before serving (#168) (2026-08-11)

daari measured its semantic-cache false-hit rate but still served any hit above a cosine threshold. Correct and incorrect similarity distributions overlap, so a threshold cannot separate the two cases: "what is 15% of 200" and "what is 15% of 300" are textually near-identical with different answers, while "how do I list files" and "how can I list files" are further apart with the same answer. Embedding distance ranks those two pairs in the wrong order.

cache.l1.verify (none | lexical | model, default lexical) adds a second stage between the cosine match and the response. The lexical verifier rejects differing numbers, differing or reordered units, flipped negations, swapped opposites, and substituted content words; rejections fall through to generation and increment daari_cache_false_hits_avoided_total.

Two deliberate choices worth knowing:

  • Entries without stored prompt text are not served. Caches written before this change have no text to verify against, and serving them unverified would preserve exactly the false hits this exists to prevent. They are re-learned on the next miss, so the cost is a one-time warm-up.
  • Additions pass, substitutions do not. Extra words are filler; a swapped word may change the answer.

The known limitation is honest and tracked rather than hidden: a lexical stage cannot distinguish a harmless synonym substitution ("fix" for "resolve") from a meaningful one ("staging" for "production"), because both are one-word swaps. It errs toward regeneration and loses those hits. verify = "model" is the path to recovering them.

evals/cache/verification.jsonl labels 36 pairs and runs in CI as a gate: paraphrase retention and near-miss rejection both floor at 90%, currently 100% each, with synonym retention reported but ungated at 0/6. Verification costs 0.007 ms per call against the hundreds of milliseconds a hit saves.

Covered by tests/unit/test_l1_verification.py (21 tests) and tests/unit/test_l1_verification_corpus.py (4 gates).


Per-key frontier budgets (#158)

Virtual-key budgets were compared against global frontier spend. The middleware said so in its own comment: the ledger had no per-client frontier helper, so it used the global figure "conservatively". The effect was that one key's traffic exhausted every other key's allowance, and a key with a small cap could be blocked by spend it never caused — while per-key budgets were a headline feature of the virtual-keys work (#111).

UsageLedger.frontier_spend_usd_for_client() now answers the question directly, over day or month, priced per model like the global figure. No migration was needed: client_usage already gained the model and token columns in #156.

Two behaviors changed beyond the isolation fix:

  • Monthly caps now do something. Only the daily window was ever checked, so --monthly-budget was silently inert.
  • The 402 names what tripped. The body carries window, budget_usd, and spend_usd, so a client can tell "I am out of budget" from "the org is out of budget" instead of inferring it.

The global caps still apply as an outer ceiling through the router's _frontier_budget_state(), unchanged — a per-key allowance cannot be used to exceed the org cap.

Covered by tests/unit/test_virtual_key_budgets.py (11 tests), including two-key isolation and attribution for keys created without --client-id.


Upstream retries and per-tier timeouts (#159)

Every upstream call got exactly one attempt, so a single transient 429, connection reset, or 503 failed the request outright. Worse, the frontier pool advanced to the next provider on any exception, spending a provider slot on a blip that a 200 ms backoff would have cleared.

daari/router/retry.py adds bounded exponential backoff with equal jitter, wired into Ollama, MLX, and frontier calls. Three properties matter more than the backoff itself:

  • Only transient failures retry. 408, 429, 5xx, connect and read timeouts. A 401 or malformed body fails immediately, because retrying it only delays an error the caller has to see.
  • The retry budget never outlives the request timeout. A backoff that would land past the deadline is not attempted, so retries cannot turn a slow request into a hung one.
  • Retries sit below the ledger write. One client request stays one ledger row, so a flaky upstream cannot inflate request counts or spend.

Retry-After is honored when present, in both seconds and HTTP-date form, and overrides the computed backoff. Jitter spreads retries from requests that failed together, which otherwise return in lockstep and rebuild the load that caused the failure.

Timeouts are now per-tier rather than a hardcoded 120s: upstream.local_timeout_seconds defaults to 120s (a cold local model is genuinely slow) and upstream.frontier_timeout_seconds to 90s (a hosted API silent for 90s usually stays silent).

Each retry becomes an upstream_retry trace step carrying the status and delay, and increments daari_upstream_retries_total. A rising counter means backoff is absorbing instability the client never saw.

Covered by tests/unit/test_upstream_retry.py (35 tests) and tests/unit/test_upstream_retry_wiring.py (14 tests).


Test isolation: a flake and a hazard

tests/integration/test_redis_l1_gateway.py failed roughly one run in ten while passing in isolation. Two independent causes, found by reproducing it rather than by reading the test:

The flake was cache.l1.shadow_sample_rate. It ships at 0.05, so one in twenty L1 hits spawns a background task that re-executes the request to audit the cached answer. The test asserted its mock executor ran exactly once. Because the audit runs as a task, whether it had incremented that counter by assertion time was a race — the cache assertions always passed, which is why the failure surfaced as a call count and looked unrelated to caching. The shared fixture now pins the rate to 0; tests that exercise shadow sampling set it themselves, and the shipped default is asserted separately so disabling it for tests cannot quietly disable it for users.

The hazard was ~/.daari. Six settings defaulted there, including the command-context store that a locally running daari serve writes, its L1 cache, and — worst — the real virtual-keys database. The suite was reading and writing a live daemon's state, and a developer's actual credential store. HOME is now redirected per test, which fixes the whole class rather than the six paths found today.

Verified by 25 consecutive full-suite runs with no failures, against two failures in the preceding 22. Guarded by tests/unit/test_fixture_isolation.py (8 tests), which walks the settings tree and fails on any path escaping to the real home.


Sampling parameters (#161)

ChatCompletionRequest declared six fields and set extra="ignore", so max_tokens, top_p, stop, seed, and response_format were accepted with a 200 and dropped. A client asking for a bounded or deterministic answer got neither, and no error. The other three surfaces were worse: the Anthropic endpoint parsed the max_tokens that API requires and never used it, the Responses endpoint did the same with max_output_tokens, and the Ollama facade read temperature out of options and discarded num_predict beside it.

daari/gateway/sampling.py now owns one SamplingParams model with a reader per surface, and each executor maps it on the way out. Three rules shaped it:

  • Unset means absent. Sending None for an omitted parameter would override the backend's own default, so unset keys never reach a payload.
  • What cannot be honored is reported. presence_penalty, n > 1, logprobs, and tool_choice: required become a daari_meta.warning. Appended, not assigned: a low-confidence answer already sets a warning, and that is when a client most needs to know what else was dropped.
  • Only honored parameters split the cache. Sampling is in the cache key, so a 16-token answer cannot be served to a request asking for 500, while a parameter that never reached the model does not fragment the cache. Requests that set nothing keep hitting pre-#161 entries.

Two bugs the mocked tests could not have found, both caught by asserting on the wire: max_completion_tokens is present-but-None in a parsed body, so get(key, default) read the None and never fell back to max_tokens; and Ollama matches stop case-sensitively, which broke a test rather than the mapping.

Covered by tests/unit/test_sampling_params.py (mapping), test_sampling_end_to_end.py (values reach the wire, across all four surfaces), and tests/integration/test_sampling_live.py, which confirms against a real llama3.2:3b that the cap truncates, a shared seed reproduces, a stop sequence ends generation, and JSON mode parses.


PyPI and Homebrew (#160)

v1.1.1, v1.1.2, and v1.2.0 all built and then failed invalid-publisher because no trusted publisher was registered. That is one form under a PyPI account — no API, no token — so it waited on a human. On 2026-08-13 the pending publisher was added with the OIDC claims publish.yml sends, and python scripts/release_pypi.py publish --target pypi reran the failed job from the v1.2.0 tag so the upload matched the tag rather than main. verify --version 1.2.0 then installed into a throwaway venv and ran daari --help. The Homebrew formula and tap were already filled from the earlier prep; the formula job in publish.yml did not run on the rerun because it was added after the tag, and had nothing left to write.


Multimodal images (#164)

content_to_text kept only text blocks, so an image_url part was accepted with a 200 and the model answered as if no picture was sent. The capability check then looked for the substring "image_url" in that already-flattened text, so vision could never fire.

Images now live on Message.images. Ollama gets them as images: [b64]; OpenAI- compatible backends get image_url content parts. A stack with no vision-capable model returns 422 rather than a confident wrong answer. Cache keys hash the image bytes; a request with no images keeps its pre-#164 key.

Embeddings endpoint (#163)

OllamaEmbedder already ran in-process for L1, but there was no POST /v1/embeddings, so any app that wanted a vector had to point at a second host. The endpoint is OpenAI-shaped (string or batch), served by cache.l1.embedding_model, cached in L0 under an __embed__: key, listed on /v1/models, and recorded as tier embed. model: daari aliases the configured embedder; anything else is 400.

Native Anthropic L6 egress (#166)

provider: anthropic used to POST an OpenAI-shaped body at /chat/completions and sprinkle cache_control on system strings. That is not the Anthropic API, so Claude keys 400'd and prompt-cache billing never applied. FrontierExecutor now selects a native Messages path when provider is anthropic/claude or anthropic.com is in base_url: x-api-key + anthropic-version: 2023-06-01, system text in the top-level system field, tool_use/tool_result blocks, image sources, and cache_control: ephemeral on the last system block. Stream deltas parse content_block_delta / text_delta. OpenAI-compatible providers are unchanged.

Ingress gained POST /v1/messages/count_tokens — a local estimate_tokens of system + messages + tools, not an L6 call. Claude-family clients use it for context budgeting.

Covered by tests/unit/test_anthropic_egress.py and tests/integration/test_anthropic_egress_live.py (skipped without ANTHROPIC_API_KEY).

MCP JSON-RPC server (#162)

POST /v1/mcp/query was a bespoke body, not JSON-RPC, so no real MCP client could connect even though the README claimed tools/list / tools/call. POST /mcp is now a streamable-HTTP JSON-RPC 2.0 endpoint: initialize, notifications/* (202), tools/list, tools/call, with parse/invalid/method-not-found codes. Tools are route, stats, and registered integration: / mcp: providers, each with an inputSchema. Auth is the existing master / virtual-key middleware. The old query path stays as a deprecated alias (Deprecation: true, Link: </mcp>).

Covered by tests/unit/test_mcp_server.py. Client config: MCP guide.

Responses API completeness (#165)

The adapter was text-only: function_call input items were skipped, tool-call output items were never emitted, and there was no conversation state. Agent frameworks moving off Assistants (sunset 2026-08-26) could not use this surface.

function_call / function_call_output now map to internal tool_calls / role=tool. Non-stream output emits function_call items; streams emit response.function_call_arguments.delta. previous_response_id prepends the stored conversation (sqlite next to the trace store). store: false skips persistence. background: true returns queued and GET /v1/responses/{id} polls to completed. include is 400; metadata is echoed.

Covered by tests/integration/test_responses_api.py, including a call through the official OpenAI AsyncOpenAI.responses client.

Distributed rate limiting (#169)

Virtual-key RPM used to be a SQLite INSERT + COUNT on every request — no TPM, no global cap, no concurrency gate, and no shared counters across replicas. One runaway agent loop could saturate the local GPU.

daari/auth/rate_limit.py now owns one limiter with Redis counters when cache.backend: redis and SQLite otherwise. RPM and TPM apply per key and per model (rate_limit.rpm / tpm / model_rpm / model_tpm; a virtual key's --rpm / --tpm override the defaults). max_in_flight plus queue_size bounds concurrency; overflow is 503 + Retry-After. Successful and rejected responses carry X-RateLimit-Limit / Remaining / Reset. /metrics exposes the configured limits and current in-flight / queued gauges. The Redis path never opens SQLite.

Covered by tests/unit/test_rate_limit.py.

Local backend pool (#170)

Local tiers accepted one Ollama URL. A dead or overloaded host failed every request until restart — no health checks, no load balancing, and circuit breakers existed only for frontier providers.

routing.local_pool.backends is a per-tier host list (empty still means ollama.base_url). A background loop probes /api/version (or MLX /v1/models) without blocking requests. Pick is least_outstanding or round_robin, with warm-model hosts preferred. Each host has a CircuitBreaker. /ready is ready, degraded (200, some hosts down), or not_ready (503). The chosen host is daari_meta.backend_id, a backend_pick trace step, and daari_backend_* Prometheus series. All hosts down is a typed backend_unavailable 503.

Covered by tests/unit/test_local_pool.py.

Redis L1 lost-update (#150)

RedisSemanticCache stored the whole entry list under one key and did an unsynchronized GET/SET. Two replicas that loaded the same list each appended and the later SET dropped the earlier write — the opposite of sharing nearest-neighbor hits.

Puts (and TTL prune) now go through WATCH/MULTI optimistic locking with five retries, then a last-snapshot write that never raises into the request path. max_entries eviction still applies to the merged list. No new dependency.

Covered by tests/unit/test_redis_semantic.py.

OTel GenAI semantic conventions (#167)

OTel export used to emit ad-hoc daari.* attributes with everything stringified — no gen_ai.* names, so daari traces joined nothing in Langfuse/Grafana/Datadog.

Root spans are now chat {model} with gen_ai.operation.name, provider.name, request.model, response.model, response.finish_reasons, usage.* (real provider counts only — estimates are flagged daari.usage_estimated, never reported as measurements), and error.type. Metrics: gen_ai.client.token.usage, gen_ai.client.operation.duration, and for streams gen_ai.server.time_to_first_token / time_per_output_token. daari facts stay under daari.*; numbers are numbers. Startup installs OTLP providers when OTEL_EXPORTER_OTLP_ENDPOINT is set and nothing configured OTel first. Conventions are Development status and may shift.

Covered by tests/unit/test_otel_genai.py; wire-verified by scripts/smoke_otel_genai.py against an in-process OTLP collector.

Live product benchmark (#189)

scripts/bench.sh was a curl smoke with UUID prompts — it could not back a routing or cache-trust claim. scripts/bench_live.py now runs the labeled corpora (evals/routing/prompts.jsonl, evals/cache/verification.jsonl) against a hermetic daari serve + real Ollama (fresh cold-cache instance per phase), reports $0-tier rate, routing accuracy, L1 paraphrase retention / near-miss rejection, p50/p95 per tier, and frontier USD avoided from provider-reported tokens, and publishes developer/resources/benchmarks.md with commit, hardware, model IDs, and date. Never calls a paid API by default (X-Daari-No-Frontier; expected-L6 rows excluded unless --allow-frontier). Reproduce guide: developer/guides/observability/live-benchmark.md.

First honest run found a real trust bug: the #168 lexical verifier is bypassed on the serve path (router calls nearest(), never get()), so near-misses serve from L1 — filed as #206 (P1).

Pure logic covered by tests/unit/test_bench_live.py; the live path runs via pytest -m benchmark or the script itself, skipping cleanly without Ollama.

L1 verifier restored on the serve path (#206)

The #168 lexical verifier only ran inside SemanticCache.get(), but the draft-band refactor (779a146) had switched the router to nearest() — so no live request was ever verified and near-misses served from cache ("15% of 300" got the answer for "15% of 200"). nearest_with_source() now returns the stored prompt text and both serve paths (non-streaming and streaming) apply verify_for_serving() before serving; a vetoed hit falls back to the draft band instead. Live bench: near-miss rejection 17% → 100%; retention dips to 61% because the verifier vetoed benign synonym substitutions — recovered in #208 (SYN 6/6, near-miss still 18/18).

Covered by tests/unit/test_l1_verify_serve_path.py (router-level, both paths, veto + paraphrase + draft fallback + avoided-counter).

Competitive comparison bench (#190)

scripts/bench_compare.py answers "why daari" with the #189 corpus run three ways on the same machine: raw Ollama, daari with caches (hermetic cold daemon), daari with X-Daari-No-Cache. Publishes developer/resources/benchmark-comparison.md (linked from the benchmarks page and mkdocs nav) with per-prompt latency, tier, implied frontier USD (gpt-4o rates priced onto recorded tokens — never billed), and aggregates: median cache-hit speedup, total USD avoided, $0-tier rate. Prompt IDs match #189 so rows join. Default run never calls a paid API; --live-frontier exists but is not required to publish.

Pure logic covered by tests/unit/test_bench_compare.py; live path via pytest -m benchmark or the script, skipping cleanly without Ollama.

Client-path live E2E pack (#191)

tests/integration/test_client_path_live.py exercises the surfaces an IDE client actually sends against real Ollama: OpenAI streaming and Anthropic /v1/messages streaming each yield more than one chunk; a vision request on a text-only stack returns 422; POST /v1/embeddings returns a vector whose length matches the embedder (nomic-embed-text). Sampling (max_tokens binds, seed reproduces) stays in test_sampling_live.py. The color-conditioned vision check skips unless a vision-capable model is pulled. Default suite stays green (@pytest.mark.integration).

Note: stock capability defaults tag L5 as vision even when L5 is collapsed onto a text-only 3B — the live 422 test declares the catalog explicitly.

daari vs LiteLLM (#214)

scripts/bench_vs_litellm.py runs the #189 routing corpus through a default LiteLLM OpenAI-compat proxy (same Ollama, no paid providers) and a hermetic daari serve. Publishes developer/resources/benchmark-vs-litellm.md joined by prompt ID. LiteLLM is not a runtime dependency: skip without it, or --spawn into a throwaway venv. Covered by tests/unit/test_bench_vs_litellm.py.

Load harness (#215)

scripts/bench_load.py measures achieved RPS and p50/p95 on a hermetic daari serve: a warmed L0 replay mix, a unique no-cache generate mix (max_tokens capped), and a tool-bearing agent mix (G1 / #223) that reports L0 hit rate and implied frontier input $ avoided. Publishes developer/resources/benchmark-load.md with commit, hardware, concurrency, RPS, p95, and errors. No vegeta/k6 dependency. Capacity guide now points at the measured page. Covered by tests/unit/test_bench_load.py (reporter + skip).

OIDC ES256 JWKS (#151)

verify_oidc_token dispatches on JWK kty (RSA → RS256/384/512, EC → ES256/384/512). Unsupported kty raises ValueError naming the type. JWKS documents that mix use: enc and use: sig prefer the signing key. Covered by tests/unit/test_oidc_jwks.py.

Discussions welcome GTM-6 (#233)

Templates under gtm/discussions/ (welcome, show-report, setup-help) plus gh discussion create instructions. Welcome thread is #238. README and CONTRIBUTING link Discussions. Do not post a second welcome. Covered by tests/unit/test_gtm_discussions.py.

OpenRouter gap roadmap (2026-08-26)

Stripe agreed to acquire OpenRouter. Forward plan is prd/ROADMAP-v3.md: do not clone the 400-model marketplace; win agent token economics (G1 prefix cache — exact L0 on identical tools + history; L1 still off) and honor OpenRouter’s provider object on L6 (G2/G3).

OpenRouter L6 slot G3 (#225)

Documented id: openrouter slot (openrouter/auto, OPENROUTER_API_KEY). Outbound calls send HTTP-Referer / X-Title. L6 meta has upstream cost_usd vs daari_cost_usd: 0. Local :floor / :nitro aliases pick the smallest capable or warmest local tier. Live chat test skips without the key. Covered by tests/unit/test_openrouter_slot.py.

OpenRouter provider object G2 (#224)

OpenAI and Anthropic adapters parse the client provider object onto InternalRequest. zdr: true with no frontier.providers[].zdr slot is HTTP 400. OpenRouter L6 slots receive the object on the outbound body. daari_meta records provider_prefs, cost_usd, and cached_tokens. Covered by tests/unit/test_provider_object.py (mocked HTTP, no live key).

Browser extension content script (#171)

MV3 content script + site profiles intercept in-page chat widgets, route to local daari with X-Daari-Boundary-Profile, and fall through when the daemon is down or the boundary refuses. Popup shows session tier / estimated savings. Shipped demos: ports 8765 (fintech) and 8766 (docs support). See packages/browser-extension/README.md.

Cost-of-pass + agent $0-tier G4 (#243)

scripts/bench_live.py retries each routing row until the observed tier matches (or --cost-of-pass-cap) and publishes a Cost of pass table (attempts, ms, implied $). A separate tool-bearing corpus (evals/routing/agent.jsonl, AG-01–AG-08) reports the share of agent prompts that never left the device. Linked from developer/guides/observability/live-benchmark.md. Reporter tests live in tests/unit/test_bench_live.py (default suite).

First live run (2026-08-27, Apple M4 Pro, Ollama 0.18.2): agent $0-tier 100% of 8 tool-bearing prompts; routing 16/19; cost-of-pass missed GP-10 / GP-12 / GP-19 at cap 3. Page: developer/resources/benchmarks.md.

Availability G5 (#244)

A context-length error on L3 remaps to L4/L5 (then compress+retry L3) and records context_length_failover on the request trace. GET /v1/models (and retrieve) include capabilities for local models and configured L6 slots (zdr when the provider is tagged). The live max_tokens check uses usage.completion_tokens + finish_reason — supersedes the brittle "twenty" not in text assertion that filed #213 / #217 / #220. Covered by tests/unit/test_context_length_failover.py, tests/unit/test_models_catalog.py, tests/unit/test_max_tokens_bind.py.

Agent prefix cache G1 (#223)

ADR-0004 no longer skips L0 on tools / tool history. Identical agent turns hit exact L0 (full messages + tools schema are already in the key); changing only the last tool result misses. L1 and Lt stay off for agent turns. Load harness agent mix publishes RPS / L0 hit rate / implied frontier $ on developer/resources/benchmark-load.md.

Settings validate_assignment (#152)

Runtime-mutated settings models (RoutingSettings, FrontierSettings, CacheSettings + L0/L1, BoundariesSettings, ObservabilitySettings) use validate_assignment and field constraints (ge/le, Literal modes). setattr of an out-of-range value raises ValidationError. Config PATCH still returns 400 via daari/config/validate.py.

Boundaries B2/B3 + embed B0 (#172)

B0 can cosine-score topics/examples with the L1 embedder and falls back to lexical matching when embeddings fail. Ambiguous cases take an N-vote local quorum (B2, quorum_votes) and an optional frontier judge (B3) that will not spend past frontier_judge_daily_budget_usd. Enabling B3 without a frontier warns at startup. Labeled fixtures in evals/boundaries/fixtures.jsonl gate CI on false-refuse = 0. Covered by tests/unit/test_boundaries.py.

Routing eval harness (#173)

Labeled corpus is evals/routing/prompts.jsonl (plus agent.jsonl). Live numbers live on developer/resources/benchmarks.md. CI gates $0-tier ≥30% and routing accuracy ≥80% via daari.eval.routing_score (published headline + mocked GP-01–20). ROADMAP-v2 Phase B “never measured” row is retired. Covered by tests/unit/test_routing_score.py.

IdP-minted virtual keys (#176)

enterprise.sso.key_mappings maps an IdP claim (groups, department, …) to budget, RPM, tier cap, team, and boundary profile. First verified SSO session mints the key; later sessions resync. The key is revoked when the mapped claim disappears. Unmapped claims use default_policy or 403 (deny_unmapped). Audit records include the claim. Documented next to daari enterprise bootstrap in developer/guides/configuration/auth-and-keys.md. Covered by tests/unit/test_sso_key_mappings.py.

Multi-window budgets and teams (#174)

Virtual keys store budget_windows: [{duration, max_usd}] and optional team_id. A teams table holds inherited caps; the tighter of key vs team wins per duration. daari keys create --team / --window, daari keys team-create, and daari report --by-team. 402s include reset_at and scope. Flat daily_budget_usd / monthly_budget_usd rows migrate to day/month windows. Covered by tests/unit/test_budget_windows.py, tests/unit/test_virtual_key_budgets.py.

Lexical synonym allowlist (#208)

LexicalVerifier normalizes British/American spelling and a small curated verb/adj allowlist (fix/resolve, start/launch, create/write, cause/trigger, fast/quick) before the content-word substitution check. SYN-01–06 retain; all 18 near-miss rows still veto. Covered by tests/unit/test_l1_verification_corpus.py.

Stalled auto-merge watch (#200)

Conflicted auto-merge PRs never get CI (DIRTY / empty checks). AGENTS.md now requires merging origin/main before open and checking mergeStateStatus after --auto. scripts/autodev_pr_watch.py comments on the PR and files auto-dev,regression once; autodev-cycle runs it on the same 6h schedule (no CURSOR_API_KEY required). Covered by tests/unit/test_autodev_pr_watch.py.

Local watchdog readiness poll (#181)

Issue #181 filed false positives: a single 5s /health probe marked the daemon unreachable while the same cycle's Cursor smoke passed once serve finished booting; a transient Ollama disconnect failed live integration tests without a retry. scripts/autodev_local.py polls /ready (then /health) with 30s bounded backoff; autodev-local.sh kickstarts then waits, and retries integration tests once after 3s. Covered by tests/unit/test_autodev_local.py.

daari onboard (#256)

daari onboard --yes is the pip/brew first-run: probe Ollama (print https://ollama.com/download if down), pull L3 + embed when missing, run doctor, print daari serve. daari install still runs scripts/install.sh in a clone and falls back to onboard when that file is absent. Covered by tests/unit/test_onboard.py. Remaining setup train: #257–#261.

Default model stack (#258)

scripts/install.sh pulls L3 + nomic-embed-text unless MINIMAL=1. daari install --minimal / daari onboard --minimal skip embed. When L1 is enabled, daari doctor fails if the embedding model is missing. Covered by tests/test_doctor.py and tests/unit/test_install_default_stack.py.

Install CI gate (#257)

The install GitHub Actions job runs RUN_DOCTOR=0 ./scripts/install.sh on a fresh checkout (no Ollama, no model pulls) and asserts .venv/bin/daari --help. tests/unit/test_install_sh.py execs the script with a fake python3.12 / ollama and requires the job to stay in ci.yml. Doctor required checks remain in tests/test_doctor.py (already on the test job).

Cursor tunnel one-liner (#259)

daari setup cursor --tunnel --yes --daemonize starts daari serve if /health is down, opens cloudflared, writes the public /v1 URL into Cursor, and returns without waiting on the tunnel. Missing cloudflared prints brew install cloudflared. scripts/tunnel.sh --setup-cursor delegates to the same command. Covered by tests/unit/test_cursor_oneclick.py.

User service install (#260)

daari service install writes a user systemd unit (~/.config/systemd/user/daari.service) or launchd plist (~/Library/LaunchAgents/com.daari.gateway.plist). Logs go to ~/.daari/serve.log. status / uninstall operate on those files only — CI does not start a real service. Native Windows is refused with a WSL pointer (#261). Covered by tests/unit/test_service.py.

Windows / WSL first-run (#261)

Honest Windows path is WSL2. docs/developer/get-started/install-windows.md and scripts/install.ps1 install via pip inside WSL and run daari onboard --yes. They do not claim a native-Windows daemon. Covered by tests/unit/test_install_windows.py.

Onboard --serve (#268)

daari onboard --yes --serve starts the daemon in the background after a successful first-run (ensure_local_daemon) and prints the /v1 URL plus GET /ready. Serve is skipped when Ollama is down. Default remains --no-serve. Covered by tests/unit/test_onboard.py.

Stale agent:working sweep (#272)

A dev-cycle agent that dies mid-issue leaves agent:working behind, and the picker skips the issue forever. scripts/autodev_pr_watch.py now sweeps the label from open issues untouched for 24h with no open PR referencing them (branch autodev/<n>- or #<n> in the PR title/body), leaving a marker comment. Runs in the existing stall-watch job. Covered by tests/unit/test_autodev_pr_watch.py.

Relicense to Apache 2.0 (#227)

Human decision: whole tree Apache 2.0, not the NC/fleet hybrid. Recorded in ADR-0016. Public copy, PyPI classifier, and Homebrew license match. v1.3.0 as tagged stays PolyForm NC.

Agent prefix L1 (#269)

G1b turns L1 back on for agent turns, but only over the stable prefix (system + tools + history minus trailing tool results). The trailing tool results are hashed into the L1 lookup context, so an identical prefix with a different last tool result can never serve the previous answer; an identical prefix and tool result can hit L0 or prefix-L1 (nearest_agent_prefix / put_agent_prefix on SemanticCache). Turns that emit tool calls are still never stored as answers. ADR-0004 amended. Covered by tests/integration/test_agent_prefix_l1.py.

Service install --now (#270)

daari service install --now writes the same user unit as S5 and then enables and starts it (systemctl --user daemon-reload + enable --now, or launchctl load -w <plist>). daari service uninstall --now disables/unloads before removing the file. Commands go through an injectable runner (default_runner), so tests never touch a real service manager; a non-zero exit raises ServiceCommandError and the CLI exits 1. Without --now behavior is unchanged. Covered by tests/unit/test_service.py.

OpenAI-compat local backend kind (#275)

routing.local_pool.backends[].kind: openai binds an OpenAI-compat executor (POST /v1/chat/completions, stream converted to Ollama events) and probes GET /v1/models. The slot model is sent to the server; capability tags on that name apply the same as Ollama models. Failures trip the existing circuit breaker. Covered by tests/unit/test_openai_executor.py and tests/unit/test_local_pool.py.

SSE keepalive heartbeat on streaming routes (#276)

server.sse_keepalive_seconds (default 10, 0 disables) drives a shared stream_with_keepalive() helper that emits SSE comment frames (: keepalive) while waiting for the first model chunk on /v1/chat/completions, /v1/messages, /v1/responses, and the Ollama NDJSON facade (blank-line no-op). Keepalives stop after the first upstream chunk. Covered by tests/unit/test_streaming_keepalive.py and keepalive integration cases in tests/integration/test_gateway_flow.py.

Backlog picker off the search index (#291)

scripts/autodev_backlog.py picks the next auto-dev issue (--pick) or lists all eligible ones (--list) through the GraphQL repository connection only — never search( — because GitHub's search index lagged the repository on 2026-08-30 and the dev cycle silently exited on a "empty" backlog. Pick rules match AGENTS.md: P1 > P2 > P3, oldest first, skipping agent:working and issues with an open linked PR (shared matcher from autodev_pr_watch.py). The autodev.yml prompt now calls the script (edit authorized by #291), autodev-local.sh regression dedupe uses a repository-connection query with exact-title matching, and the surviving search-backed callsites in autodev_pr_watch.py carry audit comments explaining why their failure modes are safe. Covered by tests/unit/test_autodev_backlog.py.

MCP tool governance (#277)

daari/gateway/mcp_policy.py resolves a glob-style allow / deny tool policy per caller: integrations.mcp_policy (global default, governs the master key and open installs too) → integrations.mcp_team_policies[<team>] → the virtual key's metadata.mcp. Denies accumulate, the narrowest allow wins, deny beats allow. POST /mcp returns JSON-RPC error -32003 for a denied tools/call, the legacy /v1/mcp/query returns HTTP 403 (MCP_ERR_TOOL_DENIED), and tools/list on both routes is filtered to the caller's allowed tools. Every ingress tools/call — allowed or denied — writes an mcp.tools/call row to the enterprise audit log (key/team, tool, decision, transport) and never the arguments. The egress client sends Mcp-Method / Mcp-Name (MCP 2026-07-28) on outbound requests; the ingress honours the same headers for policy when a client supplies them. Covered by tests/unit/test_mcp_policy.py, tests/integration/test_mcp_governance.py and an egress header case in tests/unit/test_mcp_egress.py.

Claude Desktop one-click setup recipe (#279)

daari setup claude-desktop [--dry-run] [--force] (daari/clients/claude_desktop/recipe.py) writes a Claude Desktop third-party-inference configuration into the app's saved-configuration library (~/Library/Application Support/Claude-3p/configLibrary/daari.json on macOS; %LOCALAPPDATA% / ~/.config equivalents elsewhere) using the documented flat keys: inferenceProvider=gateway, inferenceGatewayBaseUrl (server root — the app appends /v1/messages), inferenceGatewayApiKey, inferenceGatewayAuthScheme=x-api-key, and a pinned inferenceModels entry for daari. Backup + daari setup --undo claude-desktop restore (or removal when the recipe created the file), idempotent apply, registered in default_registry() so daari setup all includes it and skips cleanly when Claude Desktop is not installed. Docs: docs/developer/guides/clients/claude-desktop.md (manual Import-configuration fallback + MDM note), cross-linked from the MCP guide. Covered by tests/unit/test_claude_desktop_recipe.py.

Cost-split and savings response headers (#278)

daari/gateway/cost_headers.py puts x-daari-response-cost (USD spent; 0 for every local tier, provider usage.cost or pricing.models × tokens for L6), x-daari-response-cost-avoided (frontier-implied USD for a $0 serve, on the daari report chars/4 × usage.frontier_price_per_1k_tokens basis), x-daari-tier and x-daari-cache (hit|miss|draft) on every non-streaming /v1/chat/completions and /v1/messages response. DaariMeta.draft now records L1 draft injection so the cache header can say draft. Streams get tier/cache through a StreamOutcome the router fills before its first chunk and a DeferredHeadersStreamingResponse that holds the HTTP start line until that chunk (or the keepalive frame) is ready; cost headers are never sent on streams. Contract documented in docs/developer/reference/headers.md. Covered by tests/unit/test_cost_headers.py and tests/integration/test_cost_headers.py.

Honor reasoning_effort (#297)

ChatCompletionRequest.reasoning_effort is declared (no longer dropped by extra="ignore"). SamplingParams carries it to frontier via openai_payload(), and maps it to Ollama's top-level think (minimal → omit; low/medium/high → same) when the local model name looks thinking-capable (gpt-oss, qwen3, thinking, deepseek-r1); other models ignore it without error. Optional routing.reasoning_effort_escalation (default false) lets high mark the profile complex and floor tier selection at L4. daari_meta.reasoning_effort and the profile trace step record the value. Covered by tests/unit/test_reasoning_effort.py, tests/unit/test_sampling_params.py, and cases in tests/integration/test_gateway_flow.py.

Stall classification for action_required / suppressed CI (#294)

scripts/autodev_pr_watch.py now inspects Actions workflow runs for the PR head SHA when auto-merge is enabled and the check rollup is empty, and classifies the stall as conflict (DIRTY / mergeable=CONFLICTING), awaiting-approval (latest run conclusion=action_required), no-ci-triggered (no runs for the SHA — typical of GITHUB_TOKEN-authored PRs), or unknown. The filed issue/comment body states the classification and the matching remedy (merge main; Approve and run with run URL; close/reopen or different token). Marker <!-- autodev-pr-stall --> and conflict-path merge-main guidance are unchanged. Covered by tests/unit/test_autodev_pr_watch.py.

Upgrade and config migration guide (#287)

docs/developer/guides/operations/upgrade.md (nav: Operations) is the operator runbook for moving a fleet N → N+1: pip / Homebrew / Docker / Helm (helm upgrade --atomic) steps with the restart each needs, the config compatibility policy, the per-store survival table for ~/.daari/ (diskcache/Redis L0+L1 disposable and version-agnostic; .sqlite3 ledgers, virtual keys, audit durable with additive in-place migrations; Postgres create-if-missing only), rollback, and fleet ordering (gateway first; the HMAC-signed policy bundle has no schema version and laptops ignore unknown keys, so skew is tolerated both ways). Config claims are pinned by three new tests in tests/unit/test_settings.py: unknown top-level YAML section fails Settings.load() (BaseSettings extra="forbid"), unknown nested key is ignored, wrong type fails. Cross-linked from capacity-helm.md.

Secret references for provider and org keys (#288)

Secret-bearing config values accept secret:// URIs resolved once at daemon startup: secret://env-file/<path>#<KEY>, secret://exec/<command> (covers op/vault/aws CLIs), and secret://keychain/<service>/<account> (macOS security, Linux secret-tool) — all shell-outs, no new runtime dependency. Resolution lives in daari/security/secret_refs.py, is wired into create_app() (fatal on failure, message names the ref and config path, never the value), and registers resolved values so log_gateway_event() redacts them. daari doctor gains a secret_refs check verifying every configured ref resolves. Plain strings unchanged. Covered by tests/unit/test_secret_refs.py and tests/test_doctor.py.

Signed ghcr images with SBOM and provenance (#295)

.github/workflows/docker.yml (edit authorized by #295) now signs every image pushed on main/v* with cosign keyless (GitHub OIDC; id-token: write scoped to the build job only, signing by digest so all tags are covered) and attaches a Syft SBOM plus SLSA provenance via docker/build-push-action (sbom: true, provenance: true). PR builds still build without pushing and skip signing. Verify instructions (cosign verify with the expected identity and issuer, SBOM/provenance inspection) live in the docker-compose operations guide. Covered by tests/unit/test_docker_supply_chain.py.

MCP Tasks extension (#289)

POST /mcp advertises io.modelcontextprotocol/tasks on protocol 2026-07-28+. Clients that opt in via _meta get a taskId for eligible long-running tools/calls (route by default, or all tools when threshold_ms > 0); tasks/get / tasks/update / tasks/cancel poll and cooperatively cancel. Non-opt-in callers keep blocking behavior. Task state lives in-process with a diskcache directory (integrations.mcp_tasks.path). Governance audits still run before task creation. Docs: mcp.md. Covered by tests/unit/test_mcp_tasks.py.

daari service restart + Cursor tunnel hint label fix (#323)

daari service restart (restart_service() in daari/setup/service.py) runs systemctl --user restart daari.service on Linux and launchctl kickstart -k gui/<uid>/com.daari.gateway on macOS, refuses native Windows like its siblings, and raises ServiceNotInstalledError (CLI exit 1 with the install hint) when no unit/plist exists. The daari setup cursor --secure post-key message used to name com.daari.serve — the dev-watchdog plist in scripts/launchd/, not the user service — and now says daari service restart via restart_hint(). Docs: install stay-up section, upgrade guide restart steps, CLI reference row, AUTOMATION.md label note. Covered by tests/unit/test_service.py (TestRestart, CLI cases) and test_setup_cursor_secure_hint_points_at_service_restart.

Shadow evals for tier decisions (#318)

routing.shadow_sample_rate (default 0) samples responses served by a local tier (not cache hits, not tool flows) and replays them in a background task at routing.shadow_compare_tier — empty = highest configured local tier, L6 only while routing.shadow_daily_usd > 0 (in-memory daily estimate via cost_usd, never the ledger). Answer similarity uses the L1 shadow-check embedder/cosine path; FeedbackStore.record_tier_shadow / tier_shadow_stats (new tier_shadow_checks table, additive) give per-category samples, agree rate, divergence rate, compare/served tier counts. Replays are executor-only: no cache write, no ledger row, no learned-routing outcome; served responses are never delayed or altered. Surfaced in daari learn stats ("Tier divergence"), daari report + /v1/daari/report (tier_divergence), /v1/daari/learn/stats (tier_shadow), Prometheus daari_tier_shadow_samples_total{agreed}. Docs: routing-tiers.md. Covered by tests/unit/test_tier_shadow.py and test_shadow_sampled_tier_decision_records_divergence in the gateway flow suite.

MCP guardrails on tools/call arguments and results (#317)

integrations.mcp_guardrails (same GuardrailSettings shape as chat, off by default) feeds a GuardrailEngine through daari/gateway/mcp_guardrails.py. input_rules run over flattened tools/call arguments before execution; output_rules run over every text item of the result after, including task results before they are stored. An input trip is JSON-RPC -32003 with data.{tool,rule,direction} on /mcp and HTTP 403 MCP_ERR_GUARDRAIL_BLOCKED on /v1/mcp/query; an output block swaps the result for block_message (isError: true), redact rewrites in place. The egress client checks outbound arguments before posting and scrubs inbound results. Every trip writes an mcp.guardrail audit row (tool, rule, direction, action, transport — never the payload). GuardrailEngine gained check_input_text / check_output_text so chat and MCP share one rule evaluator. Docs: mcp.md. Covered by tests/unit/test_mcp_guardrails.py and tests/integration/test_mcp_guardrails.py.

Streaming usage accounting counts once (#320)

Status: Done (2026-09-02). Locked the "one ledger row per streamed request" contract with tests/integration/test_streaming_usage.py across the OpenAI SSE path, /v1/messages, the /api/chat NDJSON facade, and client cancellation, for backends that report usage on a final chunk, as running totals, or not at all. Defects the tests surfaced and fixed: streamed /v1/messages never wrote a ledger row (or a metrics sample) and emitted chars/4 usage even when Ollama reported real counts; OpenAICompatExecutor.stream dropped the usage block (and never asked for it — now sends stream_options.include_usage); the Ollama facade's final line hard-coded prompt_eval_count/eval_count to 0; and the OpenAI stream's client-visible usage chunk ignored reported counts. Running totals are handled by keeping the last report, never summing. Docs: savings-report.md.

Budget-remaining response headers (#319)

daari/auth/budgets.py gained WindowStatus / budget_status / tightest_window / reset_epoch / window_header_label (first_exceeded_window now builds on them). The auth middleware evaluates every effective key+team window once per request and, for virtual keys with at least one budget, adds x-daari-budget-remaining / -limit / -window (1d, 1mo, 7d, 12h) / -reset (epoch seconds = reset_at) / -scope (key|team) for the window with the least USD left — on every 2xx, streams included. The budget-exhausted 402 keeps its body and status (pinned since

174/#158) and now carries the same headers with remaining 0 plus

Retry-After until reset. No budget ⇒ no headers; master key unchanged. Rendering lives in daari/gateway/budget_headers.py next to the #308 cost headers (shared usd_string). Docs: headers reference "Budget headers", budgets-frontier guide. Covered by tests/unit/test_budget_headers.py and the test_budget_headers_* / test_no_budget_means_no_budget_headers cases in the gateway flow suite.

secret://oauth resolver — client-credentials upstream auth (#321)

Status: Done (2026-09-02). New secret://oauth/<token-url>?client_id=…&client_secret=<secret-ref> scheme in daari/security/secret_refs.py performs an RFC 6749 client-credentials grant (HTTP Basic by default, auth=post optional; scope, audience, resource passthrough) and caches the access token until expires_in minus refresh_margin (default 60s), serialising concurrent refreshes behind one lock. The client secret must itself be an env-file/exec/keychain ref — inline secrets are rejected. Tokens resolve to a RefreshableSecret (str subclass carrying its ref) so the values written into settings stay plain strings everywhere; current_secret() re-mints on read and is wired into ProviderSlot.pick_key (every L6 attempt) and the org cache/learning clients' bearer headers. A failed refresh raises SecretRefError naming the endpoint (never the secret) and the frontier pool fails over instead of sending a stale token. No new dependency (httpx already in tree). Docs: auth-and-keys.md. Covered by tests/unit/test_secret_refs_oauth.py.

Virtual key expiry (expires_at + SSO key_ttl) (#331)

Status: Done (2026-09-03). Virtual keys gain an additive expires_at column (NULL = never; existing keys unaffected). daari keys create --expires 30d|12h|ISO sets it; keys list shows expiry and active / expired / revoked. Auth rejects expired keys with 401 key_expired (distinct from invalid/revoked) and writes auth.key_expired. SSO key_policy.key_ttl mints a TTL; find_sso_key treats an expired SSO key as absent so the next login remints. Docs: auth-and-keys.md. Covered by tests/unit/test_key_expiry.py.

Issue auto-labeler — intended labels applied at filing time (#330)

Status: Done (2026-09-03). scripts/apply_intended_labels.py parses the **Intended labels:** first line of an issue body, intersects it with a hard allowlist (auto-dev, P1–P3, bug, regression, documentation, enhancement — never agent:working) and adds the missing ones via gh api; it never creates labels and is idempotent. The explicitly authorized .github/workflows/issue-labeler.yml runs it on issues: opened, edited with issues: write as its only permission. Removes the last recurring human step in the dev loop: prd-cycle refills are pickable by autodev_backlog.py the moment they are filed. Docs: AUTOMATION.md. Covered by tests/unit/test_apply_intended_labels.py (parser, allowlist, idempotence, CLI, workflow shape).

Budget alert webhooks at soft thresholds (#333)

Status: Done (2026-09-03). alerts.budget_webhook_url + budget_thresholds (default [0.8, 1.0]) POST a JSON payload when a request pushes a key/team window across a ratio. Delivery is a background task; failures log budget.alert_failed and never delay the response. In-memory dedupe per (scope, id, window, threshold, reset_epoch) until reset (multi-replica caveat documented). Audit budget.alert; Prometheus daari_budget_alerts_total{scope,threshold}. Docs: budgets-frontier.md. Covered by tests/unit/test_budget_alerts.py.

Retention policy and prune for traces, ledger, audit, shadow, tasks (#332)

Status: Done (2026-09-03). observability.retention.{traces,ledger,audit,shadow,tasks}_days defaults to 0 (keep forever). daari prune [--dry-run] and a daily daari serve background sweep delete older rows on SQLite and Postgres (traces + ledger). Audit prune writes a retention.prune summary row after the delete. Sweep failures log retention.sweep_failed and never raise. Docs: traces-stats.md, upgrade.md. Covered by tests/unit/test_retention.py.

Draft v1.4.0 release notes and version bump (#334)

Status: Done (2026-09-03). Prep-only release package: docs/RELEASE-v1.4.0.md (Apache 2.0 + cosign/SBOM lead, human tag/release steps at top, upgrade notes → upgrade.md #316), pyproject.toml / daari.__version__ / README / CHANGELOG bumped to 1.4.0, docs/RELEASING.md verified against docker.yml cosign + SBOM + provenance. No git tag, GitHub release, or PyPI/ghcr publish from the agent. Covered by tests/unit/test_release_v140_prep.py.

Skip human-gated stall re-picks (#342)

Status: Done (2026-09-06). Stall findings comments carry <!-- autodev-blocked: <classification> run=<id> --> (helpers in scripts/autodev_pr_watch.py). scripts/autodev_backlog.py::pick skips issues whose body has autodev-pr-stall when the latest blocked marker matches the referenced PR's current fingerprint; a new CI run, cleared block, or closed/merged PR re-eligibilizes the issue. At most one findings comment per distinct fingerprint (post_blocked_findings_if_new). Covered by tests/unit/test_autodev_backlog.py and tests/unit/test_autodev_pr_watch.py.

Opt-in budget window rollover (#344)

Status: Done (2026-09-06). Per-window rollover: true (default off) carries unused headroom into the next period's effective limit, capped at rollover_cap_multiple (default 2×). Carry is persisted on budget_window_state in the SQLite and Postgres usage ledgers. Headers, 402 bodies, and budget alert webhooks use the effective limit. Docs: budgets-frontier.md. Covered by tests/unit/test_budget_rollover.py.

Audit log list and JSONL export (#345)

Status: Done (2026-09-06). daari audit list (--limit/--actor/ --action prefix/--since/--json) and daari audit export --format jsonl stream newest-first rows from enterprise.audit_path in seq batches. Docs: auth-and-keys.md. Covered by tests/unit/test_audit_cli.py.

Shipped pricing and capability tables for the September 2026 frontier lineup (#355)

Status: Done (2026-09-07). Default pricing.models now includes Claude Fable 5.1 ($10/$50, cache $0.25), Opus 5, Sonnet 5, Haiku 4.5, GPT-6 Astra ($10/$50, cache $1), the GPT-5.6 family (Sol promo through 2026-11-21), and Gemini 3.8 Flash intro rate ($0.75/$3.75, cache $0.075 through 2026-12-31). Dated and vendor-prefixed ids (claude-fable-5-1-20260901, anthropic.claude-fable-5-1) resolve via longest key. Known frontier capabilities advertise tools/json/vision/long_context. GPT-6 Astra's >272K input surcharge is documented as out of scope. Existing 2024 entries are unchanged. Docs: budgets-frontier.md. Covered by tests/unit/test_token_accounting.py and tests/unit/test_capabilities.py.

Session affinity across tool-turn continuations (#356)

Status: Done (2026-09-07). routing.session_affinity (default off) pins the served tier for a session. Tool-result continuations and an unchanged user-turn prefix replay the pin; a new human turn re-routes. Session key is X-Daari-Session or OpenAI user, else a conversation-prefix hash, with routing.session_affinity_ttl_seconds (default 1800). Tier cap, an open circuit, a down model, confidence escalation, and context-length failover override the pin (session_pin / session_pin_override). Docs: routing-tiers.md. Covered by tests/unit/test_session_affinity.py and tests/integration/test_gateway_flow.py.

Stall escalation for a stuck agent loop (#357)

Status: Done (2026-09-07). routing.stall_escalation (default off; repeats 3, window 6) reads the request's tool history only. N identical calls (name + normalized arguments) in the last window, or N consecutive error tool results, bump the heuristic tier by one. X-Daari-Tier-Cap and routing.max_tier_for_chat still win. Logged as stall_escalation with pattern and count. Docs: routing-tiers.md. Covered by tests/unit/test_stall.py and tests/integration/test_gateway_flow.py.

MCP egress follows tools/list nextCursor (#358)

Status: Done (2026-09-07). Egress tools/list follows nextCursor until absent, aggregates tools in first-seen order, and de-duplicates by name. A page cap (20) and a 15s listing timeout stop an infinite-cursor upstream. Single-page servers are unchanged. Guardrail result checks see the aggregated catalog. Covered by tests/unit/test_mcp_egress.py.

ChatGPT Desktop via Ollama facade generate/embed (#343)

Status: Done (2026-09-06). Ollama facade gains /api/generate (stream NDJSON + non-stream, same router/ledger path as /api/chat) and /api/embed + legacy /api/embeddings (shared embeddings_api helper with /v1/embeddings). Unknown Ollama 0.34 fields are ignored (extra="ignore"). Recipe: chatgpt-desktop.md (11435 vs 11434). Covered by tests/integration/test_ollama_compat.py.

PR-watch auto-drain of BEHIND auto-merge PRs (#368)

Status: Done (2026-09-08). scripts/autodev_pr_watch.py merges origin/main into BEHIND auto-merge PRs (keep-both docs/TRACKING.md), skips a second attempt after a failed push marker, and approves first-party action_required workflow runs. autodev.yml uses AUTODEV_GH_TOKEN when set so new PRs are not attributed to github-actions[bot] (GitHub's 2026-06-11 bot-PR approval gate has no repo-level opt-out). Covered by tests/unit/test_autodev_pr_watch.py and tests/unit/test_autodev_backlog.py.

Pre-dispatch context-window escalation (#385)

Status: Done (2026-09-09). routing.context_window_escalation (default on) compares prompt_tokens_est to per-tier routing.context_windows × buffer 0.95 and picks the cheapest higher local tier that fits before the first hop. Unknown windows are left alone; X-Daari-Tier-Cap still wins. Trace/event context_window_escalation. Docs: routing-tiers.md. Covered by tests/unit/test_context_window.py.

OpenRouter cost_tier body mapping (#388)

Status: Done (2026-09-09). OpenAI / Anthropic / Responses accept cost_tier and plugins: [{id: "auto-router", cost_tier}]. Map low→L3, medium→L4, high→L5, xhigh/max→L6 onto meta.tier_cap. X-Daari-Tier-Cap wins; unknown values log cost_tier_ignored. Anthropic now also reads the header. Docs: routing-tiers.md. Covered by tests/unit/test_cost_tier.py and tests/integration/test_gateway_flow.py.

Vision hop for tool-result images (#397)

Status: Done (2026-09-09). Anthropic tool_result image blocks populate Message.images (OpenAI role=tool parts already did). required_capabilities then requires vision and the existing catalog filter hops off text-only L3. Event modality_escalation. Docs: routing-tiers.md. Covered by tests/unit/test_multimodal.py and tests/integration/test_anthropic_tools.py.

Honor response_format json_schema (#398)

Status: Done (2026-09-09). type: json_schema is stored on SamplingParams and passed to Ollama as format (the schema object) and to OpenAI-compat as response_format. Requires the json capability like json_object. Malformed schemas log json_schema_ignored and are dropped. Covered by tests/unit/test_sampling_params.py.

Status: Done (2026-09-20). Chargeback guide documents daari spend export --tier with asr / embed examples. Covered by tests/unit/test_chargeback_docs.py.

Status: Done (2026-09-20). CLI help contract asserts daari cache invalidate --help mentions --token for SSO daemon auth. Covered by tests/unit/test_cache_invalidate_cli_help.py.

Docs: cache invalidate --token (#828)

Status: Done (2026-09-20). cli.md and org-cache guide document cache invalidate --token for SSO/master Bearer. Covered by tests/unit/test_cache_invalidate_docs.py.

Unit tests for ASR frontier_fallback findings (#834)

Status: Done (2026-09-20). Direct unit coverage for asr_frontier_fallback_findings. Covered by tests/unit/test_config_validate.py.

How to update; on conflict keep both -->

Budget alert fleet dedupe via Redis (#369)

Status: Done (2026-09-08). When cache.backend=redis, BudgetAlerter claims each (scope, id, window, threshold, reset_epoch) crossing with SET NX EX (TTL through window reset) before the webhook POST, so replicas notify once. No Redis keeps the in-process _seen path. Redis errors still deliver and log budget.alert_dedupe_degraded. Docs: budgets-frontier.md. Covered by tests/unit/test_budget_alerts.py.

Subtask/phase routing from tool history (#374)

Status: Done (2026-09-09). routing.phase_routing (default off; window 6) classifies the last N tool-call names as explore / implement / verify and applies a configurable tier delta (defaults explore: -1, floor L3; implement/verify: 0). Stall escalation beats a phase downgrade; session affinity pins the phase-adjusted served tier; X-Daari-Tier-Cap and routing.max_tier_for_chat still cap. Trace/event phase_route. Non-agent requests are unchanged. Docs: routing-tiers.md. Covered by tests/unit/test_phase_routing.py and tests/integration/test_gateway_flow.py.

Incremental streaming output guardrails (#375)

Status: Done (2026-09-09). guardrails.stream_mode (buffered default | incremental) with stream_holdback_chars (default 256). Incremental mode scans SSE deltas through a holdback window so secrets spanning chunks never leak, keeps L6 frontier relay eligible, records hits like the buffered path, and caches only scanned text. Tool-call streams stay exempt. Docs: guardrails.md. Covered by tests/unit/test_stream_guardrails.py, tests/unit/test_stream_policy_parity.py, and tests/integration/test_gateway_flow.py.

MCP semantic tool search (#376)

Status: Done (2026-09-09). integrations.mcp_tool_search (default off; min_catalog_size 40, top_k 40) ranks aggregated egress tools/list catalogs by local embedding similarity when over the threshold. Query is trailing text after @mcp … tools/list, else recent user/tool message text. Governance allow/deny runs before ranking; embed failures log mcp_tool_search_degraded and return the unranked catalog. Tool embeddings are cached per (server, name, description hash). Docs: mcp.md. Covered by tests/unit/test_mcp_egress.py.

Zero-downtime virtual key rotation (#377)

Status: Done (2026-09-09). daari keys rotate <key_id> [--grace 24h] mints a new secret for the same key identity; budgets/team/policies/RPM/TPM untouched. Old secret authenticates until grace ends (--grace 0 immediate), then rejects as expired via the #331 path. Both secrets share one budget and rate-limit bucket. keys.rotate audit row (no secret material). keys list shows grace_until. Docs: auth-and-keys.md. Covered by tests/unit/test_key_rotate.py.

Tamper-evident audit hash chain (#378)

Status: Done (2026-09-09). Every new audit row stores prev_hash / row_hash (SHA-256 over canonical seq/ts/actor/role/action/detail/prev_hash; genesis 0×64). daari audit verify [--json] walks oldest-first; legacy (null-hash) rows counted; exits non-zero on hash_mismatch / seq_gap. Export JSONL includes both hash fields. Retention prune re-anchors the chain. Docs: auth-and-keys.md. Covered by tests/unit/test_audit_cli.py.

Streamed usage.cost on the final usage chunk (#386)

Status: Done (2026-09-09). The OpenAI chat.completion.chunk usage object and Anthropic message_delta.usage include cost (USD float). Local / L0 / L1 are $0; L6 uses the same cost_usd() / provider-reported figure as x-daari-response-cost on non-stream responses. Stream cost headers stay absent (usage is unknown at the HTTP start line). Docs: headers.md. Covered by tests/unit/test_stream_usage_cost.py, tests/integration/test_streaming_usage.py, and tests/integration/test_gateway_flow.py.

Scan chat tool-result messages (#387)

Status: Done (2026-09-09). Opt-in guardrails.scan_tool_results (default off). When on, OpenAI role=tool and Anthropic-converted tool_result contents run through input + output-style rules before the model hop: block refuses with block_message (stream or JSON); redact rewrites the tool message in place so execute and cache keys never see the secret. System/user/assistant unchanged; MCP tools/call scanning unchanged. Docs: guardrails.md. Covered by tests/unit/test_guardrails.py and tests/unit/test_stream_policy_parity.py.

Skip re-profiling on tool-result continuations (#389)

Status: Done (2026-09-09). Opt-in routing.classify_user_turn (default off). Tool-result continuations reuse the prior user-turn category/complexity instead of re-running build_prompt_profile; prompt_tokens_est still reflects full message size. Phase routing and stall escalation still inspect tool history; a new user message re-profiles. Trace/event classify_user_turn with reused: true. Docs: routing-tiers.md. Covered by tests/unit/test_session_affinity.py.

Streamed usage cached_tokens (#399)

Status: Done (2026-09-09). Final OpenAI stream usage always includes prompt_tokens_details.cached_tokens (0 when unknown; L0/L1 equals prompt_tokens; L6 from daari_meta.cached_tokens). Anthropic message_delta.usage adds cache_read_input_tokens only when known and non-zero. Docs: headers.md. Covered by tests/unit/test_stream_usage_cost.py, tests/integration/test_streaming_usage.py, and tests/integration/test_gateway_flow.py.

Status: Done (2026-09-09). Local tier cards from openai_model_cards / GET /v1/models include context_length from routing.context_windows when known (omit if missing). Frontier/L6 cards are not overwritten. Docs: routing-tiers.md. Covered by tests/unit/test_models_catalog.py and tests/integration/test_gateway_flow.py.

Derive length hops from context_windows only (#401)

Status: Done (2026-09-09). Removed the 24k-char long_context inference from required_capabilities. Prompt length hops solely via routing.context_windows × buffer (#385), so capability filter and context-window escalation no longer disagree. Docs: routing-tiers.md. Covered by tests/unit/test_capabilities.py and tests/unit/test_context_window.py.

Stall issues get Intended-labels + post-create label retry (#409)

Status: Done (2026-09-10). autodev_pr_watch._cli_create_issue prefixes **Intended labels: \auto-dev`, `regression`**on stall issue bodies soissue-labeler(#330) applies them even when a PAT silently drops--labelat create time (#408). Also retriesgh issue edit --add-labelafter create (failure logged, not fatal). Cleanup: mergedorigin/maininto PR #404 to unpark #398. Covered bytests/unit/test_autodev_pr_watch.py`.

End-user spend attribution and per-user caps (#410)

Status: Done (2026-09-10). Usage ledger user_usage table records the OpenAI user (or unknown) per virtual-key client_id. daari usage --by-user / GET /v1/daari/report users array group spend. Optional user_daily_usd_cap on a key returns the same 402 shape with scope: user when that named user exceeds it; requests without user are never capped. Docs: budgets-frontier.md, savings-report.md. Covered by tests/unit/test_user_usage.py and tests/integration/test_gateway_flow.py.

Context-threshold pricing for gpt-6-astra (#411)

Status: Done (2026-09-10). ModelPrice optional input_threshold_tokens / above_input_per_1m / above_output_per_1m; cost_usd selects the tier from prompt token count. Default table encodes gpt-6-astra at 272K → 2× input / 1.5× output. Cost headers and the budget ledger share that path. Docs: budgets-frontier.md. Covered by tests/unit/test_threshold_pricing.py.

Redact secrets from gateway error details (#412)

Status: Done (2026-09-10). Client-facing HTTPException / error-envelope text in gateway modules runs through safe_detail / routing_failure_detail (daari/gateway/client_errors.py), which apply redact_secrets(). Upstream httpx failures on the 503 path are summarized as host + status (no URLs). Covered by tests/unit/test_gateway_error_redaction.py.

Harness-aware profiling ignores catalogs (#418)

Status: Done (2026-09-11). routing.harness_aware_profile (default on) drops role=system text and recognized Codex/Claude Code blocks (<environment_context>, <recommended_plugins>, <system-reminder>) from category/complexity. prompt_tokens_est still counts the full request so context-window escalation sees size. Event harness_profile with stripped_chars. Docs: routing-tiers.md. Covered by tests/unit/test_prompt_profile.py.

Close stall issues when the PR merges (#419)

Status: Done (2026-09-11). scripts/autodev_pr_watch.py closes open issues whose body contains autodev-pr-stall once the referenced PR is MERGED or CLOSED, with a one-line comment. Open PRs are left alone. Covered by tests/unit/test_autodev_pr_watch.py.

Honor Anthropic output_format json_schema (#420)

Status: Done (2026-09-11). /v1/messages output_format.type == json_schema (with schema or json_schema.schema) is stored on SamplingParams.json_schema and forwarded the same way as OpenAI #398. Malformed payloads log json_schema_ignored and are dropped. Covered by tests/unit/test_sampling_params.py.

Agent UA shortcut for classify_user_turn (#421)

Status: Done (2026-09-11). When routing.classify_user_turn is false (default), User-Agents matching cursor / claude-code / claude code / codex still reuse category/complexity on tool-result continuations via routing.classify_user_turn_agents (default on). Set agents false to disable; explicit classify_user_turn: true still applies to every client. Event classify_user_turn includes source: ua vs source: config. Docs: routing-tiers.md. Covered by tests/unit/test_classify_user_turn.py.

Merge multiple SSO JWKS URLs (#422)

Status: Done (2026-09-11). enterprise.sso.jwks_urls (list) combines with singular jwks_url; tokens validate against any configured JWKS. Unknown kid on one document tries the next; all miss → existing 401. Docs: auth-and-keys.md. Covered by tests/unit/test_oidc_jwks.py.

Honor service_tier for pricing and L6 forward (#430)

Status: Done (2026-09-11). OpenAI/Anthropic service_tier is stored on SamplingParams, forwarded on L6 OpenAI and Anthropic payloads, and multiplies cost_usd (flex 0.5×, priority 2×, standard/default/auto 1×). Unknown values log service_tier_ignored. Docs: budgets-frontier.md. Covered by tests/unit/test_service_tier.py.

Preserve Anthropic thinking blocks on L6 replay (#431)

Status: Done (2026-09-11). Inbound thinking / redacted_thinking blocks with text, signature, or redacted data are stored on Message.thinking_blocks and replayed on Anthropic L6 /v1/messages assistant turns. Empty blocks are omitted (Kong parity). content_to_text and sanitize_messages_for_ollama strip them so local tiers stay plain text. Covered by tests/unit/test_anthropic_gateway.py.

Circuit-breaker state on /ready and stats (#432)

Status: Done (2026-09-11). LocalBackendPool.readiness() / snapshot() include circuit (closed|open|half_open) per backend; /ready and GET /v1/daari/stats expose it (backends: [] when no pool). Prometheus backend series carry a circuit label. /health stays {status: ok}. Covered by tests/unit/test_local_pool.py.

OpenAI Batch API local drain (#433)

Status: Done (2026-09-12). POST/GET /v1/batches and POST /v1/batches/{id}/cancel — OpenAI-shaped jobs (validating → in_progress → completed / failed / cancelled). Inline requests (chat bodies or JSONL-line objects) drain sequentially through the existing router; concurrent batch jobs capped at 1. Cancel marks remaining items skipped. input_file_id accepted; file upload is a follow-up (fails validation without inline requests). Covered by tests/unit/test_batches.py and tests/integration/test_gateway_flow.py.

Anthropic cache_control TTL write rates (#434)

Status: Done (2026-09-12). System/tool cache_control.ttl (5m/1h) is preserved on L6 Anthropic payloads; default auto-hint stays ephemeral without TTL. ModelPrice.cache_write_1h_per_1m + cost_usd(..., cache_write_tokens, cache_ttl) bill 1h writes distinctly; missing TTL keeps today's input rate. Docs: budgets-frontier.md. Covered by tests/unit/test_cache_ttl_pricing.py.

Batch items carry creating-key governance (#441)

Status: Done (2026-09-12). POST /v1/batches snapshots key id, client_id, tier cap, no_frontier, user, and boundary profile onto BatchJob.governance. Items route with that meta (L5 cap / no_frontier never reach L6); per-key/team and per-user budget checks fail frontier-capable items with a 402-shaped error while siblings continue; ledger rows carry client_id/user; output guardrails apply via the shared router path. Covered by tests/unit/test_batches.py and tests/integration/test_gateway_flow.py.

OpenAI Files API for Batch JSONL (#442)

Status: Done (2026-09-12). POST/GET/DELETE /v1/files and GET /v1/files/{id}/content — OpenAI-shaped file objects on disk under files.path (default ~/.daari/files, files.max_bytes). POST /v1/batches with input_file_id parses JSONL (bad lines report errors.data[].line); completed jobs set output_file_id / error_file_id while keeping inline results. Adds runtime dep python-multipart for multipart upload. Covered by tests/unit/test_files_api.py, tests/unit/test_batches.py, and tests/integration/test_gateway_flow.py.

Durable batch jobs across restarts (#443)

Status: Done (2026-09-12). BatchStore persists jobs/items/results to SQLite (batches.path, default ~/.daari/batches/jobs.sqlite3). Restart reloads unfinished work and resumes pending items (mid-flight retried once); past-expires_at jobs become expired with remaining items skipped. Multi-replica caveat documented in batches.md. Covered by tests/unit/test_batches.py.

Batch drain yields to interactive traffic (#444)

Status: Done (2026-09-12). Batch worker waits while interactive HTTP in-flight > 0 (batches.yield_to_interactive, default true; batches.idle_poll_seconds). Emits batch.waiting_for_idle once per wait episode; cancel still wins while waiting. Docs: batches.md. Covered by tests/unit/test_batches.py.

One-command client onboarding (#445)

Status: Done (2026-09-12). daari configure <client> writes the same settings as daari setup (claude-code, vscode, claude-desktop, …), backs up modified files, is idempotent / --dry-run safe, and prints a one-line verify curl. Unknown clients list supported names and exit 1. Client docs link the fast path. Covered by tests/unit/test_configure_cli.py.

Batch and Files reads scoped to creating key (#452)

Status: Done (2026-09-13). FileStore records owner_key_id (nullable for master/no-auth) and persists it in index.json. Virtual-key auth scopes GET/DELETE /v1/files and content downloads to the caller's files (404 for cross-tenant / ownerless); master sees everything. GET /v1/batches and get/cancel use BatchGovernance.key_id the same way. Batch output_file_id / error_file_id inherit the job owner. Covered by tests/unit/test_files_api.py, tests/unit/test_batches.py, and tests/integration/test_gateway_flow.py.

Stored Responses scoped to creating key (#453)

Status: Done (2026-09-13). ResponseStore gains owner_key_id (migrates legacy rows to master-only). Virtual-key GET /v1/responses/{id} and previous_response_id reject cross-tenant ids with the same not-found shapes; master/no-auth unrestricted. Background polls keep the create-time owner. Covered by tests/unit/test_response_store.py, tests/unit/test_responses_tenancy.py, and tests/integration/test_gateway_flow.py.

Anthropic-native GET /v1/models (#454)

Status: Done (2026-09-13). GET /v1/models returns Anthropic list shape (type/display_name/created_at, has_more/first_id/last_id) when the request carries anthropic-version or bare x-api-key (Claude Code / Desktop + daari configure claude-desktop verify hint). Bearer / no Anthropic headers keep the OpenAI card shape. Same catalog ids as openai_model_cards. Covered by tests/unit/test_anthropic_gateway.py and tests/integration/test_gateway_flow.py.

Forward client anthropic-beta / anthropic-version on L6 (#455)

Status: Done (2026-09-13). Anthropic /v1/messages captures inbound anthropic-beta and anthropic-version onto RequestMeta. The L6 Anthropic leg forwards beta verbatim when present and lets client anthropic-version override the pinned 2023-06-01 default; absent beta is not injected. Local tiers unaffected. Covered by tests/unit/test_anthropic_gateway.py and tests/unit/test_anthropic_egress.py.

Files store retention, expires_after, and size cap (#456)

Status: Done (2026-09-13). POST /v1/files accepts OpenAI expires_after (created_at + seconds); objects expose expires_at; expired files 404 and are deleted on access or daari prune / retention sweep. files.retention_days (default 0) sets fallback expiry including batch output/error files; files.max_total_bytes (default 0) rejects over-cap uploads with 413 naming cap and usage. Covered by tests/unit/test_files_api.py and tests/unit/test_retention.py.

Redis outage resilience (#463)

Status: Done (2026-09-14). All Redis clients use cache.redis_timeout_seconds (default 2s) for socket connect/command timeouts. Rate-limit Redis errors log one rate_limit.degraded event and fall back to per-replica SQLite (or rate_limit.fail_open); counting recovers when Redis answers again. /ready probes Redis when cache.backend=redis (degraded-but-200). Docs: auth-and-keys.md, capacity-helm.md. Covered by tests/unit/test_rate_limit.py and tests/integration/test_ready_probe.py.

Audit coverage: lifecycle, failed auth, tenancy (#464)

Status: Done (2026-09-14). CLI audits keys.create / keys.revoke / teams.create / teams.update (new daari keys team-update). Invalid-key 401s record flood-safe auth.invalid_key (prefix + path). Cross-tenant file/batch/response denials record tenancy.denied. Hash chain + daari audit verify unchanged. Docs catalog in auth-and-keys.md. Covered by tests/unit/test_audit_coverage.py.

Cross-replica Batches and Files via Postgres (#465)

Status: Done (2026-09-14). batches.backend / files.backend default sqlite; postgres reuses observability.postgres_url for shared jobs and file BYTEA content. Drain uses claim + heartbeat (claim_ttl_seconds) so one replica executes a job and crashed workers are reclaimed. Helm sets both backends when postgres.enabled. Docs: batches.md. Covered by tests/unit/test_postgres_batches_files.py.

Region-pinned frontier fallback (#466)

Status: Done (2026-09-14). FrontierProviderConfig.region plus virtual-key / team region_pin filter L6 slots fail-closed (RegionUnavailable). Escalation soft-locals with region_pin_unavailable when possible; L6 responses expose daari_meta.region and x-daari-region. CLI: --region-pin on keys/teams. Docs: data-residency.md. Covered by tests/unit/test_region_pin.py.

Request-count quotas per key/team window (#467)

Status: Done (2026-09-15). Budget windows accept max_requests alongside USD (--window-requests 1d=5000, YAML/SSO max_requests). Exceeding returns 402 with quota: "requests" plus x-daari-quota-requests-* headers. Counting is ledger-backed (requests − cache_hits); local tiers count, L0/L1 cache hits do not. Team inheritance matches USD (tighter wins per dimension). daari keys list surfaces req used/cap next to USD. Docs: budgets-frontier.md. Covered by tests/unit/test_request_quotas.py.

Session-cumulative cost-avoided rollup (#475)

Status: Done (2026-09-15). x-daari-session-cost-avoided is the running sum of per-response x-daari-response-cost-avoided for X-Daari-Session, TTL matching session-affinity pins. Omitted without a session id. Streaming final usage includes session_cost_avoided when usage is requested. Covered by tests/unit/test_cost_headers.py and tests/integration/test_cost_headers.py.

Never-empty PRD and autodev refill (#474)

Status: Done (2026-09-15). Empty backlog is a refill trigger, not a stop. .github/workflows/prd-cycle.yml runs daily at 14:00 UTC. Autodev and AGENTS.md must file 3–5 auto-dev issues (features or performance / usability / testability / benchmarking / documentation / tooling) when --pick is empty, then implement. docs/automations/prd-cycle.md forbids no-delta filing. Covered by tests/unit/test_autodev_backlog.py.

Helm chart image pin + multi-replica SQLite warnings (#476)

Status: Done (2026-09-15). Chart appVersion / image.tag track daari.__version__ (1.4.0). Defaults are replicaCount / HPA min 1 until postgres.enabled; NOTES + DAARI_FLEET_REPLICAS / daari doctor fleet_artifacts warn on multi-replica + SQLite artifacts. Docs: capacity-helm.md, batches.md. Covered by tests/unit/test_helm_chart.py and tests/test_doctor.py.

Hermetic benchmark suite beyond L0 (#477)

Status: Done (2026-09-15). tests/benchmark/test_hermetic_paths.py adds no-Ollama ceilings for ExactCache get, budget_status, SQLite RateLimiter.check, and BatchStore.create (still @pytest.mark.benchmark). Docs: benchmark-load.md, live-benchmark.md.

Doctor flags fleet SQLite artifacts + stale Helm tag (#478)

Status: Done (2026-09-15). fleet_artifacts also trips on cache.backend=redis or observability.backend=postgres with sqlite batches/files; warning names the postgres settings to flip. Optional helm_image_tag compares checkout chart image.tag to daari.__version__. Docs: doctor-health.md. Covered by tests/test_doctor.py.

Cross-replica Responses via Postgres (#481)

Status: Done (2026-09-15). responses.backend defaults to sqlite; postgres reuses observability.postgres_url so store: true, previous_response_id, and background polling work across replicas. Owner scoping (#453) unchanged. Helm sets DAARI_RESPONSES__BACKEND with batches/files. Docs: batches.md. Covered by tests/unit/test_postgres_responses.py.

Fleet-shared session pins + cost-avoided via Redis (#482)

Status: Done (2026-09-15). When cache.backend: redis, session affinity pins and the session cost-avoided accumulator use Redis (TTL = session_affinity_ttl_seconds); Redis errors fail open to in-process and log session_affinity.degraded. Docs: routing-tiers.md, headers.md. Covered by tests/unit/test_session_affinity_redis.py.

Fleet-shared audit log via Postgres (#483)

Status: Done (2026-09-15). enterprise.audit_backend: sqlite|postgres (default sqlite); postgres reuses observability.postgres_url with one serialized hash chain (daari audit verify is fleet-global). Helm sets DAARI_ENTERPRISE__AUDIT_BACKEND. Docs: auth-and-keys.md. Covered by tests/unit/test_postgres_audit.py.

Signed budget alert webhooks (#484)

Status: Done (2026-09-15). alerts.budget_webhook_secret (supports secret://) adds Stripe-style X-Daari-Timestamp + X-Daari-Signature (HMAC-SHA256 over timestamp + "." + exact body bytes). Unset keeps unsigned POSTs. Docs: budgets-frontier.md. Covered by tests/unit/test_budget_alerts.py.

W3C trace-context propagation (#485)

Status: Done (2026-09-15). When OTel is on, inbound traceparent / tracestate parent the GenAI span tree; outbound Ollama / OpenAI-compat / MLX / frontier calls inject the same context. Disabled / missing packages stay no-op. Docs: otel-genai.md. Covered by tests/unit/test_otel_genai.py.

Fleet-shared profile pins via Redis (#495)

Status: Done (2026-09-15). ProfilePinStore uses the same Redis path as session pins when cache.backend: redis (TTL = session_affinity_ttl_seconds); Redis errors fail open and log session_affinity.degraded. Docs: routing-tiers.md. Covered by tests/unit/test_session_affinity_redis.py.

Doctor warns on sqlite audit and unsigned budget webhooks (#496)

Status: Done (2026-09-15). fleet_artifacts also flags enterprise.audit_backend=sqlite under fleet signals; new optional budget_webhook_secret warns when a webhook URL lacks a signing secret. Docs/NOTES mention responses + audit. Covered by tests/test_doctor.py.

Stored Responses retention (#497)

Status: Done (2026-09-15). responses.retention_days (default 0 = forever) prunes SQLite and Postgres stored Responses via daari prune / daily sweep. Docs: batches.md. Covered by tests/unit/test_response_store.py, tests/unit/test_retention.py, tests/unit/test_postgres_responses.py.

Request-quota soft warning and budget alerts (#498)

Status: Done (2026-09-16). Request-count quotas reuse frontier.soft_budget_ratio for a soft band before the hard 402: x-daari-quota-requests-warning: soft and daari_meta.warning=request_quota_warning. BudgetAlerter fires on request-quota threshold crossings with quota: "requests" payload fields. Docs: budgets-frontier.md, headers.md. Covered by tests/unit/test_request_quotas.py and tests/unit/test_budget_alerts.py.

L0 exact-cache singleflight (#499)

Status: Done (2026-09-16). Concurrent identical L0 cold misses share one upstream fill via in-process singleflight; waiters get the same body; cache writes once. Upstream errors clear the in-flight slot so retries work. Docs: routing-tiers.md. Covered by tests/unit/test_l0_singleflight.py.

Stream L0 singleflight (#506)

Status: Done (2026-09-16). Streaming cold misses join the same in-process singleflight map as non-stream fills; waiters replay the leader body as SSE; cache writes once. Mixed stream + route() coalesce. Docs: routing-tiers.md. Covered by tests/unit/test_l0_singleflight.py.

Responses input_tokens endpoint (#507)

Status: Done (2026-09-16). POST /v1/responses/input_tokens returns a local input_tokens estimate (instructions + input + tools) via estimate_tokens — same pattern as Anthropic count_tokens, no L6 hop. Docs: clients-and-gateways.md, http-api.md. Covered by tests/unit/test_responses_input_tokens.py.

Prometheus TTFT histogram (#508)

Status: Done (2026-09-16). Stream path records time-to-first-token into daari_ttft_ms (by tier) on /metrics. Non-stream requests omit TTFT. Docs: metrics-prometheus.md. Covered by tests/unit/test_prometheus.py, tests/unit/test_ttft_metrics.py.

Request-quota soft warn on Anthropic/Responses (#509)

Status: Done (2026-09-16). Soft request-quota band sets daari_meta.warning=request_quota_warning on Anthropic Messages and Responses (with X-Daari-Meta), matching OpenAI chat. Header soft warn was already middleware-wide. Docs: budgets-frontier.md. Covered by tests/unit/test_request_quotas.py.

Doctor fleet cache without Redis (#510)

Status: Done (2026-09-16). daari doctor warns (fleet_cache) when DAARI_FLEET_REPLICAS>1 without cache.backend=redis (L0/session/singleflight per-pod). Single-replica stays quiet. Docs: doctor-health.md. Covered by tests/test_doctor.py.

L1 semantic-cache cold-miss singleflight (#517)

Status: Done (2026-09-16). Concurrent Ask-path L1 lookups/fills for the same normalized embed key share one nearest + upstream via in-process singleflight; agent turns stay on exact-L0 coalescing only. Docs: routing-tiers.md. Covered by tests/unit/test_l1_singleflight.py.

Virtual-key RPM soft warning (#518)

Status: Done (2026-09-16). RPM/TPM usage at or above frontier.soft_budget_ratio (still under the hard cap) sets x-daari-ratelimit-warning: soft and optional daari_meta.warning= rate_limit_warning. Hard 429 + existing rate-limit headers unchanged. Docs: headers.md, auth-and-keys.md. Covered by tests/unit/test_rate_limit.py.

Prometheus TTFT Grafana panel (#516)

Status: Done (2026-09-16). Grafana overview dashboard charts stream TTFT p50/p95 by tier via daari_ttft_ms. Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Report request-quota soft-band (#519)

Status: Done (2026-09-16). GET /v1/daari/report and daari report expose request_quotas (used/cap/remaining/soft); keys list marks soft windows. Docs: savings-report.md, budgets-frontier.md. Covered by tests/unit/test_request_quotas.py.

L0 singleflight hermetic ceiling (#520)

Status: Done (2026-09-16). Hermetic @pytest.mark.benchmark burst: N concurrent identical L0 cold misses → exactly one upstream under a wall ceiling. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

Soft-warning Prometheus counters (#526)

Status: Done (2026-09-16). /metrics exposes daari_soft_warnings_total{kind="request_quota"|"rate_limit"} when soft headers are set; hard 402/429 do not increment. Docs: metrics-prometheus.md. Covered by tests/unit/test_soft_warning_metrics.py.

Report CLI JSON format (#527)

Status: Done (2026-09-16). daari report --format json dumps the same payload as GET /v1/daari/report (including request_quotas / frontier). Docs: savings-report.md. Covered by tests/unit/test_render_markdown.py.

L1 singleflight hermetic ceiling (#528)

Status: Done (2026-09-16). Hermetic @pytest.mark.benchmark burst: N concurrent same-embed-key L1 cold misses (distinct L0 keys) → exactly one embed before fill and one upstream under a wall ceiling. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

TTFT-aware local tier preference (#529)

Status: Done (2026-09-16). Opt-in routing.ttft_aware (default off) prefers a faster local tier with better recent stream TTFT percentile when ttft_min_samples are present. Docs: routing-tiers.md, config.md. Covered by tests/unit/test_ttft_aware_routing.py.

Doctor soft_budget_ratio=0 warning (#530)

Status: Done (2026-09-16). daari doctor warns (soft_budget_ratio) when frontier.soft_budget_ratio is 0 while request quotas or rate_limit RPM/TPM are configured. Quiet when ratio > 0 or no caps. Docs: doctor-health.md. Covered by tests/unit/test_doctor_soft_budget_ratio.py.

Soft-warning Grafana panel (#537)

Status: Done (2026-09-16). Grafana overview charts daari_soft_warnings_total by kind. Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Route preview dry-run (#538)

Status: Done (2026-09-16). POST/GET /v1/daari/route/preview and daari route preview return the would-be initial local tier plus heuristic / phase / latency_budget / ttft_preference reason fields, without calling Ollama or frontier. Docs: routing-tiers.md. Covered by tests/unit/test_route_preview.py.

Hard-reject Prometheus counters (#551)

Status: Done (2026-09-16). /metrics exposes daari_rejects_total{kind="budget"|"request_quota"|"rate_limit"} on hard 402/429 only; soft warning headers unchanged. Grafana hard-rejects panel. Docs: metrics-prometheus.md. Covered by tests/unit/test_reject_metrics.py.

Harness strip parity for Claude Code / Codex (#541)

Status: Done (2026-09-16). strip_harness_text matches LiteLLM Codex reminder markers (user_instructions, environments_instructions, agents.md envelope) plus existing Claude Code blocks; fixtures assert category/complexity and router tier stay on the user task. Docs: routing-tiers.md. Covered by tests/unit/test_harness_strip_parity.py.

Postgres virtual keys / teams (#544)

Status: Done (2026-09-16). server.virtual_keys.backend=postgres shares keys/teams via observability.postgres_url; factory wires app/SSO/CLI; Helm + doctor fleet warnings list the auth store. Docs: config.md. Covered by tests/unit/test_postgres_virtual_keys.py.

Helm graceful rollout lifecycle (#545)

Status: Done (2026-09-16). Chart defaults: terminationGracePeriodSeconds=60, lifecycle.preStopSleepSeconds=5 (omit when 0), rollingUpdate maxUnavailable: 0 / maxSurge: 1. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

Team-level aggregate RPM/TPM (#546)

Status: Done (2026-09-16). Team.rpm/tpm (0 = unlimited) with SQLite migration + Postgres schema, daari keys team-create/update --rpm/--tpm, and gateway checks of rpm:team:{id} / tpm:team:{id} after per-key counters (same Redis/SQLite backend). Soft-warn header + Retry-After on 429 match key limits. daari report --by-team shows configured limits. Docs: auth-and-keys.md, virtual-keys.md. Covered by tests/unit/test_team_rate_limits.py.

Keys/teams export and import (#548)

Status: Done (2026-09-16). daari keys export --json [--out FILE] writes a versioned document (teams + key hashes, never plaintext). daari keys import FILE [--dry-run] upserts by team_id/key_id and refuses unknown schema versions. Round-trip preserves rotated-key grace. Docs: upgrade.md. Covered by tests/unit/test_keys_export_import.py.

Package version discovery (#552)

Status: Done (2026-09-16). daari --version prints daari.__version__. GET /health returns {"status":"ok","version":"…"} (status unchanged for probes). Docs: upgrade.md, doctor-health.md. Covered by tests/unit/test_version_discovery.py.

Rate-limit Redis degraded gauge (#553)

Status: Done (2026-09-16). /metrics exposes daari_rate_limit_degraded{mode="sqlite_fallback"|"fail_open"} (1 while Redis counting is degraded, 0 after recover). Docs + Grafana panel: metrics-prometheus.md. Covered by tests/unit/test_rate_limit.py.

Helm securityContext and PDB (#554)

Status: Done (2026-09-16). Chart defaults: non-root uid 1000, drop ALL caps, readOnlyRootFilesystem with emptyDir mounts for ~/.daari and /tmp. Optional podDisruptionBudget (off by default). Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

SSO RBAC: analyst read / admin mutate (#555)

Status: Done (2026-09-17). When SSO is on, mutating admin routes (reload-caches, config PATCH, org-learning sync) require admin_min_role; ops reads (stats / traces / report / audit) allow analyst+. Master API key remains admin. Docs: auth-and-keys.md. Covered by tests/integration/test_oidc_sso_gateway.py.

TTFT preference Prometheus counter (#539)

Status: Done (2026-09-17). daari_ttft_preference_total{from,to} increments when routing.ttft_aware rewrites the heuristic local tier. Docs: metrics-prometheus.md. Covered by tests/unit/test_ttft_aware_routing.py.

Soft rate-limit warn hermetic burst (#540)

Status: Done (2026-09-17). @pytest.mark.benchmark burst: N concurrent soft-band RPM requests (memory limiter + frozen window) → all 200 with soft header under a wall ceiling. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

Ollama facade capability advertisement (#547)

Status: Done (2026-09-17). /api/tags and /api/show advertise Ollama-shaped capabilities (completion, plus tools / vision / thinking from the catalog). Docs: intellij.md. Covered by tests/integration/test_ollama_compat.py.

Helm ServiceMonitor for /metrics (#570)

Status: Done (2026-09-17). Optional serviceMonitor.enabled (default false) renders a Prometheus Operator ServiceMonitor scraping the chart Service /metrics on port http. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

Grafana TTFT preference panel (#571)

Status: Done (2026-09-17). Overview dashboard adds TTFT preference by from→to on rate(daari_ttft_preference_total[5m]). Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

SSO analyst GET config (#572)

Status: Done (2026-09-17). When SSO is on, GET /v1/daari/config allows analyst+; PATCH still requires admin_min_role. Docs: auth-and-keys.md. Covered by tests/integration/test_oidc_sso_gateway.py.

ChatGPT Desktop capabilities note (#573)

Status: Done (2026-09-17). Recipe notes /api/tags and /api/show advertise completion / tools / vision / thinking from the catalog. Docs: chatgpt-desktop.md.

Hermetic TTFT preference burst (#574)

Status: Done (2026-09-17). @pytest.mark.benchmark burst: N concurrent _choose_initial_tier preference rewrites (seeded TTFT) → all faster tier, counter ≥ N, wall ceiling. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

Helm orgPool Deployment env (#581)

Status: Done (2026-09-17). orgPool.enabled wires DAARI_ROUTING__ORG_POOL__ENABLED + BASE_URL into the Deployment. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

ServiceMonitor bearer auth for /metrics (#582)

Status: Done (2026-09-17). Optional serviceMonitor.bearerTokenSecret (name + key) renders Prometheus Operator authorization Bearer credentials on the ServiceMonitor endpoint so scrapes work when the master API key protects /metrics. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

Grafana backend pool and concurrency panels (#583)

Status: Done (2026-09-17). Overview dashboard adds Backend pool health & outstanding (daari_backend_up / _outstanding / request rate) and Concurrency gate (daari_rate_limit_in_flight vs _max / _queued). Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Doctor Redis PING and GET /ready (#584)

Status: Done (2026-09-17). Optional redis check PINGs cache.redis_url when cache.backend=redis; optional ready check hits GET /ready when the daemon is up and warns on degraded / not_ready. Docs: doctor-health.md. Covered by tests/unit/test_doctor_redis_ready.py.

Hermetic hard-reject burst ceiling (#585)

Status: Done (2026-09-17). @pytest.mark.benchmark burst: N concurrent chat requests after RPM exhausted → all 429, daari_rejects_total ≥ N, wall ceiling. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

Stats + web-ui soft_warnings / rejects (#593)

Status: Done (2026-09-17). GET /v1/daari/stats returns soft_warnings and rejects maps (empty {} when zero); web-ui tables render both by kind. Docs: metrics-prometheus.md, web-ui README. Covered by tests/unit/test_stats_cliff_counters.py and web-ui DOM tests.

Optional Prometheus metrics scrape port (#594)

Status: Done (2026-09-17). observability.metrics_port (0 = off) binds a scrape-only 127.0.0.1 listener with the same /metrics body and no API key; main-port auth unchanged. Docs: config.md, metrics-prometheus.md. Covered by tests/unit/test_metrics_port.py.

Anthropic SSE L0 exact-cache parity (#600)

Status: Done (2026-09-17). stream_anthropic_events performs the same exact L0 lookup (and org-L0 when enabled) as the OpenAI stream path before the model tier chain; hits emit daari_meta.tier=L0 / L0-org, record cache-hit metrics / usage ledger, and skip upstream. Successful Anthropic streams write back exact L0. Agent turns keep exact-key semantics; no new L1. ADR-0004 negative note updated. Covered by tests/unit/test_anthropic_stream_l0.py.

Grafana panels for retries / false-hits / budget alerts (#601)

Status: Done (2026-09-17). Bundled Grafana dashboard adds rate panels for daari_upstream_retries_total, daari_cache_false_hits_avoided_total, and daari_budget_alerts_total. Docs note in metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Helm Service port for metrics_port scrapes (#602)

Status: Done (2026-09-17). Chart observability.metricsPort (0 = off) wires DAARI_OBSERVABILITY__METRICS_PORT, containerPort, and Service port metrics; ServiceMonitor scrapes metrics (no bearer) when set, else API http (+ optional bearer). Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

Prometheus counters for MCP tool ingress (#603)

Status: Done (2026-09-17). daari_mcp_tool_calls_total{tool,outcome} for MCP ingress (ok / deny / error / guardrail); no-op when observability.prometheus=false. Docs: metrics-prometheus.md. Covered by tests/unit/test_mcp_prometheus.py.

Expose agent_turn on daari_meta (#604)

Status: Done (2026-09-17). DaariMeta.agent_turn is set true on agent turns (tools / tool history); omitted for plain chat via exclude_none. ADR-0004 checklist item checked. Covered by tests/unit/test_agent_turn_meta.py.

Hermetic hard 402 budget/quota burst (#592)

Status: Done (2026-09-17). @pytest.mark.benchmark bursts: N concurrent chat requests after USD budget or max_requests exhausted → all 402, daari_rejects_total{kind="budget"|"request_quota"} ≥ N, wall ceiling, upstream must not run. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

Helm NOTES ServiceMonitor bearer and orgPool (#595)

Status: Done (2026-09-17). Post-install NOTES.txt echoes ServiceMonitor /metrics scrape + Bearer/bearerTokenSecret when enabled, and org pool env (including baseUrl) when orgPool.enabled. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.


Time-boxed autodev dev-cycle loop (#611)

Status: Done (2026-09-17). autodev-cycle exports AUTODEV_DEADLINE_EPOCH (job start + 50 min) and raises timeout-minutes to 60; the agent loops pick → PR → auto-merge and stops picking with under 10 min left, exiting 0 instead of being killed mid-wait. Documented in docs/automations/dev-cycle.md and docs/AUTOMATION.md. Contract test test_workflow_time_boxes_loop_before_job_timeout.

Doctor metrics_auth when API key protects /metrics (#596)

Status: Done (2026-09-17). Optional metrics_auth check: daemon up + prometheus on + server.api_key set and unauthenticated GET /metrics → 401 warns with Bearer / serviceMonitor.bearerTokenSecret guidance; quiet when prometheus off, no api_key, or scrape already open. Docs: doctor-health.md. Covered by tests/unit/test_doctor_metrics_auth.py.

Team budget remaining Prometheus gauges (#616)

Status: Done (2026-09-17). GET /metrics exposes daari_team_budget_remaining_usd / _limit_usd / _remaining_hours per team USD window (scrape-time snapshot via list_teams + ledger). Docs: metrics-prometheus.md. Covered by tests/unit/test_prometheus.py.

Team rate-limit scope headers and gauges (#617)

Status: Done (2026-09-17). X-RateLimit-Scope: key|team|model on success and 429 when a counter is active; /metrics exposes daari_team_rate_limit_remaining / _limit for teams with rpm/tpm. Docs: headers.md, auth-and-keys.md, metrics-prometheus.md. Covered by tests/unit/test_team_rate_limits.py and tests/unit/test_prometheus.py.

RFC 7662 POST /introspect (#618)

Status: Done (2026-09-17). Authenticated POST /introspect (JSON or form token) returns RFC 7662 active payloads for master/virtual keys without leaking secrets; inactive tokens → {"active": false} / 200; no caller auth → 401. Docs: mcp.md, auth-and-keys.md. Covered by tests/unit/test_introspect.py.

Grafana MCP tool ingress panels (#619)

Status: Done (2026-09-17). Overview dashboard adds MCP tool ingress by outcome (sum by (outcome) (rate(daari_mcp_tool_calls_total[5m]))). Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Hermetic soft request-quota warn burst (#620)

Status: Done (2026-09-17). Hermetic @pytest.mark.benchmark guard: N concurrent soft-band request-quota requests (VK ledger prefilled to soft_budget_ratio) all return 200 with x-daari-quota-requests-warning: soft, daari_soft_warnings_total{kind="request_quota"} ≥ N under a wall ceiling, and no hard 402. Docs: benchmark-load.md. Covered by tests/benchmark/test_hermetic_paths.py.

Soft USD budget warn header + soft_warnings{kind=budget} (#626)

Status: Done (2026-09-17). Soft-band USD windows emit x-daari-budget-warning: soft and daari_meta.warning=budget_warning; daari_soft_warnings_total{kind="budget"} increments on 2xx (hard 402 does not). Hermetic concurrent soft-band burst under a wall ceiling. Docs: headers.md, metrics-prometheus.md, benchmark-load.md. Covered by tests/unit/test_soft_warning_metrics.py and tests/benchmark/test_hermetic_paths.py.

Grafana team budget and rate-limit remaining panels (#627)

Status: Done (2026-09-17). Overview dashboard adds Team budget remaining (USD) (daari_team_budget_remaining_usd / _limit_usd) and Team rate-limit remaining (daari_team_rate_limit_remaining / _limit). Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Stats tier p50/p95 latency (#628)

Status: Done (2026-09-17). GET /v1/daari/stats tier objects include p50_ms / p95_ms from latency histogram buckets (same helper as TTFT). Web-ui tier table already rendered these fields; DOM test pins non-dash values. Docs: web-ui README. Covered by tests/unit/test_stats_cliff_counters.py and web-ui DOM tests.

Web-ui local pool backends (#629)

Status: Done (2026-09-17). Dashboard Local pool backends table renders stats.backends (id, healthy, circuit, outstanding) with an empty-state row when the pool is empty. Docs: web-ui README. Covered by web-ui DOM tests.

OTel daari.agent_turn on GenAI root spans (#630)

Status: Done (2026-09-17). export_trace() stamps boolean daari.agent_turn on the GenAI root span when daari_meta.agent_turn is set (true/false); omitted when unset. Docs: otel-genai.md. Covered by tests/unit/test_otel_genai.py.

Grafana soft USD budget warnings panel (#636)

Status: Done (2026-09-17). Overview dashboard adds Soft USD budget warnings (rate(daari_soft_warnings_total{kind="budget"}[5m])). Docs: metrics-prometheus.md. Covered by tests/unit/test_grafana_dashboard.py.

Doctor soft_budget_ratio covers USD windows (#637)

Status: Done (2026-09-17). daari doctor warns when frontier.soft_budget_ratio=0 while any VK/team USD budget window is set (same advisory as request-quota/RPM). Docs: doctor-health.md. Covered by tests/unit/test_doctor_soft_budget_ratio.py.

Document USD soft budget warning in budgets guide (#638)

Status: Done (2026-09-17). budgets-frontier.md documents soft USD band → x-daari-budget-warning: soft, daari_meta.warning=budget_warning, and daari_soft_warnings_total{kind="budget"}, parallel to request-quota soft band. Docs: budgets-frontier.md.

Stats backend_summary counts (#639)

Status: Done (2026-09-17). GET /v1/daari/stats adds backend_summary (total / healthy / unhealthy / open_circuit) derived from backends. Docs: traces-stats.md, web-ui README. Covered by tests/unit/test_local_pool.py.

Assert backend_summary on empty stats cliff maps (#647)

Status: Done (2026-09-18). test_stats_includes_empty_cliff_maps asserts backend_summary == {total:0, healthy:0, unhealthy:0, open_circuit:0} so the shared stats contract cannot drop the field on non-pool installs.

Note backend_summary on architecture stats surface (#648)

Status: Done (2026-09-18). docs/ARCHITECTURE.md API table notes soft_warnings / rejects and backend_summary on GET /v1/daari/stats.

Prune shipped ENTERPRISE.md gap rows after soft-budget drain (#649)

Status: Done (2026-09-18). docs/prd/ENTERPRISE.md drops shipped night-of-17 rows (Anthropic L0 → hermetic 402) plus soft-budget follow-ons; changelog bullet; watch rows only. Docs-only.

Note agent_turn on daari_meta in architecture (#653)

Status: Done (2026-09-18). docs/ARCHITECTURE.md package map notes DaariMeta.agent_turn (ADR-0004) on agent tool-history turns.

Web-ui backend_summary counts (#654)

Status: Done (2026-09-18). Dashboard renders backend_summary (total / healthy / unhealthy / open_circuit) above the local pool backends table. Covered by packages/web-ui/test/dashboard.test.js.

Expand package-map observability for stats cliff maps (#656)

Status: Done (2026-09-18). package-map.md observability/ row notes stats soft_warnings / rejects and backend_summary (link to traces-stats).

Note stats tier p50/p95 in traces-stats (#655)

Status: Done (2026-09-18). traces-stats.md documents optional tiers.*.p50_ms / p95_ms on GET /v1/daari/stats.

Note daari stats JSON includes backend_summary (#664)

Status: Done (2026-09-18). daari stats CLI docstring notes the payload includes soft_warnings / rejects and backend_summary.

Document POST /introspect in architecture API table (#657)

Status: Done (2026-09-18). docs/ARCHITECTURE.md HTTP API table lists POST /introspect (RFC 7662 virtual-key introspection).

Mention backend_summary in metrics-prometheus.md (#663)

Status: Done (2026-09-18). metrics-prometheus.md notes backend_summary on GET /v1/daari/stats (link to traces-stats).

Assert POST /introspect in architecture API table (#665)

Status: Done (2026-09-18). tests/unit/test_architecture_introspect.py asserts docs/ARCHITECTURE.md lists POST /introspect (RFC 7662).

Note agent_turn in traces-stats meta section (#666)

Status: Done (2026-09-18). traces-stats.md daari_meta list includes agent_turn.

Status: Done (2026-09-18). savings-report.md links to traces-stats backend_summary for local pool pressure beside spend.

Emphasize open_circuit count on web-ui when > 0 (#667)

Status: Done (2026-09-18). Dashboard open-circuit metric uses metric-warn when backend_summary.open_circuit > 0. Covered by packages/web-ui/test/dashboard.test.js.

Note soft_warnings/rejects on architecture metrics row (#674)

Status: Done (2026-09-18). docs/ARCHITECTURE.md metrics.py package row mentions soft_warnings / rejects and backend_summary on stats.

Assert daari stats CLI help mentions backend_summary (#675)

Status: Done (2026-09-18). daari stats --help contract test asserts backend_summary appears in Typer help. Covered by tests/unit/test_stats_cli_help.py.

Repair metrics-prometheus series table after prose insert (#679)

Status: Done (2026-09-18). Moved mid-table soft_warnings / backend_summary prose below the contiguous Series table in metrics-prometheus.md. Covered by tests/unit/test_metrics_prometheus_docs.py.

Sync CLI reference with Typer catalog (#681)

Status: Done (2026-09-18). cli.md lists all top-level Typer commands; learn stale names removed; route preview documented. Covered by tests/unit/test_cli_reference_catalog.py.

Regenerate http-api.md to include POST /introspect (#680)

Status: Done (2026-09-18). Committed http-api.md includes | POST | /introspect |; test_gen_reference asserts generator + on-disk.

Assert daari doctor --help mentions --suggest-models (#683)

Status: Done (2026-09-18). daari doctor --help contract test asserts --suggest-models appears in Typer help. Covered by tests/unit/test_doctor_cli_help.py.

Note soft_warnings/rejects cliff maps in traces-stats (#682)

Status: Done (2026-09-18). traces-stats.md documents soft_warnings / rejects on GET /v1/daari/stats with a metrics-prometheus cross-link. Covered by tests/unit/test_traces_stats_docs.py.

Grafana tier-shadow samples panel (#689)

Status: Done (2026-09-18). Overview dashboard adds Tier shadow agree/disagree on daari_tier_shadow_samples_total; metrics-prometheus intro mentions the panel. Covered by tests/unit/test_grafana_dashboard.py.

Contract-test boundary_decisions_total Prometheus export (#691)

Status: Done (2026-09-18). test_boundary_decisions_counter asserts daari_boundary_decisions_total{stage,label} in render_prometheus(). Covered by tests/unit/test_prometheus.py.

Grafana boundary decisions panel (#690)

Status: Done (2026-09-18). Overview dashboard adds Boundary decisions by stage on daari_boundary_decisions_total. Covered by tests/unit/test_grafana_dashboard.py.


Status: Done (2026-09-18). Series table row for daari_tier_shadow_samples_total{agreed} with routing-tiers link; contract test asserts presence. Covered by tests/unit/test_metrics_prometheus_docs.py.

Assert prune --help documents retention stores (#692)

Status: Done (2026-09-18). daari prune --help contract test asserts traces, ledger, audit, shadow, and tasks appear in Typer help. Covered by tests/unit/test_prune_cli_help.py.

Fix route preview stale --json in cli.md (#699)

Assert report --help documents format and breakdown flags (#701)

Status: Done (2026-09-18). daari report --help contract test asserts format, by-client, by-team, and by-user. Covered by tests/unit/test_report_cli_help.py.

Assert context clear --help documents L0/L1/CCS (#700)

Status: Done (2026-09-18). daari context clear --help contract test asserts l0, l1, and ccs appear in Typer help. Covered by tests/unit/test_context_clear_cli_help.py.

Fix route preview stale --json in cli.md (#699)

Status: Done (2026-09-18). cli.md documents --model / --latency-budget-ms for route preview (not --json). Covered by tests/unit/test_cli_reference_catalog.py.

Per-key and per-team model allowlists (#708)

Status: Done (2026-09-18). VirtualKey and Team take optional allowed_models globs plus named model_groups from settings. Team and key lists intersect. Denied gateway calls (chat, responses, embeddings, Anthropic messages, Ollama facade) return 403 model_not_allowed and an auth.model_denied audit row. Frontier fallback skips models outside the list. Unset keeps the previous unrestricted behavior. Covered by tests/unit/test_model_allowlists.py.

Config validate and strict nested keys (#710)

Status: Done (2026-09-18). daari config validate reports unknown keys (top-level and nested), type errors, and out-of-range values. Settings.load warns on unknown nested keys via daari.config and fails when DAARI_STRICT_CONFIG=1 or daari serve --strict. daari doctor mentions them on config_keys. Covered by tests/unit/test_config_validate.py.

Per-request spend rows and chargeback export (#709)

Status: Done (2026-09-18). Opt-in usage.spend writes one row per completed request (SQLite by default, Postgres when observability.backend is postgres). daari spend export --since --format csv|jsonl streams rows; spend_days prunes them. Off by default, the day ledger is unchanged. Covered by tests/unit/test_spend_export.py.

Master key overlap rotation (#711)

Status: Done (2026-09-18). server.api_key accepts a string or a list. Every entry is checked with a constant-time compare. secret:// refs resolve per entry. An auth.master_key_overlap audit row stores the count, never the secrets. daari doctor warns when more than two keys are active. Covered by tests/unit/test_master_key_overlap.py.

Responses cancel and delete (#713)

Status: Done (2026-09-18). POST /v1/responses/{id}/cancel is idempotent (in-flight background jobs stay cancelled; terminal objects return 200). DELETE /v1/responses/{id} removes the row so later GET is 404. Tenancy matches GET (cross-key 404 + audit, master can act on any row). SQLite and Postgres stores both implement delete. Covered by tests/unit/test_responses_tenancy.py, tests/unit/test_response_store.py, and tests/unit/test_postgres_responses.py.

Per-provider and per-tier retry/timeout (#712)

Status: Done (2026-09-18). Each frontier.providers entry accepts optional timeout_s, retry_attempts, and retry_backoff_s; unset fields inherit upstream.frontier_timeout_seconds / upstream.retry. models.timeout_s overrides the local timeout per L3/L4/L5. Failover spends one hop's budget then advances; one timeout does not shrink the next slot. Route preview returns policy and chain. Covered by tests/unit/test_provider_retry_timeout.py.

Local-first audio transcriptions (#715)

Status: Done (2026-09-18). POST /v1/audio/transcriptions forwards the OpenAI multipart form to asr.base_url when set. With no local ASR the route returns 501 unless asr.frontier_fallback is true, frontier is enabled, and a key is present. Auth, request quotas, and rate limits apply; a successful transcription counts as one request. Covered by tests/unit/test_audio_transcriptions.py.

Optional KEDA request-rate autoscaling (#716)

Status: Done (2026-09-18). autoscaling.keda.enabled (default false) renders a KEDA ScaledObject on sum(rate(daari_requests_total[1m])) with a configurable threshold. CPU HPA is unchanged. minReplicaCount above 1 is refused while postgres.enabled is false. Covered by tests/unit/test_helm_chart.py.

Daily request caps beside rpm (#717)

Status: Done (2026-09-19). VirtualKey.rpd and Team.rpd (0 = unlimited) use the existing rate-limit backends on a 86400-second UTC day window, separate from rpm. Key and team caps both apply; the tighter remaining counter wins. daari keys create/update --rpd and team-create/update --rpd persist it, and export/import round-trips it. A deny is 429 naming rpd. Covered by tests/unit/test_daily_request_caps.py.

Status: Done (2026-09-19). The boundaries guide Observe subsection links daari_boundary_decisions_total and metrics-prometheus.md. Covered by tests/unit/test_boundaries_docs.py.

Document escalations and errors series (#703)

Status: Done (2026-09-19). metrics-prometheus intro names the Grafana Escalations & errors panel, and the series table rows daari_escalations_total with companion daari_errors_total. Covered by tests/unit/test_metrics_prometheus_docs.py.

Surface rpd on introspect and keys list (#730)

Status: Done (2026-09-19). POST /introspect includes rpd when the key's day cap is set, and daari keys list prints an rpd column. Zero stays omitted, matching rpm. Covered by tests/unit/test_introspect.py and tests/unit/test_virtual_keys.py.

Scrape team rpd remaining (#731)

Status: Done (2026-09-19). team_rate_gauges emits kind="rpd" on the 86400-second window for teams with a day cap. Prometheus renders it next to rpm/tpm. Covered by tests/unit/test_rate_limit.py and tests/unit/test_prometheus.py.

Doctor warns when rpm has no daily cap (#732)

Status: Done (2026-09-19). daari doctor warns when a key or team has rpm > 0 and rpd == 0, naming both rpd and the key or team. A set day cap, or no rpm, stays quiet. Covered by tests/unit/test_doctor_soft_budget_ratio.py.

Document rpd on keys guides (#733)

Status: Done (2026-09-19). Virtual-keys examples include --rpd (0 = unlimited) and the config reference notes the day cap is per key/team, not a rate_limit field, with a link to the auth guide. Covered by tests/unit/test_rpd_docs.py.

Transcription model allowlists (#739)

Status: Done (2026-09-19). POST /v1/audio/transcriptions returns 403 model_not_allowed when the resolved model (asr.model, else the form model) is outside the key or team allowlist, and does not call the ASR upstream. Unset allowlists stay unrestricted.

Daily-cap Retry-After (#738)

Status: Done (2026-09-19). An rpd 429 sets Retry-After to the seconds until the UTC day boundary. rpm/tpm still use rate_limit.retry_after_seconds.

Team rpd on stats and dashboard (#741)

Status: Done (2026-09-19). GET /v1/daari/stats includes team_rate_limits (team, kind, limit, remaining), including rpd. The web dashboard renders one row per entry. Covered by tests/unit/test_stats_team_rate_limits.py and packages/web-ui/test/dashboard.test.js.

Doctor ASR probe (#740)

Status: Done (2026-09-19). daari doctor warns when asr.base_url is unreachable and when asr.frontier_fallback is set without frontier or a resolvable key. Unconfigured ASR stays quiet. Covered by tests/unit/test_doctor_asr.py.

Per-key rpd scrape (#742)

Status: Done (2026-09-19). Prometheus emits daari_key_rate_limit_remaining / _limit for keys with rpd > 0, labeled by key name. A scrape does not consume the cap. Covered by tests/unit/test_prometheus.py.

Per-key rpd on stats and dashboard (#749)

Status: Done (2026-09-19). GET /v1/daari/stats includes key_rate_limits for keys with rpd > 0. A stats read does not consume the cap. The dashboard renders one row per entry. Covered by tests/unit/test_stats_key_rate_limits.py.

Per-key rpd Grafana panel (#750)

Status: Done (2026-09-19). Overview Grafana charts daari_key_rate_limit_remaining vs _limit under Key rate-limit remaining, legend by key name and kind. Covered by tests/unit/test_grafana_dashboard.py.

Transcription chargeback rows (#751)

Status: Done (2026-09-19). A successful transcription writes a spend row with tier asr or L6 and the caller's key and team, so chargeback export can tell local ASR from frontier fallback. Allowlist 403 and unconfigured 501 do not write a row. Covered by tests/unit/test_audio_transcriptions.py.

Chargeback guide names transcription tiers (#756)

Status: Done (2026-09-19). The chargeback guide states that a successful transcription exports as asr or L6, and that allowlist 403 / unconfigured 501 write no row. Covered by tests/unit/test_chargeback_docs.py.

Embedding chargeback rows (#755)

Status: Done (2026-09-19). POST /v1/embeddings writes a spend row with tier embed and the caller's key and team. An unknown model returns 400 and writes no row. Covered by tests/unit/test_embeddings.py.

Tenant cache scope (#768)

Status: Done (2026-09-19). Virtual keys and teams take an optional cache_scope (global default, team, key). Team and key scopes fold team_id or key_id into L0, Redis L0, and L1; global keeps today's cache hashes. Covered by tests/unit/test_cache_scope.py.

Embed and ASR latency (#762)

Status: Done (2026-09-19). Embedding cache misses and local or frontier transcriptions record a positive latency_ms on the embed, asr, and L6 histograms. Covered by tests/unit/test_embeddings.py and tests/unit/test_audio_transcriptions.py.

Helm Ollama base URL (#763)

Status: Done (2026-09-19). ollama.baseUrl sets DAARI_OLLAMA__BASE_URL for the embedder and L1. Empty leaves the image default. Distinct from orgPool. Covered by tests/unit/test_helm_chart.py.

Doctor embed probe (#764)

Status: Done (2026-09-19). When L1 is on, daari doctor posts one embed request and fails the check if the endpoint errors or returns no vector. L1 off skips the probe. Covered by tests/unit/test_doctor_embeddings.py.

Audio upload TPM (#765)

Status: Done (2026-09-19). Multipart POST /v1/audio/transcriptions charges TPM from len(file_bytes) // 4 instead of 1. JSON chat and embeddings still use the character estimate. A TPM denial is still 429 with Retry-After. Covered by tests/unit/test_rate_limit.py.

Client disconnect cancels upstream (#769)

Status: Done (2026-09-19). Non-streaming chat, Anthropic, and Responses calls cancel the router task when the client disconnects, so the executor sees CancelledError. Closing a stream stops the upstream generator and records daari_cancelled_requests_total{phase} plus a request_cancelled gateway event. Cancelled requests do not add a second ledger row. Covered by tests/unit/test_client_disconnect.py.

Selective cache invalidation (#770)

Status: Done (2026-09-19). ExactCache and RedisExactCache delete by served model or entry hash (Redis DELETE, not a no-op). L1 filters on context_key / answer_hash. POST /v1/daari/cache/invalidate and daari cache invalidate return removed counts and log cache_invalidate. Redis daari cache prune says expiry is TTL and does not scan. Covered by tests/unit/test_cache_invalidate.py.

Request log retention (#772)

Status: Done (2026-09-19). observability.retention.request_log_days (default 0) folds cursor-requests.log into prune_all. Rotated backups that are entirely old are deleted; the active file keeps newer lines. daari prune prints a request_log line. Covered by tests/unit/test_retention.py.

Request deadline across hops (#771)

Status: Done (2026-09-19). Optional X-Daari-Deadline-Ms (wins) and upstream.request_deadline_seconds bound the escalation chain. Each upstream call uses min(tier timeout, remaining); a spent budget returns 504 naming the deadline, records daari_request_deadline_exceeded_total, and does not call frontier. Streaming applies the budget to time-to-first-token only. Covered by tests/unit/test_request_deadline.py.

Helm local ASR base URL (#757)

Status: Done (2026-09-19). asr.baseUrl (empty by default) sets DAARI_ASR__BASE_URL on the Deployment when non-empty, so a cluster can point transcriptions at an on-box whisper/vLLM server. Covered by tests/unit/test_helm_chart.py.

Audio translations (#758)

Status: Done (2026-09-19). POST /v1/audio/translations forwards multipart audio to {asr.base_url}/audio/translations and returns JSON text. Unconfigured ASR is 501 with no upload; response_format other than json is 400. Covered by tests/unit/test_audio_translations.py.

Batch Ollama embeddings (#766)

Status: Done (2026-09-20). Cache-miss list inputs go out as one POST /api/embed with input string[]; L0 hits stay off the wire. Single-string embed is a batch of one. A 404 on /api/embed falls back to per-string /api/embeddings. Covered by tests/unit/test_embed_batch.py.

Helm request deadline and log retention (#786)

Status: Done (2026-09-20). upstream.requestDeadlineSeconds and observability.retention.requestLogDays set DAARI_UPSTREAM__REQUEST_DEADLINE_SECONDS and DAARI_OBSERVABILITY__RETENTION__REQUEST_LOG_DAYS when non-empty. Defaults omit both. Covered by tests/unit/test_helm_chart.py.

Cancel embed and ASR on disconnect (#787)

Status: Done (2026-09-20). Embeddings and audio transcription/translation wrap upstream work in await_unless_disconnected and return 499 with daari_cancelled_requests_total{phase=embed|asr|translation}. Covered by tests/unit/test_client_disconnect.py.

Grafana cancelled and deadline panels (#788)

Status: Done (2026-09-20). Overview dashboard charts daari_cancelled_requests_total by phase and daari_request_deadline_exceeded_total; metrics docs list the deadline series. Covered by tests/unit/test_grafana_dashboard.py.

Cache invalidate by team or key scope (#790)

Status: Done (2026-09-20). L0 put persists optional scope (team:<id> / key:<id>); invalidate(team_id=…) / invalidate(key_id=…) drop matching L0 rows and L1 rows whose context_key contains that segment. CLI --team / --key and admin team_id / key_id wired. Covered by tests/unit/test_cache_invalidate.py.

Ollama /api/show thinking controls (#789)

Status: Done (2026-09-20). When facade capabilities include thinking, POST /api/show also returns thinking: {values, default} with levels low/medium/high (aligned with ollama_think; no max). Plain models omit the object; /api/tags stays capability-string-only. Covered by tests/unit/test_ollama_show_thinking.py.

Cancel Ollama facade non-stream on disconnect (#797)

Status: Done (2026-09-20). Non-stream /api/chat and /api/generate wrap router.route in await_unless_disconnected (phase chat) and return 499 on disconnect. Streaming NDJSON unchanged. Covered by tests/unit/test_client_disconnect.py.

Cancel MCP tools/call on disconnect (#798)

Status: Done (2026-09-20). Router-backed MCP tools/call and legacy /v1/mcp/query wrap work in await_unless_disconnected (phase mcp) and return 499 on disconnect. Background MCP tasks stay unwrapped. Covered by tests/unit/test_client_disconnect.py.

Audio translation TPM uses upload bytes (#796)

Status: Done (2026-09-20). Multipart POST /v1/audio/translations charges TPM from len(file_bytes) // 4 like transcriptions; chat/embeddings stay on the character estimate. Covered by tests/unit/test_rate_limit.py.

Document cache invalidate --team/--key and refresh http-api (#800)

Status: Done (2026-09-20). org-cache.md documents --team / --key and admin team_id / key_id. Regenerated http-api.md lists POST /v1/audio/translations and POST /v1/daari/cache/invalidate. Covered by tests/unit/test_gen_reference.py.

Document request deadline header and config (#799)

Status: Done (2026-09-20). headers.md documents X-Daari-Deadline-Ms (504 / request_deadline_exceeded; stream = TTFT-only). config.md lists upstream.request_deadline_seconds. Covered by tests/unit/test_deadline_docs.py.

Distinct chargeback tier for audio translations (#807)

Status: Done (2026-09-20). Local /v1/audio/translations spend/ledger rows use tier translation (frontier stays L6); transcriptions stay asr. Chargeback guide updated. Covered by tests/unit/test_audio_translations.py.

Redis L0 age prune when TTL is off (#806)

Status: Done (2026-09-20). When cache.l0.ttl_seconds == 0, Redis L0 prune deletes keys whose stored t is older than 7 days; when TTL is on, prune stays no-scan. CLI reports the count. Covered by tests/unit/test_redis_cache.py.

Hermetic happy-path chat leaves cancel counter at zero (#808)

Status: Done (2026-09-20). Non-stream /v1/chat/completions with a fast executor returns 200 and does not increment daari_cancelled_requests_total for phase chat. Covered by tests/unit/test_client_disconnect.py.

Request deadline on audio and embeddings (#814)

Status: Done (2026-09-20). X-Daari-Deadline-Ms (and upstream.request_deadline_seconds) binds on /v1/audio/transcriptions, /v1/audio/translations, and /v1/embeddings; upstream httpx timeouts use nonstream_timeout / remaining budget. Exhaustion returns 504 request_deadline_exceeded and increments daari_request_deadline_exceeded_total. Covered by tests/unit/test_request_deadline.py.

Request deadline on Ollama facade (#815)

Status: Done (2026-09-20). /api/chat and /api/generate accept X-Daari-Deadline-Ms into RequestMeta.deadline_ms so the router binds the same wall-clock budget as OpenAI chat. Already-spent budgets return 504 before NDJSON streaming starts. Covered by tests/unit/test_request_deadline.py.

Daemon cache invalidate Bearer under SSO (#816)

Status: Done (2026-09-20). daari cache invalidate sends Authorization: Bearer (master key or --token) when SSO admin gate is active; missing credentials yield a clear "SSO token required" error. Covered by tests/unit/test_cache_invalidate.py.

Config validate ASR frontier_fallback (#817)

Status: Done (2026-09-20). daari config validate reports when asr.frontier_fallback is true but frontier is disabled or no API key resolves (empty asr.base_url — fallback-only path). Covered by tests/unit/test_config_validate.py.

Request deadline on MCP tools/call (#827)

Status: Done (2026-09-20). MCP tools/call (JSON-RPC /mcp and legacy query) binds X-Daari-Deadline-Ms before tool execution; exhaustion returns 504 request_deadline_exceeded. Covered by tests/unit/test_mcp_server.py.

Docs: deadline header on audio and embeddings (#824)

Status: Done (2026-09-20). docs/developer/reference/headers.md lists audio transcriptions/translations and embeddings among X-Daari-Deadline-Ms surfaces. Covered by tests/unit/test_deadline_docs.py.

Docs: spend export --tier filter (#825)

Status: Done (2026-09-20). Chargeback guide documents daari spend export --tier with asr / embed examples. Covered by tests/unit/test_chargeback_docs.py.

Spend export --tier filter (#818)

Status: Done (2026-09-20). daari spend export --tier filters chargeback rows by the ledger tier column (combinable with --key / --team). Covered by tests/unit/test_spend_export.py.

Client guides document /api/show thinking controls (#809)

Status: Done (2026-09-20). ChatGPT Desktop and IntelliJ guides note that /api/show returns thinking controls (low/medium/high, default medium) when the capability is advertised. Covered by tests/unit/test_thinking_controls_docs.py.

Cache invalidate --help documents --token (#826)

Status: Done (2026-09-20). CLI help contract asserts daari cache invalidate --help mentions --token for SSO daemon auth. Covered by tests/unit/test_cache_invalidate_cli_help.py.

Docs: cache invalidate --token (#828)

Status: Done (2026-09-20). cli.md and org-cache guide document cache invalidate --token for SSO/master Bearer. Covered by tests/unit/test_cache_invalidate_docs.py.

Unit tests for ASR frontier_fallback findings (#834)

Status: Done (2026-09-20). Direct unit coverage for asr_frontier_fallback_findings. Covered by tests/unit/test_config_validate.py.

Batch RPM/TPM/RPD + tenant meta on drain (#840)

Status: Done (2026-09-20). Batch drain charges virtual-key RPM/TPM/RPD (and team caps), copies key_id / team_id / cache_scope onto item RequestMeta, and records structured 429 / 403 item failures for rate limit and model allowlist denials. Master / no-auth batches stay open. Covered by tests/unit/test_batches.py.

Helm master key, rate limits, frontier (#842)

Status: Done (2026-09-20). Chart values server.apiKeySecret, rateLimit.*, and frontier.* wire DAARI_SERVER__API_KEY, DAARI_RATE_LIMIT__*, and frontier enable/secret env; NOTES and capacity-helm document multi-replica combos. Covered by tests/unit/test_helm_chart.py.

L1 namespace includes embedding model (#845)

Status: Done (2026-09-20). L1 context_key folds in cache.l1.embedding_model so switching embedders yields clean misses; trim prefers dropping stale-model rows. upgrade.md updated. Covered by tests/unit/test_semantic_cache.py.

MCP virtual-key governance on tools/call (#839)

Status: Done (2026-09-20). MCP tools/call (route + provider execute) applies apply_auth_claims_to_meta so virtual keys carry tier_cap, cache_scope, key_id, and team_id; disallowed models return the same 403 model_not_allowed shape and audit rows as chat; spend and L0 cache scope follow the key. Covered by tests/unit/test_mcp_vk_governance.py.

Embedding L0 honors cache_scope (#841)

Status: Done (2026-09-20). POST /v1/embeddings and Ollama /api/embed apply virtual-key cache_scope / key_id / team_id to embed L0 keys so scoped tenants do not share vectors; daari cache invalidate --key/--team removes those entries. Covered by tests/unit/test_embeddings.py.

CLI models warm preload (#843)

Status: Done (2026-09-20). daari models warm loads configured L3–L5 and the embed model via Ollama generate/embeddings; onboard --warm runs it after pull; doctor optional warm_models hints when the daemon is up but /api/ps is cold. Covered by tests/unit/test_models_warm.py.

Opt-in frontier failover when local pool is down (#846)

Status: Done (2026-09-20). routing.local_pool.frontier_fallback (default false) escalates chat to L6 when every local backend for the chosen tier is unavailable, respecting no_frontier, allowlists, budgets, and PII scrub. Event local_pool_frontier_fallback. Covered by tests/unit/test_local_pool.py.

Local-first POST /v1/audio/speech TTS (#847)

Status: Done (2026-09-20). tts.base_url proxies OpenAI-shaped POST /v1/audio/speech to a local TTS server; unconfigured returns 501 tts_unavailable. Virtual-key allowlists, RPM, deadlines, and disconnect cancel apply; spend ledger uses tier tts. Covered by tests/unit/test_speech.py.

Per-key priority classes on the admission gate (#848)

Status: Done (2026-09-20). Virtual keys/teams accept priority (high|normal|low); the global in-flight gate admits by priority then FIFO. Batch drain acquires at low. Covered by tests/unit/test_rate_limit.py.

Export gateway events as OTLP logs (#849)

Status: Done (2026-09-20). Opt-in observability.otlp_logs emits each log_gateway_event as an OTel LogRecord (event name + attributes) on the same OTLP endpoint as traces/metrics, correlated with the active span when present; fail-open. Covered by tests/unit/test_otel_logs.py.

Docs: MCP tools/call honors X-Daari-Deadline-Ms (#835)

Status: Done (2026-09-20). MCP guide documents tools/call + X-Daari-Deadline-Ms / 504 request_deadline_exceeded; headers contract asserts the same. Covered by tests/unit/test_deadline_docs.py.

CLI reference mentions spend export --tier (#836)

Status: Done (2026-09-20). cli.md lists spend export with --tier and links the chargeback guide. Covered by tests/unit/test_chargeback_docs.py.

Grafana deadline panel covers audio/embed/MCP (#837)

Status: Done (2026-09-20). Overview panel description and metrics docs note deadlines are not chat-only (audio, embeddings, MCP tools/call); panel still scrapes daari_request_deadline_exceeded_total. Covered by tests/unit/test_grafana_dashboard.py.

Skip resolved stall issues in autodev --pick (#857)

Status: Done (2026-09-20). Stall tickets whose referenced PR is no longer open (merged/closed) are skipped by autodev_backlog.py --pick so they cannot block the backlog after conflict repair. Covered by tests/unit/test_autodev_backlog.py.

CLI --priority for admission QoS (#868)

Status: Done (2026-09-21). daari keys create / team-create accept --priority high|normal|low; list shows prio; Postgres VK schema stores priority on keys and teams. Covered by tests/unit/test_keys_priority_cli.py.

Doctor warns when request deadline unset (#867)

Status: Done (2026-09-21). Optional doctor check request_deadline fails when upstream.request_deadline_seconds is unset or <= 0, passes with a positive value. Documented in doctor-health. Covered by tests/unit/test_doctor_deadline.py.

Helm exposes tts.baseUrl (#870)

Status: Done (2026-09-21). Chart tts.baseUrl mounts DAARI_TTS__BASE_URL when set; empty default omits the env. Documented in capacity-helm. Covered by tests/unit/test_helm_chart.py.

Doctor probes TTS reachability (#869)

Status: Done (2026-09-21). Optional doctor check tts probes {tts.base_url}/models when configured; unreachable fails optionally; empty config stays quiet (501 path). Documented in doctor-health. Covered by tests/unit/test_doctor_tts.py.

Docs: virtual-keys --priority (#877)

Status: Done (2026-09-21). Virtual-keys guide documents admission --priority high|normal|low and the list prio column. Covered by priority docs contract tests.

Docs: speech under X-Daari-Deadline-Ms (#871)

Status: Done (2026-09-21). headers.md lists POST /v1/audio/speech among deadline-honoring routes; test_deadline_docs asserts it. Covered by tests/unit/test_deadline_docs.py.

Doctor surfaces local_pool frontier failover (#879)

Status: Done (2026-09-21). Optional doctor check calls local_pool_frontier_fallback_findings and fails when failover is on without frontier.enabled. Documented in doctor-health. Covered by tests/unit/test_doctor_local_pool_failover.py.

Doctor warns otlp_logs without OTEL endpoint (#878)

Status: Done (2026-09-21). Optional doctor check otlp_logs fails when observability.otlp_logs is true without OTEL_EXPORTER_OTLP_ENDPOINT. Documented in doctor-health. Covered by tests/unit/test_doctor_otlp_logs.py.

Docs: otlp_logs in otel-genai guide (#881)

Status: Done (2026-09-21). Contract test asserts otel-genai documents otlp_logs and OTEL_EXPORTER_OTLP_ENDPOINT beside traces/metrics. Covered by tests/unit/test_otel_docs.py.

Docs: capacity-helm documents tts.baseUrl (#880)

Status: Done (2026-09-21). Contract test asserts capacity-helm mentions tts.baseUrl and DAARI_TTS__BASE_URL. Covered by tests/unit/test_capacity_helm_docs.py.

Helm observability.otlpLogs + OTEL endpoint (#888)

Status: Done (2026-09-21). Chart observability.otlpLogs / otlpEndpoint mount DAARI_OBSERVABILITY__OTLP_LOGS and OTEL_EXPORTER_OTLP_ENDPOINT; defaults omit both. Documented in capacity-helm. Covered by tests/unit/test_helm_chart.py.

Helm localPool.frontierFallback (#887)

Status: Done (2026-09-21). Chart localPool.frontierFallback mounts DAARI_ROUTING__LOCAL_POOL__FRONTIER_FALLBACK when true; default omits env. Documented in capacity-helm. Covered by tests/unit/test_helm_chart.py.

Docs: local text-to-speech backend guide (#890)

Status: Done (2026-09-21). New backends/tts.md; overview and clients-and-gateways link it for /v1/audio/speech. Covered by tests/unit/test_tts_docs.py.

TTS disconnect cancelled_requests phase=tts (#889)

Status: Done (2026-09-21). Unit coverage for speech disconnect 499 + daari_cancelled_requests_total{phase="tts"}; metrics-prometheus lists tts. Covered by tests/unit/test_client_disconnect.py and tests/unit/test_metrics_prometheus_docs.py.

Helm tts.model and tts.voice (#900)

Status: Done (2026-09-21). Chart tts.model / tts.voice mount DAARI_TTS__MODEL / DAARI_TTS__VOICE when set; empty defaults omit env. Also wires tts.baseUrl so capacity-helm matches. Covered by tests/unit/test_helm_chart.py.

Doctor scoped_cache_fleet check (#891)

Status: Done (2026-09-21). Optional doctor check fails when multi-replica fleets use disk cache with non-global virtual-key/team cache_scope. Documented in doctor-health. Covered by tests/unit/test_doctor_scoped_cache_fleet.py.

Docs: capacity-helm observability.otlpLogs (#898)

Status: Done (2026-09-21). Contract test asserts capacity-helm mentions otlpLogs, DAARI_OBSERVABILITY__OTLP_LOGS, and OTEL_EXPORTER_OTLP_ENDPOINT. Covered by tests/unit/test_capacity_helm_docs.py.

Docs: capacity-helm localPool.frontierFallback (#897)

Status: Done (2026-09-21). Contract test asserts capacity-helm mentions localPool.frontierFallback and DAARI_ROUTING__LOCAL_POOL__FRONTIER_FALLBACK. Covered by tests/unit/test_capacity_helm_docs.py.

Docs: scoped_cache_fleet in org-cache guide (#899)

Status: Done (2026-09-21). Org-cache guide documents Redis requirement for multi-replica cache_scope and links doctor scoped_cache_fleet. Covered by tests/unit/test_scoped_cache_fleet_docs.py.

Helm tts.baseUrl render contract (#906)

Status: Done (2026-09-21). Helm unit tests assert empty default omits DAARI_TTS__BASE_URL and set tts.baseUrl renders it. Covered by tests/unit/test_helm_chart.py.

Docs: doctor-health scoped_cache_fleet row (#908)

Status: Done (2026-09-21). Contract test asserts the doctor-health scoped_cache_fleet row names cache.backend / Redis and cache_scope. Covered by tests/unit/test_scoped_cache_fleet_docs.py.

Docs: mkdocs nav lists TTS guide (#905)

Status: Done (2026-09-21). Contract test asserts mkdocs.yml lists developer/guides/backends/tts.md beside the ASR guide. Covered by tests/unit/test_tts_docs.py.

Docs: TTS guide names Helm tts.model/voice (#907)

Status: Done (2026-09-21). backends/tts.md names Helm tts.model / tts.voice and DAARI_TTS__MODEL / DAARI_TTS__VOICE. Covered by tests/unit/test_tts_docs.py.

Helm asr.frontierFallback (#914)

Status: Done (2026-09-21). Chart asr.frontierFallback mounts DAARI_ASR__FRONTIER_FALLBACK=true when enabled; default false omits env. Documented in capacity-helm. Covered by tests/unit/test_helm_chart.py.

Helm asr.model (#913)

Status: Done (2026-09-21). Chart asr.model mounts DAARI_ASR__MODEL when set; empty default omits env. Documented in capacity-helm. Covered by tests/unit/test_helm_chart.py.

Docs: ASR guide names Helm asr.baseUrl (#916)

Status: Done (2026-09-21). backends/asr.md names Helm asr.baseUrl and DAARI_ASR__BASE_URL. Covered by tests/unit/test_asr_docs.py.

Docs: cancelled_requests phase includes mcp (#915)

Status: Done (2026-09-21). metrics-prometheus lists mcp among daari_cancelled_requests_total phases. Covered by tests/unit/test_metrics_prometheus_docs.py.

Docs: doctor-health ASR frontier_fallback row (#917)

Status: Done (2026-09-21). Contract test asserts the doctor-health asr row names asr.base_url and frontier_fallback. Covered by tests/unit/test_doctor_health_asr_docs.py.

Docs: capacity-helm asr.model contract (#926)

Status: Done (2026-09-21). Contract test asserts capacity-helm mentions asr.model and DAARI_ASR__MODEL. Covered by tests/unit/test_capacity_helm_docs.py.

Docs: ASR guide names Helm asr.frontierFallback (#925)

Status: Done (2026-09-21). backends/asr.md names Helm asr.frontierFallback and DAARI_ASR__FRONTIER_FALLBACK. Covered by tests/unit/test_asr_docs.py.

Docs: ASR guide names Helm asr.model (#924)

Status: Done (2026-09-21). backends/asr.md names Helm asr.model and DAARI_ASR__MODEL. Covered by tests/unit/test_asr_docs.py.

Request body size cap with early 413 (#933)

Status: Done (2026-09-21). server.max_body_bytes (default 10 MiB) rejects oversized bodies with 413 before buffering; OpenAI/Anthropic error shapes; upload routes use a higher floor; daari_rejects_total{kind="body_too_large"}. Covered by tests/unit/test_body_limit.py and tests/integration/test_gateway_flow.py.

Native TLS and optional mTLS for daari serve (#932)

Status: Done (2026-09-21). server.tls + --tls-cert/--tls-key/--tls-client-ca wire uvicorn HTTPS/mTLS; secret:// key refs materialize to temp PEM files; doctor warns on auth + non-loopback without TLS; Helm tls.enabled + existingSecret; SECURITY.md documents native vs reverse-proxy TLS. Covered by tests/unit/test_server_tls.py, test_doctor_tls.py, test_helm_chart.py.

Auth throttle for invalid API keys (#935)

Status: Done (2026-09-21). Per-IP invalid-key counter (auth.max_failures / window_seconds) returns 429 + Retry-After; Redis when cache.backend=redis, in-process otherwise, fail-open; loopback exempt; auth.throttled audit + daari_rejects_total{kind="auth_throttled"}. Covered by tests/unit/test_auth_throttle.py.

Frontier tool parity — streamed tool-calls, tool_choice, output_format (#934)

Status: Done (2026-09-21). Frontier SSE relays delta.tool_calls / Anthropic tool blocks; tool_choice and output_format/json_schema reach L6 payloads; OpenAI + Anthropic gateways preserve escalated tool turns. Covered by tests/unit/test_frontier_tool_parity.py and tests/integration/test_gateway_flow.py.

CORS allowlist and default security headers (#938)

Status: Done (2026-09-22). server.cors_origins enablelist + OPTIONS 204; baseline X-Content-Type-Options / X-Frame-Options / Referrer-Policy (toggle security_headers); Helm corsOrigins / securityHeaders.enabled; SECURITY.md + web-ui README. Covered by tests/unit/test_cors_security_headers.py.

Lifetime spend caps and temporary budget boosts (#936)

Status: Done (2026-09-22). BudgetWindow accepts lifetime/total (all-time ledger sum, no rollover); daari keys budget-boost / team-budget-boost grant audited auto-expiring increases; daari keys show lists active boosts; expiry audited at enforcement. Covered by tests/unit/test_lifetime_budget_boosts.py.

Passthrough parallel_tool_calls, logit_bias, top_logprobs (#940)

Status: Done (2026-09-22). Declared on ChatCompletionRequest + SamplingParams; forwarded in openai_payload() / cache fingerprint; local unsupported_locally notes. Covered by tests/unit/test_sampling_params.py.

Skip rate-limit body buffer on safe HTTP methods (#939)

Status: Done (2026-09-22). GET/HEAD/OPTIONS skip request.body() (TPM=0); POST/PUT/PATCH/DELETE unchanged. Covered by tests/unit/test_rate_limit.py.

Capacity-helm documents asr.frontierFallback (#927)

Status: Done (2026-09-22). Contract test locks asr.frontierFallback and DAARI_ASR__FRONTIER_FALLBACK in capacity-helm.md. Covered by tests/unit/test_capacity_helm_docs.py.

Status: Done (2026-09-22). backends/asr.md Next links capacity-helm; contract test locks the path. Covered by tests/unit/test_asr_docs.py.

MCP tool and task stats on /v1/daari/stats and web-ui (#941)

Status: Done (2026-09-22). Stats payload includes mcp_tool_calls + mcp_tasks snapshot; web-ui panel; McpTaskStore.snapshot(). Covered by tests/unit/test_stats_mcp.py and packages/web-ui/test/dashboard.test.js.

daari migrate dry-run and policy/store schema skew (#942)

Status: Done (2026-09-22). daari migrate / --dry-run inspects or opens ledger, virtual-keys, audit, responses, and mcp-tasks via existing _migrate paths; policy bundles gain integer schema (POLICY_SCHEMA=1) with unknown majors refused; daari doctor warns on pending migrate (--strict required). No Alembic. Docs: upgrade guide. Covered by tests/unit/test_migrate_schema.py.

Doctor probes integrations.mcp_servers (#963)

Status: Done (2026-09-22). Optional mcp:<id> doctor rows POST a lightweight JSON-RPC initialize to each configured egress URL (Bearer when token set); empty list adds no rows. Covered by tests/unit/test_doctor_mcp_servers.py.

Reuse httpx.AsyncClient for MCP egress (#964)

Status: Done (2026-09-22). McpEgressProvider lazily reuses one httpx.AsyncClient per instance (aclose() for teardown). Covered by tests/unit/test_mcp_egress.py.

Honor and echo X-Request-ID (#965)

Status: Done (2026-09-22). Chat completions resolve sanitized inbound X-Request-ID (or generate one), echo it on the response, and prefer it for spend ledger request_id. Covered by tests/unit/test_request_id.py.

Docs: capacity-helm documents tts.model and tts.voice (#962)

Status: Done (2026-09-22). Doc contract asserts capacity-helm.md names tts.model / tts.voice and DAARI_TTS__MODEL / DAARI_TTS__VOICE. Covered by tests/unit/test_capacity_helm_docs.py.

Drain stuck PRs first; watcher survives label-permission errors (#983)

Status: Done (2026-09-22). apply_sweep logs and skips label edits the PAT cannot perform instead of aborting stall-watch; the dev-cycle prompt drains open autodev/* PRs (update-branch → wait → gh pr merge --squash; resolve DIRTY conflicts) in priority order before picking new work. Covered by test_apply_sweep_survives_label_edit_failure and test_workflow_drains_stuck_prs_before_picking.

Reuse pooled httpx.AsyncClient across upstream hops (#971)

Status: Done (2026-09-22). Ollama, OpenAI-compat, MLX, frontier, embedder, TTS, and ASR reuse a long-lived httpx.AsyncClient with configurable upstream.pool_max_connections / upstream.pool_keepalive_connections; per-request timeouts unchanged. Covered by tests/unit/test_http_pool.py.

Forward X-Request-ID on all upstream hops (#977)

Status: Done (2026-09-22). Middleware binds the resolved id; inject_trace_headers forwards X-Request-ID on Ollama, OpenAI-compat, MLX, frontier, ASR, TTS, embeddings, and MCP egress. Covered by tests/unit/test_request_id.py.

Honor Idempotency-Key on chat and Responses (#714)

Status: Done (2026-09-22). Idempotency-Key on POST /v1/chat/completions and POST /v1/responses replays the original status/body for the same principal + body hash within TTL (default 24h) without a second router call; mismatched body returns 409 idempotency_conflict; in-flight duplicates wait. SQLite default / postgres when configured; daari prune sweeps expired rows. Covered by tests/unit/test_idempotency.py.

SSE keepalive for the entire stream lifetime (#972)

Status: Done (2026-09-22). stream_with_keepalive emits heartbeats between chunks for the full stream (not only TTFT); optional server.stream_idle_timeout_seconds ends hung streams with an in-band error. Covered by tests/unit/test_streaming_keepalive.py.

In-band stream_incomplete + breaker on truncated streams (#973)

Status: Done (2026-09-23). OpenAI and Anthropic streams that die after emitting deltas append a stream_incomplete in-band error before stream end, log stream_incomplete via log_gateway_event, and trip the serving host's circuit breaker; partial streams stay out of L0/L1. Covered by tests/unit/test_stream_incomplete.py.

Pre-first-token host failover for streamed local requests (#974)

Status: Done (2026-09-23). Streaming local requests that fail before the first chunk retry the next healthy host in the same tier (OpenAI and Anthropic paths); post-first-token behavior unchanged; breakers record failure/success. Covered by tests/unit/test_stream_host_failover.py.

Pooled Postgres connections for fleet stores (#975)

Status: Done (2026-09-23). Spend/batches/files/responses share a per-DSN psycopg_pool.ConnectionPool (observability.postgres_pool_min/max, default 1/4); pools close on app shutdown. Covered by tests/unit/test_pg_pool.py.

Echo X-Request-ID on Anthropic, Responses, Ollama, embeddings, audio (#978)

Status: Done (2026-09-23). Middleware echoes X-Request-ID on every response; Anthropic/Responses/Ollama bind the id on RequestMeta; embeddings/ASR/TTS spend rows carry the same request_id. Covered by tests/unit/test_request_id.py.

MCP tools/list ttlMs and cacheScope hints (#979)

Status: Done (2026-09-23). tools/list (JSON-RPC + legacy) emits _meta with configurable ttlMs (integrations.mcp_list_cache.ttl_ms, default 60000) and cacheScope (public unfiltered / private when ACL-filtered). Covered by tests/unit/test_mcp_server.py.

RetryPolicy on ASR, TTS, and embedding HTTP (#980)

Status: Done (2026-09-23). Transcriptions, speech, and Ollama embed HTTP go through run_upstream with RetryPolicy.from_settings(upstream.retry); transient 5xx/connect retry, non-retryable 4xx fail fast. Covered by tests/unit/test_modality_retry.py.

OpenAI input_audio content blocks (#981)

Status: Done (2026-09-23). extract_audio keeps input_audio parts on Message.audio; L6 OpenAI payloads rebuild them; when asr.base_url is set, inline clips are transcribed into the local-tier prompt. Docs: asr.md. Covered by tests/unit/test_input_audio.py.

Docs: server.cors_origins and security_headers (#966)

Status: Done (2026-09-23). docs/developer/reference/config.md documents server.cors_origins and server.security_headers. Covered by tests/unit/test_cors_security_headers_docs.py.

Responses API input_audio (#997)

Status: Done (2026-09-23). Responses message content extracts input_audio onto Message.audio and runs the same ASR inject path as chat. Docs: asr.md. Covered by tests/unit/test_responses_input_audio.py.

Accept service_tier on OpenAI and Anthropic HTTP ingress (#1005)

Status: Done (2026-09-23). ChatCompletionRequest and AnthropicRequest declare service_tier so it survives validation and reaches openai_payload() / to_anthropic_payload(). Covered by tests/unit/test_service_tier.py.

Non-stream frontier OpenAI includes tools (#1006)

Status: Done (2026-09-23). _openai_payload forwards tools on both stream and non-stream L6 OpenAI calls so agent loops with stream: false get tool schemas. Covered by tests/unit/test_frontier_tool_parity.py and tests/integration/test_gateway_flow.py.

Preserve json_schema name and strict (#1008)

Status: Done (2026-09-23). SamplingParams keeps client name/strict and forwards them on openai-kind response_format; Ollama format stays schema-only. Covered by tests/unit/test_sampling_params.py.

Ollama facade think/format/keep_alive (#1011)

Status: Done (2026-09-23). Facade chat/generate declare and forward think, format, and keep_alive onto local Ollama payloads. Docs: chatgpt-desktop.md. Covered by tests/integration/test_ollama_compat.py.

Doctor CORS web-ui origin advisory (#999)

Status: Done (2026-09-23). Optional cors_origins doctor check tips empty allowlists and warns when the web-ui origin is missing. Covered by tests/unit/test_doctor_cors_origins.py.

Docs: server body/TLS and auth throttle rows (#998)

Status: Done (2026-09-23). config.md documents server.max_body_bytes, server.tls.*, and auth.* throttle knobs without wiping the rpd footnote. Covered by tests/unit/test_config_server_tls_auth_docs.py.

Forward remaining OpenAI chat params (#1007)

Status: Done (2026-09-23). ChatCompletionRequest / SamplingParams declare store, metadata, prediction, modalities, audio, verbosity, web_search_options; openai_payload() also emits logprobs / n; local tiers warn via always-on x-daari-warning (no X-Daari-Meta required). Covered by tests/unit/test_remaining_openai_chat_params.py.

Anthropic thinking, metadata, and top_k parity (#1009)

Status: Done (2026-09-23). /v1/messages accepts thinking / metadata; to_anthropic_payload forwards them plus top_k; thinking budgets map to Ollama think via reasoning_effort. Docs: claude-code.md. Covered by tests/unit/test_anthropic_thinking_parity.py.

Map Responses-native sampling shapes (#1012)

Status: Done (2026-09-23). from_responses_body maps reasoning.effort, text.format (json_object / json_schema with name/strict), tool_choice, parallel_tool_calls, service_tier; logs responses_truncation_ignored. Docs: clients-and-gateways.md. Covered by tests/unit/test_responses_sampling.py and tests/integration/test_responses_api.py.

Default dropped-parameter visibility (#1013)

Status: Done (2026-09-23). Local tiers set daari_meta.dropped_params and emit always-on x-daari-dropped-params (plus dropped_params log event); stream sets the header via StreamOutcome when the served tier is not L6. Docs: headers.md. Covered by tests/unit/test_dropped_params.py.

MCP empty resources/list and prompts/list (#1014)

Status: Done (2026-09-23). initialize advertises empty resources / prompts capabilities; list methods return []. Docs: mcp.md. Covered by tests/unit/test_mcp_server.py.

Hermetic stream host failover ceilings (#1015)

Status: Done (2026-09-23). Benchmark pins pre-first-token failover under STREAM_FAILOVER_CEILING_S (0.500s) and happy-path under STREAM_HAPPY_PATH_CEILING_S (0.200s). Covered by tests/benchmark/test_hermetic_paths.py.

Integration coverage for chat input_audio L6 (#1000)

Status: Done (2026-09-24). Gateway integration pin: chat input_audio escalates to mocked L6 with OpenAI payload preserving the block; optional asr.base_url path injects the transcript into the local-tier prompt. Covered by tests/integration/test_gateway_flow.py.

L1 semantic embed includes audio tokens (#1001)

Status: Done (2026-09-24). extract_embed_text appends ContentAudio.cache_token() when messages carry audio so different clips with the same caption cannot share an L1 hit; text-only embed strings unchanged. Docs: caching-and-trust.md. Covered by tests/unit/test_semantic_cache.py.

L1 semantic embed includes image tokens (#1029)

Status: Done (2026-09-24). extract_embed_text appends ContentImage.cache_token() when messages carry images (parity with audio

1001); text-only and audio-only embed strings unchanged. Docs:

caching-and-trust.md. Covered by tests/unit/test_semantic_cache.py.

Agent prefix L1 folds multimodal tokens (#1030)

Status: Done (2026-09-24). agent_prefix_text shares _message_embed_chunk with extract_embed_text so image/audio cache tokens differentiate agent-turn L1 prefixes; text-only prefixes unchanged. Covered by tests/unit/test_semantic_cache.py.

Ollama facade logprobs / top_logprobs (#1031)

Status: Done (2026-09-24). Facade chat/generate accept top-level logprobs and top_logprobs, map them onto SamplingParams (local dropped-params / L6 OpenAI forward). Docs: chatgpt-desktop.md. Covered by tests/unit/test_sampling_params.py and tests/integration/test_ollama_compat.py.

Integration coverage for x-daari-dropped-params (#1032)

Status: Done (2026-09-24). Gateway integration pin: local chat sets x-daari-dropped-params for unsupported knobs; L6 omits the header. Covered by tests/integration/test_gateway_flow.py.

Integration pin: distinct audio clips miss L1 (#1033)

Status: Done (2026-09-24). Router L1 integration (mocked embedder): same caption with clip-B misses after clip-A is stored; caption+clip-A still hits. Covered by tests/integration/test_l1_semantic_cache.py.

Integration pin: distinct images miss L1 (#1039)

Status: Done (2026-09-24). Router L1 integration (mocked embedder): same caption with image-B misses after image-A is stored; caption+image-A still hits. Covered by tests/integration/test_l1_semantic_cache.py.

ExactCache L0 multimodal key differentiation (#1040)

Status: Done (2026-09-24). normalize_messages keeps audio tokens (parity with images) so cache_key differs for same caption with distinct clips; text-only keys still match a no-media request. Covered by tests/unit/test_exact_multimodal_keys.py.

Integration pin: facade generate logprobs (#1041)

Status: Done (2026-09-24). /api/generate maps logprobs / top_logprobs onto SamplingParams and local dropped-params (parity with chat #1031); malformed top_logprobs ignored. Covered by tests/integration/test_ollama_compat.py.

Agent-prefix L1 misses when multimodal tokens differ (#1042)

Status: Done (2026-09-24). Router agent-prefix L1 integration: same tools and suffix with image-B miss after image-A is stored; image-A replay (fresh tool_call id) still hits. Covered by tests/integration/test_agent_prefix_l1.py.

Docs: agent-prefix multimodal fold in caching-and-trust (#1043)

Status: Done (2026-09-24). Trust concept doc states agent-prefix L1 shares _message_embed_chunk with embed text for image/audio tokens. Covered by tests/unit/test_caching_trust_docs.py.

Agent-prefix L1 misses when audio clips differ (#1049)

Status: Done (2026-09-24). Router agent-prefix L1 integration: same tools and suffix with clip-B miss after clip-A is stored; clip-A replay (fresh tool_call id) still hits (parity with image pin #1042). Covered by tests/integration/test_agent_prefix_l1.py.

OpenAI POST /v1/moderations L6 passthrough (#1050)

Status: Done (2026-09-24). POST /v1/moderations forwards OpenAI-shaped {input, model?} to the configured frontier when enabled+keyed; returns 501 when frontier is off (never invents scores). Docs: http-api.md. Covered by tests/unit/test_moderations.py.

Cohere-compatible POST /v1/rerank (#1051)

Status: Done (2026-09-24). POST /v1/rerank accepts Cohere/LiteLLM-shaped query + documents (+ optional top_n/model) and forwards to configured frontier; 501 when frontier is off. Docs: http-api.md. Covered by tests/unit/test_rerank.py.

Key governance + spend for moderations/rerank (#1058)

Status: Done (2026-09-24). /v1/moderations and /v1/rerank honor no_frontier / local tier_cap (403 frontier_not_allowed), model allowlists (reject_disallowed_model), and write usage + spend ledger rows attributed to key/team. Covered by tests/unit/test_moderations.py and tests/unit/test_rerank.py.

Guardrails on embeddings/audio/moderations/rerank (#1059)

Status: Done (2026-09-24). Shared apply_endpoint_input_policy / apply_endpoint_output_policy apply chat guardrails to embeddings, TTS, ASR (prompt + transcript; frontier fallback honors no_frontier), moderations, and rerank. Covered by tests/unit/test_guardrails_endpoints.py.

Retry + slot failover + region_pin for moderations/rerank (#1060)

Status: Done (2026-09-24). Moderations and rerank use run_upstream with provider retry policy, iterate frontier slots in chat order on failure, and filter by virtual-key region_pin (400 when none match). Covered by tests/unit/test_moderations.py and tests/unit/test_rerank.py.

OpenRouter region_pin → us/eu hosts (#1052)

Status: Done (2026-09-24). region_pin us/eu keeps global OpenRouter slots eligible and rewrites L6 base URL to https://{us|eu}.openrouter.ai/api/v1 for the attempt (restored after). Docs: compare-openrouter.md. Covered by tests/unit/test_openrouter_region.py.

ASR frontier fallback honors no_frontier and region_pin (#1061)

Status: Done (2026-09-24). ASR frontier fallback reuses resolve_l6_targets so no_frontier keys never upload audio, region_pin filters slots (400 when none match), and eligible slots fail over in pool order. Covered by tests/unit/test_audio_transcriptions.py.

Response cost headers on embeddings and audio (#1062)

Status: Done (2026-09-24). Embeddings, speech, transcriptions/translations, moderations, and rerank emit the same x-daari-response-cost* header set as chat, matching spend_ledger amounts. Covered by tests/unit/test_cost_headers.py, test_embeddings.py, test_speech.py, test_audio_transcriptions.py, test_moderations.py, and test_rerank.py.

OpenAI images/generations L6 passthrough (#1064)

Status: Done (2026-09-24). POST /v1/images/generations is a governed L6 passthrough (no_frontier, model allowlist, region_pin, run_upstream retry/slot failover, usage+spend rows). Covered by tests/unit/test_images.py.

Idempotency-Key on embeddings, audio, moderations, and rerank (#1065)

Status: Done (2026-09-25). Idempotency-Key replays embeddings, TTS (binary), ASR/translations (multipart form+file digest), moderations, and rerank without a second upstream call; same key + different body → 409 idempotency_conflict. Docs: headers.md, config.md. Covered by tests/unit/test_idempotency.py.

Honor or declare Ollama 0.34 tool_search and response_compaction (#1066)

Status: Done (2026-09-25). Facade /api/chat and /api/generate forward tool_search / response_compaction on local Ollama hops; cache and non-Ollama tiers declare them via daari_meta.dropped_params. Docs: chatgpt-desktop.md. Covered by tests/integration/test_ollama_compat.py.

Week/weekly/rpw budget duration aliases (#1067)

Status: Done (2026-09-25). normalize_duration accepts week / weekly / rpw / 1w as ISO calendar weeks (period_id like 2026-W38); header label 1w; CLI --window week=10 persists. Docs: budgets-frontier.md. Covered by tests/unit/test_budget_windows.py, test_budget_rollover.py, test_virtual_keys.py.

ExactCache L0 router pin for distinct audio clips (#1053)

Status: Done (2026-09-25). Router integration: L1 off + ExactCache on; caption+clip-A stores L0, caption+clip-B misses, clip-A hits L0. Covered by tests/integration/test_l1_semantic_cache.py.

Integration pins for moderations and rerank (#1068)

Status: Done (2026-09-25). Hermetic ASGI coverage for POST /v1/moderations and POST /v1/rerank: OpenAPI path registration, auth middleware on (401 without key / 200 mocked happy path), and frontier-disabled / no-key → 501 not_implemented. Covered by tests/integration/test_moderations_rerank.py.

Idempotency-Key on images/generations (#1082)

Status: Done (2026-09-25). Idempotency-Key replays POST /v1/images/generations without a second upstream call; same key + different body → 409 idempotency_conflict. Docs: headers.md, config.md. Covered by tests/unit/test_idempotency.py.

Guardrails on images/generations (#1083)

Status: Done (2026-09-25). Input guardrails run on the images prompt before upstream (block → 400 guardrail_blocked; redact mutates prompt). Covered by tests/unit/test_guardrails_endpoints.py.

Response cost headers on images/generations (#1084)

Status: Done (2026-09-25). POST /v1/images/generations emits the same x-daari-response-cost* / tier header set as other modality routes, matching spend_ledger. Docs: headers.md. Covered by tests/unit/test_images.py.

Integration pins for images/generations (#1081)

Status: Done (2026-09-25). Hermetic ASGI coverage for POST /v1/images/generations: OpenAPI path, auth middleware on (401 without key / 200 mocked happy path), frontier-disabled / no-key → 501. Covered by tests/integration/test_images.py.

Doctor probe for images/generations (#1092)

Status: Done (2026-09-25). When frontier.enabled and a key resolves, daari doctor dry-checks OpenAPI for POST /v1/images/generations; frontier on without a key warns that the route returns 501. Docs: doctor-health.md. Covered by tests/unit/test_doctor_images_generations.py, test_doctor_health_images_docs.py.

Images edits/variations surface honesty (#1090)

Status: Done (2026-09-25). OpenAPI audit: only POST /v1/images/generations exists (no edits/variations routes). Docs state edits/variations are not supported. Covered by tests/unit/test_images_surface_docs.py.

Images cost headers in shared suite (#1091)

Status: Done (2026-09-25). Shared tests/unit/test_cost_headers.py modality matrix pins tier=images / zero reported cost for POST /v1/images/generations, matching the route helper.

Images edits L6 passthrough (#1097)

Status: Done (2026-09-25). POST /v1/images/edits is a governed multipart L6 passthrough (frontier gate, allowlists, region_pin, guardrails on prompt, spend/cost headers). Binary multipart bodies no longer crash rate-limit JSON parse. Docs: clients-and-gateways.md. Covered by tests/unit/test_images_edits.py, test_images_surface_docs.py.

SSO bearers control-plane only (#1103)

Status: Done (2026-09-25). Verified SSO access tokens are accepted only on /v1/daari/* and /v1/org-learning/*; inference routes return 401 sso_key_required and audit auth.sso_key_required. Docs note in auth-and-keys.md. Covered by tests/integration/test_oidc_sso_gateway.py::test_sso_bearer_rejected_on_inference_routes.

Images variations L6 passthrough (#1098)

Status: Done (2026-09-25). POST /v1/images/variations is a governed multipart L6 passthrough (frontier gate, allowlists, region_pin, spend/cost headers). Docs: clients-and-gateways.md + regenerated http-api.md. Covered by tests/unit/test_images_variations.py, test_images_surface_docs.py.

Endpoint-family RPM/TPM on virtual keys (#1099)

Status: Done (2026-09-25). Virtual-key metadata rate_families (and daari keys create --rate-family) adds per-modality RPM/TPM counters so images cannot starve chat on a shared key. Docs: auth-and-keys + config.md. Covered by tests/unit/test_rate_families.py.

Coordinated graceful shutdown (#1104)

Status: Done (2026-09-25). SIGTERM/SIGINT flips /ready to 503 shutting_down (liveness /health stays 200), admission drain rejects new in-flight arrivals with 503 + Retry-After while queued/admitted work finishes, daari serve --graceful-timeout / Helm gracefulTimeoutSeconds wire uvicorn timeout_graceful_shutdown, and lifespan awaits budget-alert tasks before closing pools. Docs: upgrade.md + capacity-helm.md. Covered by tests/unit/test_graceful_shutdown.py, tests/integration/test_gateway_flow.py::test_shutdown_drain_ready_flip_inflight_and_reject.

Cache-read/cache-write token dimensions (#1105)

Status: Done (2026-09-26). Anthropic cache_read_input_tokens / cache_creation_input_tokens land on DaariMeta and feed cost_usd(cache_write_tokens=…). OTel emits gen_ai.token.type cache_read / cache_creation plus span attrs. Usage + spend ledgers persist both dims (sqlite+postgres migrate); spend export includes cache_write_tokens. Covered by tests/unit/test_cache_token_dimensions.py, tests/unit/test_otel_genai.py::test_cache_read_and_creation_token_dims.

Per-modality token/spend metrics (#1106)

Status: Done (2026-09-26). daari_tokens_total{modality,tier,direction} plus additive modality labels on daari_requests_total; images / moderations / rerank call metrics.record; OTel gen_ai.operation.name follows modality; Grafana panels for tokens and requests by modality. Covered by tests/unit/test_modality_metrics.py, tests/unit/test_otel_genai.py::test_operation_name_reflects_non_chat_modality.

Request-time team selection with membership (#1107)

Status: Done (2026-09-26). Optional X-Daari-Team selects among teams the authenticated subject is entitled to (team_members table, sqlite+postgres); pinned keys require metadata.allow_team_override; non-members get 403 + audit. SSO mint/refresh re-syncs entitlements; CLI daari keys team-members. Covered by tests/unit/test_team_selection.py.

Shared USD budget per named model_group (#1109)

Status: Done (2026-09-26). Keys/teams attach model_group_budgets (metadata) keyed by settings.model_groups; org-wide L6 spend for matching models shares one window; gateway 402 / soft budget_warning before escalate; report model_group_spend + spend export model_group column. Covered by tests/unit/test_model_group_budgets.py.

Doctor probes for moderations and rerank (#1110)

Status: Done (2026-09-26). daari doctor dry-checks OpenAPI for POST /v1/moderations and POST /v1/rerank when frontier can escalate (skip/501 tip when disabled or missing key), mirroring images. Covered by tests/unit/test_doctor_moderations_rerank.py.

Config live-vs-file ownership honesty (#1111)

Status: Done (2026-09-26). GET /v1/daari/config returns per-field ownership (source/editable/diverged/file_value); PATCH without persist returns a runtime-only warning + audit runtime_only; persist still returns persisted_to. Covered by tests/unit/test_config_ownership.py.

Pre-auth request-header allow/block policy (#1112)

Status: Done (2026-09-26). server.header_policy (default off) screens required headers, deny exact/regex rules, and optional allowlists before require_api_key; open probes stay exempt; denies emit header_policy_error + log_gateway_event / audit. Doctor warns when enabled but empty/malformed. Covered by tests/unit/test_header_policy.py and tests/unit/test_doctor_header_policy.py.

Team-level model_max_budget with key overrides (#1113)

Status: Done (2026-09-26). Teams/keys accept model_max_budget {model_or_pattern: usd|window} (bare float = daily); key overlays team map; pre-check before L6 soft/hard via soft_budget_ratio; report exposes team_model_spend. Covered by tests/unit/test_model_max_budget.py.

Anthropic-native moderations ingress (#1100)

Status: Done (2026-09-26). POST /v1/messages/moderations accepts OpenAI input or Anthropic messages bodies and reuses L6 moderations governance (501 when frontier off / no key). Rate family maps to moderations. Covered by tests/unit/test_anthropic_moderations.py.

Integration pins for images edits (#1101)

Status: Done (2026-09-26). Hermetic ASGI pins for POST /v1/images/edits: OpenAPI path, auth middleware (401 without key / 200 mocked), frontier-disabled → 501. Covered by tests/integration/test_images_edits.py.

Frontier model-aware parameter compatibility (#1129)

Status: Done (2026-09-26). Per-model frontier param-compat table (longest-prefix lookup) strips unsupported sampler knobs for gpt-6-astra, floors reasoning_effort=none → minimal, and warns + logs frontier.tools_transport when tools hit chat completions. Covered by tests/unit/test_frontier_param_compat.py.

Subject erasure across stores (#1130)

Status: Done (2026-09-26). daari erase --key|--team|--user [--dry-run|--yes] sweeps spend, usage, request log, responses, files, batches, idempotency, and scoped caches; audit appends compliance.erase (hash chain intact). Guide: docs/developer/guides/operations/erasure.md. Covered by tests/unit/test_erasure.py.

Full-state backup/restore CLI (#1131)

Status: Done (2026-09-26). daari backup create|restore archives durable sqlite/JSONL/dir stores with a versioned manifest; postgres backends listed with pg_dump commands; doctor hints when no recent backup; guide docs/developer/guides/operations/backup-restore.md. Covered by tests/unit/test_backup_restore.py.

Spend user_id + team-member chargeback (#1132)

Status: Done (2026-09-26). Spend rows gain nullable user_id (sqlite + postgres migrate); daari spend export --user filters; daari spend report --team T --by-user rolls member×spend. Guide: docs/developer/guides/observability/chargeback.md. Covered by tests/unit/test_spend_user_attribution.py.

Responses reasoning items across turns (#1133)

Status: Done (2026-09-26). /v1/responses accepts, stores, and replays type: reasoning items on previous_response_id chains; output emits reasoning when InternalResponse.reasoning_content is set; include=[reasoning.encrypted_content] allowlisted. Covered by tests/unit/test_responses_reasoning.py and tests/integration/test_responses_api.py.

Responses reject hosted tool types (#1135)

Status: Done (2026-09-26). POST /v1/responses returns 400 for non-function tools (web_search, mcp, …) with an explicit allowlist; docs note in clients-and-gateways.md. Covered by tests/unit/test_responses_hosted_tools.py.

Batches + L0/L1 cache in prune_all (#1136)

Status: Done (2026-09-27). observability.retention.batches_days (default 0) and cache_prune fold BatchStore / PostgresBatchStore and L0/L1 prune() into prune_all. Dry-run counts without deleting. Guide: docs/developer/guides/observability/traces-stats.md. Covered by tests/unit/test_retention.py.

Responses prompt_cache_key / retention on L6 (#1137)

Status: Done (2026-09-27). ResponsesRequest declares prompt_cache_key, prompt_cache_retention, and prompt_cache_options; they reach L6 via SamplingParams.openai_payload() and are named in dropped_params on local tiers. Docs note in clients-and-gateways.md. Covered by tests/unit/test_responses_prompt_cache.py.

Policy-sync drift hash and policy-status (#1138)

Status: Done (2026-09-27). Successful policy apply records policy_hash, applied_at, and redacted source_url (in-memory + ~/.daari/policy-sync-state.json). daari enterprise policy-status prints them and exits 1 when configured but never applied. Covered by tests/unit/test_policy_status.py.

Responses stream resume via starting_after (#1139)

Status: Done (2026-09-27). GET /v1/responses/{id}?stream=true replays terminal stored output as Responses SSE with sequence_number; starting_after skips earlier events; non-terminal background returns 409; plain GET stays JSON. Covered by tests/unit/test_responses_stream_resume.py.

Responses create-stream sequence_number (#1151)

Status: Done (2026-09-27). POST /v1/responses stream events include contiguous sequence_number (keepalives excluded); resume remains a synthesized 0-based replay. Covered by tests/unit/test_responses_stream_resume.py.

Responses Last-Event-ID stream resume (#1152)

Status: Done (2026-09-27). Resume SSE frames emit id: <seq>; Last-Event-ID acts as starting_after when the query is omitted (query wins; invalid/negative ignored). Covered by tests/unit/test_responses_stream_resume.py.

Responses compact endpoint (#1153)

Status: Done (2026-09-27). POST /v1/responses/compact summarizes via the router from previous_response_id / input, stores a shorter conversation, and documents the route. Covered by tests/unit/test_responses_compact.py.

Anthropic hoist only on local Ollama (#1154)

Status: Done (2026-09-27). Trailing system hoist moved from the Anthropic gateway into OllamaExecutor._payload so Anthropic/frontier egress preserves mid-conversation system turns. Covered by tests/unit/test_anthropic_hoist.py.

Responses stream resume http-api docs (#1155)

Status: Done (2026-09-27). http-api.md documents GET /v1/responses/{id} query params stream / starting_after and 409 for non-terminal status. Covered by tests/unit/test_responses_stream_resume_docs.py.

OpenAPI + doctor for Responses stream resume (#1161)

Status: Done (2026-09-27). OpenAPI documents stream / starting_after descriptions and 409 on GET responses; doctor dry-check pins them. Covered by tests/unit/test_doctor_responses_stream_resume.py.

Doctor tip for enterprise policy-status (#1163)

Status: Done (2026-09-27). Optional policy_status doctor check warns when policy_sync_url is set but never applied; docs mention it. Covered by tests/unit/test_doctor_policy_status.py.

Responses stream resume Last-Event-ID in http-api (#1162)

Status: Done (2026-09-27). http-api.md resume section documents Last-Event-ID and sequence_number. Covered by tests/unit/test_responses_stream_resume_docs.py.

Responses compact/create govern like chat (#1169)

Status: Done (2026-09-27). Compact and POST /v1/responses share RequestMeta auth claims, model_max_budget 402, and rate-family chat bucketing for /v1/responses/... subpaths. Covered by tests/unit/test_responses_compact.py, tests/unit/test_model_max_budget.py, tests/unit/test_rate_families.py, and tests/integration/test_responses_api.py.

Erasure reaches Redis/Postgres/user_id/traces (#1170)

Status: Done (2026-09-27). Subject erase covers spend user_id (and legacy client_id), Redis L0/L1, Postgres batches, and traces; cache dry-run reports candidates or unknown. Covered by tests/unit/test_erasure.py and docs/developer/guides/operations/erasure.md.

OTel gen_ai.conversation.id from session (#1175)

Status: Done (2026-09-27). export_trace stamps gen_ai.conversation.id from RequestMeta.session_id (X-Daari-Session) when set; never synthesizes. Covered by tests/unit/test_otel_genai.py and tests/unit/test_otel_docs.py.

Backup catalog honesty + restore interlock (#1171)

Status: Done (2026-09-27). PG batches/files catalogued as external with pg_dump hints; rotated request logs archived; restore refuses when /health succeeds unless --i-know-server-is-stopped. Covered by tests/unit/test_backup_restore.py.

Spend user_id on modalities + Responses (#1172)

Status: Done (2026-09-27). Images/embeddings/moderations/speech/ transcriptions/rerank binders pass request user into spend user_id; ResponsesRequest/CompactRequest accept user on RequestMeta. Covered by tests/unit/test_spend_user_attribution.py.

Config-driven frontier param compat + stream warnings (#1173)

Status: Done (2026-09-27). frontier.param_compat merges over the builtin table; Anthropic egress applies the strip; streamed L6 attaches dropped-param warnings to daari_meta / stream headers. Covered by tests/unit/test_frontier_param_compat.py.

Optional encrypted backup archives (#1176)

Status: Done (2026-09-27). backup create --encrypt openssl|age wraps the tarball via host CLI; restore decrypts with passphrase/identity env or flags; doctor hints when recent backups are all plaintext. Covered by tests/unit/test_backup_restore.py.

Embed Postgres dumps into backup archive (#1177)

Status: Done (2026-09-28). When pg_dump is on PATH, postgres-backed stores are dumped into stores/pg/*.sql and marked backend=embedded with size/hash; missing/failed dumps stay external unless --require-pg-dump. Restore documents extract paths and applies dumps with --restore-pg + psql. Covered by tests/unit/test_backup_restore.py.

Helm sessionAffinity + policySync knobs (#1178)

Status: Done (2026-09-28). Chart values expose routing.sessionAffinity (+ TTL) and enterprise.policySync (url + signingSecret); Deployment emits DAARI_ROUTING__SESSION_AFFINITY* / DAARI_ENTERPRISE__POLICY_SYNC_URL / CONFIG_SIGNING_SECRET. NOTES + capacity-helm document them. Covered by tests/unit/test_helm_chart.py and tests/unit/test_capacity_helm_docs.py.

Hermetic ceilings for Responses compact + stream resume (#1179)

Status: Done (2026-09-28). Benchmark suite pins SQLite ResponseStore compact put+get and stream append + starting_after resume ceilings in tests/benchmark/test_hermetic_paths.py (still skipped by default CI).

Responses stream resume starting_after gateway pin (#1164)

Status: Done (2026-09-28). Hermetic gateway ASGI pin in tests/integration/test_responses_api.py for GET ?stream=true&starting_after= skip semantics and 409 on non-terminal; unit coverage also asserts 409 with a cursor. Covered by those tests.

Keep committed config.md in sync with Settings (#1165)

Status: Done (2026-09-28). Regenerated docs/developer/reference/config.md from render_config_reference(); hermetic equality test fails on drift. Non-Settings rpd / rate_families footnotes live in the generator after the table. Covered by tests/unit/test_gen_reference.py.

Gateway ASGI pin for header_policy deny (#1197)

Status: Done (2026-09-28). Hermetic gateway pin asserts denied chat returns header_policy_error and /health stays exempt when policy is enabled. Covered by tests/integration/test_header_policy_gateway.py.

CLI docs for backup --encrypt and --restore-pg (#1193)

Status: Done (2026-09-28). cli.md documents backup create --encrypt openssl/age, --require-pg-dump, restore decrypt env, and --restore-pg, with a pointer to the backup-restore guide. Covered by tests/unit/test_backup_cli_docs.py.

Docs: operator guide for server.header_policy (#1194)

Status: Done (2026-09-28). Auth-and-keys guide documents server.header_policy knobs, header_policy_error deny shape, and open-probe exemptions. Covered by tests/unit/test_header_policy_docs.py.

Stall-watch survives issue write failures (#1207)

Status: Done (2026-09-28). apply_resolved_stalls and apply_sweep route every gh issue side effect through _issue_write, which logs a warning: and continues when the token lacks Issues write instead of aborting the stall-watch job. AUTOMATION.md lists Issues among the AUTODEV_GH_TOKEN permissions. Covered by tests/unit/test_autodev_pr_watch.py.

Docs-site strict build green (#1208)

Status: Done (2026-09-28). mkdocs.yml uses pymdownx.slugs.slugify so GitHub-style anchors (#providers--fallback) resolve on the site; links to repo files outside docs/ (SECURITY.md, LICENSE, workflows, web-ui README) and to bare directories now use GitHub URLs or a concrete page. mkdocs build --strict passes with zero warnings.

Per-key/team MCP server allowlists (#1201)

Status: Done (2026-09-28). McpServerPolicy layers integrations.mcp_policy.servers → team → metadata.mcp.servers (deny wins, key over team). Denied egress servers are filtered from tools/list, blocked on tools/call with data.server in the existing denial shape, audited with the server id, and never POSTed by the egress client. CLI --mcp-server-allow/--mcp-server-deny, keys show, and daari doctor (mcp_server_policy) surface the grants. Covered by tests/unit/test_mcp_policy.py, tests/unit/test_mcp_egress.py, tests/integration/test_mcp_governance.py, tests/unit/test_virtual_keys.py.

MCP tools/call metering + mcp rate family (#1202)

Status: Done (2026-09-28). Non-route MCP tools/call (provider/egress included) writes usage + spend rows attributed to key/team; /mcp maps to a dedicated mcp rate-limit family so ceilings do not consume chat. Docs: MCP guide metering section + auth-and-keys family list. Covered by tests/unit/test_mcp_vk_governance.py, tests/unit/test_rate_families.py.

MCP egress resilience — spans, retry, circuit breaker (#1203)

Status: Done (2026-09-29). MCP egress POSTs emit an OTel client span (mcp.tools/call / mcp.tools/list) with server id and tool name, retry transient failures via run_upstream/RetryPolicy (upstream.retry), and trip a per-server CircuitBreaker (integrations.mcp_egress.failure_threshold / cooldown_seconds) with fail-fast + daari_mcp_egress_total circuit labels. Covered by tests/unit/test_mcp_egress_resilience.py.

MCP server/discover + per-request protocol _meta (#1213)

Status: Done (2026-09-29). POST /mcp implements server/discover (supportedVersions, capabilities, serverInfo) and honors params._meta["io.modelcontextprotocol/protocolVersion"] without a prior initialize. Unsupported versions return JSON-RPC -32022 with data.supported / data.requested. Legacy initialize unchanged. Covered by tests/unit/test_mcp_server.py.

MCP egress SSRF / private-network guards (#1214)

Status: Done (2026-09-29). Before MCP egress POST, URLs are resolved and checked (http/https only; deny loopback, RFC1918, link-local, metadata). integrations.mcp_egress.allow_private_networks opts in for lab/docker. Doctor fails closed without probing blocked URLs. Covered by tests/unit/test_egress_url.py, tests/unit/test_mcp_egress.py, tests/unit/test_doctor_mcp_servers.py.

GTM: scoreboard refreshed, v1.4.0 note, honest copy (#1222)

Status: Done (2026-09-29). First scoreboard refresh since the 2026-08-26 seed: 3 stars, 4 unique viewers / 14d, PyPI 135 / 30d. DEFAULT_SHIPPED / DEFAULT_WAITING now reflect Apache 2.0, v1.4.0, green docs site, and the four launch drafts. docs/gtm/drafts/shipping-note.md regenerated for 1.4.0; the generator no longer leaves a stray ** in the headline. Welcome Discussion #238 retitled to Apache 2.0 with the Homebrew tap command. Covered by tests/unit/test_gtm_shipping_note.py, tests/unit/test_gtm_scoreboard.py.

GTM measure on a schedule (#1223)

Status: Done (2026-09-29). .github/workflows/gtm-scoreboard.yml runs weekly (Mon 14:00 UTC): refreshes docs/gtm/SCOREBOARD.md + REPORT.md, opens an auto-merging docs(gtm): scoreboard <date> PR when they changed, and files a gtm,regression issue on a 14-day viewer drought (deduped by title). Shell only, AUTODEV_GH_TOKEN fallback to GITHUB_TOKEN. PLAN.md Measure row and AUTOMATION.md components table point at it.

Scoreboard names the PAT permission on 403 (#1226)

Status: Done (2026-09-29). scripts/gtm_scoreboard.py catches a failed gh api call and exits 1 with one line naming the endpoint and the fix (Administration: read on the fine-grained PAT). AUTOMATION.md lists it next to Issues write. Covered by tests/unit/test_gtm_scoreboard.py.

Refreshable secret:// resolution (#1204)

Status: Done (2026-09-29). Non-oauth secret:// refs (env-file/exec/keychain) resolve to RefreshableSecret when secrets.refresh_ttl_seconds > 0 (default 300); env-file also re-resolves on source mtime change. TTL 0 keeps boot-only plain strings. Boot failures stay fatal. Doctor hints when refresh is disabled without oauth refs. Docs: auth-and-keys rotation runbook. Covered by tests/unit/test_secret_refs_refresh.py, tests/test_doctor.py.

Helm header_policy knobs + MCP deny pin (#1205)

Status: Done (2026-09-29). Chart server.headerPolicy (disabled by default) mounts DAARI_SERVER__HEADER_POLICY__* env; ASGI pin proves /mcp is denied pre-auth while health/ready stay open. Docs cross-link capacity-helm and auth-and-keys. Covered by tests/unit/test_helm_chart.py, tests/integration/test_header_policy_gateway.py.

Honor Anthropic output_config.effort (#1230)

Status: Done (2026-09-29). AnthropicRequest / SamplingParams keep output_config; L6 to_anthropic_payload forwards it; effort maps to local reasoning_effort / Ollama think when adaptive or without budget_tokens (budget still wins); Opus 4.5 injects effort-2025-11-24 beta. Docs: claude-code.md. Covered by tests/unit/test_anthropic_output_config.py.

Per-key/team MCP client allowlists (#1215)

Status: Done (2026-09-29). Nested mcp.clients allow/deny (global/team/key) gates initialize / server/discover / tools/list / tools/call by clientInfo / OAuth client_id / User-Agent; unset = passthrough. CLI --mcp-client-allow/deny. Docs: mcp.md. Covered by tests/unit/test_mcp_policy.py, tests/integration/test_mcp_governance.py.

Scheduled encrypted backups via Helm CronJob (#1216)

Status: Done (2026-09-29). Optional chart backup.* CronJob (off by default) runs daari backup create --encrypt with encrypt secret + retain prune + PVC; values document schedule/encrypt/retain/secret keys. Docs add systemd user-timer equivalent. Covered by tests/unit/test_helm_chart.py, backup-restore.md.

Spend report --by-tool (#1217)

Status: Done (2026-09-29). Spend rows keep provider (MCP metering ids); daari spend report --by-tool rolls USD/requests by provider-or-model. --by-user unchanged. Docs: chargeback.md. Covered by tests/unit/test_spend_report_by_tool.py.

MCP in-flight activity + admin force-abort (#1231)

Status: Done (2026-09-30). In-process registry tracks tools/call with request_id, key/principal, method, tool, started_at; cleared on completion/cancel. Admin GET /v1/daari/mcp/activity lists in-flight work; POST .../activity/{request_id}/abort cancels the awaitable (idempotent when finished; 404 unknown), writes mcp.activity.abort audit. Covered by tests/unit/test_mcp_activity.py.

Reject Mcp-Method / Mcp-Name body mismatches (#1233)

Status: Done (2026-09-30). When Mcp-Method / Mcp-Name are present on POST /mcp they must match JSON-RPC method / params.name; mismatch → HTTP 400 + JSON-RPC -32023 (HEADER_MISMATCH). Absent headers unchanged. Docs: mcp.md. Covered by tests/unit/test_mcp_header_match.py.

Hermetic http-api.md equality (#1195)

Status: Done (2026-09-30). test_committed_http_api_md_matches_generator fails if committed http-api.md drifts from render_api_reference(); regenerated to match. Covered by tests/unit/test_gen_reference.py.

Doctor-health plaintext backup encrypt tip (#1196)

Status: Done (2026-09-30). doctor-health.md documents the optional backup check and plaintext → --encrypt openssl|age tip with a link to backup-restore. Covered by tests/unit/test_doctor_health_backup_docs.py.

Responses server-side MCP tools (#1232)

Status: Done (2026-09-30). Opt-in integrations.mcp_egress.server_side_responses lets Responses accept type: mcp tools that resolve to configured mcp_servers (server_label); daari lists tools, runs egress tools/call on model tool rounds (SSRF/policy/metering unchanged), and continues the turn. Default off keeps the #1135 honesty 400. Covered by tests/unit/test_responses_server_side_mcp.py.

Docs: cli.md pins spend report --by-tool (#1245)

Status: Done (2026-09-30). CLI catalog spend row mentions report, --by-tool, and --by-user with a link to chargeback.md. Covered by tests/unit/test_chargeback_docs.py.

Hermetic pin for Claude Code output_config.effort (#1246)

Status: Done (2026-09-30). Unit test reads claude-code.md Effort section and asserts output_config, effort levels, xhigh/max→high, and anthropic-beta: effort-2025-11-24. Covered by tests/unit/test_claude_code_effort_docs.py.

Docs: doctor header_policy empty-rules tip (#1247)

Status: Done (2026-09-30). doctor-health.md troubleshoot row covers doctor header_policy when enabled with empty required/deny/allow, linking auth-and-keys. Covered by tests/unit/test_doctor_health_header_policy_docs.py.

Hermetic pin for secrets.refresh_ttl_seconds runbook (#1248)

Status: Done (2026-09-30). Unit tests pin auth-and-keys Refresh/rotation (default 300, boot-only 0 / env, mtime vs TTL, doctor secret_refs) and the secrets.refresh_ttl_seconds config.md row. Covered by tests/unit/test_secret_refresh_docs.py.

Hermetic pin for MCP activity + force-abort (#1253)

Status: Done (2026-09-30). Unit test pins mcp.md activity list/abort (/v1/daari/mcp/activity, /abort, 404, mcp.activity.abort). Covered by tests/unit/test_mcp_activity_docs.py.

Hermetic pin for Responses server_side_responses (#1254)

Status: Done (2026-09-30). Unit tests pin mcp.md server_side_responses / type: mcp / server_label / honesty 400 and the config.md knob. Covered by tests/unit/test_responses_server_side_mcp_docs.py.

Anthropic output_config.format structured outputs (#1260)

Status: Done (2026-09-30). SamplingParams.from_anthropic_body honors output_config.format (type: json_schema) for local schema enforcement, prefers it over legacy output_format, logs json_schema_ignored on malformed format, and L6 still forwards full output_config. Covered by tests/unit/test_anthropic_output_config.py and tests/unit/test_claude_code_effort_docs.py.

Messages server-side MCP mcp_servers + mcp_toolset (#1261)

Status: Done (2026-09-30). Opt-in integrations.mcp_egress.server_side_messages accepts Anthropic mcp_servers + mcp_toolset against configured integrations.mcp_servers ids; lists/calls via egress; fails closed when disabled; unknown labels → 400. Covered by tests/unit/test_messages_server_side_mcp.py and tests/unit/test_messages_server_side_mcp_docs.py.

MCP RFC 9728 oauth-protected-resource discovery (#1262)

Status: Done (2026-09-30). Opt-in integrations.mcp_oauth.protected_resource serves /.well-known/oauth-protected-resource (+ /mcp scoped), shapes unauthenticated /mcp 401 with WWW-Authenticate, doctor tip when auth is on without metadata. Covered by tests/unit/test_mcp_oauth_protected_resource.py and tests/unit/test_mcp_oauth_docs.py.

OpenAI-compatible POST /v1/ocr (#1263)

Status: Done (2026-10-01). POST /v1/ocr accepts LiteLLM/Mistral-shaped document bodies (URL or base64), prefers ocr.base_url then ocr.vision_model (Ollama multimodal) else L6 passthrough, returns structured pages payloads, meters spend/usage, and uses the ocr rate-limit family. Covered by tests/unit/test_ocr.py.

MCP OpenAPI /mcp/proxy schema discovery (#1264)

Status: Done (2026-10-01). Opt-in integrations.mcp_openapi_proxy exposes POST /mcp/proxy for OpenAPI→MCP tools/list and proxied tools/call under existing SSRF egress guards and the mcp rate family. Covered by tests/unit/test_mcp_openapi_proxy.py.

Docs pin: Mcp-Method/Name HEADER_MISMATCH (#1255)

Status: Done (2026-10-01). Hermetic mcp.md pin for Mcp-Method / Mcp-Name / HEADER_MISMATCH / -32023. Covered by tests/unit/test_mcp_header_match_docs.py.

Docs pin: Helm scheduled encrypted backups (#1256)

Status: Done (2026-10-01). Hermetic backup-restore.md pin for Helm CronJob / backup.enabled / backup.schedule / --encrypt openssl. Covered by tests/unit/test_backup_cronjob_docs.py.

Docs pin: mcp_tool_calls / mcp_tasks stats (#1257)

Status: Done (2026-10-01). Hermetic mcp.md pin for /v1/daari/stats, mcp_tool_calls, and mcp_tasks. Covered by tests/unit/test_mcp_stats_docs.py.

Doctor tip when OCR knobs unset (#1276)

Status: Done (2026-10-01). Optional doctor ocr tip when both ocr.base_url and ocr.vision_model are empty (L6 fallthrough). Covered by tests/unit/test_doctor_ocr.py and test_doctor_health_ocr_docs.py.

MCP Registry GET /v1/mcp/registry.json (#1277)

Status: Done (2026-10-01). Opt-in integrations.mcp_registry.enabled serves auth-agnostic GET /v1/mcp/registry.json listing built-in /mcp and configured mcp_servers URLs. Covered by tests/unit/test_mcp_registry.py.

Docs pin: POST /v1/ocr modality (#1274)

Status: Done (2026-10-01). Hermetic clients-and-gateways.md pin for POST /v1/ocr / document / local vision / L6. Covered by tests/unit/test_ocr_docs.py.

Docs pin: OpenAPI /mcp/proxy cues (#1275)

Status: Done (2026-10-01). Hermetic mcp.md pin for /mcp/proxy, mcp_openapi_proxy, tools/list, and SSRF / allow_private_networks. Covered by tests/unit/test_mcp_openapi_proxy_docs.py.

Docs pin: http-api OCR and /mcp/proxy rows (#1278)

Status: Done (2026-10-01). Hermetic http-api.md pin for POST /v1/ocr and POST /mcp/proxy catalog rows. Covered by tests/unit/test_http_api_ocr_mcp_proxy_docs.py.

Docs pin: MCP registry.json cues (#1284)

Status: Done (2026-10-01). Hermetic mcp.md pin for mcp_registry, /v1/mcp/registry.json, and mcp_servers. Covered by tests/unit/test_mcp_registry_docs.py.

Docs pin: http-api MCP registry row (#1285)

Status: Done (2026-10-01). Hermetic http-api.md pin for GET /v1/mcp/registry.json catalog row. Covered by tests/unit/test_http_api_mcp_registry_docs.py.

POST /v1/systemone decision-model facade (#1291)

Status: Done (2026-10-01). Opt-in (default on) POST /v1/systemone proxies Ollama 0.35+ TypeSafe/Jev decision models via ollama.base_url; same /v1/* auth; preserves answers/usage and adds daari_meta; meters usage/metrics; malformed → 400; Ollama down → 503 backend_unavailable; systemone.enabled=false → 501. Docs: ollama.md + regenerated http-api.md. Covered by tests/unit/test_systemone.py.

Optional decision-model complexity classifier (#1292)

Status: Done (2026-10-01). Opt-in routing.decision_classifier (default off) runs an Ollama /v1/systemone difficulty hop before the heuristic local tier pick; maps choice/score → L3–L5; timeout/failure falls back to heuristics with a degraded event; daari_meta.decision + trace step record model/answer/tier. Covered by tests/unit/test_decision_classifier.py.

MCP local OAuth authorize/token mint (#1293)

Status: Done (2026-10-02). Opt-in integrations.mcp_oauth.local_as exposes AS metadata + POST /oauth/token (client_credentials), mints short-lived HS256 Bearer JWTs bound to API/virtual keys, usable on /mcp; bad/expired → 401 + existing challenge; discovery lists local issuer when authorization_servers empty. Docs: mcp.md + regenerated config.md. Covered by tests/unit/test_mcp_oauth_token_mint.py and tests/unit/test_mcp_oauth_docs.py.

MCP aggregate egress tools/list on /mcp (#1294)

Status: Done (2026-10-02). Opt-in integrations.mcp_aggregate_egress.enabled merges mcp_servers tools into /mcp tools/list as {server_id}__{tool}; tools/call routes via egress; server/tool policy filter; per-server list failures degrade with mcp_aggregate_egress_list_failed. Docs: mcp.md + regenerated config.md. Covered by tests/unit/test_mcp_aggregate_egress.py and tests/unit/test_mcp_aggregate_egress_docs.py.

Escalate web_search_options to L6 (no silent local drop) (#1295)

Status: Done (2026-10-02). Chat web_search_options skips local tiers and escalates to L6 (payload forwarded); no-frontier / frontier-off / budget → clear 4xx/501 with web_search_unavailable; daari_meta.warning / trace reason web_search_required. Docs: clients-and-gateways.md. Covered by tests/unit/test_web_search_escalate.py.

Docs: hermetic pin for mcp_openapi_proxy and mcp_registry config (#1286)

Status: Done (2026-10-02). Hermetic config.md pin for integrations.mcp_openapi_proxy.enabled, integrations.mcp_registry.enabled, /mcp/proxy, and /v1/mcp/registry.json. Covered by tests/unit/test_mcp_proxy_registry_config_docs.py.

Doctor tip when mcp_registry.enabled (#1287)

Status: Done (2026-10-02). Optional doctor tip when integrations.mcp_registry.enabled advertises GET /v1/mcp/registry.json (auth-agnostic) while /mcp still enforces keys; absent when disabled. Docs: doctor-health.md. Covered by tests/unit/test_doctor_mcp_registry.py.

Docs: hermetic pin for web_search_options escalate note (#1305)

Status: Done (2026-10-02). Hermetic clients-and-gateways.md pin for web_search_options escalate / fail-closed wording from #1295. Covered by tests/unit/test_web_search_escalate_docs.py.

Docs: refresh ENTERPRISE.md after web_search escalate + MCP P2 wave (#1304)

Status: Done (2026-10-02). docs/prd/ENTERPRISE.md marks #1291–#1295 Shipped, keeps hot-reload (#6) Open, updates inward theme so web_search is no longer a silent local drop, and adds a 2026-10-02 changelog line.

Doctor tip when mcp_aggregate_egress.enabled (#1306)

Status: Done (2026-10-02). Optional doctor tip when integrations.mcp_aggregate_egress.enabled merges egress tools into /mcp tools/list as {server_id}__{tool} with policy/SSRF still enforced; absent when disabled. Docs: doctor-health.md. Covered by tests/unit/test_doctor_mcp_aggregate_egress.py.

Unit pin WebSearchUnavailable HTTP status mapping (#1307)

Status: Done (2026-10-02). Direct unit coverage for _web_search_unavailable_response status table (no_frontier→403, frontier_disabled→501, frontier_budget_exceeded→402, tier_cap:L3→403) and web_search_unavailable error type/code. Covered by tests/unit/test_web_search_unavailable_status.py.

Doctor tip when mcp_oauth.local_as mints tokens (#1312)

Status: Done (2026-10-02). Optional doctor tip when integrations.mcp_oauth.local_as mints via POST /oauth/token while /mcp still requires a Bearer; absent when disabled. Docs: doctor-health.md. Covered by tests/unit/test_doctor_mcp_oauth_local_as.py.

Doctor tip when decision_classifier.enabled (#1313)

Status: Done (2026-10-02). Optional doctor tip when routing.decision_classifier.enabled runs an Ollama /v1/systemone difficulty hop before heuristic tier pick (fallback on timeout/failure); absent when disabled. Docs: doctor-health.md. Covered by tests/unit/test_doctor_decision_classifier.py.

RFC 8693 OBO token exchange for egress mcp_servers (#1319)

Status: Done (2026-10-02). Opt-in per integrations.mcp_servers entry: auth_type: oauth2_token_exchange with token_exchange_endpoint, client_id, client_secret, optional audience / scopes / subject_token_type. On tools/list and tools/call, exchanges the inbound Authorization bearer (RFC 8693), caches until expiry, and attaches only the exchanged token upstream. Fail closed: missing subject → 401; exchange error or SSRF-blocked endpoint → 502 (log_gateway_event; secrets never logged). Doctor tip mcp_token_exchange when any server uses OBO. Docs: mcp.md, config.md, doctor-health.md. Covered by tests/unit/test_mcp_token_exchange.py.

Refuse weak or unset master key at serve (#1320)

Status: Done (2026-10-02). daari serve refuses empty/whitespace or denylisted master keys (sk-1234 / changeme / daari-local / …) before bind unless server.dangerously_permit_weak_or_unset_api_key is true. Doctor tip master_key_strength when the escape hatch is on or the key is unset while virtual keys / mcp_oauth.local_as imply auth. Docs: auth-and-keys.md, config.md, doctor-health.md. Covered by tests/unit/test_refuse_weak_master_key.py.

Doctor tip when decision_classifier model missing from Ollama (#1321)

Status: Done (2026-10-02). When routing.decision_classifier.enabled is true, doctor probes Ollama /api/tags for routing.decision_classifier.model (default nimble); missing → optional tip with ollama pull <model>. Quiet when the tag is present or Ollama is unreachable (existing ollama check). Docs: doctor-health.md. Covered by tests/unit/test_doctor_decision_classifier_model.py.

Extend /v1/daari/config ownership to decision_classifier + MCP knobs (#1322)

Status: Done (2026-10-03). Config editor ownership covers routing.decision_classifier.* and MCP leaves (mcp_oauth.local_as / protected_resource, mcp_aggregate_egress.enabled, mcp_registry.enabled) with source/editable metadata; PATCH validates types, updates live Settings, and deep-merges yaml on persist. Secrets stay non-editable. Docs: config.md, http-api.md. Covered by tests/unit/test_config_ownership.py.

Hermetic pin for systemone facade docs (#1314)

Status: Done (2026-10-03). Unit pins assert ollama.md and http-api.md mention POST /v1/systemone and systemone.enabled so the decision-model facade cannot drift without CI failure. Covered by tests/unit/test_systemone_docs.py.

Hermetic pin for decision_classifier config rows (#1315)

Status: Done (2026-10-03). Unit pin asserts config.md lists routing.decision_classifier.enabled / .model / .timeout_seconds and timeout fallback cues. Covered by tests/unit/test_decision_classifier_docs.py.

ASGI pin web_search_options when frontier disabled → 501 (#1316)

Status: Done (2026-10-03). ASGI chat with web_search_options and frontier.enabled=false returns 501 web_search_unavailable / frontier_disabled; absent-web_search local path unchanged. Covered by tests/unit/test_web_search_escalate.py.

Hermetic pin doctor-health MCP/decision rows (#1323)

Status: Done (2026-10-03). Unit pins assert doctor-health.md rows for mcp_aggregate_egress, mcp_oauth_local_as, and decision_classifier include the tip config path and route cue. Covered by tests/unit/test_doctor_health_mcp_aggregate_egress_docs.py, tests/unit/test_doctor_health_mcp_oauth_local_as_docs.py, and tests/unit/test_doctor_health_decision_classifier_docs.py.

Local watchdog serve after weak master-key gate (#1328)

Status: Done (2026-10-03). Watchdog com.daari.serve launchd unit sets DAARI_SERVER__DANGEROUSLY_PERMIT_WEAK_OR_UNSET_API_KEY=true so #1320 does not KeepAlive crash-loop sandbox serve. Cursor smoke is skipped when the daemon is already unreachable; filed issues include serve.err.log tail. Re-run scripts/autodev-local.sh --install to reload the plist. Covered by tests/unit/test_autodev_local.py.

Watchdog dedupe local E2E issues by failure list (#1337)

Status: Done (2026-10-03). Filing skips when an open regression issue has the same title or the same ## Failures bullets, so SHA-stamped watchdog tickets no longer duplicate. Covered by tests/unit/test_autodev_local.py.

Hermetic pin http-api config ownership classifier/MCP (#1338)

Status: Done (2026-10-03). Unit pin asserts http-api.md /v1/daari/config ownership names decision_classifier and MCP knobs. Covered by tests/unit/test_http_api_config_ownership_docs.py.

Watchdog skip live integration when daemon unreachable (#1339)

Status: Done (2026-10-03). Local watchdog skips pytest -m integration with SKIP: live integration (daemon unreachable) when serve is already down, even if Ollama /api/tags answers. Covered by tests/unit/test_autodev_local.py.

Opt-in compact-to-fit before L6 (#1340)

Status: Done (2026-10-03). routing.compact_to_fit (default off) trims oldest unprotected chat turns before frontier escalate. System messages and tool-call payloads are never dropped; if the remainder still exceeds the cap, history stays oversized. Covered by tests/unit/test_compact_to_fit.py.

Watchdog sandbox master-key hatch docs (#1341)

Status: Done (2026-10-03). auth-and-keys.md documents the watchdog DAARI_SERVER__DANGEROUSLY_PERMIT_WEAK_OR_UNSET_API_KEY plist hatch and scripts/autodev-local.sh --install reload. Covered by tests/unit/test_watchdog_sandbox_key_docs.py.

Extend /v1/daari/config ownership to compact_to_fit knobs (#1348)

Status: Done (2026-10-03). Config editor GET/PATCH expose routing.compact_to_fit.enabled / .max_messages / .max_tokens with source/editable metadata; PATCH validates bool / positive int / non-negative int. Default remains off. Covered by tests/unit/test_config_ownership.py.

Doctor tip when compact_to_fit.enabled (#1349)

Status: Done (2026-10-03). Optional daari doctor row compact_to_fit when routing.compact_to_fit.enabled is true (advisory, not a failure); silent when off. Covered by tests/unit/test_doctor_compact_to_fit.py.

daari_meta and stats when compact_to_fit trims (#1351)

Status: Done (2026-10-03). Successful history trims before L6 set daari_meta.compact_to_fit (messages_before / messages_after) and increment /v1/daari/stats compact_to_fit_applied. Unchanged or fail-closed (tool-protected) history does not claim a trim. Covered by tests/unit/test_compact_to_fit.py.

Opt-in fail-closed MCP when key has no MCP grant (#1352)

Status: Done (2026-10-03). integrations.mcp_policy.require_key_access_defined (default false) fail-closes virtual keys whose metadata.mcp lacks allow / deny / servers.allow|deny: empty tools/list, tools/call deny. Master key and flag-off unchanged. Doctor tip mcp_require_key_access. Docs: mcp.md, config.md. Covered by tests/unit/test_mcp_require_key_access.py, tests/unit/test_doctor_mcp_require_key_access.py, tests/integration/test_mcp_governance.py.

Hermetic pin compact_to_fit in config and routing-tiers (#1350)

Status: Done (2026-10-03). Unit pins assert config.md lists the three routing.compact_to_fit.* keys with fail-closed / tool-payload wording, and routing-tiers.md documents compact before L6 with the same rules. Covered by tests/unit/test_compact_to_fit_docs.py.

Config ownership for mcp_policy.require_key_access_defined (#1359)

Status: Done (2026-10-03). GET/PATCH /v1/daari/config exposes integrations.mcp_policy.require_key_access_defined with ownership metadata; PATCH validates bool and persists. Covered by tests/unit/test_config_ownership.py.

Hermetic pin require_key_access_defined in mcp and config (#1360)

Status: Done (2026-10-04). Unit pins assert config.md lists integrations.mcp_policy.require_key_access_defined with empty tools/list or tools/call deny / metadata.mcp grant wording, and mcp.md documents fail-closed grants with master-key unchanged. Covered by tests/unit/test_mcp_require_key_access_docs.py.

Hermetic pin compact_to_fit_applied in stats docs (#1361)

Status: Done (2026-10-04). traces-stats.md documents compact_to_fit_applied incrementing only when a trim changes message count. Covered by tests/unit/test_compact_to_fit_stats_docs.py.

ASGI pin servers-only MCP grant under require_key_access_defined (#1362)

Status: Done (2026-10-04). Flag on + metadata.mcp.servers.allow keeps a non-empty first-party tools/list; clients alone still fail-closes. Covered by tests/integration/test_mcp_governance.py.

Mark compact live-tune and MCP grant rows shipped in ENTERPRISE.md (#1363)

Status: Done (2026-10-04). ENTERPRISE.md rows 19–23 are Shipped; path retargeted to live config reload then remaining gaps. Changelog line for this pass.

Hermetic pin compact_to_fit_applied Prometheus series (#1369)

Status: Done (2026-10-04). metrics-prometheus.md lists daari_compact_to_fit_applied_total for actual compact-to-fit trims. Covered by tests/unit/test_metrics_prometheus_docs.py.

Hermetic pin ENTERPRISE compact and MCP grant rows shipped (#1370)

Status: Done (2026-10-04). Unit pin asserts ENTERPRISE.md gap rows for compact ownership, doctor tip, docs pin, meta/stats, and MCP grant fail-closed stay Shipped and do not say File this run. Covered by tests/unit/test_enterprise_prd_docs.py.

Hermetic pin daari_meta.compact_to_fit on traces-stats (#1371)

Status: Done (2026-10-04). traces-stats.md documents daari_meta.compact_to_fit (messages_before / messages_after) with X-Daari-Meta: true. Covered by tests/unit/test_compact_to_fit_stats_docs.py.

Hermetic pin mcp.md servers-only grant vs clients-only (#1372)

Status: Done (2026-10-04). Unit pin asserts mcp.md treats servers.allow/servers.deny as a grant under require_key_access_defined and a clients block alone as not a tool grant. Covered by tests/unit/test_mcp_require_key_access_docs.py.

Hermetic pin http-api compact_to_fit ownership leaves (#1373)

Status: Done (2026-10-04). Unit pin asserts http-api.md documents routing.compact_to_fit on GET/PATCH /v1/daari/config ownership. Covered by tests/unit/test_http_api_config_ownership_docs.py.

Prometheus pin compact_to_fit_applied series increment (#1379)

Status: Done (2026-10-04). render_prometheus emits daari_compact_to_fit_applied_total 0 on empty metrics and 1 after record_compact_to_fit_applied(). Covered by tests/unit/test_prometheus.py.

MCP initialize fail-closed without key grant (#1389)

Status: Done (2026-10-04). When require_key_access_defined is true, virtual keys with no MCP grant fail initialize (HTTP 403, JSON-RPC -32003). Granted keys, master key, and flag-off still handshake. tools/list empty catalog and tools/call deny unchanged. Covered by tests/unit/test_mcp_require_key_access.py and tests/integration/test_mcp_governance.py.

CLI pin daari stats --help for compact_to_fit_applied (#1380)

Status: Done (2026-10-04). daari stats --help names compact_to_fit_applied alongside other stats JSON keys. Covered by tests/unit/test_stats_cli_help.py.

Docs pin headers.md X-Daari-Meta compact_to_fit (#1381)

Status: Done (2026-10-04). headers.md documents compact_to_fit (messages_before / messages_after) on X-Daari-Meta. Covered by tests/unit/test_compact_to_fit_stats_docs.py.

HTTP pin require_key_access_defined ownership (#1382)

Status: Done (2026-10-04). Unit pin asserts http-api.md documents require_key_access_defined on GET/PATCH /v1/daari/config ownership. Covered by tests/unit/test_http_api_config_ownership_docs.py.

Docs pin cli.md stats compact_to_fit_applied (#1383)

Status: Done (2026-10-04). cli.md stats row names compact_to_fit_applied on GET /v1/daari/stats JSON. Covered by tests/unit/test_stats_cli_help.py.

MCP grant fail-closed dedicated counter (#1390)

Status: Done (2026-10-04). mcp_grant_denied / daari_mcp_grant_denied_total increments on initialize, tools/list, and tools/call when require_key_access_defined fail-closed applies. Ordinary tool-name deny does not. Series present at 0 when unused. Covered by tests/unit/test_prometheus.py and tests/unit/test_mcp_require_key_access.py.

Grafana compact_to_fit_applied overview panel (#1391)

Status: Done (2026-10-04). Overview dashboard charts daari_compact_to_fit_applied_total under Compact-to-fit applied. Covered by tests/unit/test_grafana_dashboard.py.

Stats for tokens dropped by compact_to_fit (#1392)

Status: Done (2026-10-04). When compact-to-fit actually trims, increment compact_to_fit_tokens_dropped / daari_compact_to_fit_tokens_dropped_total by tokens_before - tokens_after. Meta includes tokens_before / tokens_after. Covered by tests/unit/test_compact_to_fit.py and tests/unit/test_prometheus.py.

Refresh compare-litellm for LiteLLM 1.104 GA (#1393)

Status: Done (2026-10-04). compare-litellm.md notes 1.104 GA parity for master-key refuse, compact-to-fit, and MCP grant fail-closed; stdio MCP marked N/A/watch. Covered by tests/unit/test_compare_litellm_1104.py.

Grafana compact_to_fit_tokens_dropped overview panel (#1400)

Status: Done (2026-10-05). Overview dashboard charts daari_compact_to_fit_tokens_dropped_total under Compact-to-fit tokens dropped. Covered by tests/unit/test_grafana_dashboard.py.

Grafana mcp_grant_denied overview panel (#1401)

Status: Done (2026-10-05). Overview dashboard charts daari_mcp_grant_denied_total under MCP grant denied. Covered by tests/unit/test_grafana_dashboard.py.

CLI pin stats help for compact_to_fit_tokens_dropped (#1402)

Status: Done (2026-10-05). daari stats --help and cli.md mention compact_to_fit_tokens_dropped. Covered by tests/unit/test_stats_cli_help.py.

ENTERPRISE operate-gates rows marked shipped (#1403)

Status: Done (2026-10-05). PRD rows 24–28 (Grafana compact, grant counter, initialize refuse, token-drop stats, compare-litellm 1.104) marked Shipped; inward theme and path no longer treat those as open. Covered by tests/unit/test_enterprise_prd_docs.py.

compact_to_fit trace step tokens_before/after (#1404)

Status: Done (2026-10-05). Successful compact-to-fit trims record tokens_before / tokens_after on the compact_to_fit trace step; no-op paths still omit the step. Covered by tests/unit/test_compact_to_fit.py.

routing-tiers compact_to_fit tokens_before/after (#1410)

Status: Done (2026-10-05). routing-tiers.md notes tokens_before / tokens_after and compact_to_fit_tokens_dropped when a trim changes message count. Covered by tests/unit/test_compact_to_fit_docs.py.

doctor compact_to_fit tip mentions token-drop stats (#1411)

Status: Done (2026-10-05). Optional compact_to_fit doctor tip names tokens_before / tokens_after and compact_to_fit_tokens_dropped when compact is enabled. Covered by tests/unit/test_doctor_compact_to_fit.py.

CLI pin trace compact_to_fit token fields (#1412)

Status: Done (2026-10-05). cli.md trace row documents compact_to_fit tokens_before / tokens_after. Covered by tests/unit/test_cli_reference_catalog.py.

HTTP API pin traces compact_to_fit token fields (#1413)

Status: Done (2026-10-05). http-api.md notes compact_to_fit trace step tokens_before / tokens_after when a trim ran. Covered by tests/unit/test_http_api_traces_compact_docs.py.

ARCHITECTURE pin compact_to_fit token fields (#1418)

Status: Done (2026-10-05). ARCHITECTURE.md routing overview names routing.compact_to_fit and tokens_before / tokens_after. Covered by tests/unit/test_architecture_introspect.py.

README pin compact-to-fit in feature snapshot (#1419)

Status: Done (2026-10-05). README routing snapshot mentions compact-to-fit tokens_before / tokens_after and compact_to_fit_tokens_dropped. Covered by tests/unit/test_gtm_public_copy.py.

Docs: compare-litellm compact-to-fit token telemetry (#1420)

Status: Done (2026-10-05). compare-litellm 1.104 compact row names tokens_before / tokens_after and compact_to_fit_tokens_dropped. Covered by tests/unit/test_compare_litellm_1104.py.

Web-ui shows compact_to_fit tokens on traces (#1421)

Status: Done (2026-10-06). Trace detail panel surfaces compact_to_fit tokens_before / tokens_after when the step is present (plus full JSON). Covered by packages/web-ui/test/dashboard.test.js.

Docs: config.md pin compact_to_fit FinOps token fields (#1422)

Status: Done (2026-10-06). routing.compact_to_fit.enabled description documents tokens_before / tokens_after and compact_to_fit_tokens_dropped (via settings Field + gen_reference). Covered by tests/unit/test_compact_to_fit_docs.py.

Web-ui stats show compact_to_fit_tokens_dropped and mcp_grant_denied (#1429)

Status: Done (2026-10-06). Dashboard summary cards render compact_to_fit_tokens_dropped and mcp_grant_denied from /v1/daari/stats (zeros when absent). Covered by packages/web-ui/test/dashboard.test.js.

Config ownership for observability.structured_json_logs (#1430)

Status: Done (2026-10-06). GET/PATCH /v1/daari/config exposes observability.structured_json_logs with ownership metadata; PATCH validates bool and updates live configure_request_log without restart. Covered by tests/unit/test_config_ownership.py.

Doctor tip for observability.structured_json_logs (#1431)

Status: Done (2026-10-06). daari doctor emits an optional tip when observability.structured_json_logs is on (stdout JSON); quiet when off. Covered by tests/unit/test_doctor_structured_json_logs.py.

Docs: pin compact_to_fit_tokens_dropped in savings-report (#1432)

Status: Done (2026-10-06). Savings-report guide points operators at compact_to_fit_tokens_dropped and trace tokens_before / tokens_after. Covered by tests/unit/test_savings_report_compact_docs.py.

Docs: LiteLLM 1.105 RC watch in compare-litellm (#1433)

Status: Done (2026-10-06). compare-litellm notes LiteLLM 1.105 as RC/watch (M365 MCP catalog, Straiker, Lens/agent traces) while keeping 1.104 GA as the stable floor. Covered by tests/unit/test_compare_litellm_1104.py.

Require master key for config writes when SSO is off (#1441)

Status: Done (2026-10-06). With SSO off and a master key set, virtual keys get 403 on PATCH /v1/daari/config (and other _require_admin_role surfaces); master key still succeeds. GET stays viewer-style for virtual keys. Sandbox (no master key) stays open; daari doctor warns config_editor_ungoverned. Covered by tests/unit/test_config_admin_master_key.py.

Add gpt-6.1-sol pricing, capabilities, and param compat (#1442)

Status: Done (2026-10-06). Shipped $2/$10 (cached $0.10, cache write $2.50) for gpt-6.1-sol with capabilities + GPT-6 family param compat; no invented above-272K surcharge. Covered by tests/unit/test_gpt_6_1_sol_pricing.py.

Forward systemone multimodal images + rate family (#1443)

Status: Done (2026-10-06). POST /v1/systemone accepts and forwards Ollama 0.35.1+ images (base64 list); rate_limit_family maps the path to systemone. Covered by tests/unit/test_systemone.py, tests/unit/test_rate_families.py.

Add claude-sonnet-5-5 pricing, capabilities, and param compat (#1450)

Status: Done (2026-10-06). Shipped $2/$10 (cached $0.20, 1h cache write $4.00) for claude-sonnet-5-5 with Claude 5.x capabilities and strip of non-default temperature/top_p/top_k. Covered by tests/unit/test_claude_sonnet_5_5_pricing.py.

Web-ui: escape DOM, CSP, session key storage (#1444)

Status: Done (2026-10-06). Dashboard renders API fields via textContent, ships a restrictive CSP meta, and keeps the Bearer key in memory / sessionStorage (opt-in) instead of localStorage. Covered by packages/web-ui/test/dashboard.test.js.

OCR modality Prometheus label, guardrails, OTel span (#1445)

Status: Done (2026-10-06). ocr is a first-class Prometheus modality; OCR page markdown runs through output guardrails; upstream calls emit a daari.ocr CLIENT span. Covered by tests/unit/test_ocr.py, tests/unit/test_modality_metrics.py.

Anthropic Models API line on native /v1/models (#1451)

Status: Done (2026-10-06). Anthropic-shaped model cards include nullable line from an explicit Claude family map; local/unknown ids stay null. Covered by tests/unit/test_anthropic_gateway.py.

Prometheus latency and outcome series for decision classifier (#1452)

Status: Done (2026-10-07). daari_decision_classifier_total{outcome} and daari_decision_classifier_latency_ms for the optional /v1/systemone difficulty hop (success / heuristic_fallback / error / skipped). Covered by tests/unit/test_decision_classifier.py, tests/unit/test_prometheus.py.

Classify fatal serve config and stop KeepAlive crash-loop spam (#1453)

Status: Done (2026-10-07). Watchdog classifies master_key / hard refuse stderr, skips kickstart, unloads serve for the cycle, and includes the reason in regression issue bodies. Covered by tests/unit/test_autodev_local.py.

compact_to_fit counts images/audio and protects multimodal turns (#1454)

Status: Done (2026-10-07). estimate_tokens adds per-image / per-audio heuristics; image-only and audio-only turns are protected like tool pairs. Covered by tests/unit/test_compact_to_fit.py.

Watchdog cursor smoke overwrites stale success JSON (#1425)

Status: Done (2026-10-07). Cursor-shaped smoke runs via autodev_local.py cursor-smoke and always rewrites smoke-latest.json, including on ConnectError, so FAIL logs cannot show a prior cycle's status_code: 200 / content_chunks > 0. Daemon crash-loop root cause remains covered by #1453. Covered by tests/unit/test_autodev_local.py.

grok-4.7 pricing, capabilities, and param compat (#1466)

Status: Done (2026-10-07). Lab $2/$6 + $0.50 cache read for grok-4.7 (LiteLLM 1.105 RC day-one); capabilities match other frontier chat IDs; no sampler strip (none published). Covered by tests/unit/test_grok_4_7_pricing.py.

claude-opus-5-5 pricing, capabilities, and param compat (#1467)

Status: Done (2026-10-07). Lab $4/$20 + cache rates for claude-opus-5-5 (LiteLLM 1.104); capabilities + Anthropic line: opus; sampler strip matches Sonnet 5.5. Covered by tests/unit/test_claude_opus_5_5_pricing.py.

MCP OAuth mint/deny audit_log events (#1468)

Status: Done (2026-10-07). POST /oauth/token records mcp_oauth.token_minted / mcp_oauth.token_denied via the enterprise audit sink (kind, expires_in, scope, token fingerprint — never the raw token). Covered by tests/unit/test_mcp_oauth_token_mint.py.

OpenAI-compatible /v1/decisions on local models + gpt-6-luna (#1474)

Status: Done (2026-10-07). POST /v1/decisions accepts the OpenAI Decisions beta shape; local decision models (nimble/clef/…) run via the systemone executor; gpt-6-luna passes through L6 with no_frontier / region_pin fences. Pricing $0.10/1M input (no output), decisions rate family, cost headers + usage ledger. Covered by tests/unit/test_decisions.py.

MCP proxy response cost headers (#1469)

Status: Done (2026-10-07). Successful /mcp/proxy tools/call responses emit the standard x-daari-response-cost* set via modality_response_headers (zero USD when no model tokens). tools/list omits cost headers. Covered by tests/unit/test_mcp_openapi_proxy.py.

MCP OAuth scope enforcement + RFC 7009 revocation (#1475)

Status: Done (2026-10-08). Local AS mint rejects scopes outside scopes_supported (invalid_scope + audit); /mcp requires the mcp scope claim (403 + audit); POST /oauth/revoke denylists by jti until remaining exp; AS metadata advertises revocation_endpoint. Covered by tests/unit/test_mcp_oauth_token_mint.py.

OTel, guardrails, and retry on /mcp/proxy + /v1/systemone (+ OCR input) (#1476)

Status: Done (2026-10-08). /mcp/proxy and /v1/systemone emit OTel client spans and wrap upstream HTTP in run_upstream; MCP guardrails screen proxy tool args/results; systemone screens state/questions (+ answer output); OCR applies input guardrails before dispatch. Covered by tests/unit/test_mcp_openapi_proxy.py, tests/unit/test_systemone.py, tests/unit/test_ocr.py.

Read-only keys/teams/spend admin plane (#1477)

Status: Done (2026-10-08). Admin-gated GET /v1/daari/keys and GET /v1/daari/teams return redacted inventories (id/alias/team/budgets/tier caps/expiry + current spend; never secrets or hashes). Web-ui adds Keys and Teams sections plus spend breakdown from report.teams / report.model_group_spend. Covered by tests/unit/test_admin_read_plane.py and packages/web-ui/test/dashboard.test.js.

Responses multi-agent beta fields (#1478)

Status: Done (2026-10-08). ResponsesRequest.multi_agent is declared and forwarded on the L6 OpenAI payload (plus OpenAI-Beta: responses_multi_agent=v1); local / no-frontier paths return an honest 400. unsupported_responses_tools is wired at ingress. Covered by tests/unit/test_responses_multi_agent.py.

Anthropic capabilities.thinking.types.disabled (#1481)

Status: Done (2026-10-08). Anthropic-native /v1/models cards include capabilities.thinking.types.disabled.supported (true by default, including local ids). Covered by tests/unit/test_anthropic_gateway.py.

Age-encrypt backup create/restore round-trip (#1482)

Status: Done (2026-10-08). Hermetic age encrypt/restore covers virtual keys, spend, and audit when age is on PATH, and skips when it is not. Missing recipient and missing identity stay fail-closed with BackupError. Covered by tests/unit/test_backup_restore.py.

Grafana panels for decision classifier series (#1483)

Status: Done (2026-10-08). Overview dashboard charts daari_decision_classifier_total by outcome and daari_decision_classifier_latency_ms p50/p95. Covered by tests/unit/test_grafana_dashboard.py.

Decisions-path input-only rates for gpt-6-luna (#1484)

Status: Done (2026-10-08). Chat/Responses keep $0.10 / $0.50 for gpt-6-luna. POST /v1/decisions bills that id input-only ($0.10, $0 output, $0 cache read/write) via billing_path="decisions". Covered by tests/unit/test_gpt_6_luna_decisions_pricing.py and tests/unit/test_decisions.py.

Guardrails + OTel + local retry on /v1/decisions (#1497)

Status: Done (2026-10-08). /v1/decisions screens flattened input+questions and answer text via endpoint guardrails, opens daari.decisions OTel spans, routes the local Ollama hop through _post_systemone retry, and binds optional OpenAI-shaped user onto spend context. Covered by tests/unit/test_decisions.py.

Normalize gpt-6-luna-decisions aliases on /v1/decisions (#1485)

Status: Done (2026-10-08). normalize_decisions_model maps gpt-6-luna / gpt-6-luna-decisions / openai/gpt-6-luna-decisions to the frontier Decisions target; unknown ids return 400. Covered by tests/unit/test_decisions.py.

Add claude-haiku-5-5 pricing + reprice sonnet/opus-5-5 cache reads (#1505)

Status: Done (2026-10-09). claude-haiku-5-5 catalog with ≤100K rates and 100K-threshold scaling (cache read $0.05 / 1h write $1.00 above); Sonnet/Opus 5.5 cache reads halved to $0.10 / $0.20; capabilities, Anthropic line: haiku, and sampler strip. Covered by tests/unit/test_claude_haiku_5_5_pricing.py.

Bill gpt-6-luna chat/responses cached input at $0.01/MTok (#1506)

Status: Done (2026-10-09). Chat/Responses gpt-6-luna sets cached_input_per_1m: 0.01 so prompt-cache hits are not billed at the $0.10 input rate; Decisions overlay still zeros cache/output. Covered by tests/unit/test_gpt_6_luna_decisions_pricing.py.

Fleet-durable MCP OAuth revoked-jti denylist (#1498)

Status: Done (2026-10-09). Revoked JTIs use Redis (cache.backend=redis) or Postgres (observability.postgres_url, memory: in tests); single-pod keeps in-process. Doctor warns on multi-replica local_as without a shared store; denylist is TTL-bound and not backup-critical. Covered by tests/unit/test_revoked_jti_store.py.

Paginate + audit GET /v1/daari/keys and /teams (#1499)

Status: Done (2026-10-09). Admin inventories accept limit/offset (default cap 100, max 500) and return total/has_more; successful GETs emit admin.keys.list / admin.teams.list audit events. Covered by tests/unit/test_admin_read_plane.py.

Meter multi_agent subagent token usage on L6 (#1500)

Status: Done (2026-10-09). OpenAI aggregated top-level usage (includes subagents) stays the metered total; when nested agent_usages / usage_by_agent under-count relative to top-level, parent+nested tokens and cost are summed before ledger write. Per-agent rows land on daari_meta.agent_usage for attribution without double-billing. Covered by tests/unit/test_responses_multi_agent.py.

Anthropic server_tools + lifecycle on /v1/models (#1507)

Status: Done (2026-10-09). Anthropic-native model cards expose lifecycle / deprecated_at / retires_at and capabilities.server_tools (Claude 5.x true; locals false). List honors lifecycle query (default active+deprecated; retired only when requested). Covered by tests/unit/test_anthropic_gateway.py.

Decisions model/user budget 402 + user_id ledger (#1518)

Status: Done (2026-10-09). POST /v1/decisions applies the same model_group / model_max_budget / user_daily_usd_cap 402 pre-checks as chat before any local or frontier call. usage_ledger.record carries user_id from the request body so /v1/daari/report users attributes decisions traffic. Covered by tests/unit/test_decisions.py.

Decisions L0 exact cache for typed judgments (#1508)

Status: Done (2026-10-09). Identical POST /v1/decisions requests (normalized model + input + questions) hit the shared L0 exact cache with daari_meta.tier=L0 / cache_hit=true. Default-on via decisions.cache_enabled (requires cache.l0.enabled); X-Daari-No-Cache: true bypasses. Guardrail blocks never write. Prom records L0 hits with modality=decisions. Covered by tests/unit/test_decisions.py and tests/integration/test_gateway_flow.py.

Map OpenAI chat-latest rolling alias in catalog (#1509)

Status: Done (2026-10-09). chat-latest prices at the published ChatGPT Instant rates ($5 / $0.50 cached / $30) as a rolling alias (snapshot moves; prefer gpt-6-astra for pins). Capabilities + /v1/models when configured as a frontier provider. Covered by tests/unit/test_chat_latest_pricing.py.

Alias service_tier fast to 2.0× (OpenAI Fast mode) (#1527)

Status: Done (2026-10-09). service_tier: "fast" (OpenAI Fast mode, former Priority) multiplies cost_usd at 2.0× like priority; no service_tier_ignored. Chat + Responses HTTP models forward fast unchanged. Docs: budgets-frontier service-tier note. Covered by tests/unit/test_service_tier.py.

Ultrafast service_tier (6×) with model + EU gating (#1519)

Status: Done (2026-10-09). service_tier: "ultrafast" bills at 6× Standard for gpt-6.1-sol / gpt-6-astra; unsupported models and EU-pinned Astra clear the tier with daari_meta.warning (ultrafast_unsupported_model / ultrafast_eu_unsupported). Docs: budgets-frontier. Covered by tests/unit/test_service_tier.py.

claude-haiku-5-5 fidelity (budget_tokens, 5m write, cards) (#1520)

Status: Done (2026-10-09). Haiku 5.5 egress maps budget_tokens thinking to adaptive + output_config.effort with a daari_meta warning; 5m cache writes bill $0.125 / $0.625 (above 100K); Anthropic /v1/models cards advertise 1M context, 128K max output, adaptive default medium. Covered by tests/unit/test_claude_haiku_5_5_pricing.py.

OpenAI /v1/models lifecycle + server_tools and retired routing (#1521)

Status: Done (2026-10-10). OpenAI-shaped list/retrieve cards expose the same lifecycle / server_tools data as the Anthropic facade; lifecycle query filter works on both shapes. Catalog retired models fail closed with HTTP 410 (models.reject_retired, default true); deprecated models still serve with daari_meta.warning=model_deprecated. Covered by tests/unit/test_openai_models_lifecycle.py.

Pooled Postgres revoked-jti denylist hot path (#1522)

Status: Done (2026-10-10). PostgresRevokedJtiStore uses the shared psycopg_pool (#975); is_revoked / revoke prune on the same checkout (interval-throttled DELETE) so MCP token validation no longer opens two raw connections per lookup. Redis + in-process backends unchanged. Covered by tests/unit/test_revoked_jti_store.py.

Honor Idempotency-Key on POST /v1/decisions (#1528)

Status: Done (2026-10-10). Idempotency-Key replays local and frontier POST /v1/decisions responses from the on-box store without a second route/upstream call; same key + different body → 409 idempotency_conflict; missing key is a no-op. Docs: headers.md, config.md. Covered by tests/unit/test_idempotency.py.

Reject stream=true on POST /v1/decisions (#1529)

Status: Done (2026-10-10). stream: true on Decisions returns HTTP 400 (invalid_request) stating streaming is unsupported; stream: false / omitted continue to succeed. OpenAPI does not advertise a stream field. Covered by tests/unit/test_decisions.py.

Paginate GET /v1/daari/audit with limit/offset (#1530)

Status: Done (2026-10-10). Audit list clamps limit/offset like keys/teams and returns entries, total, limit, offset, has_more (default cap 100). SQLite + Postgres audit stores gain count() and list(offset=). Covered by tests/unit/test_admin_read_plane.py.

OpenAI regional-processing 10% uplift when region_pin set (#1531)

Status: Done (2026-10-10). cost_usd applies 1.10× for us/eu region_pin on OpenAI models released on or after 2026-03-05 (chat, Responses headers, Decisions input-only, spend ledger). Unset pin unchanged. Docs: budgets-frontier, data-residency. Covered by tests/unit/test_regional_pricing.py.

Refresh ENTERPRISE.md after Oct 7–10 drain (#1470)

Status: Done (2026-10-10). Living PRD marks rows 55–59 Shipped, rewrites stand + next-5 for ops-docs / hot-reload / MCP allowlist, and drops watch rows that already landed. Covered by tests/unit/test_enterprise_prd_docs.py.

Doctor + Helm + auth-and-keys coverage for /v1/decisions (#1501)

Status: Done (2026-10-10). Doctor decisions tip for systemone/frontier gaps; Helm values comment for decisions/systemone via ollama+frontier; auth-and-keys lists decisions rate family. Covered by tests/unit/test_doctor_decisions.py, test_doctor_health_decisions_docs.py, test_auth_and_keys_decisions_family.py.

Filter GET /v1/models by virtual-key allowlist (#1555)

Status: Done (2026-10-10). OpenAI and Anthropic /v1/models list payloads honor key ∪ team allowed_models / model groups (master / unrestricted VK unchanged). Retrieve returns 404 outside the allowlist; lifecycle filters compose. Docs: virtual-keys. Covered by tests/unit/test_models_catalog_allowlist.py.

Track last_used_at on virtual keys + status/idle filters (#1545)

Status: Done (2026-10-10). SQLite + Postgres VK stores gain nullable last_used_at (throttled touch on successful auth). Surfaced on CLI list/show and GET /v1/daari/keys; --status / ?status= and --idle-days / ?idle_days= filter inventory. Docs: virtual-keys. Covered by tests/unit/test_vk_last_used.py.

Proactive key-expiry signals — doctor, Prometheus, webhook (#1546)

Status: Done (2026-10-10). daari doctor warns on keys expiring within alerts.key_expiry_warn_days (default 14) and counts expired-but-not-revoked. Prometheus daari_virtual_keys{status=...} gauge refreshes on scrape. Opt-in signed key_expiry webhook (Redis-deduped, fail-open). Covered by tests/unit/test_doctor_key_expiry.py and tests/unit/test_key_expiry_alerts.py.

Retired-model audit, reject metric, doctor lifecycle (#1549)

Status: Done (2026-10-10). Retired 410s write models.retired_denied audit rows and increment daari_rejects_total{kind="retired"}. daari doctor errors on configured retired tier/frontier/pool models and tips on deprecated. Covered by tests/unit/test_model_lifecycle_signals.py.

Decisions L0 cache_scope tenancy isolation test (#1554)

Status: Done (2026-10-10). Hermetic ASGI test: two cache_scope=key VKs miss each other's identical /v1/decisions bodies; same key hits L0. Optional same-team hit / cross-team miss for cache_scope=team. Covered by tests/unit/test_decisions_cache_scope.py.

Doctor tip for unsigned key_expiry webhooks (#1563)

Status: Done (2026-10-10). Optional doctor key_expiry_webhook_secret warns when alerts.key_expiry_webhook_url is set without a signing secret. Covered by tests/test_doctor.py.

Grafana panels for virtual_keys lifecycle and retired rejects (#1564)

Status: Done (2026-10-11). Stock Grafana overview adds Virtual keys by status (daari_virtual_keys{status}) and Retired model rejects (daari_rejects_total{kind="retired"}). Covered by tests/unit/test_grafana_dashboard.py.

Hermetic scrape refreshes daari_virtual_keys gauge (#1566)

Status: Done (2026-10-11). ASGI/prometheus_text_for_app pin: mixed active / expiring_soon / expired / revoked keys appear on GET /metrics; enabled key_expiry alerter posts once on scrape. Covered by tests/unit/test_metrics_virtual_keys.py.

Docs: key_expiry webhook + warn_days next to budget alerts (#1565)

Status: Done (2026-10-11). Operator guides document key_expiry_webhook_url / _secret / warn_days, payload event: key_expiry, X-Daari-Signature reuse, doctor key_expiry, and daari_virtual_keys. Covered by tests/unit/test_key_expiry_docs.py.

Docs: keys list --status / --idle-days in cli.md (#1571)

Status: Done (2026-10-11). CLI reference documents keys list --status and --idle-days plus last_used_at throttle. Covered by tests/unit/test_keys_list_cli_docs.py.

Pin keys list --help for --status and --idle-days (#1572)

Status: Done (2026-10-11). Unit test asserts daari keys list --help mentions --status and --idle-days. Covered by tests/unit/test_keys_list_cli_help.py.

Stats virtual_keys lifecycle counts (#1573)

Status: Done (2026-10-11). GET /v1/daari/stats includes virtual_keys (active / expired / expiring_soon / revoked) matching the Prom gauge. Docs: metrics-prometheus / traces-stats. Covered by tests/unit/test_stats_virtual_keys.py.

Pin Grafana dashboard panel ids unique (#1574)

Status: Done (2026-10-11). Unit pin asserts every panel id in deploy/grafana/daari-dashboard.json is unique. Covered by tests/unit/test_grafana_dashboard.py.

Doctor-health docs name key_expiry checks (#1575)

Status: Done (2026-10-11). Doc pin asserts doctor-health.md names key_expiry and key_expiry_webhook_secret. Covered by tests/unit/test_doctor_key_expiry_docs.py.

Keys export/import round-trips last_used_at, priority, cache_scope (#1581)

Status: Done (2026-10-11). daari keys export / import carry last_used_at, priority, and cache_scope (additive on schema 1; older docs default). Docs: virtual-keys.md backup section. Covered by tests/unit/test_keys_export_import.py.

RBAC analyst floors on route preview / diversity / learn / feedback (#1588)

Status: Done (2026-10-11). GET/POST /v1/daari/route/preview, GET /v1/daari/cache/diversity, GET /v1/daari/learn/stats, and POST /v1/daari/feedback require analyst via _require_role (same SSO-off viewer convention as stats). Docs: auth-and-keys role matrix. Covered by tests/unit/test_daari_admin_role_floors.py and SSO RBAC matrix.

Doctor tip for empty VK allowlist∩catalog (#1582)

Status: Done (2026-10-11). daari doctor emits optional vk_empty_allowlist when an active virtual key's non-empty allowed_models / model_groups match zero effective catalog cards. Covered by tests/unit/test_doctor_vk_empty_allowlist.py.

Local decision-model guardrail check (#1589)

Status: Done (2026-10-11). Guardrail kind: decision evaluates a local /v1/systemone predicate (no frontier egress); on_error warn/block. Docs: guardrails.md decision-model checks. Covered by tests/unit/test_guardrails_decision.py.

Configurable local decisions catalog (#1590)

Status: Done (2026-10-11). Built-in local decisions catalog adds drex-1.5 / microsoft-decision-1; decisions.local_models extends it (PATCH/ownership). Doctor tip decisions_local_models. Docs: ollama.md. Covered by tests/unit/test_decisions_local_catalog.py.

Helm NetworkPolicy + topology spread (#1591)

Status: Done (2026-10-12). Optional networkPolicy.enabled (default off) renders ingress/egress NetworkPolicy; affinity / topologySpreadConstraints pass through to the Deployment. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py.

Per-key concurrency + named per-model rate map (#1592)

Status: Done (2026-10-12). VirtualKey/Team.max_in_flight and rate_limit.models (exact/longest-prefix) with Redis/SQLite gauges; rejects kinds concurrency vs key_concurrency/team_concurrency. Docs: auth-and-keys.md. Covered by tests/unit/test_rate_limit.py.

Team cache_scope/priority in keys export/import (#1595)

Status: Done (2026-10-12). export_document / import_document round-trip team cache_scope and priority (SQLite + Postgres); legacy docs default to global/normal. Docs: virtual-keys.md Backup. Covered by tests/unit/test_keys_export_import.py.

Web UI keys/teams last_used_at, priority, cache_scope (#1596)

Status: Done (2026-10-12). Admin keys table shows last_used_at (or "never"), priority, and cache_scope; teams table shows priority and cache_scope. XSS-safe text cells. Covered by packages/web-ui/test/dashboard.test.js.

Web UI keys status and idle_days filters (#1597)

Status: Done (2026-10-12). Virtual keys section filters via status / idle_days query params on GET /v1/daari/keys; status line reflects filter and has_more. Covered by packages/web-ui/test/dashboard.test.js.

Bench Decisions / System One vs LiteLLM (#1599)

Status: Done (2026-10-12). bench_vs_litellm corpus adds /v1/decisions and /v1/systemone scenarios; skips LiteLLM when the peer lacks the route; report section with ms + speedup. Covered by tests/unit/test_bench_vs_litellm.py.

Guardrails decision-rule data plane (#1610)

Status: Done (2026-10-12). Decision predicates offload via asyncio.to_thread / async client; incremental streams defer decision rules to final flush; chat blocks write guardrail.blocked audit rows; daari_guardrail_trips_total gains a kind label; optional per-rule timeout_seconds. Covered by tests/unit/test_guardrails_decision.py.

Teams metadata export/import + Postgres parity (#1611)

Status: Done (2026-10-12). Team metadata_json (max_in_flight, model_group_budgets, model_max_budget) round-trips through keys export/import; Postgres create_team / export / import persist the same fields. Covered by tests/unit/test_keys_export_import.py.

Audit budget / rate-limit / concurrency rejects (#1616)

Status: Done (2026-10-12). Hard 402/429/503 control-plane rejects write budget.exceeded, rate_limit.exceeded, and concurrency.exceeded audit rows (scope detail, no prompts/secrets). Covered by tests/unit/test_reject_audit.py.

Operable concurrency caps — CLI/admin/Helm/gauges (#1612)

Status: Done (2026-10-13). --max-in-flight on keys create / team-create; list/export --json + admin/web-ui expose the cap; Helm rateLimit.maxInFlight / rateLimit.models; scrape daari_in_flight{scope,id} / _max. Covered by tests/unit/test_keys_max_in_flight_cli.py, tests/unit/test_metrics_in_flight_scopes.py, tests/unit/test_helm_chart.py.

Helm external netpol egress + scheduling knobs (#1613)

Status: Done (2026-10-13). NetworkPolicy Redis/Postgres egress when rateLimit.redisUrl / postgres.url are set; extraEgress; Deployment priorityClassName / nodeSelector / tolerations / initContainers / extraEnv. Docs: capacity-helm.md. Covered by tests/unit/test_helm_chart.py, tests/unit/test_capacity_helm_docs.py.

Hermetic RPM window clock for flake-free tests (#1614)

Status: Done (2026-10-13). Counter backends bucket via _wall_time() (monkeypatchable); test_rpm_returns_429 freezes mid-window and at :59.9. Covered by tests/unit/test_rate_limit_clock.py, tests/unit/test_virtual_keys.py.

Soft rate-limit warning with concurrency gauge binding (#1617)

Status: Done (2026-10-13). Soft RPM/TPM warning header and metric stay when a tighter per-key/team/model max_in_flight gauge wins X-RateLimit-* headers. Docs: headers.md. Covered by tests/unit/test_soft_warning_metrics.py.

Idempotency-Key on POST /v1/systemone (#1618)

Status: Done (2026-10-13). Idempotency-Key replays systemone with the same semantics as /v1/decisions (identical body replay, 409 on mismatch, in-flight wait). Docs: headers.md, http-api.md. Covered by tests/unit/test_idempotency.py.

Helm PrometheusRule alert pack (#1630)

Status: Done (2026-10-13). Opt-in prometheusRules PrometheusRule for backend/budget/errors/queue/guardrails/key-expiry; thresholds in values. Docs: metrics-prometheus.md. Covered by tests/unit/test_helm_chart.py.

Local decisions catalog on /v1/models and /api/tags (#1619)

Status: Done (2026-10-13). When systemone.enabled, built-in + decisions.local_models ids appear on OpenAI /v1/models and Ollama /api/tags with capability decision; VK allowlists still filter. Docs: ollama.md, config.md. Covered by tests/unit/test_models_catalog.py, tests/unit/test_ollama_decisions_tags.py.

Per-server MCP rpm/tpm ceilings (#1620)

Status: Done (2026-10-14). Optional rpm / tpm on each integrations.mcp_servers[] entry (0 = unlimited) enforced on egress tools/call and tools/list with 429, Retry-After, and X-RateLimit-Scope: mcp_server:<id>. Independent of key-family mcp ceilings (both apply). Doctor tip mcp_server_rate_limits; config.md + mcp.md. Covered by tests/unit/test_mcp_server_rate_limits.py.

Fleet-durable request/event log backend (#1631)

Status: Done (2026-10-14). observability.request_log.backend jsonl|postgres (default jsonl); postgres writes land in daari_request_log via observability.postgres_url, best-effort. Retention prune, backup catalog, doctor fleet warnings, and Helm postgres.enabled cover the shared backend. Covered by tests/unit/test_postgres_request_log.py.

MCP proxy/task Prometheus parity (#1632)

Status: Done (2026-10-14). Register mcp modality so /mcp/proxy spans use gen_ai.operation.name=mcp; proxy tools/call bumps daari_mcp_tool_calls_total (proxy.<name>) and modality request series; MCP tasks emit daari_mcp_tasks_total + OTel spans; Grafana MCP panel includes proxy/task queries. Covered by tests/unit/test_mcp_proxy_metrics.py.

Helm Grafana overview dashboard ConfigMap (#1636)

Status: Done (2026-10-14). Opt-in grafanaDashboard.enabled renders a ConfigMap embedding files/grafana/daari-dashboard.json with grafana_dashboard: "1" sidecar labels; capacity-helm + metrics docs. Covered by tests/unit/test_helm_chart.py.

Helm optional image.digest pin (#1637)

Status: Done (2026-10-14). Optional image.digest (empty default); when set, Deployment + backup CronJob use repository@sha256:… (digest wins over tag). NOTES + capacity-helm document cosign verify via RELEASING.md. Covered by tests/unit/test_helm_chart.py.

Grafana scoped in-flight panels (#1638)

Status: Done (2026-10-14). Overview dashboard panel for daari_in_flight / daari_in_flight_max with legend by scope/id; metrics-prometheus docs list the series next to the global gate. Covered by tests/unit/test_grafana_dashboard.py.

Prometheus cache invalidate counters (#1639)

Status: Done (2026-10-14). daari_cache_invalidate_total{scope,result} plus daari_cache_invalidate_entries_removed_total{layer} on admin invalidate; metrics-prometheus.md. Covered by tests/unit/test_cache_invalidate_metrics.py.

Subject erasure for Postgres request/event log (#1651)

Status: Done (2026-10-14). daari erase deletes matching daari_request_log rows when observability.request_log.backend=postgres (dry-run counts only); JSONL path unchanged. Covered by tests/unit/test_erasure.py, tests/unit/test_postgres_request_log.py.

Postgres outage fail-closed auth + budget degrade (#1652)

Status: Done (2026-10-14). Postgres VK unreachable → 503 / postgres_unavailable (never open); usage-ledger spend errors soft-allow with X-Daari-Budget-Degraded + daari_budget_degraded; /ready + doctor PG probes; outage matrix in auth-and-keys.md. Covered by tests/unit/test_postgres_outage.py.

Default OpenAI model lifecycle + shutdown_date (#1653)

Status: Done (2026-10-14). Shipped _DEFAULT_MODEL_LIFECYCLE for the 2026-10-23 and 2027-01-14 OpenAI waves (operator overrides win); OpenAI /v1/models cards expose shutdown_date; stock frontier.model → gpt-6-luna; doctor flags deprecated defaults; retired wave models 410 without operator config. Covered by tests/unit/test_default_model_lifecycle.py.

MCP per-server 429 audit + reject metrics (#1654)

Status: Done (2026-10-15). Per-server MCP rpm/tpm 429s (ingress HTTP and chat soft path) bump daari_rejects_total{kind="rate_limit"} and write rate_limit.exceeded audit with bucket=mcp_server:<id>; Retry-After / X-RateLimit-Scope unchanged. Covered by tests/unit/test_mcp_server_rate_limits.py.

Postgres store schema visibility in migrate + doctor (#1655)

Status: Done (2026-10-15). daari migrate --dry-run lists postgres.request-log / virtual-keys / audit with schema version status (unreachable reported, not omitted); apply is additive/idempotent; doctor store_migrate warns on PG lag; upgrade.md documents migrate-before-rollout. Covered by tests/unit/test_migrate_schema.py.

Enterprise audit row for admin cache invalidate (#1657)

Status: Done (2026-10-15). Successful POST /v1/daari/cache/invalidate writes cache.invalidate audit (actor, role, scope, l0/l1 removed); error path bumps Prom result=error without a success audit row; auth-and-keys catalog lists the action. Covered by tests/unit/test_cache_invalidate.py.

Anthropic /v1/models decision capability parity (#1658)

Status: Done (2026-10-15). Local decisions ids on Anthropic-shaped GET /v1/models (and retrieve) expose capabilities.decision.supported; OpenAI/Ollama tags unchanged; non-decision cards keep thinking/server_tools only. Docs: ollama.md, config.md. Covered by tests/unit/test_models_catalog.py.

MCP server rpm/tpm in config editor + Helm (#1659)

Status: Done (2026-10-15). Config editor ownership exposes integrations.mcp_servers as {id, rpm, tpm} ceiling rows (secrets omitted); PATCH merges ceilings by id; Helm integrations.mcpServers renders DAARI_INTEGRATIONS__MCP_SERVERS. Docs: mcp.md, http-api.md, config.md. Covered by tests/unit/test_config_ownership.py, tests/unit/test_helm_chart.py.

Grafana + PrometheusRule for cache_invalidate (#1660)

Status: Done (2026-10-15). Overview dashboard panels for daari_cache_invalidate_total and entries-removed; opt-in DaariCacheInvalidateErrors alert; metrics-prometheus.md alert-pack table. Covered by tests/unit/test_grafana_dashboard.py, tests/unit/test_helm_chart.py.

Label gateway errors by HTTP status (#1661)

Status: Done (2026-10-15). record_error(status=…) buckets into errors_by_status; Prometheus emits daari_errors_total{status}; stats + web-ui Errors breakdown; DaariErrorRateHigh uses sum(rate(...)). Covered by tests/unit/test_metrics.py, tests/unit/test_prometheus.py, web-ui dashboard tests.

CLI: mention virtual_keys in daari stats --help (#1583)

Status: Done (2026-10-15). daari stats docstring lists virtual_keys lifecycle counts alongside cliff maps / backend_summary / compact-to-fit. Covered by tests/unit/test_stats_cli_help.py.

Docs: LiteLLM 1.105 RC Decisions vs daari (#1584)

Status: Done (2026-10-15). compare-litellm RC watch notes Decisions API in 1.105 RC and on-box /v1/decisions + /v1/systemone; 1.104 remains GA floor. Covered by tests/unit/test_compare_litellm_1104.py.

Docs pin: virtual-keys.md GET /v1/models allowlist (#1585)

Status: Done (2026-10-15). Hermetic pin that virtual-keys.md documents filtered GET /v1/models against key/team allowlists. Covered by tests/unit/test_virtual_keys_priority_docs.py.

Lifecycle 410 on every inference facade (#1676)

Status: Done (2026-10-15). reject_retired_model gates Ollama /api/chat + /api/generate, /v1/decisions, /v1/embeddings, /v1/ocr, and /v1/systemone with the existing retired-model 410 shape; deprecated ids keep working. Covered by tests/unit/test_lifecycle_facade_410.py.

W3C traceresponse reply header (#1682)

Status: Done (2026-10-15). OTel middleware emits W3C traceresponse (00-<trace-id>-<child-id>-<flags>) when inbound/active span context exists; absent when otel off or no context. Docs: otel-genai.md. Covered by tests/unit/test_otel_genai.py, tests/unit/test_otel_docs.py.

Team-scoped admin reads (#1677)

Status: Done (2026-10-15). ?team_id= on /v1/daari/traces, /stats, /report; VK/SSO analysts restricted to their team (cross-team → 403); SQLite + Postgres trace stores filter by team_id. Docs: auth-and-keys.md. Covered by tests/unit/test_team_scoped_admin_reads.py.

Consistent cross-store backup (#1678)

Status: Done (2026-10-15). daari backup create --consistent refuses a live /health, checkpoints SQLite WAL, and embeds one shared pg_dump (stores/pg/consistent.sql). Fast path unchanged. Docs: guarantees matrix in backup-restore.md. Covered by tests/unit/test_backup_restore.py.

Doctor TLS for remote Postgres/Redis (#1679)

Status: Done (2026-10-16). daari doctor warns on non-localhost observability.postgres_url without sslmode=require+ and on redis:// remote cache.redis_url (prefer rediss://); localhost/unix sockets stay quiet. Docs: config.md + Helm values TLS examples + doctor-health. Covered by tests/unit/test_doctor_data_plane_tls.py.

Webhook signing-secret rotation (#1680)

Status: Done (2026-10-16). daari alerts rotate-secret moves current→ previous with audit row; dual digests on X-Daari-Signature during grace; doctor tips stale previous (~7d). Docs: budgets-frontier + doctor-health. Covered by tests/unit/test_webhook_secret_rotation.py.

Per-key invalid-auth lockout (#1684)

Status: Done (2026-10-16). auth.key_lockout counts invalid attempts by key prefix (independent of the IP throttle), returns 429 auth_key_locked with Retry-After, and writes auth.key_locked. Redis fail-open; loopback follows auth.exempt_loopback. Docs: config.md, auth-and-keys.md. Covered by tests/unit/test_key_lockout.py.

HTTP GET /v1/daari/events (#1683)

Status: Done (2026-10-16). Newest-first GET /v1/daari/events over the postgres request log (limit/offset/cursor, optional team_id/key_id, default 100, max 1000). JSONL-only returns 501. Docs: http-api.md, traces-stats.md. Covered by tests/unit/test_request_log_events_http.py.

Grafana errors by HTTP status (#1685)

Status: Done (2026-10-16). Overview dashboard adds "Errors by HTTP status" (sum by (status) (rate(daari_errors_total[5m]))). Helm copy stays identical to deploy/grafana/daari-dashboard.json. Covered by tests/unit/test_grafana_dashboard.py.

ChatGPT Desktop configure recipe (#1686)

Status: Done (2026-10-16). daari configure chatgpt-desktop and daari setup chatgpt-desktop print the Ollama host (port 11435, no /v1), the 11434 reminder, and X-Daari-Client-Id: chatgpt-desktop. App preferences are not written. Docs: chatgpt-desktop.md. Covered by tests/unit/test_configure_cli.py.

Per-member budget threshold webhooks (#1633)

Status: Done (2026-10-16). alerts.member_budget_thresholds (empty by default) POSTs scope=member when a named user crosses a ratio of user_daily_usd_cap on a team key. Redis dedupe fail-open matches team alerts; signing reuses X-Daari-Signature. Docs: budgets-frontier, config.md. Covered by tests/unit/test_budget_alerts.py.

LiteLLM 1.106 decision-model guardrail watch (#1598)

Status: Done (2026-10-16). compare-litellm.md watches LiteLLM 1.106's decision-model guardrail and states daari does not ship one yet (heuristic guardrails + /v1/decisions). Comparison floor stays 1.104.0 GA. Covered by tests/unit/test_compare_litellm_1104.py.

How to update

  1. Mark tasks [x] when merged to main; add commit hash in Notes when helpful.
  2. Refresh Last updated and pytest count after test changes.
  3. Do not mark done without implementation — check daari/cli/, tests/, and git log.
  4. Keep Phase B+ as preview; detail stays in ROADMAP and phase-a.md. Forward work: ROADMAP-v2.
  5. Append a new ### section above `### Docs: spend export --tier filter (#825)

Status: Done (2026-09-20). Chargeback guide documents daari spend export --tier with asr / embed examples. Covered by tests/unit/test_chargeback_docs.py.

  1. Append a new ### section above `### Cache invalidate --help documents --token (#826)

Status: Done (2026-09-20). CLI help contract asserts daari cache invalidate --help mentions --token for SSO daemon auth. Covered by tests/unit/test_cache_invalidate_cli_help.py.

Docs: cache invalidate --token (#828)

Status: Done (2026-09-20). cli.md and org-cache guide document cache invalidate --token for SSO/master Bearer. Covered by tests/unit/test_cache_invalidate_docs.py.

Unit tests for ASR frontier_fallback findings (#834)

Status: Done (2026-09-20). Direct unit coverage for asr_frontier_fallback_findings. Covered by tests/unit/test_config_validate.py.

How to update(unique` comment). If two PRs conflict here, keep both sections.