odysseus/AGENTS.md

17 KiB

AGENTS.md — Odysseus

This file is written for AI coding agents that need to understand the Odysseus project quickly. It assumes no prior knowledge of the codebase. Everything below is grounded in the actual files in this repository; if a detail drifts, trust the source file over this summary.

Project overview

Odysseus is a self-hosted AI workspace. The backend is a Python FastAPI application, and the frontend is vanilla HTML/CSS/JavaScript served as static assets. It is designed to run locally or on a private server, connecting to local models (Ollama, vLLM, llama.cpp, LM Studio, etc.) and/or remote API providers (OpenAI, etc.).

Major feature areas:

  • Chat + agents with tools, memory, skills, MCP, file uploads, and shell access.
  • Cookbook — hardware-aware model recommendations, downloads, and local serving.
  • Deep Research — multi-step web research with source reading and report generation.
  • Compare — blind side-by-side model testing.
  • Documents — writing-first editor with AI edits and Markdown/HTML/CSV support.
  • Email — IMAP/SMTP inbox with triage, tags, summaries, reminders, and reply drafts.
  • Notes, tasks, calendar, and CalDAV/CardDAV sync.
  • Gallery/image editor, themes, presets, web search, TTS/STT, and 2FA.

Repository branches:

  • dev — default branch; latest changes land here.
  • main — curated, stable branch; fast-forwarded from dev at releases.

License: AGPL-3.0-or-later (see LICENSE and ACKNOWLEDGMENTS.md).

Repository layout

