This project is not finished. Treat every part of it as unstable. Interfaces, routes, and configuration keys can change without notice.
The CopilotKit and AG-UI stage is the largest open item. The front end and the agent endpoint talk to each other, but the full chat experience is not complete. This stage will take a long time to finish. Until it is done, expect gaps in the chat UI, in tool calls, and in streamed agent state.
An Nx monorepo. A Next.js chat front end talks to a Python FastAPI agent service over the AG-UI protocol. The service runs a RAG pipeline over Qdrant and Postgres. Every model call goes through LiteLLM.
The browser calls FastAPI directly. There is no CopilotKit runtime and no Next.js proxy route between them.
flowchart LR
subgraph Browser
UI[CopilotChat v2]
AGENT[HttpAgent]
end
subgraph FastAPI["pythonapi (:8000)"]
ROUTE["/api/agent"]
CHAT[run_chat_agent]
RAG[RagPipeline]
end
subgraph Data
QD[(Qdrant)]
PG[(Postgres)]
RD[(Redis)]
end
LLM[LiteLLM :4000]
LMS[LM Studio]
LF[Langfuse :4002]
UI --> AGENT
AGENT -->|AG-UI over SSE| ROUTE
ROUTE --> CHAT
CHAT --> RAG
RAG --> QD
RAG --> PG
CHAT --> LLM
LLM --> LMS
LLM --> LF
ROUTE -.idempotency.-> RD
Key contracts:
apps/pythonapi/pythonapi/routes/agent.pyis the only contract between the two apps. It accepts aRunAgentInput. It returns AG-UI events over SSE.- The front end uses
@copilotkit/react-core/v2. The v1 remote-endpoint protocol is not used. The PythoncopilotkitSDK is deliberately absent. - Every model call goes through LiteLLM at
LLM_BASE_URL. Never call a model provider directly. - Qdrant holds chunk vectors only. Postgres holds all document, chunk, and order metadata.
| Layer | Technology |
|---|---|
| Monorepo | Nx 23, pnpm 11 (JS/TS), uv (Python), @nxlv/python plugin |
| Front end | Next.js 16, React 19, CopilotKit v2 (react-core/v2), AG-UI |
| API | FastAPI, Pydantic Settings, uvicorn, Python 3.10–3.14 |
| Agents | AG-UI protocol, LangChain, LangGraph, BAML |
| Model gateway | LiteLLM → LM Studio (OpenAI-compatible) |
| Vectors | Qdrant (dense + sparse BM25 through fastembed) |
| Relational | Postgres 16, SQLAlchemy 2.0 async, asyncpg |
| Cache | Redis 7 (idempotency, rate limits) |
| Tracing | Langfuse v2 |
| Documents | Docling (parsing and hybrid chunking) |
| Reranking | sentence-transformers cross-encoder |
| PII | Presidio analyzer and anonymizer, encrypted vault |
| Tests | pytest + pytest-asyncio (Python), Jest (React), Playwright (e2e) |
| Lint / format | Ruff (Python), ESLint + Prettier (TypeScript) |
You need Docker, Node with pnpm 11, and uv. LM Studio is optional. It serves the models that LiteLLM points to.
# 1. Install JavaScript dependencies
pnpm install
# 2. Create your environment file
Copy-Item .env.example .env
# 3. Set the required secrets in .env (see Configuration below)
# 4. Build and start the whole stack
nx up appsRun all commands from the repo root. Use PowerShell.
nx up apps # build and start every container
nx watch apps # dev stack with live sync and auto-rebuild
nx down apps # stop the compose stack
nx build apps # build the Docker images only
nx config apps # print the resolved compose confignx serve pythonapi # uvicorn on :8000
nx test pythonapi # pytest with coverage
nx lint pythonapi # ruff check
nx format pythonapi # ruff format
nx baml-generate pythonapi # regenerate pythonapi/baml_client from baml_src
nx sync pythonapi # sync the uv environment
nx lock pythonapi # refresh uv.locknx dev @agentic-executor/agentic-executor
nx build @agentic-executor/agentic-executor
nx test @agentic-executor/agentic-executor
nx e2e @agentic-executor/agentic-executor-e2enx run-many -t lint test
nx affected -t lint testpnpm add -w <package> # JavaScript or TypeScript, at the root
nx add pythonapi --name <package> # Python, updates uv.lock| Service | URL |
|---|---|
| Web app | http://localhost:4001 |
| Python API | http://localhost:8000 |
| LiteLLM | http://localhost:4000 |
| Langfuse | http://localhost:4002 |
| Qdrant | http://localhost:6333 |
| Redis | localhost:6379 |
The API mounts every router under /api. OpenAPI docs are at
http://localhost:8000/docs.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/health |
Health and integration status |
| POST | /api/agent |
AG-UI event stream over SSE |
| POST | /api/documents |
Upload and parse a document |
| GET | /api/documents |
List documents |
| GET | /api/documents/{id} |
Get one document |
| DELETE | /api/documents/{id} |
Delete a document |
| POST | /api/search |
Hybrid search over chunks |
| POST | /api/orders |
Create an order |
| GET | /api/v1/models |
OpenAI-compatible model list |
| POST | /api/v1/chat/completions |
OpenAI-compatible chat |
| POST | /api/v1/responses |
OpenAI-compatible responses |
| POST | /api/v1/embeddings |
OpenAI-compatible embeddings |
nx watch apps does the following:
- Changes under
apps/agentic-executor/srcandpublicsync into the container. Next dev reloads automatically. - Changes under
apps/pythonapi/pythonapisync into the container.uvicorn --reloadrestarts automatically. - Dependency or config changes rebuild the affected image.
Examples:
- Edit
apps/agentic-executor/src/...to update the web app without a full image rebuild. - Edit
apps/pythonapi/pythonapi/...to restart only the Python API process. - Edit
package.json,pnpm-lock.yaml, orapps/pythonapi/uv.lockto trigger a container rebuild.
Copy .env.example to .env before you start the stack. Compose reads that
file. The Python service reads the same values through the Settings class in
apps/pythonapi/pythonapi/config.py.
These keys have no default. Compose fails to start without them:
LITELLM_MASTER_KEYLITELLM_UPSTREAM_API_KEYLANGFUSE_PUBLIC_KEYLANGFUSE_SECRET_KEYNEXTAUTH_SECRETSALTENCRYPTION_KEYLANGFUSE_INIT_USER_PASSWORD
Other useful keys:
| Key | Purpose |
|---|---|
LITELLM_UPSTREAM_API_BASE |
Model backend. Defaults to LM Studio on port 1234. |
LITELLM_CHAT_BACKEND_MODEL |
Chat model behind the chat-default alias. |
LITELLM_EMBEDDING_BACKEND_MODEL |
Embedding model behind embedding-default. |
NEXT_PUBLIC_PYTHON_API_URL |
API base URL the browser calls. |
CORS_ALLOW_ORIGINS |
Comma-separated allowed origins. |
PII_VAULT_ENCRYPTION_KEY |
Key for the encrypted PII vault. |
HF_TOKEN |
Hugging Face token for model downloads. |
Optional integrations degrade, they do not crash. Redis, Langfuse, and
Postgres can all stay unset. Qdrant always works through embedded :memory:.
Three providers start in mock mode. Change them when you want real models:
EMBEDDING_PROVIDER:mockoropenai_compatibleRERANK_PROVIDER:mockorcross_encoderGENERATION_PROVIDER:mockorbaml
The compose stack runs a self-hosted Langfuse v2 service with its own
Postgres. The Python container uses the internal URL http://langfuse:3000.
The UI is exposed at http://localhost:4002.
The stack creates these on first start:
- organization id
local-org - project id
pythonapi - API keys that match
LANGFUSE_PUBLIC_KEYandLANGFUSE_SECRET_KEY
The health route reports Langfuse details only when the client is configured.
apps/
├── agentic-executor/ # Next.js 16 front end (port 4001)
│ ├── specs/ # Jest component tests
│ └── src/app/
│ ├── layout.tsx # Wraps the tree in CopilotProvider
│ ├── page.tsx
│ └── features/chat/ # copilot_provider.tsx, chat_window.tsx
├── agentic-executor-e2e/ # Playwright end-to-end tests
└── pythonapi/ # FastAPI service (port 8000)
├── baml_src/ # BAML source: clients, generators, rag
├── litellm.config.yaml # LiteLLM model aliases
├── tests/ # pytest suite
└── pythonapi/
├── main.py # App assembly only
├── config.py # Settings. All env vars land here.
├── dependencies.py # FastAPI DI providers
├── baml_client/ # GENERATED — never edit
├── core/ # Business logic. No HTTP, no I/O clients.
├── infrastructure/ # External client builders
├── repositories/ # SQLAlchemy and Qdrant persistence
├── models/ # Pydantic schemas and SQLAlchemy ORM
├── routes/ # Thin HTTP layer
├── middleware/ # idempotency.py
└── workers/ # embedding_worker.py
baml_client/ is generated from baml_src/. Regenerate it with
nx baml-generate pythonapi. Never edit it by hand.
Layer rule: routes/ → core/ → repositories/ → infrastructure/. Never
import in the other direction.
See CLAUDE.md for the full rules. The short version:
- Python follows PEP 8. Functions and variables use
snake_case. Classes usePascalCase. - TypeScript files use
snake_case.tsx. Components and types usePascalCase. Variables usecamelCase. - Every I/O path is
async. No blocking call sits inside anasync def. - All Postgres access uses SQLAlchemy 2.0 async. No raw SQL anywhere.
- No abbreviations. Write
cancellation_token, notct. - No magic strings. Config goes in
Settings. UI text goes in module constants. - Diagrams use Mermaid. No ASCII box art.
- Ruff enforces line length 88. Run
nx format pythonapibefore you commit.