* fix(cookbook): stop-all no longer auto-retries interrupted HF downloads When C-c was sent to a running download, the bash wrapper printed DOWNLOAD_FAILED on non-zero exit (SIGINT = 130). The reconnect polling loop was still running at that point, saw the failure marker, and silently relaunched the download — making "Stop all" appear to have no effect while the UI showed the toast as if it succeeded. Fix: abort the reconnect controller immediately when the stop button is clicked (before the kill command is dispatched), and guard the auto-retry condition with !controller.signal.aborted so that any in-flight poll that completes after abort cannot trigger a retry. Fixes #1458 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Edge/Chromium sidebar section-title clipping (#1420) Sidebar section titles were vertically clipped in Chromium/Edge (fine in Firefox). Raise line-height 1 → 1.3, mirroring the existing .list-item fix. The titles are flex-centred in a fixed-height (29px) header, so this adds glyph headroom without any reflow. * Drop GPU-only flags from the CPU-only (-ngl 0) serve command (#1433) A CPU-only llama.cpp serve config still emitted --flash-attn on and exported GGML_CUDA_ENABLE_UNIFIED_MEMORY=1 (independent toggles, often left on by an Auto profile), so the command mixed "zero GPU layers" with CUDA/flash-attn and failed to start (issue #1291). Gate both on a _cpuOnly check (ngl == 0). GPU serving is unchanged — the gate only affects the ngl=0 path. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: APIKeyManager.load crashes app startup on a corrupt/wrong-shape api_keys.json (#1565) * Don't lose deep-research findings when synthesis times out (#1551) (#1562) Two problems made deep research report "No information could be gathered" even after it had extracted findings, on slow local models (reporter served a 20B via LM Studio): - _synthesize hard-capped its LLM call at timeout=60, while extraction uses the user's extraction_timeout (300s here) and the final report uses 180s. The slow model needed >60s to synthesize the round's findings, so synthesis timed out after 3 attempts. Raised it to 180s to match the final-report call. - When synthesis produced no report (it returns the unchanged, still-empty report on failure during round 1), the run hit `if not report: return "No information could be gathered…"` and discarded the findings it had already gathered. Now it falls back to a compiled report built from those findings (_fallback_report) so the user keeps the gathered material. Tests stub the LLM (no live model/DB), pin the synthesis timeout >= 180, that the fallback surfaces the findings rather than the give-up message, and that a failed synthesis preserves the previous report. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: return sorted model list on first call in group chat (#1484) Both _getModels() and getAllModels() store the sorted copy in a cache variable but return the original unsorted array on first invocation. Subsequent calls return the cache (sorted), causing inconsistent model picker ordering on first render. * fix: guard sp.destroy() in _loadScheduled against null spinner (#1495) When the scheduled folder is opened with cached data, sp is null (the loading spinner is skipped). _loadScheduled receives null and calls sp.destroy() unconditionally, crashing with TypeError. * fix: capture download exit code before test consumes it (#1497) The shell pattern 'if [ $? -eq 0 ]; ... else ... echo DOWNLOAD_FAILED (exit $?)' always reports 'exit 1' because $? inside the else branch is the exit code of the [ test command, not the download. Capture into _ec first. * fix: guard uid.decode() in auto-classify warning log against str UIDs (#1472) Every other uid.decode() call in this function uses 'uid.decode() if isinstance(uid, bytes) else str(uid)' but the warning at line 832 does bare uid.decode(), crashing with AttributeError when uid is already a string. * fix: guard AI tidy verdict against non-string LLM output (#1486) The AI document-tidy endpoint parses verdicts from LLM JSON output and calls .lower().strip() directly. If the model returns null or a non-string element, this crashes with AttributeError. Coerce to str so malformed output is treated as 'keep' instead of crashing. * fix: rename local url-quote import to avoid shadowing module-level _q (#1471) The 'from urllib.parse import quote as _q' at line 734 shadows the module-level _q (istrstrstrstrstrstrIMAPutility) imported from email_helpers, causing UnboundLocalError at lines 191 and 278 where _q is used before the local import executes. This silently breaks the entire auto-summarize pass. * fix(ui): add missing Escape key handlers for email-lib-modal, model-picker-menu, and sort dropdowns (#1487) CONTEXT: Several interactive elements lacked Escape key handlers: the email library modal was not in dynamicModals, the model-picker popup had no Escape close, and the session/model sort dropdowns only closed on outside click. CHANGE: Adds email-lib-modal to the dynamicModals array in the Escape handler so it gets dismissed via dismissModal. Adds a check for model-picker-menu.open before the modal chain to close the dropdown on Escape. Adds checks for session-sort-dropdown and model-sort-dropdown display=block before the document panel minimize fallback. WHY: Users expect consistent Escape-to-close behavior across all modals, overlays, and popups. These four were the only interactive containers in the app that ignored the Escape key entirely. IMPACT: Pressing Escape now closes the email library modal, model picker popup, session sort dropdown, and model sort dropdown -- matching user expectations and the behavior of every other modal in the app. * fix: mcp CLI _serialize crashes when stored env JSON is a list (#1609) * fix: validate_caldav_url crashes with TypeError on a non-string URL (#1608) * fix: _sanitize_export_filename crashes on a non-string session name (#1607) * fix: shared MCP truncate() crashes on None/non-string tool output (#1605) * fix: search query helpers crash on a non-string query (#1604) * fix: rag_server add/remove_directory crashes on a non-string directory arg (#1614) * fix: gallery CLI image serialization crashes on a non-string prompt (#1598) * fix: research CLI summary crashes on a non-string query (#1596) * fix: skills CLI summary crashes on a non-string description (#1595) * fix(cookbook): set UTF-8 encoding for detached download/serve subprocesses (#1599) On Windows, Python defaults to the active code page (cp1252) for subprocess I/O. HuggingFace CLI outputs U+2713 (✓) when validating tokens, which cp1252 cannot encode, crashing the download process. Set PYTHONUTF8=1 and PYTHONIOENCODING=utf-8 in the subprocess environment so Unicode output from hf/pip/llama-server is handled correctly. Fixes #1543 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: clarify host Ollama with Docker (#1594) * fix(ui): stop welcome-screen tip from clipping on narrow phones (#1612) The empty-state tip ("Add an AI endpoint from Settings...") shares a 60px max-height ceiling with the one-line .welcome-sub / .welcome-version. On narrow phones the welcome block shrink-wraps and the tip wraps to 4-5 lines (~67px), so the shared ceiling clipped its last line ("...key into the chat.") - the only setup hint a first-run user gets. Give .welcome-tip its own taller max-height (120px), placed above the @media (max-height: 650px) block so that rule's max-height:0 still collapses the tip on short viewports. .welcome-sub / .welcome-version are untouched, and desktop is unchanged (the tip is ~50px there, well under the ceiling). * Save only string personal doc paths (#1566) * Reject backup output inside data dir (#1587) * Parse all AMD GPU check args (#1586) * Require runnable dispatcher subcommands (#1585) * Require runnable dispatcher subcommands * Use modern dispatcher test loader * Remove duplicate update database body (#1584) * Skip invalid research service sources (#1583) * Reject CalDAV writeback events without uid (#1582) * Reject empty mail CLI recipients (#1581) * Reject empty mail CLI recipients * Keep mail CLI test imports isolated * Validate signature CLI PNG data (#1580) * Validate signature CLI PNG data * Keep signature CLI test imports isolated * Reject invalid preset CLI entries (#1579) * Reject invalid preset CLI entries * Use modern preset CLI test loader * Normalize session CLI counters (#1578) * Normalize session CLI counters * Keep sessions CLI test imports isolated * fix: monthly schedule label shows 21th/22th/31th (ordinal suffix for days >20) (#1577) * fix: split_chunks emits a duplicate trailing chunk for text over size-overlap (#1573) * fix: builtin_actions heuristics crash on a truthy non-string input (#1639) * fix: skill test-task / precision helpers crash on a non-dict skill (#1638) * fix: logs CLI _resolve crashes on a non-string name (#1631) * fix: _extract_skill_json crashes on a truthy non-string teacher response (#1630) * fix: tool-block parsing crashes on a non-string input (#1628) * fix: check_outbound_url crashes on a truthy non-string URL (#1623) * fix: document_actions title/content helpers crash on non-string input (#1621) * fix: inside_base_dir raises TypeError on a non-string path instead of failing closed (#1619) * fix: is_markitdown_format crashes on a non-string path (#1618) * Close app_api blocklist gap for bare /api/tokens and /api/users The blocklist prefixes had trailing slashes, so path.startswith() only matched /api/tokens/{id} but not /api/tokens itself — the bare GET (list) and POST (mint) endpoints were reachable via app_api. Same gap on /api/users (list/create/delete). Drop trailing slashes so both bare and sub-resource forms are blocked. /api/auth and /api/admin had no bare endpoints today but get the same treatment to prevent future drift. Caught by #1462. * Decrypt CalDAV password before write-back (#1731) writeback_event read cfg["password"] (the encrypted blob) and passed it straight to DAVClient, so every local create/edit/delete authenticated with the literal ciphertext, the remote rejected it, and the change never reached the server — the exact silent-write-loss this module was built to prevent. The pull path src/caldav_sync.py already decrypts; mirror that. decrypt() is a no-op on legacy plaintext. Caught by #1731. * Memory MCP delete: match exact id, not prefix (#1303) The delete action looked up the target with startswith() to capture full_id, but then re-applied startswith() to filter the list — so a short or ambiguous memory_id silently deleted every memory whose id shared the prefix, while the success message reported only the first match. The edit action used the first match and stopped, so the two actions disagreed on multi-match behaviour. Use full_id for both. Caught by #1303. * Rebuild memory vector index from the full saved set, not just the audited owner (#1747) audit_memories saves final_entries merged with other owners' entries (correct), but then rebuilt the shared vector collection from final_entries alone — wiping every other owner from semantic search until they happened to run their own audit. Keyword fallback masked it, so it degraded silently. Capture saved_entries once and rebuild from that. Caught by #1747. * Owner-scope RAG doc ids so identical chunks across users don't collide (#1738, #1760) _generate_doc_id hashed only text. add_document / add_documents_batch early-return when the id exists, so the second owner indexing a byte-identical chunk hit the first owner's id, was silently dropped, and never stored under their owner — their owner-filtered search then quietly omitted it. Hash owner + text; empty owner reproduces the legacy id, so the unowned/base index keeps existing ids and isn't re-churned. Same-owner identical chunks still dedupe. Caught by #1738 and #1760 (independent reports of the same bug). * Removed duplicate definition of _preview_text() --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Zeus-Deus <100132710+Zeus-Deus@users.noreply.github.com> Co-authored-by: lekt8 <lewistham9x@gmail.com> Co-authored-by: Afonso Coutinho <afonso@omelhorsite.pt> Co-authored-by: Paulo Victor Cordeiro <146781332+pvcordeiro@users.noreply.github.com> Co-authored-by: Zarl-prog <asimjunaidi5u@gmail.com> Co-authored-by: Wes Huber <wesleybaxterhuber@gmail.com> Co-authored-by: .bulat <its.bulat@icloud.com> Co-authored-by: Mahdi Salmanzade <mahdisalmanzadehasl@gmail.com> Co-authored-by: red person <redpersoncoding@gmail.com> Co-authored-by: pewdiepie-archdaemon <pewdiepie-archdaemon@users.noreply.github.com>
Odysseus
───────────────────────────────────────────────
⊹ ࣪ ˖ ૮( ˶ᵔ ᵕ ᵔ˶ )っ Odysseus vers. 1.0
───────────────────────────────────────────────
A self-hosted AI workspace -- meant to be the self-hosted version of the UI experience you get from ChatGPT and Claude. But with more jank and fun. Running on your own hardware, with your own data -- local-first, privacy-first, and no trojan.
Features
- Chat -- chat with any local model or API; adding them is super simple.
vLLM · llama.cpp · Ollama · OpenRouter · OpenAI - Agent -- hand it tools and let it run the whole task itself.
built on opencode · MCP · web · files · shell · skills · memory - Cookbook -- Scans your hardware, recommends models, click to download and serve.. easy!
built on llmfit · VRAM-aware · GGUF / FP8 / AWQ · fit scoring · vLLM / llama.cpp serving - Deep Research -- multi-step runs that gather, read, and synthesize sources into a nice visual report.
adapted from Tongyi DeepResearch - Compare -- a fun tool to compare models side by side. Test completely blind, no bias!
multi-model · blind test · synthesis - Documents -- YOU write the text, AI is there to assist, not the opposite.
multi-tab editor · markdown · HTML · CSV · syntax highlighting · AI edits · suggestions - Memory / Skills -- Persistent memory and skills, your agent evolves over time as it better understands you and your tasks!
ChromaDB · fastembed (ONNX) · vector + keyword retrieval · import/export - Email -- IMAP/SMTP inbox with AI triage built in: urgency reminders, auto-tag, auto-summary, auto-reply drafts, auto-spam.
IMAP · SMTP · per-account routing · CalDAV-aware - Notes & Tasks -- Quick notes with reminders, a todo list, and scheduled tasks the agent can act on.
note pings · checklist · cron-style tasks · ntfy / browser / email channels - Calendar -- Local-first calendar with CalDAV sync to Radicale / Nextcloud / Apple / Fastmail.
CalDAV pull · .ics import/export · per-calendar colors · agent-aware - Works on mobile -- looks and runs great on your phone, not just desktop.
responsive · installable (PWA) · touch gestures - Extras -- more to explore, happy if you give it a go!
image editor · theme editor · file uploads (vision + PDF) · web search · presets · sessions · 2FA
Demo
A full, hover-to-play tour lives on the landing page (docs/index.html).
Quick Start
Defaults work out of the box: clone, run, then configure models/search/email
inside Settings. Only edit .env for deployment-level overrides like
APP_BIND, APP_PORT, AUTH_ENABLED, DATABASE_URL, or a pre-seeded admin password.
On first setup, Odysseus creates an admin account (admin unless
ODYSSEUS_ADMIN_USER is set) and prints a temporary password in the terminal.
For Docker installs, the same line is in docker compose logs odysseus.
Use that for the first login, then change it in Settings.
Contributing? See CONTRIBUTING.md for setup, testing, and pull request guidelines.
Docker (recommended)
git clone https://github.com/pewdiepie-archdaemon/odysseus.git
cd odysseus
cp .env.example .env # optional, but recommended for explicit defaults
docker compose up -d --build
Open http://localhost:7000 when the containers are healthy. Docker Compose
binds the web UI to 127.0.0.1 by default. If the port is taken, set
APP_PORT=7001 in .env and recreate the container. Set APP_BIND=0.0.0.0
only when you intentionally want LAN/reverse-proxy access.
Native Linux / macOS
git clone https://github.com/pewdiepie-archdaemon/odysseus.git
cd odysseus
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
Requirements: Python 3.11+. Cookbook also needs tmux for background model
downloads and serves. The app itself is lightweight; local model serving is the
heavy part and depends on the model, runtime, GPU, and VRAM, so small hosts can
connect to API or remote model servers instead. Use --host 0.0.0.0 only when you intentionally want LAN/reverse-proxy access.
Apple Silicon
Docker on macOS cannot use the Metal GPU. For GPU-accelerated Cookbook on an M-series Mac, run Odysseus natively:
git clone https://github.com/pewdiepie-archdaemon/odysseus.git
cd odysseus
./start-macos.sh
It launches at http://127.0.0.1:7860. To expose it to your phone over a trusted LAN/VPN such as Tailscale, bind all interfaces:
ODYSSEUS_HOST=0.0.0.0 ./start-macos.sh
# then open http://<tailscale-ip>:7860
The script also reads .env at startup, so APP_BIND=0.0.0.0 and APP_PORT
set there are picked up automatically without a command-line override each run.
Keep AUTH_ENABLED=true (the default) before binding outside loopback. Do not
expose this port directly to the public internet. To build a clickable app wrapper:
./build-macos-app.sh
Cookbook, GPU, Ollama, and troubleshooting notes
Docker bundled services. Compose starts Odysseus, ChromaDB, SearXNG, and
ntfy. Odysseus and the bundled service ports bind to 127.0.0.1 by default, so
they are reachable from the host but not exposed to your LAN/public internet
unless you opt in.
Cookbook storage in Docker. Downloads live in ./data/huggingface
(~/.cache/huggingface in the container). Cookbook-installed Python CLIs and
serve engines live in ./data/local (~/.local in the container), so they
survive container recreation.
Remote servers. In Cookbook -> Settings -> Servers, generate the
Odysseus SSH key and add the public key to the remote server's
~/.ssh/authorized_keys. From the host you can also run:
ssh-copy-id -i data/ssh/id_ed25519.pub user@server
Docker GPU overlays. CPU-only users can skip this section. Cookbook can only detect GPUs that Docker exposes to the container — if the host runtime or device passthrough is not configured, Cookbook sees the iGPU, another card, or CPU instead of your intended GPU.
For NVIDIA, scripts/check-docker-gpu.sh diagnoses GPU passthrough and can
optionally install the host runtime or update .env.
# Read-only diagnostic (default — installs nothing, never edits .env):
scripts/check-docker-gpu.sh
# Print OS-specific install commands without running them:
scripts/check-docker-gpu.sh --print-install-commands
# Install NVIDIA Container Toolkit on Ubuntu/Debian (requires sudo):
scripts/check-docker-gpu.sh --install-nvidia-toolkit
# Write COMPOSE_FILE to .env (only when GPU passthrough is confirmed working):
scripts/check-docker-gpu.sh --enable-nvidia-overlay
# Full assisted setup — install toolkit, then enable overlay if passthrough works:
scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay
Safety notes:
- The app never installs host GPU runtime automatically.
- The app never edits
.envautomatically. .envis only modified when--enable-nvidia-overlayis explicitly passed, and only after GPU passthrough succeeds.--yesskips prompts but does not bypass the passthrough gate..env.bak.*backups created by--enable-nvidia-overlayare ignored by Git and the Docker build context.
To enable manually without the script, add this to .env:
COMPOSE_FILE=docker-compose.yml:docker/gpu.nvidia.yml
AMD / ROCm. AMD setup is read-only diagnostic plus manual .env edit. Run:
scripts/check-docker-amd-gpu.sh
Then add the reported values to .env, replacing RENDER_GID with your host's
numeric render group id:
COMPOSE_FILE=docker-compose.yml:docker/gpu.amd.yml
RENDER_GID=989
For NVIDIA/AMD GPU support, also read the comments in the selected overlay file: docker/gpu.nvidia.yml or docker/gpu.amd.yml.
Stack-management UIs (Portainer, Coolify, Dockhand, etc.). These tools
often accept only a single Compose file and do not reliably honor COMPOSE_FILE
or multiple -f overlays. CLI users should keep using the COMPOSE_FILE
overlay workflow above. For stack UIs, point the stack at one of the standalone
files instead, which bundle the base stack plus the GPU settings:
docker-compose.gpu-nvidia.yml— still requires the NVIDIA Container Toolkit on the host.docker-compose.gpu-amd.yml— still requires host ROCm/kfd/DRI setup, thevideo/rendergroup membership, andRENDER_GIDwhen needed.
The base docker-compose.yml plus the docker/gpu.*.yml overlays remain the
source of truth; the standalone files mirror them for single-file deployments.
Verify after enabling either overlay:
docker compose exec odysseus nvidia-smi -L # NVIDIA
docker compose exec odysseus sh -lc 'test -e /dev/kfd && test -d /dev/dri && ls -l /dev/kfd /dev/dri/renderD*' # AMD
GPU passthrough ≠ llama.cpp CUDA.
nvidia-smipassing inside the container confirms Docker GPU access, but llama.cpp also needscudartand the CUDA Toolkit at runtime. If Cookbook logs showUnable to find cudart library,Could NOT find CUDAToolkit,CUDA Toolkit not found, or tensors/layers assigned to CPU, that is a Cookbook/llama.cpp build issue — not a Docker passthrough failure. Re-install the serve engine via Cookbook → Dependencies to get a CUDA-enabled build.The same split applies to AMD/ROCm: seeing
/dev/kfdand/dev/driinside the container confirms device passthrough, not ROCm userspace or a ROCm-enabled vLLM/llama.cpp build.rocm-smiandrocminfoare not expected inside the slim Odysseus image.
Ollama with Docker. If Ollama runs on the host, add this endpoint in Settings:
http://host.docker.internal:11434/v1
Ollama must listen outside its own loopback interface:
OLLAMA_HOST=0.0.0.0:11434 ollama serve
This connects Odysseus in Docker to an Ollama server that is already running on
your host machine; it does not start Ollama inside the container.
host.docker.internal is Docker's hostname for the host machine from inside the
container. Cookbook Serve is a separate workflow for serving downloaded
models through Odysseus/llama.cpp, so Windows users with an existing Ollama
install usually only need to add the endpoint in Settings.
Useful checks.
docker compose ps
docker compose logs --tail=120 odysseus
docker compose logs odysseus | grep -E 'ChromaDB|MemoryVectorStore|DEGRADED'
macOS details. start-macos.sh installs Homebrew deps, creates the venv,
runs setup, and starts uvicorn on port 7860 because AirPlay often holds
7000. It uses llama.cpp/Ollama for Metal. vLLM/SGLang are CUDA/ROCm-only and
do not run on macOS. MLX-only models are not served by Odysseus.
Native Windows
One-command launcher (creates the venv, installs deps, runs setup, starts the server; safe to re-run):
git clone https://github.com/pewdiepie-archdaemon/odysseus.git
cd odysseus
powershell -ExecutionPolicy Bypass -File .\launch-windows.ps1
Or do it by hand:
git clone https://github.com/pewdiepie-archdaemon/odysseus.git
cd odysseus
py -3.11 -m venv venv
venv\Scripts\Activate.ps1
pip install -r requirements.txt
python setup.py
python -m uvicorn app:app --host 127.0.0.1 --port 7000
If python points at an older interpreter, use py -3.12 (or another installed
3.11+ version) for the venv step.
Requirements: Python 3.11+. The core app (chat, agent, memory, documents,
email, calendar, deep research) runs fully native. For full Cookbook background
model downloads and the agent shell tool, also install
Git for Windows (provides bash.exe).
Local GPU serving of vLLM/SGLang needs Linux/WSL2; for a local model on Windows,
Ollama is the easiest path — point Odysseus at
http://localhost:11434/v1 in Settings.
Open http://localhost:7000, log in with the generated admin password,
and configure everything else inside Settings.
Troubleshooting & Advanced Setup
chromadb-client conflicts with embedded ChromaDB
If chromadb-client (the lightweight HTTP-only package) is installed alongside the full chromadb package, Odysseus starts but ChromaDB silently falls back to HTTP-only mode and fails.
Fix: uninstall chromadb-client and force-reinstall the full package:
./venv/bin/pip uninstall chromadb-client -y
./venv/bin/pip install --force-reinstall chromadb
HTTPS + LAN/Tailscale exposure
To expose Odysseus on a local network or Tailscale with HTTPS:
- Change the bind address to
0.0.0.0in.env(APP_BIND=0.0.0.0orODYSSEUS_HOST=0.0.0.0). - Generate a locally-trusted cert for your LAN/Tailscale IPs using mkcert:
mkcert -install mkcert -cert-file cert.pem -key-file key.pem 192.168.1.100 tailscale-ip - Run
uvicornwith the generated certs:python -m uvicorn app:app --host 0.0.0.0 --port 7000 --ssl-certfile=cert.pem --ssl-keyfile=key.pem - Install the
mkcertCA on any other device you want to access Odysseus from (e.g., for iOS, email therootCA.pemto yourself, install the profile, and trust it in Certificate Trust Settings).
Optional Dependencies
requirements-optional.txt contains packages that unlock extra features. It is not installed by default.
| Package | Feature unlocked |
|---|---|
faster-whisper |
Local speech-to-text (microphone -> text) via the "local" STT provider. |
duckduckgo-search |
DuckDuckGo as a search provider option. |
PyMuPDF |
PDF page rendering in the side viewer panel and form-filling. (Note: AGPL-3.0) |
markitdown |
Office/EPUB document text extraction (converts .docx/.xlsx/.pptx/.xls/.epub to Markdown). |
Security Notes
Odysseus is a self-hosted workspace with powerful local tools: shell access, file uploads, model downloads, web research, email/calendar integrations, and API tokens. Treat it like an admin console.
- Keep
AUTH_ENABLED=truefor any network-accessible deployment. - Keep
LOCALHOST_BYPASS=falseoutside local development. - Use
SECURE_COOKIES=truewhen Odysseus is served through HTTPS by a trusted reverse proxy or private access gateway. - Do not expose it directly to the public internet without HTTPS and a trusted reverse proxy or private access layer.
- Keep
.env,data/,logs/, databases, uploads, generated media, backups, auth/session files, API keys, and model/provider tokens out of Git and private shares. They are ignored by default. - Review
data/auth.jsonafter first boot: disable open signup unless you intentionally want it, make only your own account admin, and keep demo/test accounts non-admin. - Non-admin users do not get shell/Python/file read/write by default, and admin-only routes/tools such as MCP management, API tokens, webhooks, model/cookbook serving, backup/vault, and app settings are admin-gated. Other features are controlled by per-user privileges, so review each user's privileges before exposing a deployment.
- Rotate any API keys or tokens that were ever pasted into a shared chat, demo, screenshot, or log.
- If you enable API tokens or webhooks, create separate tokens per integration and delete unused ones.
- Prefer binding manual development runs to
127.0.0.1; bind to0.0.0.0only when you intentionally want LAN/reverse-proxy access. - Keep ChromaDB, SearXNG, ntfy, Ollama, vLLM, llama.cpp, databases, and raw model/provider APIs internal-only. Expose only the authenticated Odysseus web/API entrypoint through your trusted proxy or private access layer.
- Before publishing a fork, run
git status --shortand confirm no private files from.env,data/,logs/, uploads, backups, or local databases are staged.
Private or proxied deployments
Odysseus serves plain HTTP on its app port. Docker Compose binds Odysseus and the bundled services to 127.0.0.1 by default, so a typical production/private setup is:
- Keep Odysseus on localhost, for example
127.0.0.1:7000. - Terminate HTTPS at a trusted reverse proxy or private access gateway.
- Put the authenticated Odysseus web/API entrypoint behind that layer.
- Keep raw service and model ports internal-only.
Cloudflare Access, Tailscale, Caddy, nginx, and Traefik can all fit this pattern; none are required by Odysseus. If your access layer reaches Odysseus on the same host, proxy to http://127.0.0.1:7000 and keep AUTH_ENABLED=true, LOCALHOST_BYPASS=false, and SECURE_COOKIES=true.
Common internal-only ports from the default docs/compose setup:
| Port | Service |
|---|---|
7000 |
Odysseus raw app port |
8080 |
SearXNG |
8091 |
ntfy |
8100 |
ChromaDB host port for manual/compose access |
11434 |
Ollama |
8000-8020 |
Common local model/provider APIs |
Contributing
Help is welcome. The best entry points are fresh-install testing, provider setup bugs, mobile/editor polish, docs, and small focused refactors. See ROADMAP.md for the current help-wanted list.
Configuration
Most setup is done inside the app with /setup or Settings. Use .env
for deployment-level defaults and secrets you want present before first boot.
Key settings:
| Variable | Default | Description |
|---|---|---|
LLM_HOST |
localhost |
Your LLM server (e.g. llm-host.local:8000) |
LLM_HOSTS |
-- | Comma-separated list for model discovery |
OPENAI_API_KEY |
-- | Optional OpenAI key. Prefer adding providers in the app unless pre-seeding. |
SEARXNG_INSTANCE |
http://localhost:8080 |
SearXNG URL. Docker overrides this to http://searxng:8080. |
SEARXNG_SECRET |
generated on first Docker boot | Optional SearXNG cookie/CSRF secret. Leave blank unless you need to pin it. |
APP_BIND |
127.0.0.1 |
Docker Compose host bind address for the web UI. Use 0.0.0.0 only for intentional LAN/reverse-proxy access. |
APP_PORT |
7000 |
Docker Compose host port for the web UI. |
AUTH_ENABLED |
true |
Enable/disable login |
LOCALHOST_BYPASS |
false |
Development-only auth bypass for loopback requests. Keep false for shared/network deployments. |
SECURE_COOKIES |
false |
Set true when serving Odysseus through HTTPS at a trusted proxy or private access gateway. |
DATABASE_URL |
sqlite:///./data/app.db |
Database connection string |
CHROMADB_HOST |
localhost |
ChromaDB host for vector memory. Docker overrides this to chromadb. |
CHROMADB_PORT |
8100 |
ChromaDB port for manual host runs. Docker overrides this to 8000. |
EMBEDDING_URL |
-- | OpenAI-compatible embeddings endpoint |
Built-in MCP servers (optional setup)
Odysseus auto-registers a few built-in MCP servers at startup. The npx-based ones (currently the browser server, @playwright/mcp) only start when their npm package is already in the local npx cache. If a package isn't cached, that server is skipped with a startup log message explaining what to do, so a fresh install does not block on a multi-minute npm download or hang if Playwright system deps are missing.
To enable the browser MCP (page navigation, screenshots, vision), run once:
npx -y @playwright/mcp@latest --version
That installs @playwright/mcp plus Playwright (~300MB total). Restart Odysseus and the server will register at startup.
Architecture
app.py # FastAPI entry point
core/ auth, database, middleware, constants
src/ llm_core, agent_loop, agent_tools, chat_processor, search/
routes/ chat, session, document, memory, model … endpoints
services/ docs, memory, search, hwfit (Cookbook) …
static/ index.html + app.js + style.css + js/ (modular front-end)
docs/ landing page (index.html) + preview clips
Data
All user data lives in data/ (gitignored): app.db (sessions, messages, documents),
memory.json, presets.json, uploads/, personal_docs/, chroma/, settings.json.
Star History
License
MIT -- see LICENSE and ACKNOWLEDGMENTS.md.
|
|||
|||||
| | | |||||||
)_) )_) )_) ~|~
)___))___))___)\ |
)____)____)_____)\\|
_____|____|____|_____\\\__
\ /
~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~
~^~ all aboard! ~^~
~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~





