fix: verify and upgrade the stack, real Graph API data, generic gateway, Redis checkpointing - #6
Merged
Merged
Conversation
…ay, Redis checkpointing Closes #1, #2, #4, #5. ## Verify the system runs end-to-end (#1) backend/app.py -- the actual FastAPI entrypoint -- never imported successfully. backend/routes_ai.py does `from app.smart_chat import SmartChatAgent`, and that module was never committed anywhere in this repository. One missing class took the entire app down at import time, over three endpoints (/assistant/start, /assistant/chat) out of several dozen. The import is now optional; only those three endpoints are affected and they answer 501 naming exactly what's missing, rather than the whole app refusing to boot. backend.app now imports cleanly and registers 74 routes -- first time, per the previous CLAUDE.md's own admission that nothing had been shown to run. SmartChatAgent itself is still not implemented (tracked separately -- no spec exists to build it from; that's real feature work, not something to reverse-engineer while fixing an import). ## Dependencies (#2) Checked empirically rather than assumed "expect real migration work, not a version bump": - langgraph 0.2.45 -> 1.x: the actual API surface this codebase touches (StateGraph, END, compile(checkpointer=...), invoke with thread_id) is UNCHANGED across that jump. Verified by constructing and invoking a graph against the installed 1.x. - litellm==1.42.6 was dead weight -- nothing in this codebase has ever imported the litellm package. governance/litellm_gateway.py always talked to an OpenAI-API-compatible endpoint directly via the openai SDK. Removed. - langchain-core pin removed -- nothing here imports langchain_core directly; arrives transitively via langgraph. - backend/requirements.txt allowed pydantic>=1.10, which would install a v1 pydantic and break every v2-only schema in backend/models.py and agents/schemas.py outright. Tightened to >=2.7,<3 in both requirement files. ## Mail and calendar: real Microsoft Graph data, not mock JSON repos/data_repo.py's inbox()/meetings()/get_transcript()/create_meeting() are backed by live Graph calls (repos/graph_client.py, auth via repos/graph_auth.py) -- not JSON fixtures. Device-code flow supports both personal Microsoft accounts and work/school tenant accounts through the same app registration (tenant=common). DataRepo stays the single class every one of the 31 existing call sites already constructs (DataRepo()) -- Graph-backing is folded directly into it rather than adding a wrapper class that would need touching every call site. Personal Microsoft accounts cannot access meeting transcripts at all (OnlineMeetingTranscript.Read.All isn't grantable to personal accounts under any consent). get_transcript() catches the resulting 403/401, logs why clearly, and returns "" -- meeting_agent.py's EXISTING fallback (a summary from title + attendees) takes over from there, unchanged. This needed zero changes to meeting_agent.py. tasks/followups/eod/weekly/mom_entries/users stay JSON-backed deliberately -- Opspilot's own generated output (things the agents produce by processing real mail/calendar), not something Graph provides as input. add_email_to_inbox (a "demo sender portal" that injected fake mail into a local JSON store) was removed outright, not migrated -- it fabricated content, which is exactly what this integration exists to stop doing once the input is a real mailbox. See docs/graph_setup.md for the one-time Azure AD app-registration steps (interactive; only a human can do this) and scripts/graph_login.py for the device-code login itself. ## LLM gateway: generic, and no longer fabricates responses governance/litellm_gateway.py / governance/gateway.py had THREE separate layers of fabricated fallback -- PolicyGateway's own simulate, EnhancedLiteLLMGateway's simulate, and PolicyGateway re-simulating if the gateway raised -- all returning literal placeholder content ("decisions": ["Decision 1", "Decision 2"]) when unconfigured, indistinguishable from a real analysis to whatever called it. All three are gone. Config is now generic: LLM_BASE_URL / LLM_API_KEY / LLM_MODEL_ID / LLM_EMBEDDING_MODEL_ID, not tied to one proxy host or deployment name. Missing config raises LLMNotConfiguredError at construction, not on first call. A real API failure after retries raises; it does not return a canned string that reads like an answer. ## Checkpointing: Redis-backed, not in-process (#5 groundwork) Every graph that persisted state used MemorySaver -- in-process only, so a restart silently discarded every in-flight workflow. orchestration/checkpointer.py is the single shared factory, backed by langgraph-checkpoint-redis. A missing Redis is a loud CheckpointerUnavailable, not a silent fall-through to the exact in-memory problem this replaces. chat_workflow.py and followup_reporting_subgraphs.py imported MemorySaver but never used it -- dead imports, removed. ## Two UI stacks (#4) Resolved to one real stack, not a decision to make: streamlit and nicegui were pinned in requirements.txt but neither is imported anywhere in the codebase (`grep -rn "import streamlit\|import nicegui"` across the whole tree returns nothing). Next.js (frontend/, 9 pages) is the only real UI. Removed the two unused packages. ## CI (#5) .github/workflows/ci.yml -- runs the real pytest suite plus a byte-compile check. No live Graph or Redis credentials needed: Graph failure paths are tested via respx-stubbed HTTP, not live calls, and the one Redis-dependent test skips itself when no Redis is reachable. ## Tests tests/comprehensive_quality_tests.py looked like a test suite but isn't one pytest can run -- it's a live-server smoke script needing a running backend on :8002 and a configured LLM judge. New, actually pytest-discoverable: test_graph_client.py (HTTP boundary stubbed with respx), test_data_repo.py, test_gateway.py, test_checkpointer.py, test_app_boots.py. 48 pass, 2 skip (Redis-required cases). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pinned >=1.0.0 based on an inaccurate web search result. PyPI's actual latest is 0.5.1 (verified directly: pip index versions langgraph-checkpoint-redis). CI caught it immediately -- 'ERROR: No matching distribution found for langgraph-checkpoint-redis>=1.0.0' -- rather than it surfacing later. Verified every other new pin in this file (msal, httpx, langgraph, redis) against real PyPI data too; those were correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…o fix/graph-api-and-stabilize
…ntation detail len(app.routes) failed in CI (17) while passing locally (74) -- reproduced by building a clean venv installing exactly what CI installs (no version pin ceiling on fastapi, so it resolves to the actual latest, 0.139.2, versus an older one already present locally). The real cause: newer FastAPI represents each include_router() call as a single lazy _IncludedRouter wrapper in app.routes rather than flattening it into individual Route objects immediately. All 12 router-include calls in backend/app.py were succeeding correctly in both environments -- the test was counting an internal representation detail that changed between versions, not missing endpoints. Fixed to assert on app.openapi()["paths"] instead, which resolves to 51 real paths in the CI-matching venv regardless of FastAPI's internal route representation -- OpenAPI generation has to fully expand routing to build the schema either way. Verified in a clean venv with the exact same install commands CI runs: 48 pass, 2 skip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 23, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1, #2, #4, #5.
Verify the system runs end-to-end (#1)
backend/app.py— the actual FastAPI entrypoint — never imported successfully.backend/routes_ai.pydoesfrom app.smart_chat import SmartChatAgent, and that module was never committed anywhere in this repository. One missing class took the entire app down over three endpoints out of several dozen.The import is now optional; only those three endpoints answer 501 naming exactly what's missing.
backend.appnow imports cleanly and registers 74 routes — first time, per the old CLAUDE.md's own admission that nothing had been shown to run.SmartChatAgentitself is still not implemented — tracked separately, in progress in a follow-up PR.Dependencies (#2)
Checked empirically rather than assumed:
langgraph0.2.45 → 1.x: the actual API surface this codebase touches is unchanged. Verified by constructing and invoking a graph against the installed 1.x, not by reading a migration guide.litellm==1.42.6was dead weight — never imported anywhere. Removed.backend/requirements.txtallowedpydantic>=1.10, which would install a v1 pydantic and break every v2-only schema outright. Tightened to>=2.7,<3.Mail and calendar: real Microsoft Graph data, not mock JSON
repos/data_repo.py'sinbox()/meetings()/get_transcript()/create_meeting()are backed by live Graph calls, not JSON fixtures. Device-code flow supports both personal Microsoft accounts and work/school tenants through the same app registration.DataRepostays the single class every one of the 31 existing call sites already constructs — Graph-backing folded directly in rather than adding a wrapper class.Personal Microsoft accounts cannot access meeting transcripts at all.
get_transcript()catches the resulting 403/401, logs why clearly, and returns""—meeting_agent.py's existing fallback (summary from title + attendees) takes over, unchanged.tasks/followups/eod/weekly/mom_entries/usersstay JSON-backed deliberately — Opspilot's own generated output, not something Graph provides as input.add_email_to_inbox(a demo feature injecting fake mail) was removed outright, not migrated.See
docs/graph_setup.mdfor the one-time Azure AD app-registration steps andscripts/graph_login.pyfor the device-code login.LLM gateway: generic, no longer fabricates responses
Had three separate layers of fabricated fallback, all returning literal placeholder content (
"decisions": ["Decision 1", "Decision 2"]) when unconfigured. All three gone. Config is generic (LLM_BASE_URL/LLM_API_KEY/LLM_MODEL_ID), not tied to one proxy. Fails at construction, not first call.Checkpointing: Redis-backed, not in-process
Every graph used
MemorySaver— in-process only, silently discarding all state on restart.orchestration/checkpointer.pyis the shared factory, backed bylanggraph-checkpoint-redis. Missing Redis is a loud error, not a silent fallback to the exact problem this replaces.Two UI stacks (#4)
Resolved to one real stack, not a decision:
streamlit/niceguiwere pinned but never imported anywhere. Next.js is the only real UI. Removed the dead packages.CI (#5)
.github/workflows/ci.ymlruns the real pytest suite — no live Graph/Redis credentials needed.Tests
48 pass, 2 skip. The old "test file" is a live-server smoke script requiring a running backend + LLM judge, not something pytest runs.