Path Purpose
app.py FastAPI entry point and application orchestrator. Registers middleware, auth, static files, exception handlers, and all routers.
setup.py First-time setup script: creates data directories, initializes the SQLite database, creates the initial admin user, and copies .env.example to .env. Idempotent.
core/ Foundational runtime: database (database.py, SQLAlchemy models), auth (auth.py), session manager, middleware, exceptions, atomic I/O, platform compat.
src/ Domain logic and services. Flat package with many modules covering the agent loop, tools, LLM core, model discovery, memory/RAG, research, scheduler, cookbook, MCP manager, etc.
routes/ HTTP route handlers. Mostly flat; a few features are grouped into sub-packages (admin_wipe/, cleanup/, compare/, contacts/, gallery/, history/, memory/, note/, research/). Each module exposes a setup_*_routes(...) factory.
services/ Domain service implementations consumed by src/ and routes/: memory, search, research, shell, STT, TTS, YouTube, hardware fitting (hwfit), docs, faces.
mcp_servers/ Built-in Model Context Protocol server implementations (email, image generation, memory, RAG).
companion/ LAN companion bridge (/api/companion/*) for pairing a mobile client to a server.
scripts/ CLI tooling. scripts/odysseus is a git-style dispatcher for scripts/odysseus-* subcommands (mail, tasks, skills, notes, etc.). Shared CLI helpers live in scripts/_lib/.
static/ Frontend assets. static/index.html is the SPA shell, static/app.js is the top-level JS, static/js/ contains feature modules, and static/style.css is the single app stylesheet.
tests/ Pytest suite (~700 files). Flat today with a phased target structure described in tests/TESTING_STANDARD.md. Shared helpers in tests/helpers/.
docker/ Docker entrypoint and helper scripts (e.g. Real-ESRGAN wheel patching).
config/searxng/ Bundled SearXNG configuration template.
docs/ User-facing documentation and setup guide.
data/ Runtime data directory (gitignored). Database, auth file, uploads, caches, models, etc.
logs/ Runtime logs directory.

A more detailed architectural snapshot, including import relationships and large-module risk map, is in specs/architecture-runtime-inventory.md.

Technology stack

  • Language: Python 3.11+ (Docker image currently uses Python 3.14; CI uses 3.11; macOS installer targets 3.11+).
  • Web framework: FastAPI on Starlette, served by Uvicorn.
  • Database: SQLAlchemy ORM. Default database is SQLite (data/app.db); DATABASE_URL can point to another SQLAlchemy-compatible backend.
  • Vector store / embeddings: ChromaDB (external container in Docker; optional manual host) with fastembed as a local ONNX embedding fallback.
  • Frontend: Plain HTML/CSS/ES modules. No bundler, no React/Vue, no npm build step for the app itself. The root package.json only declares the Bombadil dev dependency for JS testing.
  • Container: Docker + Docker Compose. Base Compose stack includes Odysseus, ChromaDB, SearXNG, and ntfy.
  • Local model serving: Cookbook drives llama.cpp, vLLM, SGLang, etc., usually inside tmux sessions. Optional GPU overlays are in docker-compose.gpu-nvidia.yml and docker-compose.gpu-amd.yml.

Core Python dependencies (see requirements.txt):

  • fastapi, uvicorn, python-multipart, python-dotenv, httpx, pydantic>=2.13.4, pydantic-settings
  • SQLAlchemy, bcrypt, pyotp, cryptography, qrcode[pil]
  • chromadb-client, fastembed
  • caldav, icalendar, python-dateutil
  • pypdf, beautifulsoup4, markdown, nh3
  • mcp, croniter
  • pytest, pytest-asyncio, httpx2

Optional dependencies are listed in requirements-optional.txt (faster-whisper, DuckDuckGo search, PyMuPDF, markitdown). The app degrades gracefully when optional packages are missing.

Configuration

  • Copy .env.example to .env and edit values there. The app loads .env with UTF-8-sig encoding to tolerate BOMs from Windows editors.
  • All deployment-level overrides live in .env: APP_BIND, APP_PORT, DATABASE_URL, AUTH_ENABLED, LOCALHOST_BYPASS, SECURE_COOKIES, LLM/search endpoints, OAuth credentials, upload limits, etc.
  • ODYSSEUS_DATA_DIR moves the entire writable tree (data/). All persisted paths are defined as constants in src/constants.py; core/constants.py re-exports them for backward compatibility. Use these constants instead of building paths from __file__ or hardcoding data/.
  • Internal loopback calls use src.constants.internal_api_base(), which respects ODYSSEUS_INTERNAL_BASE / APP_PORT.

Runtime architecture

  1. app.py builds the FastAPI app, installs middleware (CORS, gzip, security headers, request timeout, interactive-activity gating, slow-request logging), and configures auth.
  2. Components are initialized by src.app_initializer.initialize_managers() and attached to app.state.
  3. Routers from routes/ are included via their setup_*_routes(...) factories.
  4. The lifespan context manager (_lifespan) runs startup tasks: default task reconciliation, skill owner backfill, MCP connection, optional warmups, background job monitor, scheduled task runner, nightly skill audit, cookbook serve lifecycle, and a periodic null-owner sweep.
  5. Shutdown cancels upload cleanup, stops the task scheduler, closes webhooks, and disconnects MCP servers.

Auth stack:

  • AUTH_ENABLED=true by default.
  • LOCALHOST_BYPASS=true lets direct loopback requests skip auth; keep it false for any network-exposed deployment.
  • Session cookie auth for browser users; Bearer ody_* API tokens with scopes for external integrations; an internal loopback token for in-process agent tools.
  • Owner scoping is enforced throughout: users see only their own rows or legacy null-owner rows.

Module divisions

core/

Foundational, widely imported modules. core/database.py defines the SQLAlchemy Base, engine, SessionLocal, and most ORM models. It is the highest-risk file to refactor because over 100 files import it.

src/

Domain logic. Notable groupings:

  • Agent execution: agent_loop.py, builtin_actions.py, action_intents.py, teacher_escalation.py, bg_monitor.py.
  • Tools: tool_schemas.py, tool_index.py, tool_implementations.py, tool_security.py, tool_policy.py, tool_utils.py, tool_execution.py, plus src/agent_tools/ (document, filesystem, subprocess, web helpers).
  • LLM: llm_core.py, model_discovery.py, model_context.py, endpoint_resolver.py, chat_handler.py, chat_processor.py.
  • Memory/RAG: memory.py, memory_provider.py, memory_vector.py, rag_manager.py, rag_singleton.py, personal_docs.py, chroma_client.py, embedding_lanes.py.
  • Research: deep_research.py, research_handler.py, research_utils.py, visual_report.py.
  • Scheduling/background: task_scheduler.py, task_endpoint.py, task_action_policy.py, event_bus.py, cookbook_serve_lifecycle.py.
  • Cookbook: cookbook_serve_lifecycle.py, plus many route helpers in routes/cookbook_*.py.
  • Settings/config: config.py, settings.py, settings_scrub.py, constants.py, runtime_paths.py.
  • Security: url_security.py, url_safety.py, prompt_security.py, rate_limiter.py, secret_storage.py, upload_limits.py, auth_helpers.py.

routes/

HTTP handlers. Most modules export a setup_*_routes(...) function returning an APIRouter. Some large domains have helper modules (e.g. email_helpers.py, email_pollers.py, cookbook_helpers.py, chat_helpers.py). A few route modules are thin shims that import the real implementation from a sub-package (e.g. routes/memory/memory_routes.py).

services/

Self-contained domain services imported by the rest of the app. Examples: services.memory, services.search, services.research, services.shell, services.stt, services.tts, services.youtube, services.hwfit.

mcp_servers/

Standalone MCP server scripts used by the MCP manager.

companion/

LAN client pairing and discovery routes.

scripts/

Command-line wrappers. scripts/odysseus dispatches to scripts/odysseus-<name>. Subcommands include mail, tasks, skills, notes, sessions, preset, theme, cookbook, research, personal, contacts, calendar, webhook, mcp, gallery, backup, docs, logs, and signature.

Build / run / deploy

git clone https://github.com/odysseus-dev/odysseus.git
cd odysseus
cp .env.example .env        # edit as needed
docker compose up -d --build

Open http://localhost:7000 once healthy. The first admin password is printed in docker compose logs odysseus.

Optional overlays (pick one GPU file, optionally combine with host Docker):

# NVIDIA GPU
COMPOSE_FILE=docker-compose.yml:docker/gpu.nvidia.yml docker compose up -d --build

# AMD ROCm
COMPOSE_FILE=docker-compose.yml:docker/gpu.amd.yml docker compose up -d --build

# Host Docker socket access (explicit opt-in)
COMPOSE_FILE=docker-compose.yml:docker/host-docker.yml docker compose up -d --build

There are also standalone GPU Compose files (docker-compose.gpu-nvidia.yml, docker-compose.gpu-amd.yml) for stack UIs that do not honor COMPOSE_FILE.

To include optional AGPL dependencies (PyMuPDF, etc.) in the image:

docker compose build --build-arg INSTALL_OPTIONAL=true
docker compose up -d

Native Linux / macOS

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python setup.py
python -m uvicorn app:app --host 127.0.0.1 --port 7000

Cookbook needs tmux for background downloads/serves.

macOS (Apple Silicon, GPU-accelerated)

./start-macos.sh

Runs on http://127.0.0.1:7860 by default because macOS AirPlay uses port 7000. The script installs Homebrew dependencies, builds venv/, and launches the server.

Windows

A portable launcher is built via build-windows-portable.ps1/launcher.py. Native Windows Python installs are less tested; Docker on Linux/WSL is the safer path.

Systemd

install-service.sh installs odysseus-ui.service. Edit the service file to match your user and working directory before running it.

Development workflow

  • Branch from dev, not main. Open PRs against dev.
  • Use Conventional Commits: type(scope): summary (e.g. fix(search): ..., feat(notes): ...).
  • Keep PRs small and focused: one bug fix or feature per PR. Do not mix file moves, formatting, refactors, and behavior changes.
  • If you are an LLM agent, the maintainers prefer that you open an issue describing the problem before opening a bulk-generated PR.

Code style guidelines

  • Constants and paths: all writable paths are centralized in src/constants.py. Never build data paths from Path(__file__), hardcoded /app/..., or literal "data/...". Import DATA_DIR, AUTH_FILE, UPLOAD_DIR, etc. If a new persisted path is needed, add a constant to src/constants.py.
  • Internal URLs: never hardcode http://localhost:7000. Use internal_api_base() from src.constants.
  • Ports, limits, model lists: reuse existing constants or add new ones in src/constants.py rather than duplicating literals.
  • Frontend style: reuse existing CSS variables (--red, --fg, --bg, --card, --border, ...), button/input/card classes, and the monochrome inline-SVG icon style. Do not introduce new colors, spacing units, or Unicode emoji in the UI. The default theme is dark; light mode work goes through the existing theme system. Visual changes require screenshots in PRs.
  • Docstrings and comments: write in English, matching the existing tone.
  • Imports: prefer top-level imports. Some cross-layer inline imports exist (notably src/tool_implementations.py importing from routes/) to avoid circular imports; do not introduce new ones without a clear reason.

Testing

  • Run the suite with the project interpreter (./venv/bin/python -m pytest), not a system Python that may lack pinned dependencies.
  • Pytest config lives in pyproject.toml; tests/conftest.py ensures the project root is on sys.path, defaults DATABASE_URL to an in-memory SQLite, and stubs optional heavy deps when absent.
  • Tests are classified at collection time with area_* and sub_* markers (see tests/_taxonomy.py and pyproject.toml).
  • Run focused subsets with tests/run_focus.py:
./venv/bin/python tests/run_focus.py --area security
./venv/bin/python tests/run_focus.py --area services --sub-area cookbook
./venv/bin/python tests/run_focus.py --fast
  • The fast lane is not slow. Mark tests slow only with duration evidence from --durations.
  • JS files can be syntax-checked with node --check static/js/<file>.js.
  • Before a PR, run at least:
    • git diff --check
    • python -m py_compile <changed .py files>
    • focused pytest on changed files and neighboring order-sensitive groups
    • docker compose config for Docker changes

Detailed testing philosophy is in tests/TESTING_STANDARD.md; helper usage is in tests/README.md.

Security considerations

Odysseus is a self-hosted workspace with privileged local tools. Treat it as admin software:

  • Keep AUTH_ENABLED=true for any network access.
  • Keep LOCALHOST_BYPASS=false outside local development.
  • Set SECURE_COOKIES=true when serving through HTTPS by a trusted reverse proxy.
  • Put Odysseus behind a trusted reverse proxy or private access layer (Cloudflare Access, Tailscale, VPN) when exposing it beyond localhost.
  • Keep ChromaDB, SearXNG, ntfy, Ollama, vLLM, llama.cpp, databases, and raw model/provider APIs internal-only.
  • Protect .env, data/, logs/, uploads, generated media, backups, auth/session files, and API keys. Never commit these.
  • Reserve admin-only access to shell, Python, file read/write, email send/read, MCP, app API, task/skill/memory management, settings, tokens, and model serving.
  • Owner scoping is a core invariant: a user must never see another user's rows. Verify owner scoping when adding routes, tools, or DB queries.
  • The repository runs secret scanning (gitleaks), workflow security linting (actionlint, zizmor), and dependency review in CI.

For vulnerability reports, see SECURITY.md.

Useful commands

# Full test suite
./venv/bin/python -m pytest

# Focused test run
./venv/bin/python tests/run_focus.py --area security

# Syntax checks
python -m compileall -q app.py core routes src services scripts tests
node --check static/js/<file>.js

# Start native dev server
python -m uvicorn app:app --host 127.0.0.1 --port 7000

# Docker
 docker compose up -d --build
 docker compose logs --tail=120 odysseus
 docker compose config

# Setup / first-run
python setup.py

# CLI examples
./scripts/odysseus mail list --folder INBOX --limit 5
./scripts/odysseus tasks list
./scripts/odysseus skills list

Common pitfalls

  • Port 7000 on macOS is used by AirPlay Receiver; use APP_PORT=7001 or ./start-macos.sh (defaults to 7860).
  • Apple Silicon + x86 Python causes "incompatible architecture" crashes when loading compiled wheels. Use an arm64 Homebrew Python (/opt/homebrew/bin/python3.11).
  • No frontend build step — changes to static/ are served directly. Browsers are told to revalidate .js/.css/.html via Cache-Control: no-cache to avoid stale modules across deploys.
  • Tests use an in-memory SQLite by default; tests that need a file-backed DB must opt in explicitly via tests.helpers.sqlite_db.make_temp_sqlite.
  • Optional dependencies may be absent in CI or a fresh venv. Code must degrade gracefully and tests must not require them.