v4.55 — Docs AI Assistant
A public visitor on
help.objectuve.comand an@objectuve.comteammate ondocs.objectuve.comcan each ask a question in plain language and get a synthesized answer with working citations, instead of only keyword search results.
Summary
The OBJ-3612 spike proved the retrieval architecture worked — full-context stuffing for the small guide corpus, BM25 lexical retrieval for the large internal docs corpus — but deliberately shipped nothing production-ready: no auth, no rate limiting, no dedicated model alias, no enforcing cost ceiling, no CSP. This milestone built all five, plus two things the spike never touched: getting either corpus into the Rails runtime at all (the image build context is rails_api/, so neither guide_site/ nor docs/ existed there before this milestone), and tuning citation accuracy at a chosen retrieval depth.
Seven phases shipped guide-first. Phase 1 laid the shared platform plumbing, including the product's first enforcing AI budget ceiling — Ai::BudgetGuard — where a prior mechanism only alerted Slack. Phase 2 designed one widget for both VitePress themes, settling the milestone's central question by finding that the guide site already had a tuned, empty "Ask '<query>' in your own words" row waiting to be filled, rather than building a second search entry point. Phases 3–4 shipped the public surface: an unauthenticated askGuideQuestion operation defended by three independent layers (a per-IP throttle, the new budget guard, and a kill switch), plus a fourth Roy caught in review — an unbounded question length — closed before it could exhaust the production ceiling in minutes. Phases 5–6 shipped the internal surface: a Postgres-backed corpus for docs/'s 8.7 MB / 10,667 chunks, chosen on measured memory numbers rather than argument, and a Clerk-gated, @objectuve.com-restricted askDocsQuestion operation. Phase 7 reconciled the documentation — correcting the spike ADR's standing "neither has shipped" claim, writing the runtime-controls ops page, and closing a known retrieval gotcha as fixed.
Two things are true at close that the phase list alone wouldn't show, and both are stated in the milestone's own record rather than rounded up. First, the milestone's one soft metric — citation accuracy at the chosen retrieval depth — has only ever been measured against fixtures (76.9% @ k=5, from the spike). No agent in the delivery chain holds staging LiteLLM credentials to run the live re-measurement, so it's tracked as a fast-follow (OBJ-3654) rather than blocking the ship. Second, neither assistant is reachable by a real user yet: both Firebase targets deploy from master and both call the production API, but the Rails resolvers behind them only ship on the next version tag — confirmed by querying live production directly, where both operations still 404. The engineering is complete; the release is a scheduling step, not a build gap.
Goal
Ship a Mintlify-style "ask a question, get a synthesized answer with source citations" assistant on both Objectuve doc surfaces —
help.objectuve.com(public guide) anddocs.objectuve.com(Clerk-gated internal docs) — on the retrieval architecture the OBJ-3612 spike already validated, with the five production requirements that spike explicitly deferred actually built: auth, rate limiting, a dedicated model alias, an enforcing cost ceiling, and CSP.
Scope — What Shipped
Ai::BudgetGuard— the product's first enforcing per-feature AI spend ceiling, checked insideAi::ServiceClient.chatalongside the existing category and rate-limit gates. Generic-with-opt-in: any feature with a configured ceiling gets enforcement; every existing feature is untouched.- A dedicated
docs/assistantLiteLLM model alias, registered inAi::ModelCatalog::GEMINI_ALIASES,AiUsageEvent::FEATURES, andCoachInteraction::AUDIT_EXEMPT_FEATURES— a newdocs_assistantAiUsageEvent/rate-limit category distinct fromcoaching. Settings.ai.docs_assistant_enabled— a config-gem static kill switch, shipsfalse, flippable per environment via a commit + deploy (not a live override).Ai::DocsAssistant::AnswerGuideQuestion— full-context-stuffing Interaction over a committed guide-corpus snapshot, behind the unauthenticatedaskGuideQuestionGraphQL operation. Defended by a rack-attack per-IP throttle, the budget guard, the kill switch, and a 1000-char question-length cap.- A Postgres-backed docs corpus —
DocsCorpusChunk/DocsCorpusPosting, populated by a rake indexing task fromdocs/**/*.md— andAi::DocsAssistant::AnswerDocsQuestion, a BM25-retrieval Interaction at k=5, behind the Clerk-gated,@objectuve.com-restrictedaskDocsQuestionoperation. GuideAskAssistant.vueandDocsAskAssistant.vue— the widget, in each site's VitePress theme, filling an ask-row inside the existing local-search modal rather than adding a new launcher. Nine states specified and shipped: idle, armed, thinking, answer, refusal, rate-limited, couldn't-reach, disabled-in-flight, disabled-at-load.- First-ever
Content-Security-Policyheaders (report-only) on both theenkidu-guideandenkidu-docsFirebase targets. - A CI-enforced retrieval regression check tracking recall and citation accuracy as separate metrics — recall@5 verified live at 100% (up from the spike's 90.9%); citation accuracy remains fixture-backed pending OBJ-3654.
- Documentation: the spike ADR corrected, a new
docs/operations/docs-assistant-ops.md, a newdocs/features/docs-assistant.md, the BM25 gotcha closed as fixed, and a CHANGELOG entry worded to not imply production availability.
Phases
| Phase | Name | Status | Plans | Highlights |
|---|---|---|---|---|
| 1 | AI platform plumbing | Shipped | 1 | docs/assistant alias, docs_assistant category, the product's first enforcing Ai::BudgetGuard (Codi) |
| 2 | UI-SPEC — assistant widget for both surfaces | Shipped | 1 | One design pass, nine states, fills an existing tuned search-modal row instead of a new entry point (Desi) |
| 3 | Guide assistant backend | Shipped | 1 | Unauthenticated askGuideQuestion, three abuse-defense layers plus a fourth (question length) caught in review (Codi) |
| 4 | Guide widget + CSP + ship | Shipped | 1 | help.objectuve.com live; A5 (live alias resolution) closed with real evidence (Codi) |
| 5 | Docs corpus + BM25 retrieval + citation tuning | Shipped | 1 | Postgres-backed corpus, recall 90.9%→100% @ k=5; citation accuracy explicitly left unconfirmed pending a live model (Codi) |
| 6 | Docs endpoint (Clerk-gated) + widget + CSP | Shipped | 1 | askDocsQuestion, @objectuve.com-restricted server-side; live citation re-measurement deferred to OBJ-3654; production-tag gap disclosed (Codi) |
| 7 | Documentation | Shipped | 1 | ADR corrected, ops page + feature doc, gotcha closed, CHANGELOG, zero copy drift found (Dori) |
Key Decisions
- Guide-first, both surfaces in one milestone —
help.objectuve.comhas the harder abuse story (first unauthenticated money-spending endpoint in the product);docs.objectuve.comhas the harder corpus story (8.7 MB against a 512Mi Cloud Run ceiling). Sequencing this way gave each phase one hard problem instead of two. - Postgres-backed corpus chosen on measured numbers, not argument (Phase 5) — ~1.5 MB/query for the shipped design vs. +80–320 MB for the rejected in-memory alternative, against a service with documented OOM history. Keeps Rails memory flat regardless of
docs/'s future growth. - The assistant fills an existing UI slot rather than adding a new one (Phase 2) —
GuideSearchEnhancements.vuealready reserved a bottom-pinned row for exactly this purpose; a separate launcher or an answer-row above keyword results were both argued down as competing entry points on a site whose search UX was already deliberately tuned. - Three independent abuse-defense layers on the public endpoint, all required — a per-IP throttle alone is trivially defeated by a distributed caller; the enforcing budget ceiling and the kill switch are what actually bound worst-case spend.
- Recall and citation accuracy tracked as separate CI metrics, deliberately — conflating them would hide which failure mode a regression represents. Recall passed and improved; citation accuracy is the one metric this milestone could not fully close (see Outcomes).
- Neither a live citation re-measurement nor a production tag blocked this ship — both were named, tracked, and explicitly disclosed rather than silently deferred. Josh made the call on the former; the latter is a release-timing decision, not an engineering gap.
Requirements Coverage
34/37 phase-checklist items satisfied — 2 partial (recorded, accepted substitutions), 1 deliberately deferred (OBJ-3654). This milestone has no standalone REQUIREMENTS.md; see the Milestone Audit for the full item-by-item verification.
| Category | Count | Status |
|---|---|---|
| PLAT-* (Phase 1 — platform plumbing) | 5 | All satisfied |
| UI-* (Phase 2 — UI-SPEC) | 5 | All satisfied |
| GUIDE-* (Phases 3–4 — public surface) | 13 | 12 satisfied, 1 substituted (drift-guard demonstration, accepted by Roy) |
| DOCSRV-* (Phases 5–6 — internal surface) | 11 | 9 satisfied, 1 partial (citation-accuracy confirmation), 1 deferred (live re-measurement, OBJ-3654) |
| DOC-* (Phase 7 — documentation) | 3 | All satisfied |
Full phase-checklist detail: v4.55-docs-ai-assistant-ROADMAP.md on GitHub.
Outcomes
On master today: a public visitor on help.objectuve.com can ask a question in the guide's search modal and get a cited answer, defended by three independent abuse layers; an @objectuve.com teammate on docs.objectuve.com can do the same over the internal engineering corpus, with the server verifying the Clerk session and email domain before any LLM call runs — verified against the exact failure case that matters most, a valid session on a non-Objectuve email. Both fail closed gracefully to an existing "couldn't reach" state on any error. Spend is bounded by an enforcing ceiling attributable separately from coaching traffic, and ops can disable either surface with a settings flip (requiring a commit + deploy, not a live override — a correction Phase 7 made to the milestone's own original framing).
Not yet true: a real user cannot reach either assistant yet — both Firebase targets deploy from master, but the Rails resolvers behind them only reach api.objectuve.com on the next version tag. Both operations 404 in live production as of milestone close. And the milestone's one soft metric, citation accuracy at k=5, is measured only against fixtures (76.9%) — the live number is a tracked fast-follow (OBJ-3654), not yet run.
Tech Debt
- (Phase 6/close) Citation accuracy remains fixture-backed pending a live re-measurement — OBJ-3654, blocked on staging LiteLLM credentials reaching a crew agent's sandbox.
- (close) Neither assistant is live in production — blocked on the next version tag, a release-timing decision rather than a build gap.
- (Phase 2) Two pre-existing, out-of-scope findings surfaced incidentally and filed separately:
docs_site's dark-mode brand-color contrast (OBJ-3626) and its VitePress motion-vocabulary gap (OBJ-3627). - (close) A crew-infra issue, not specific to this milestone's content: OBJ-3657, the shared bare repo's local git config attributing some commits to
Codi <josh@objectuve.com>instead of the canonical identity.
Related Artifacts
- Roadmap: v4.55-docs-ai-assistant-ROADMAP.md
- Milestone Audit: v4.55-docs-ai-assistant-MILESTONE-AUDIT.md
- Phase artifacts: .planning/phases/v4.55-docs-assistant-widget/ (Phase 2 UI-SPEC only — the other six phases were coordinated via Multica sub-issues, not GSD phase directories)
- Retrieval architecture: spike ADR
- Runtime controls: Docs & guide assistant operations
- Feature doc: Docs & Guide Assistants
- Merge PRs: #3071 (Phase 1) · #3072 (Phase 2) · #3073 (Phase 3) · #3091/#3084 (Phase 4) · #3081 (Phase 5) · #3097 (Phase 6) · #3101 (Phase 7 docs)
Related Commits
058d890a459— Phase 1: AI platform plumbing,Ai::BudgetGuard(PR #3071)05d490ffd85— Phase 2: UI-SPEC for both surfaces (PR #3072)3f579edc7fc— Phase 3: guide assistant backend + abuse controls (PR #3073)1b6192c906c— Phase 4: guide widget + CSP,help.objectuve.comlive (PR #3091)5633cf5a80c— Phase 5: docs corpus + BM25 retrieval + citation tuning (PR #3081)f58e1ba4332— Phase 6: docs endpoint (Clerk-gated) + widget + CSP (PR #3097)129fbf1df73— Phase 7: documentation (PR #3101)
Last updated: 2026-09-12