Context Engine is an open-source structured context infrastructure for AI systems. It extracts structured facts from documents, organizes them into semantic models, and surfaces relationships as a living knowledge graph.
- Backend: FastAPI (async Python 3.12+) with SQLAlchemy 2.0, served by Uvicorn on port 8000
- Frontend: React 18 + Vite + Tailwind CSS, served on port 5000
- Database: PostgreSQL (Replit managed) via asyncpg driver
- LLM: LiteLLM gateway (OpenAI, Anthropic, etc.) — optional
app/
api/ FastAPI routes (sources, graph, query, repo, connectors)
agents/ GraphBuilderAgent (2-phase: ingest + cross-doc relationship inference)
cli/ CLI commands (ingest, query, graph, mcp, serve)
extract/ basic.py — regex extractor with temporal detection (used for Slack sync)
models.py 4 SQLAlchemy models (SourceDocument, Model, Component, Relationship)
processing/ extractor.py (LLM/regex unified), embedder
services/ IngestionService (extract + embed + upsert), QueryService
sync/ ai_session.py, slack.py
mcp/ MCP server implementation
config.py Settings via pydantic-settings (.env + env vars)
database.py SQLAlchemy async engine setup
frontend/ React app (Graph Explorer, Ask, Source Manager, Connectors)
tests/ Backend pytest tests
- Start application:
cd frontend && npm run dev— Vite dev server on port 5000 (webview) - Backend API:
uvicorn app.main:app --host localhost --port 8000 --reload— FastAPI on port 8000 (console)
- Database URL: Replit PostgreSQL injected via
DATABASE_URLenv var (auto-converted to asyncpg async URL) .envfile for local development overrides (SQLite fallback)- Optional:
LITELLM_API_KEY,EXTRACTION_MODEL,EMBEDDING_MODELfor AI features
- Target: autoscale
- Build:
cd frontend && npm install && npm run build - Run:
uvicorn app.main:app --host 0.0.0.0 --port 5000(serves pre-built frontend as static files)
The project ships with everything needed to self-host:
Dockerfile— Multi-stage build: Node 20 builds the frontend, Python 3.12 slim serves backend + static files. Single container, no nginx needed.docker-compose.yml— SQLite by default (zero external deps). Uncomment the Postgres variant at the bottom for production..env.example— Full variable reference with comments. Copy to.envbefore starting..dockerignore— Keeps image lean (~200 MB).scripts/setup.sh— One-command bare-metal setup (installs Python deps + builds frontend).scripts/start.sh— Production start (uvicornwith configurable PORT + WORKERS env vars).scripts/dev.sh— Dev mode: starts both backend (port 8000, --reload) and frontend (port 5000) concurrently.
Connectors.jsx uses window.location.origin to build the default Slack redirect URI so it works on any hostname/port automatically.
- The
DATABASE_URLReplit secret is a sync PostgreSQL URL;app/database.pyautomatically converts it topostgresql+asyncpg://for async SQLAlchemy - Tables are auto-created on startup via
Base.metadata.create_all temporalcolumn added tocomponentsviaALTER TABLE ... ADD COLUMN IF NOT EXISTSinmain.pylifespan (safe to run on existing DBs)- All datetime writes use
datetime.utcnow()(naive) — DB columns areTIMESTAMP WITHOUT TIME ZONE
Each Component has a temporal field (VARCHAR(20) DEFAULT 'unknown'):
| Value | Meaning | Node color |
|---|---|---|
current |
Present state — what is true/active right now | #0ea5e9 (sky blue) |
past |
Completed work, historical decisions | #94a3b8 (slate) |
future |
Planned/roadmap items | #a78bfa (violet) |
unknown |
Cannot be determined from context | #64748b (dark slate) |
In GraphView node styling: bgColor = temporal color, borderColor = model color. The toolbar has a temporal filter dropdown ("All time / Current / Future / Past / Unknown"). Side panel shows a coloured temporal pill, a Timeline row, and a legend.
app/sync/ai_session.py— parses and persists 1SourceDocumentapp/services/ingest.pyIngestionService.process_document()— callsExtractor, upsertsComponents withtemporal, embeds- LLM extraction uses
LITELLM_API_KEY+EXTRACTION_MODEL; falls back to regex inextractor.py._regex_extract
app/sync/slack.pysyncs messages →SourceDocumentsapp/extract/basic.pyextract_from_source_documents()runs regex patterns with_infer_temporalto classify components
app/agents/graph_builder.pyGraphBuilderAgent:- Phase 1: batch
IngestionService.process_documenton all unprocessed docs - Phase 2: cross-doc relationship inference via LLM (if enabled)
- Phase 1: batch
- Status polling:
GET /api/graph/agent-status
- Landing — Marketing page at
/ - Dashboard — Main app workspace
- Graph Explorer — Cytoscape.js graph with temporal colors, 4 filter dropdowns (model / source / status / time), "Build Graph" button, side panel with temporal badge + legend
- Ask (Query) — Natural language query with cited components
- Source Manager — Upload, browse, inspect source documents
- Connectors — Manage data source integrations
Full connector catalog (7 types):
| Type | Category | Notes |
|---|---|---|
slack |
Communication | OAuth; syncs channels/DMs/threads |
zoom |
Communication | Official API |
gdrive |
Documents | Google Drive |
gmail |
Gmail | |
codex |
AI Session | Paste/import OpenAI Codex session exports |
claude |
AI Session | Paste/import Claude conversation exports |
opencode |
AI Session | Paste/import OpenCode session exports |
AI session connectors accept pasted content (JSON OpenAI export format, Human:/Assistant: markdown, or plain text). Endpoint: POST /api/connectors/ai-session/ingest.
app/api/connectors.py— router,CONNECTOR_CATALOG,AI_SESSION_CONNECTORS, ingest endpoint (usesIngestionService)app/sync/ai_session.py— session parser + ingestorapp/sync/slack.py— Slack OAuth sync pipelinefrontend/src/pages/Connectors.jsx— UI cards, icons, AI session formfrontend/src/api/hooks.js—CONNECTOR_CATALOG,useConnectors,useIngestAISession
Fields: id, source_type, external_id, content, author, source_url, metadata_json (Text, use json.dumps), ingested_at, processed_at. Dedup by external_id only.
- Gmail: multicolor M on white badge (
color="#ffffff",boxShadow: "inset 0 0 0 1px #e5e7eb") - Google Drive: white badge, color triangle SVG
- OpenCode: terminal window SVG
ConnectorIconBadgeusesinsetbox-shadow border whencolor === "#ffffff"