Local-first log observability. repi continuously ingests logs, indexes them into a hybrid retrieval system (pgvector HNSW + Postgres FTS with weighted tsvector + pg_trgm fuzzy match), clusters related events by signature, builds incident timelines, and can optionally launch an autonomous root-cause investigation through a ReAct loop. Logs and investigations live in projects so different services / environments stay separated. Designed to run on a single machine against a local Postgres — no SaaS, no shared state.
repi/
├── cli.py # Typer CLI — lifecycle commands: init, serve, ui, stop
├── worker.py # Background file watcher — polls watcher_configs, ingests new log bytes
├── api/ # FastAPI — ingest, chat, investigate, conversations, projects, watchers, config endpoints
├── core/ # Settings (pydantic-settings), DI container, Redis cache
├── ingestion/ # Log parsing → signature clustering → embedding → upsert
├── retrieval/ # pgvector HNSW + PostgreSQL FTS, RRF fusion, query expansion, timeline + cluster views
├── investigation/ # ReAct gather loop + separate compile-LLM step, tools, evidence store
├── llm/ # Provider protocol + adapters (OpenAI, Anthropic, Mistral, Gemini, Ollama)
├── intent/ # Natural-language query → service / time-window / log-level extraction
└── models/ # SQLModel tables: projects, log_chunks, conversations, chat_messages,
# investigations, investigation_steps, investigation_chunks,
# watcher_configs, watcher_offsets
The Next.js web UI (web/) is the recommended surface. It drives the same FastAPI endpoints below.
There are two supported ways to run repi. Pick one.
The fastest path. No clone, no Python toolchain, no Node. Multi-arch images (linux/amd64 + linux/arm64) are published to GHCR on every push to main and every tagged release. The image bundles the FastAPI backend (:8000) and the Next.js UI (:3000) in one container.
Prerequisites: Docker.
# Grab the compose file (defines db + redis + the repi app image from GHCR)
curl -O https://raw.githubusercontent.com/VarunGitGood/repi/main/docker-compose.yml
# Bring up Postgres + Redis + repi (backend + UI)
docker compose up -d
# Open the UI, visit /config, paste your LLM provider key, save.
open http://localhost:3000On first start, the entrypoint seeds /app/.repi/config.json from a baked-in default into a named volume (repi_config). The seed has docker-internal infra URLs and an empty LLM key, so:
/healthreturns{"llm_configured": false, ...}and/investigatereturns 409 until a key is supplied.- Visit the Config page in the UI, pick a provider, paste your API key, save. The API hot-reloads.
- Your config persists across
docker compose down(lost only ondown -v).
Pin a release via REPI_IMAGE=ghcr.io/varungitgood/repi:0.2.0 docker compose up -d.
For editing the codebase with hot-reload + breakpoints. Docker runs only the infra (Postgres + Redis); backend and UI run on the host so changes pick up instantly.
Prerequisites: Docker, Python 3.11+, uv, Node.js.
git clone https://github.com/VarunGitGood/repi.git && cd repi
uv sync # resolve lockfile into .venv
uv run repi init --with-docker # db + redis up, prompts provider/key, writes .repi/config.json, applies schema
uv run repi serve # terminal 1: API on :8000 (auto-reload in development)
uv run repi ui # terminal 2: web UI on :3000 (Next.js HMR)
uv run repi stop # when done: tear down docker stack.repi/config.json is the sole source of truth. Shell env vars and .env files are ignored at runtime — there is no env-var fallback. The file is gitignored.
Three ways to edit it, in order of convenience:
- Web UI — visit the Config page; changes save immediately and the API hot-reloads them.
- Re-run
repi init --force— re-prompts for provider + key and rewrites the file. - Edit the JSON directly —
.repi/config.jsonis plain JSON; restart the API after editing.
REPI_ENV defaults to production. Flip to development in .repi/config.json (or via the UI) for verbose logs + --reload.
The web UI exposes everything below; the curl examples document the underlying API.
Drag-and-drop in the UI (Config page → Ingest file), or:
curl -X POST \
-F "service=my-service" \
-F "file=@/path/to/app.log" \
-F "project=default" \
http://localhost:8000/ingestproject accepts a name (get-or-create) or an existing UUID; omit it for the Default project.
For quick questions where you want a direct answer, not a full investigation. Retrieves logs, builds a timeline, asks the LLM, returns a streaming response. Lives at POST /chat.
curl -N -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"query": "errors in auth-svc in the last hour", "project_id": "<uuid>"}'In the UI, this is the default mode. Type /info (optionally with a window, e.g. /info 24h) to drop the project overview into the conversation.
When you need a structured root-cause analysis instead of a one-shot answer. Two-step flow: POST /investigate registers, attaching to the SSE stream executes the ReAct loop (the web UI does this for you). A POST with no stream consumer stays in started and never runs.
# 1. Register the investigation — returns {"id": "...", ...}
curl -X POST http://localhost:8000/investigate \
-H "Content-Type: application/json" \
-d '{"query": "why did checkout fail last friday night"}'
# 2. Attach to the stream to execute it and watch the ReAct steps live.
# Reconnecting replays persisted steps, then continues.
curl -N http://localhost:8000/investigations/{id}/streamIn the UI, toggle Deep Research to route a turn to /investigate instead of /chat — same conversation, heavier mode for that turn.
The worker watches directories for new log bytes and ingests them automatically.
- Register a watcher via the API:
curl -X POST http://localhost:8000/watchers \
-H "Content-Type: application/json" \
-d '{"service_name": "auth-svc", "watch_path": "/var/log/auth"}'- Start the worker:
uv run python -m repi.workerThe worker polls watcher_configs every 30s (WATCHER_CONFIG_REFRESH_SECS) and uses watchfiles to detect new bytes, seeking forward from the last stored offset.
All keys live in .repi/config.json (see config.example.json for the full schema). Setting them in the shell or a .env file does nothing — Settings reads only this file.
| Key | Default | Description |
|---|---|---|
REPI_ENV |
production |
production | development. Production = quiet logs, no auto-reload. |
LOG_LEVEL |
INFO |
DEBUG | INFO | WARNING | ERROR. |
DATABASE_URL |
postgresql+asyncpg://repi_user:password_here@localhost:5432/repi |
PostgreSQL asyncpg URL. The docker image ships a docker-aware default (db:5432). |
LLM_PROVIDER |
openai |
openai | anthropic | mistral | gemini | ollama |
LLM_MODEL |
provider default | Override model name |
OPENAI_API_KEY / ANTHROPIC_API_KEY / MISTRAL_API_KEY / GEMINI_API_KEY / LLM_API_KEY |
— | Provider API key. Set the one that matches your LLM_PROVIDER. |
REDIS_URL |
redis://localhost:6379 |
Redis for caching |
ENABLE_REDIS_CACHE |
true |
Set false to disable Redis |
TIME_WINDOW_INITIAL_MINUTES |
10 |
First search window for investigation |
TIME_WINDOW_EXPANSIONS |
60,360,1440 |
Progressive window expansion (minutes) |
UI_PORT |
3000 |
Port the web UI binds to (read by repi ui) |
WATCHER_CONFIG_REFRESH_SECS |
30 |
How often the worker polls for config changes |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama endpoint |
# Run all tests
uv run pytest tests/ -v
# Run a single file
uv run pytest tests/investigation/test_react_loop.py -v
# Eval harness — three scripted datasets graded against expected.json
uv run python eval/run_evals.pyBug reports and feature requests go in GitHub Issues. PRs welcome — tests should pass (uv run pytest tests/ -q) and the docker image should still build.
MIT.