Skip to content

[codex] Add agentic RAG and local-first runtime - #23

Merged
tomnguyen103 merged 2 commits into
mainfrom
codex/agentic-rag-local-first
Jun 5, 2026
Merged

[codex] Add agentic RAG and local-first runtime#23
tomnguyen103 merged 2 commits into
mainfrom
codex/agentic-rag-local-first

Conversation

@tomnguyen103

@tomnguyen103 tomnguyen103 commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the full local-first/agentic RAG update:

  • switches the default operating model to local-first/on-demand Docker Compose, keeping VPS/cloud as optional demo infrastructure
  • adds opt-in Agentic RAG with bounded LangGraph planning over the existing hybrid retriever
  • adds citation reliability improvements, including grouped marker parsing and one safe internal repair retry before persisting or streaming generated text
  • adds durable eval-case staging/export, demo seed tooling, and related tests/docs
  • fixes local dev CORS for 127.0.0.1, chat New Chat state reset, and duplicate history-menu grouping

Root Cause / Why

Regular RAG was solid as a baseline, but exploratory prompts exposed three practical gaps: agentic orchestration needed to stay bounded and citation-safe, Gemini output sometimes used grouped citations or uncited framing text, and repeated local test chats made the sidebar noisy. The runtime docs also needed to reflect the actual preferred usage: local-first, no recurring infrastructure bill by default.

