Skip to content

fix: verify and upgrade the stack, real Graph API data, generic gateway, Redis checkpointing - #6

Merged
kowshikdev merged 5 commits into
mainfrom
fix/graph-api-and-stabilize
Jul 23, 2026
Merged

fix: verify and upgrade the stack, real Graph API data, generic gateway, Redis checkpointing#6
kowshikdev merged 5 commits into
mainfrom
fix/graph-api-and-stabilize

Conversation

@kowshikdev

Copy link
Copy Markdown
Owner

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 over three endpoints out of several dozen.

The import is now optional; only those three endpoints answer 501 naming exactly what's missing. backend.app now imports cleanly and registers 74 routes — first time, per the old CLAUDE.md's own admission that nothing had been shown to run.

SmartChatAgent itself is still not implemented — tracked separately, in progress in a follow-up PR.

Dependencies (#2)

Checked empirically rather than assumed:

  • langgraph 0.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.6 was dead weight — never imported anywhere. Removed.
  • backend/requirements.txt allowed pydantic>=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's inbox()/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.

DataRepo stays 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/users stay 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.md for the one-time Azure AD app-registration steps and scripts/graph_login.py for 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.py is the shared factory, backed by langgraph-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/nicegui were pinned but never imported anywhere. Next.js is the only real UI. Removed the dead packages.

CI (#5)

.github/workflows/ci.yml runs 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.

kowshikdev and others added 4 commits July 23, 2026 14:59
…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>
…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>
@kowshikdev
kowshikdev merged commit 28043fa into main Jul 23, 2026
2 checks passed
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.

Verify the system runs end-to-end

1 participant