You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OpenGraph is a multi-tenant enterprise platform that turns pairs of
structured JSON knowledge bases (domain knowledge + tool catalog)
into a typed, workspace-scoped graph, then exposes a LangGraph agent that
queries that graph in natural language. Every runtime artifact — uploads,
vectors, graph, chat history, config — lives in managed cloud stores. No
local disk. No per-tenant state in the container.
What it does
Pick a template (or start blank) from a 29-template catalog
spanning healthcare, finance, legal, engineering, people, ops,
sales, marketing, customer success.
Upload your knowledge + tool JSONs.
Tune the domain profile and graph-construction knobs.
Build — the pipeline parses, extracts nodes, computes embeddings
(OpenRouter), writes the graph to Memgraph, writes vectors to
Qdrant, caches cross-KB links in Upstash.
Explore the graph visually (Cytoscape), or chat with it
from the standalone Playground using any OpenRouter model.
uploaded KB JSON files + raw source docs (authoritative)
path prefix {ws_id}/… inside a public bucket
Upstash Redis
JWKS cache, LLM cross-link cache, OpenRouter model catalog cache
keyed by {wid} / {project_id}
Auth & tenancy model
Identity: first-party JWT. POST /api/v1/auth/signup and
/auth/login exchange email + password for a 30-day HS256 JWT
(PyJWT); passwords are bcrypt-hashed
at rest. Each email maps to exactly one users row — the tenant
boundary.
Authorization: Bearer <jwt> carries the caller's identity; sub
is the user's UUID. A per-process LRU caches sub → User for 5 min
so the steady-state request path skips the SELECT.
X-Workspace-Id: <uuid> must match the workspace's user_id.
Ownership is verified in src/api/deps.py before any
work runs; mismatch returns 404 (not 403) to avoid leaking
workspace existence across tenants.
No dev fallback. Protected routes return 401 without a valid JWT
and 503 if JWT_SECRET is unset. Rotating JWT_SECRET invalidates
every live session at once.
In-memory caches (per process)
src/kb_config._WORKSPACE_CACHE — per-workspace KBConfig built by
overlaying Workspace.domain_config (JSONB) on the disk seed.
Primed in src/api/deps.py so sync consumers
(build thread, extractor, prompt loader) never deadlock re-fetching
from an already-running event loop. Invalidated per workspace by
PUT /config/domain.
src/agent/nodes._llm_cache — one ChatOpenAI client per resolved
model id; queries with different llm_model overrides don't rebuild
the client each time.
Quick start (local)
Prerequisites
Python 3.12
Node 20
Credentials for: Neon (required), OpenRouter (required), Memgraph +
Qdrant + Supabase Storage + Upstash (required for production behaviour;
the app degrades to partial functionality if any are missing).
A 48-byte JWT_SECRET (required):
python -c "import secrets; print(secrets.token_urlsafe(48))".
One-liner — bin/dev
Once dependencies are installed and .env + frontend/.env.local are
filled in, just run:
bin/dev
It starts the three processes you need — the FastAPI API, the
parse/build worker (APP_MODE=worker), and the Next.js frontend — with
interleaved tagged logs and a single Ctrl-C to stop them all. Skipping
the worker is the #1 source of "my DOCX upload is stuck at queued
forever" locally, and bin/dev removes that footgun.
Pass a component name to run just one: bin/dev backend, bin/dev worker, or bin/dev frontend.
If you prefer separate terminals, the manual instructions below still
work.
cd frontend
npm install
# frontend/.env.local — no third-party auth env vars needed
NEXT_PUBLIC_API_BASE=http://127.0.0.1:8000
BACKEND_URL=http://127.0.0.1:8000
npm run dev
# open http://localhost:3000
Body: {skip_embeddings?, skip_llm_cross_links?}. 409 if a build is already running for this workspace.
GET
/api/v1/build
Current running job id for this workspace
GET
/api/v1/build/{job_id}
Live status + log_tail (last 50 lines). Ownership-checked.
Agent query
Method
Path
Notes
POST
/api/v1/query
Body: {query, session_id?, llm_model?}. Returns the full agent state: response markdown, steps, tools_referenced, knowledge_concepts, follow_up_suggestions, traversal_path, plus the resolved llm_model. Persists the turn to chat_messages when Neon is on.
LLM catalog
Method
Path
Notes
GET
/api/v1/llm/models
Lists OpenRouter models (342 at last count). Cached 1h in Upstash. ?refresh=true busts. Filtered by OPENROUTER_ALLOWED_MODELS if set. Returns {default, models[], allowlist_active}.
Add a template: drop a new templates/<slug>.yaml with the standard
fields (slug, name, description, category, icon, domain{...}), then
touch src/templates.py to bust the lru_cache.
Graph build pipeline
Full flow of POST /api/v1/build:
Collect sources — src/api/build_jobs._collect_workspace_sources
fetches every WorkspaceFile's bytes from Vercel Blob. No local
disk is touched.
Parse — src/graph_builder/parser.parse_kb_files accepts
(filename, bytes) tuples and produces a ParsedKB per kb_source.
Extract nodes — src/graph_builder/extractor.extract_all_nodes
walks the ParsedKB into typed nodes (Chapter / Section / Concept /
Tool / Process / Table / Glossary).
Edges — src/graph_builder/edges.EdgeBuilder builds CONTAINS,
HAS_CONTENT, NEXT_STEP, INTEGRATES_WITH, IMPLEMENTS, USES_TOOL.
Cross-KB IMPLEMENTS edges are refined by an LLM call that honors
the workspace's chosen model; the result is cached in Upstash
keyed by {wid, content-hash}.
Embeddings — src/graph_builder/embeddings generates per-node
vectors via OpenRouter, upserts to Qdrant (one collection per
workspace). RELATED_TO edges are built from cosine neighbours.
Persist graph — src/graph_builder/builder writes every node +
edge into Memgraph tagged with workspace_id. A NetworkX view is
kept in memory for fast traversal; on cold start,
KnowledgeGraph.load rehydrates it directly from Memgraph (no
pickle on disk).
Build progress is persisted to build_jobs and streamed via
log_tail on GET /build/{job_id}.
GET /llm/models — browse the 342-model OpenRouter catalog (cached
1h in Upstash, optional allowlist env).
PUT /workspaces/{id}/llm — set the workspace default (validated
against the catalog before write).
POST /query — pass llm_model for a one-shot override.
The assistant reply carries llm_model — the truthful resolved model
that answered this turn, so the UI can label past turns correctly even
after the user switches models.
Frontend routes (Next.js 15 app router)
Route
Purpose
/
Landing; redirects unauth'd users to /sign-in
/sign-in
Email + password login — calls POST /api/v1/auth/login
/sign-up
Email + password signup — calls POST /api/v1/auth/signup
Standalone Playground. Independent workspace selector (chat-store). Model dropdown.
/query
Legacy redirect → /chat
/history
Audit trail (builds, chats, configs, uploads)
Auth gate: frontend/middleware.ts bounces every
unauthenticated request to /sign-in. The auth_token cookie (mirrored
in localStorage so lib/api.ts can attach Authorization: Bearer) is
the single session marker.
Operations
Render.com deploy
render.yaml provisions two stateless Docker services:
opengraph-backend — FastAPI on uvicorn, 1 worker (build-job state is
in-memory; horizontal scale requires moving _jobs to Neon — future
work).
opengraph-frontend — Next.js 15 prod build with NEXT_PUBLIC_API_BASE
baked in.