Validation

  • Backend full suite: 260 passed, 8 warnings
  • Frontend: npm run lint, npm run build, npm audit --audit-level=high
  • Eval runner: baseline,agentic --no-mlflow at 1.000 hit/recall/citation/refusal on the fake-driver set
  • Compose config rendered with dummy production env for both production and VPS override files
  • kubectl kustomize deploy/k8s
  • Workflow YAML parse for .github/workflows/*.yml
  • uv pip check --python .\.venv\Scripts\python.exe
  • python -m app.eval.export_cases --help
  • python -m app.demo.seed --help
  • normalized backup script syntax check: tr -d '\r' < deploy/cron/second-brain-backup | bash -n
  • git diff --cached --check
  • cached secret scan found only placeholder/example env references, no real secrets

Notes

This PR intentionally keeps Agentic RAG false-by-default at both backend and frontend flags. It does not add recurring infrastructure cost.

Summary by CodeRabbit

  • New Features

    • Opt-in "agentic" multi-step retrieval mode (server + UI toggle) with demo seed flow and durable eval-case recording/export.
    • Automatic citation validation + repair and agentic-aware retrieval metadata shown in messages.
  • Bug Fixes

    • Improved citation parsing to support grouped markers and tightened search cache invalidation semantics for background jobs.
    • Streaming explicitly disabled for agentic requests.
  • Documentation

    • Default runtime switched to local-first Docker Compose; updated usage, ADRs, and operational guides.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Ready to act? Review this PR in Change Stack to turn feedback into patch suggestions you can inspect and refine.

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: be1cf28b-1b30-4820-9161-97eeeba1d9a2

📥 Commits

Reviewing files that changed from the base of the PR and between 3f64629 and 41f2487.

📒 Files selected for processing (12)
  • README.md
  • backend/app/api/chat.py
  • backend/app/demo/seed.py
  • backend/app/jobs/worker.py
  • backend/tests/integration/test_jobs_worker.py
  • docs/PROGRESS.md
  • docs/USAGE.md
  • docs/adr/0015-local-first-runtime.md
  • docs/phase-6-plan.md
  • docs/project-plan.md
  • frontend/app/chat/page.tsx
  • frontend/components/ChatComposer.tsx
💤 Files with no reviewable changes (1)
  • docs/phase-6-plan.md
✅ Files skipped from review due to trivial changes (4)
  • docs/adr/0015-local-first-runtime.md
  • docs/PROGRESS.md
  • README.md
  • docs/project-plan.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • backend/app/api/chat.py
  • frontend/components/ChatComposer.tsx
  • backend/app/jobs/worker.py
  • backend/app/demo/seed.py
  • frontend/app/chat/page.tsx
  • docs/USAGE.md

📝 Walkthrough

Walkthrough

Implements opt-in agentic RAG orchestration (LangGraph), durable reviewed eval-case persistence and export, grouped-citation validation/repair integrated into chat, ingest/worker commit control with Redis cache invalidation, frontend agentic UI, CI/deploy compose rendering, and a local-first runtime decision with comprehensive docs and tests.

Changes

Agentic RAG, Eval Management, and Local-First Runtime

Layer / File(s) Summary
Agentic RAG Orchestration Service
backend/app/agentic_rag/*, backend/app/agentic_rag/service.py
Implements request-scoped LangGraph orchestration: query planning, per-subquery retrieval via hybrid_search, fusion/dedup, optional verifier path, and final answer/refusal production with retrieval metadata.
Citation Marker Parsing and Repair Validation
backend/app/chat/prompt.py, backend/app/chat/service.py
Adds grouped citation parsing (e.g., [1, 2]), centralized marker helpers, structured citation validation, and an automated repair pass that rewrites answers to comply with citation rules before persistence.
Chat Endpoint Agentic Routing and Streaming Guards
backend/app/api/chat.py, backend/app/schemas/chat.py
Routes /chat to agentic_chat when options.agentic is requested and agentic_rag_enabled is true; rejects /chat/stream agentic requests with HTTP 409.
Durable Eval Case DB Model and Migration
backend/app/db/models.py, backend/migrations/versions/0005_eval_cases.py
Adds EvalCaseRecord ORM model and Alembic migration creating eval_cases with unique case_id, optional feedback_id FK, JSONB review fields, and RLS policy.
Feedback Promotion and Validation
backend/app/api/conversations.py, backend/app/eval/dataset.py
Promotion now validates against fixed dataset and existing DB IDs via validate_new_eval_case, persists validated cases into eval_cases within a savepoint, records audit, and returns conflict on duplicates.
Eval Case Export CLI and Demo Seed
backend/app/eval/export_cases.py, backend/app/demo/seed.py
Exports reviewed DB eval_cases as YAML fragments skipping existing cases and validates against corpus; demo seed CLI runs capture→chat→feedback and prints seeded IDs.
Eval Pipeline Agentic Mode
backend/app/eval/pipeline.py, backend/app/eval/harness.py, backend/app/eval/configs.py
answer_question gains agentic flag to delegate to answer_agentic_question; eval configs include agentic variants and settings_for maps them to runtime settings.
Config, Env, and CORS
backend/app/config.py, backend/.env.example, frontend/.env.example, backend/app/main.py
New Agentic RAG settings (enabled, max_subqueries, verifier_enabled, recursion_limit), env examples, frontend build env, and CORS allow-header for admin token.
Ingest/Research Commit Control & Worker Cache
backend/app/ingest/service.py, backend/app/research/service.py, backend/app/jobs/handlers.py, backend/app/jobs/worker.py
ingest_documents/research_topic accept commit: bool to stage or commit ingests; worker supports Redis cache epoch bump after searchable job commits and wires redis_client through run_loop/main.
Frontend Agentic UI & Types
frontend/app/chat/page.tsx, frontend/components/ChatComposer.tsx, frontend/components/ConversationSidebar.tsx, frontend/components/MessageList.tsx, frontend/lib/api/types.ts
ChatComposer adds agentic toggle and banner; chat page sends non-streaming agentic requests when enabled; conversation grouping/duplicate badges; message list shows agentic retrieval badge; types add AgenticTrace and expanded ChatRetrieval.
Integration Tests
backend/tests/integration/*
Adds tests for agentic endpoint guards and success path, citation-repair persistence, demo seed flow, worker ingest rollback and cache invalidation, and promotion duplicate-race conflict.
Unit Tests
backend/tests/unit/*
Adds/extends unit tests covering parse_query_plan, grouped citation markers, CORS preflight admin header, config defaults/overrides including agentic settings, eval export validation, and grouped-marker metrics.
Docker Compose and CI
.github/workflows/ci.yml, deploy/Dockerfile.frontend, deploy/docker-compose.prod.yml, deploy/docker-compose.vps.yml.example
Adds CI compose-config job to render compose files; frontend Docker build ARG/ENV for NEXT_PUBLIC_AGENTIC_RAG_ENABLED; compose files accept templated overrides.
Docs and ADRs (Local-First Runtime)
docs/*, README.md, AGENTS.md
Adds ADR-0015 (local-first Docker Compose default) and ADR-0016 (agentic RAG v1), updates README/USAGE/phase docs/progress/case-study/implementation notes to reflect runtime shift, agentic behavior, and operational guidance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

"I nibbled at the query plan, then hopped—
subqueries stitched, small and crop-top.
Citations mended, seeds were sown,
Eval cases shelved in rows of stone.
From local burrow, the brain runs on." 🐇

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/agentic-rag-local-first

@tomnguyen103
tomnguyen103 marked this pull request as ready for review June 5, 2026 20:49
@tomnguyen103

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

264-277: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix VPS override filename mismatch in cloud deploy command.

Line 276 references deploy/docker-compose.vps.yml, but the documented tracked file is deploy/docker-compose.vps.yml.example (Line 205). This can break copy-paste deployment steps on a fresh clone unless you add an explicit “copy .example to .yml” step first.

Suggested docs patch
-DC="docker compose -p second-brain -f deploy/docker-compose.prod.yml -f deploy/docker-compose.vps.yml --env-file deploy/.env.prod"
+# First create your local override from the template:
+# cp deploy/docker-compose.vps.yml.example deploy/docker-compose.vps.yml
+DC="docker compose -p second-brain -f deploy/docker-compose.prod.yml -f deploy/docker-compose.vps.yml --env-file deploy/.env.prod"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 264 - 277, Update the cloud deployment docs where the
DC variable is defined: either change the referenced override filename from
deploy/docker-compose.vps.yml to deploy/docker-compose.vps.yml.example or add a
prior instruction to copy deploy/docker-compose.vps.yml.example to
deploy/docker-compose.vps.yml; ensure the README mentions the required step
(e.g., "cp deploy/docker-compose.vps.yml.example deploy/docker-compose.vps.yml")
so the DC command (docker compose -p second-brain -f
deploy/docker-compose.prod.yml -f deploy/docker-compose.vps.yml --env-file
deploy/.env.prod) works on a fresh clone.
backend/app/jobs/worker.py (1)

61-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set cache invalidation only after successful job completion.

Line 61 sets invalidate_search before queue.mark_done(...). If mark_done throws, the job can end as failed while Line 70 still bumps cache:search:epoch.

Suggested fix
-        result = handler(db, job.payload, embedder=embedder, llm=llm)
-        invalidate_search = isinstance(result, dict) and bool(result.get("searchable"))
+        result = handler(db, job.payload, embedder=embedder, llm=llm)
         if savepoint.is_active:
             savepoint.commit()
         queue.mark_done(db, job, result=result)
+        invalidate_search = isinstance(result, dict) and bool(result.get("searchable"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/jobs/worker.py` around lines 61 - 72, The code computes
invalidate_search before calling queue.mark_done, which can cause cache bumping
even if mark_done later fails; change the logic to determine/infer
invalidate_search only after the job has been successfully marked done (i.e.,
after queue.mark_done returns without throwing) and before db.commit, and only
then call bump_search_cache_epoch(redis_client, cfg) when invalidate_search is
true; update references around the savepoint/queue.mark_done/queue.mark_failed
block (variables: invalidate_search, result, queue.mark_done, queue.mark_failed,
bump_search_cache_epoch, savepoint, db.commit) so any exceptions from mark_done
prevent cache invalidation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/api/chat.py`:
- Around line 123-132: The code calls deps.get_llm_client(...) before checking
req.options.agentic and settings.agentic_rag_enabled, causing disabled agentic
requests to surface provider/config errors; change the flow so the agentic gate
runs before initializing the LLM client: move or delay the call to
deps.get_llm_client until after the agentic branch check (i.e., check
req.options.agentic and if true validate settings.agentic_rag_enabled and raise
the 409 via HTTPException before calling deps.get_llm_client), and ensure
agentic_chat is only invoked after the client exists when needed.

In `@backend/app/demo/seed.py`:
- Line 69: Guard the access to capture.ingest.documents before indexing: check
that capture and capture.ingest.documents exist and are non-empty (e.g., if not
capture.ingest.documents: log/skip/return) before doing document =
capture.ingest.documents[0], then only proceed with indexing using that
document; this prevents the IndexError when ingest yields no documents and keeps
the seed command from crashing.

In `@docs/adr/0015-local-first-runtime.md`:
- Line 43: Replace the awkward phrase "needs paid 24/7 uptime" with a more
standard formulation such as "requires paid 24/7 uptime" or "requires continuous
24/7 paid uptime" in the sentence that currently reads "controls without
pretending a single-user assistant needs paid 24/7 uptime." Update that sentence
so it reads e.g. "controls without pretending a single-user assistant requires
24/7 paid uptime" to improve clarity and flow.

In `@docs/phase-6-plan.md`:
- Around line 3-6: Remove the blank line immediately after the opening
blockquote so the quoted paragraph starting with "**Runtime update
(2026-06-05):** ADR-0015 supersedes the VPS default..." is contiguous inside the
blockquote; this fixes markdownlint MD028 and prevents doc-lint failures by
ensuring the entire sentence remains a single quoted paragraph.

In `@docs/project-plan.md`:
- Line 32: Update the Redis cost wording in the table row that currently reads
"| **Cache / hot path** | **Redis** for embedding cache, query cache, rate
limiting | $0 (on the VM) | named: Redis |" to align with the local-first
default runtime framing used across the docs (e.g., indicate it is local-first /
included with the runtime rather than "on the VM" and keep the
$0/no-additional-cost semantics). Ensure you edit the same table row text so the
cost column reflects "local-first (included) — $0" or equivalent phrasing
consistent with other entries.

In `@docs/USAGE.md`:
- Around line 509-512: Update the API endpoint bullets to explicitly state the
path convention used (local direct routes vs VPS proxy) by annotating each
bullet like `POST /api/admin/retention/purge` and `POST
/api/feedback/eval-candidates/{feedback_id}/promote` with a short parenthetical
e.g. "(VPS proxy: /api/...)" or "(local: /...)" and similarly clarify the
example `http://localhost:8000/data/export` as a local direct route; ensure all
bullets in this section consistently follow the same notation so readers can
unambiguously tell when to use the /api prefix versus the localhost path.

In `@frontend/app/chat/page.tsx`:
- Around line 35-49: When route changes abort in-flight work via
abortRef.current?.abort(), guard any non-stream agentic completion resolvers
from calling finishAssistant(...) (and related state updates like setIsSending
and setMessages) if that abort has occurred: before invoking finishAssistant or
updating state in the completion success paths (the handlers around
finishAssistant at the locations referenced), check
abortRef.current?.signal?.aborted and skip/return early when true so stale
responses do not land in the new route context.

In `@frontend/components/ChatComposer.tsx`:
- Around line 75-87: The agentic toggle button (the motion.button using Brain
and state via agenticMode and setAgenticMode) lacks accessible labeling and
state for assistive tech; update that button to include an explicit aria-label
(e.g., "Toggle agentic RAG") and an aria-pressed attribute bound to agenticMode
(aria-pressed={agenticMode}) and ensure the title remains or is synchronized
with the aria-label so screen readers can identify its purpose and current
state.

---

Outside diff comments:
In `@backend/app/jobs/worker.py`:
- Around line 61-72: The code computes invalidate_search before calling
queue.mark_done, which can cause cache bumping even if mark_done later fails;
change the logic to determine/infer invalidate_search only after the job has
been successfully marked done (i.e., after queue.mark_done returns without
throwing) and before db.commit, and only then call
bump_search_cache_epoch(redis_client, cfg) when invalidate_search is true;
update references around the savepoint/queue.mark_done/queue.mark_failed block
(variables: invalidate_search, result, queue.mark_done, queue.mark_failed,
bump_search_cache_epoch, savepoint, db.commit) so any exceptions from mark_done
prevent cache invalidation.

In `@README.md`:
- Around line 264-277: Update the cloud deployment docs where the DC variable is
defined: either change the referenced override filename from
deploy/docker-compose.vps.yml to deploy/docker-compose.vps.yml.example or add a
prior instruction to copy deploy/docker-compose.vps.yml.example to
deploy/docker-compose.vps.yml; ensure the README mentions the required step
(e.g., "cp deploy/docker-compose.vps.yml.example deploy/docker-compose.vps.yml")
so the DC command (docker compose -p second-brain -f
deploy/docker-compose.prod.yml -f deploy/docker-compose.vps.yml --env-file
deploy/.env.prod) works on a fresh clone.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6df475b2-359a-4156-b8d4-887c708ce004

📥 Commits

Reviewing files that changed from the base of the PR and between 97bc4ae and 3f64629.

📒 Files selected for processing (76)
  • .github/workflows/ci.yml
  • .gitignore
  • AGENTS.md
  • README.md
  • backend/.env.example
  • backend/README.md
  • backend/app/agentic_rag/__init__.py
  • backend/app/agentic_rag/service.py
  • backend/app/api/chat.py
  • backend/app/api/conversations.py
  • backend/app/chat/prompt.py
  • backend/app/chat/service.py
  • backend/app/config.py
  • backend/app/dataops/retention.py
  • backend/app/db/models.py
  • backend/app/demo/__init__.py
  • backend/app/demo/seed.py
  • backend/app/eval/configs.py
  • backend/app/eval/dataset.py
  • backend/app/eval/export_cases.py
  • backend/app/eval/harness.py
  • backend/app/eval/metrics.py
  • backend/app/eval/pipeline.py
  • backend/app/ingest/service.py
  • backend/app/jobs/handlers.py
  • backend/app/jobs/worker.py
  • backend/app/main.py
  • backend/app/research/service.py
  • backend/app/schemas/chat.py
  • backend/migrations/versions/0005_eval_cases.py
  • backend/requirements.prod.txt
  • backend/requirements.txt
  • backend/tests/integration/test_api.py
  • backend/tests/integration/test_chat.py
  • backend/tests/integration/test_demo_seed.py
  • backend/tests/integration/test_jobs_worker.py
  • backend/tests/integration/test_rls.py
  • backend/tests/integration/test_search.py
  • backend/tests/unit/test_agentic_rag.py
  • backend/tests/unit/test_api_auth.py
  • backend/tests/unit/test_chat_citation_support.py
  • backend/tests/unit/test_config.py
  • backend/tests/unit/test_eval_configs.py
  • backend/tests/unit/test_eval_export_cases.py
  • backend/tests/unit/test_eval_metrics.py
  • backend/tests/unit/test_prompt.py
  • deploy/Dockerfile.frontend
  • deploy/docker-compose.prod.yml
  • deploy/docker-compose.vps.yml.example
  • deploy/k8s/README.md
  • docs/PROGRESS.md
  • docs/USAGE.md
  • docs/adr/0001-llm-driver-local-vs-hosted.md
  • docs/adr/0005-hybrid-retrieval-rrf.md
  • docs/adr/0008-evaluation-and-mlflow.md
  • docs/adr/0011-vps-provider.md
  • docs/adr/0012-productionization-and-data-governance.md
  • docs/adr/0013-briefing-scheduling-and-worker.md
  • docs/adr/0014-kubernetes-learning-track.md
  • docs/adr/0015-local-first-runtime.md
  • docs/adr/0016-agentic-rag-v1.md
  • docs/adr/README.md
  • docs/case-study.md
  • docs/data-model/er-diagram.md
  • docs/implementation-notes.md
  • docs/k8s-evidence/11-teardown.txt
  • docs/phase-6-plan.md
  • docs/phase-7-plan.md
  • docs/project-plan.md
  • frontend/.env.example
  • frontend/app/chat/page.tsx
  • frontend/app/feedback/page.tsx
  • frontend/components/ChatComposer.tsx
  • frontend/components/ConversationSidebar.tsx
  • frontend/components/MessageList.tsx
  • frontend/lib/api/types.ts

Comment thread backend/app/api/chat.py Outdated
Comment thread backend/app/demo/seed.py
Comment thread docs/adr/0015-local-first-runtime.md Outdated
Comment thread docs/phase-6-plan.md Outdated
Comment thread docs/project-plan.md Outdated
Comment thread docs/USAGE.md Outdated
Comment thread frontend/app/chat/page.tsx
Comment thread frontend/components/ChatComposer.tsx
@tomnguyen103
tomnguyen103 merged commit 2d6c229 into main Jun 5, 2026
11 checks passed
@tomnguyen103
tomnguyen103 deleted the codex/agentic-rag-local-first branch June 5, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant