Setup Guide
Complete instructions for getting Seraph fully running with all features.
Prerequisites
| Requirement | Why |
|---|---|
| Docker Desktop | Runs backend, frontend, and sandbox containers |
| uv | Python package manager (used by the daemon and scripts) |
| macOS 13+ (Ventura) | Required for the native screen daemon (PyObjC, AppleScript) |
| Python 3.12+ | Daemon runtime (outside Docker) |
| OpenRouter account | LLM API access (powers the agent) |
Configuration
All settings live in .env.dev at the project root. The only required value is your API key:
OPENROUTER_API_KEY=<openrouter_api_key>
Optional model settings
| Variable | Default | Description |
|---|---|---|
DEFAULT_MODEL | openrouter/anthropic/claude-sonnet-4 | LLM model for the agent (any OpenRouter model) |
MODEL_TEMPERATURE | 0.7 | Sampling temperature |
MODEL_MAX_TOKENS | 4096 | Max tokens per response |
AGENT_MAX_STEPS | 10 | Max reasoning steps per turn |
DEBUG | false | Enable debug logging |
Optional runtime routing settings
These control which model/profile a specific runtime path uses, plus how that path falls back if its primary target fails:
| Variable | Default | Description |
|---|---|---|
LOCAL_MODEL | (empty) | Model id for the local runtime profile |
LOCAL_LLM_API_BASE | (empty) | API base for the local runtime profile |
LOCAL_RUNTIME_PATHS | (empty) | Comma-separated runtime paths or glob patterns that should prefer the local profile |
RUNTIME_PROFILE_PREFERENCES | (empty) | Semicolon-separated `runtime_path=profile_a |
RUNTIME_POLICY_INTENTS | (empty) | Semicolon-separated `runtime_path=intent_a |
RUNTIME_POLICY_SCORES | (empty) | Semicolon-separated `runtime_path=intent_a:weight |
RUNTIME_MODEL_OVERRIDES | (empty) | Comma-separated runtime_path=model or runtime_path=profile:model entries; runtime_path may be exact or a glob |
RUNTIME_FALLBACK_OVERRIDES | (empty) | Semicolon-separated `runtime_path=model_a |
PROVIDER_CAPABILITY_OVERRIDES | (empty) | Semicolon-separated `model_or_glob=capability_a |
FALLBACK_MODEL | (empty) | Legacy single fallback target |
FALLBACK_MODELS | (empty) | Comma-separated ordered global fallback chain |
FALLBACK_LLM_API_BASE | (empty) | Optional API base override for fallback calls |
Examples:
LOCAL_RUNTIME_PATHS=chat_agent,session_consolidation,daily_briefing
RUNTIME_PROFILE_PREFERENCES="chat_agent=local|default;session_consolidation=local|default"
RUNTIME_POLICY_INTENTS="chat_agent=local_first|reasoning|tool_use;session_title_generation=fast|cheap"
RUNTIME_POLICY_SCORES="chat_agent=reasoning:5|tool_use:4;session_title_generation=fast:5|cheap:3"
RUNTIME_MODEL_OVERRIDES=chat_agent=default:openai/gpt-4.1-mini,session_consolidation=default:openai/gpt-4o-mini
RUNTIME_FALLBACK_OVERRIDES="chat_agent=openai/gpt-4.1-mini|openai/gpt-4.1-nano;session_title_generation=openai/gpt-4o-mini|openai/gpt-4.1-mini"
PROVIDER_CAPABILITY_OVERRIDES="openrouter/anthropic/claude-sonnet-4=reasoning|tool_use;openai/gpt-4o-mini=fast|cheap;openai/gpt-4.1-mini=reasoning|tool_use"
Pattern-based examples for dynamic runtime paths:
LOCAL_RUNTIME_PATHS=mcp_*
RUNTIME_PROFILE_PREFERENCES="mcp_*=local|default"
RUNTIME_POLICY_INTENTS=mcp_*=local_first|tool_use
RUNTIME_POLICY_SCORES=mcp_*=tool_use:5
RUNTIME_MODEL_OVERRIDES=mcp_*=openai/gpt-4.1-mini,mcp_github_actions=local:ollama/coder
RUNTIME_FALLBACK_OVERRIDES="mcp_*=openai/gpt-4.1-mini|openai/gpt-4.1-nano;mcp_github_actions=openai/gpt-4o-mini|openai/gpt-4.1-mini"
PROVIDER_CAPABILITY_OVERRIDES=openai/gpt-4.1-mini=reasoning|tool_use;openai/gpt-4o-mini=fast|cheap
Current built-in runtime paths include chat_agent, onboarding_agent, strategist_agent, context_window_summary, session_title_generation, session_consolidation, daily_briefing, evening_review, activity_digest, and weekly_activity_review.
Optional scheduling settings
These control when proactive features run (briefings, reviews, working hours):
| Variable | Default | Description |
|---|---|---|
USER_TIMEZONE | UTC | Your timezone (e.g., America/New_York) |
WORKING_HOURS_START | 9 | Hour (24h) when working hours begin |
WORKING_HOURS_END | 17 | Hour (24h) when working hours end |
MORNING_BRIEFING_HOUR | 8 | Hour for the daily morning briefing |
EVENING_REVIEW_HOUR | 21 | Hour for the evening review |
MEMORY_CONSOLIDATION_INTERVAL_MIN | 30 | Minutes between memory consolidation runs |
GOAL_CHECK_INTERVAL_HOURS | 4 | Hours between goal progress checks |
CALENDAR_SCAN_INTERVAL_MIN | 15 | Minutes between calendar scans |
STRATEGIST_INTERVAL_MIN | 15 | Minutes between strategist ticks |
ACTIVITY_DIGEST_HOUR | 20 | Hour for the daily screen activity digest |
WEEKLY_REVIEW_HOUR | 18 | Hour for the Sunday weekly activity review |
SCREEN_OBSERVATION_RETENTION_DAYS | 90 | Days to keep screen observations before cleanup |
SCHEDULER_ENABLED | true | Enable/disable background scheduler |
Optional observer settings
These control the observer system and proactive behavior:
| Variable | Default | Description |
|---|---|---|
PROACTIVITY_LEVEL | 3 | Proactivity scale (1–5, higher = more proactive) |
OBSERVER_GIT_REPO_PATH | (empty) | Path to a git repo for commit-aware context |
DEEP_WORK_APPS | (empty) | Comma-separated extra app keywords that trigger deep work state |
Optional timeout settings
These control execution timeouts for agent and tool operations:
| Variable | Default | Description |
|---|---|---|
AGENT_CHAT_TIMEOUT | 120 | Seconds before chat agent execution times out |
AGENT_STRATEGIST_TIMEOUT | 60 | Seconds before strategist agent times out |
AGENT_BRIEFING_TIMEOUT | 60 | Seconds before daily briefing / evening review times out |
CONSOLIDATION_LLM_TIMEOUT | 30 | Seconds before memory consolidation LLM call times out |
WEB_SEARCH_TIMEOUT | 15 | Seconds before web search times out |
BROWSER_SITE_ALLOWLIST | empty | Comma-separated domain allowlist applied to browse_webpage and web_search; when set, only matching domains and subdomains are allowed |
BROWSER_SITE_BLOCKLIST | empty | Comma-separated domain blocklist applied to browse_webpage and web_search in addition to internal/private network blocking |
Optional context window settings
These control how conversation history is trimmed before being sent to the LLM:
| Variable | Default | Description |
|---|---|---|
CONTEXT_WINDOW_TOKEN_BUDGET | 12000 | Max tokens for conversation history |
CONTEXT_WINDOW_KEEP_FIRST | 2 | Always keep first N messages (preserves onboarding context) |
CONTEXT_WINDOW_KEEP_RECENT | 20 | Always keep last N messages |
Optional delegation settings
Recursive delegation is an experimental feature where the agent delegates to specialist sub-agents:
| Variable | Default | Description |
|---|---|---|
USE_DELEGATION | false | Enable orchestrator + specialist agents mode |
DELEGATION_MAX_DEPTH | 1 | Max nesting depth (1 = orchestrator → specialists) |
ORCHESTRATOR_MAX_STEPS | 8 | Max delegation steps for orchestrator |
Optional LLM call logging settings
These control per-call observability logging for all LiteLLM calls (direct and via smolagents):
| Variable | Default | Description |
|---|---|---|
LLM_LOG_ENABLED | true | Enable LLM call logging to JSONL file |
LLM_LOG_CONTENT | false | Include full messages and response text (large, privacy-sensitive) |
LLM_LOG_DIR | /app/logs | Directory for log files |
LLM_LOG_MAX_BYTES | 52428800 | Max bytes per log file before rotation (default 50 MB) |
LLM_LOG_BACKUP_COUNT | 5 | Number of rotated log files to keep |
All settings with their defaults are defined in backend/config/settings.py.
Start the local direct stack
This is the recommended path for browser development because it always loads the repo-root .env.dev before starting services.
./manage.sh -e dev local up
This starts:
- backend (
localhost:8004) — FastAPI + uvicorn reload - frontend (
localhost:3001) — Vite dev server
Useful local commands:
./manage.sh -e dev local status
./manage.sh -e dev local logs backend
./manage.sh -e dev local logs frontend
./manage.sh -e dev local down
For live observation from a managed Codex/Desktop shell, use foreground mode and keep the session open:
./manage.sh -e dev local run
Managed command sessions can clean up background children after the command returns. In that environment, local up may briefly print successful backend/frontend PIDs and then leave no running services, no PID files, local status reporting stopped, and immediate curl failures on 127.0.0.1:8004 or 127.0.0.1:3001. Use local run when chatting with Seraph or watching live behavior because it keeps the stack attached while tailing backend, frontend, and daemon logs.
Before declaring the app ready for a live chat test, verify all three:
./manage.sh -e dev local status
curl -sS http://127.0.0.1:8004/health
curl -sS -I http://127.0.0.1:3001/
When finished, stop through the same lifecycle entry point:
./manage.sh -e dev local down
Local VLM/GPU topology for screenshot analysis and local Gemma routing:
Seraph frontend http://127.0.0.1:3001
-> Seraph backend http://127.0.0.1:8004
-> Mac VLM wrapper http://127.0.0.1:8000
-> GPU model server http://192.168.1.26:8000/v1
The Mac VLM wrapper runs through Docker Compose from /Users/bigcube/Desktop/repos/vlm-screenshot-server; do not run the native Python wrapper for normal operation:
cd /Users/bigcube/Desktop/repos/vlm-screenshot-server
docker compose up -d --build
Verify both local services and the GPU edge before declaring the system ready:
cd /Users/bigcube/Desktop/repos/vlm-screenshot-server
docker compose ps
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/health/backend
cd /Users/bigcube/Desktop/repos/seraph
curl http://127.0.0.1:8004/health
Docker Desktop must be running on the Mac for this path. If /health/backend returns 502, Seraph and the wrapper can be up while the wrapper still cannot reach the GPU model server at 192.168.1.26:8000/v1.
Defaults for the local direct stack:
| Setting | Default |
|---|---|
| Frontend port | 3001 |
| Backend port | 8004 |
| Workspace dir | /tmp/seraph-dev-data |
| LLM log dir | /tmp/seraph-dev-logs |
Override them when needed:
LOCAL_FRONTEND_PORT=3100 LOCAL_BACKEND_PORT=8100 ./manage.sh -e dev local up
Start the Docker stack
./manage.sh -e dev up -d
This starts four containers:
- backend-dev (
localhost:8004) — FastAPI + uvicorn - sandbox-dev — snekbox (sandboxed shell execution)
- http-mcp — FastMCP HTTP request tool server (internal network only)
- frontend-dev (
localhost:3000) — Vite dev server
Verify everything is running:
# Health check
curl http://localhost:8004/health
# Open the UI
open http://localhost:3000
# Swagger API docs
open http://localhost:8004/docs
Screen daemon
The native macOS daemon runs outside Docker and posts the active window context to the backend. This enables context-aware features like deep work detection.
The daemon is a standalone native process — it is not managed by Docker. Stopping Docker services (./manage.sh -e dev down) does not stop the daemon. If the daemon was started with --ocr, it will continue capturing and analyzing screenshots even when the backend is down. Always stop the daemon explicitly when you're done.
Starting the daemon
./daemon/run.sh --verbose
To start the daemon automatically with ./manage.sh -e dev up -d, set in .env.dev:
DAEMON_ENABLED=true
DAEMON_ARGS=--verbose
Stopping the daemon
./manage.sh -e dev daemon stop
Or manually:
# Find the daemon process
ps aux | grep seraph_daemon | grep -v grep
# Kill it by PID
kill <pid>
Daemon status
./manage.sh -e dev daemon status
Daemon management commands
| Command | Description |
|---|---|
./manage.sh -e dev daemon start | Start the daemon |
./manage.sh -e dev daemon stop | Stop the daemon |
./manage.sh -e dev daemon status | Check if the daemon is running |
./manage.sh -e dev daemon logs | View daemon logs |
On first run, macOS will prompt for Accessibility permission (needed to read window titles):
- Open System Settings > Privacy & Security > Accessibility
- Click + and add your terminal app (Terminal, iTerm2, Warp, etc.)
- Toggle it on
This is a one-time grant. The daemon will then report the frontmost app and window title every 5 seconds.
Daemon options
| Flag | Default | Description |
|---|---|---|
--url | http://localhost:8004 | Backend base URL |
--interval | 5 | Poll interval in seconds |
--idle-timeout | 300 | Seconds of inactivity before skipping POSTs |
--verbose | off | Log every context POST |
Optional: Screen Activity Tracking
Screen analysis ingests local screenshot files from the configured screenshot folder, stores observations, and powers daily/weekly activity digests. Seraph does not own screenshot capture cadence for this path.
# Apple Vision (local, free, ~200ms per capture)
./daemon/run.sh --ocr --verbose
# OpenRouter cloud analysis (Gemini 2.5 Flash Lite, structured JSON, ~$1.30/mo)
OPENROUTER_API_KEY=<openrouter_api_key> ./daemon/run.sh --ocr --ocr-provider openrouter --verbose
# With custom app blocklist
./daemon/run.sh --ocr --ocr-provider openrouter --blocklist-file ~/blocklist.json --verbose
| Provider | Pros | Cons |
|---|---|---|
apple-vision (default) | Free, offline, fast (~200ms) | Text-only, no layout understanding |
openrouter | Structured JSON output, understands layout | Requires API key, small cost |
Screen analysis options
| Flag | Default | Description |
|---|---|---|
--ocr | off | Enable screenshot analysis on context switch |
--ocr-provider | apple-vision | apple-vision (local) or openrouter (cloud, structured) |
--ocr-model | google/gemini-2.5-flash-lite | Model for OpenRouter provider |
--openrouter-api-key | $OPENROUTER_API_KEY | API key for OpenRouter provider |
--blocklist-file | (none) | Path to JSON blocklist config (extends built-in defaults) |
Screen Recording permission
OCR requires the Screen Recording permission:
- Open System Settings > Privacy & Security > Screen & System Audio Recording (or Screen Recording on older macOS)
- Click + and add your terminal app
- Toggle it on
Note: Starting with macOS Sequoia (15.0), the system shows a monthly confirmation prompt asking if you want to continue allowing screen recording. This cannot be suppressed. If permission is revoked, the daemon continues in window-only mode.
Optional: Stdio MCP Proxy
The stdio proxy wraps stdio-only MCP servers (like github-mcp-server or things-mcp) as HTTP endpoints so the Dockerized backend can connect to them. It runs natively on macOS so proxied tools can access system APIs (AppleScript, URL schemes, etc.).
Configuration
Add entries to data/stdio-proxies.json:
{
"proxies": {
"things3": {
"command": "uvx",
"args": ["things-mcp"],
"port": 8100,
"enabled": true,
"description": "Things3 task manager"
}
}
}
Start the proxy
./manage.sh -e dev proxy start
Then register the proxied server with the backend:
./mcp.sh add things3 http://host.docker.internal:8100/mcp --desc "Things3 task manager"
To start the proxy automatically with ./manage.sh -e dev up -d, set in .env.dev:
PROXY_ENABLED=true
PROXY_ARGS=--verbose
Optional: MCP Servers
Seraph supports plug-and-play MCP server integration. Servers are configured in data/mcp-servers.json and can be managed three ways:
- CLI:
./mcp.sh list,add <name> <url>,remove,enable,disable,test— when the backend is running,mcp.shcalls the API for live-reload; falls back to direct file editing when offline - Settings UI: Add/remove/toggle servers in the Settings panel
- REST API:
GET/POST /api/mcp/servers,PUT/DELETE /api/mcp/servers/{name}
No config file = no MCP tools, no errors. See data/mcp-servers.example.json for the format.
Things3 Example
If you use Things3 for task management, Seraph can read and create tasks via MCP (22 tools).
-
Install and start the MCP server as a LaunchAgent:
./scripts/install-things-mcp.sh -
Grant Full Disk Access to
uvx(path shown by the script, typically/opt/homebrew/bin/uvx):- System Settings > Privacy & Security > Full Disk Access > + > enter the path
-
Restart the service:
launchctl kickstart -k gui/$(id -u)/com.seraph.things-mcp -
Register the server:
./mcp.sh add things3 http://host.docker.internal:9100/mcp --desc "Things3 task manager" -
Restart the backend and check for
Connected to MCP server: 22 tools loadedin logs.
See Things3 MCP Integration for the full tool list, LaunchAgent management, and troubleshooting.
Optional: Google Calendar
Seraph can read your Google Calendar as an observer data source. Upcoming events are included in the strategist's context, enabling calendar-aware proactive features (e.g., meeting detection, schedule-aware nudges). This is not a user-facing tool — the agent cannot create or modify calendar events directly.
Note: For full calendar management (create/edit/delete events), use a calendar MCP server instead.
1. Create OAuth credentials
- Go to Google Cloud Console
- Create a new project (or use an existing one)
- Enable the Google Calendar API
- Go to APIs & Services > Credentials > Create Credentials > OAuth client ID
- Application type: Desktop app
- Configure the OAuth consent screen (External is fine for personal use; add yourself as a test user)
- Download the credentials JSON file
2. Mount the credentials
Place the downloaded file at:
backend/config/google_credentials.json
This directory is volume-mounted into Docker at /app/config/, so the backend can access it at /app/config/google_credentials.json.
3. First-run OAuth flow
On the first calendar scan, the backend will initiate an OAuth consent flow in the container logs. Follow the URL to authorize read access. A token file (google_calendar_token.json) is created automatically in the backend data directory.
After initial authorization, the token refreshes automatically. If no credentials are configured, the calendar source silently returns empty — no errors are raised.
Optional: GitHub MCP
GitHub MCP integration is currently disabled (commented out in docker-compose.dev.yaml). The official github-mcp-server image only supports stdio transport, which doesn't work in a container network.
Two options are available:
- Stdio proxy: Use the stdio proxy (
mcp-servers/stdio-proxy/) to wrapgithub-mcp-serveras an HTTP endpoint. Add it todata/stdio-proxies.json, start with./manage.sh -e dev proxy start, then register with./mcp.sh add github http://host.docker.internal:<port>/mcp. - GitHub's hosted MCP endpoint:
https://api.githubcopilot.com/mcp/— register directly with./mcp.sh add github https://api.githubcopilot.com/mcp/and configure a GitHub token via the Settings UI.
Optional: SKILL.md Skills
Skills are zero-code markdown capability contributions that extend the agent's behavior. Drop a .md file in data/skills/ with YAML frontmatter and the agent gains new capabilities via prompt injection.
---
name: code-review
description: Reviews code for quality and best practices
requires:
tools: [read_file]
user_invocable: true
enabled: true
---
When asked to review code, follow these steps:
1. Read the file(s) mentioned
2. Check for security issues, performance problems, and style violations
3. Provide specific, actionable feedback
Skills can be managed via:
- Settings UI: Toggle skills on/off in the Settings panel
- REST API:
GET /api/skills,PUT /api/skills/{name},POST /api/skills/reload - Tool gating: Skills with
requires.toolsonly activate when required tools are available - Extension doctor security scan: packaged skills, workflows, and prompt packs are rejected if they contain instruction-override, prompt/secret exfiltration, or approval-bypass phrases that look like executable prompt injection rather than documentation/examples
Eight bundled skills: daily-standup, code-review, goal-reflection, weekly-planner, morning-intention, evening-journal, moltbook (requires http_request), web-briefing (requires http_request + web_search).
Retired: Village Map Editor
The standalone village map editor is retired and no longer part of the supported Seraph product surface. The active repo no longer ships the editor/ app.
Resetting everything
To start completely fresh (useful for testing onboarding):
./reset.sh # Reset dev environment (default)
./reset.sh prod # Reset prod environment
This stops containers, prunes Docker images/volumes, deletes all persistent data (database, memories, soul file, OAuth tokens, logs), and rebuilds from scratch.
Also clear browser state:
// In browser DevTools console
localStorage.clear()
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
curl: (7) Failed to connect to localhost:8004 | Backend not running | ./manage.sh -e dev up -d and check ./manage.sh -e dev logs -f backend-dev |
local up reports started, then local status says stopped and pids/ is empty | Managed Codex/Desktop command session cleaned up background children after the command returned | Use ./manage.sh -e dev local run for live observation, keep it open, verify health with curl -sS http://127.0.0.1:8004/health, and stop with ./manage.sh -e dev local down |
OPENROUTER_API_KEY error in logs | Missing or invalid API key | Set OPENROUTER_API_KEY in .env.dev and rebuild: ./manage.sh -e dev up -d |
| Daemon: "No window title" / title is always None | Accessibility permission not granted | Grant in System Settings > Privacy & Security > Accessibility |
| Daemon: "Backend not reachable" | Backend not running or wrong URL | Start backend first; check --url flag |
| Daemon still capturing after stopping services | Daemon runs outside Docker and isn't stopped by docker compose down | Run ./manage.sh -e dev daemon stop or kill <pid> |
| Daemon exits immediately | Missing PyObjC | Run cd daemon && uv pip install -r requirements.txt |
Failed to connect to MCP server | Things3 MCP not running | Check: curl http://localhost:9100/mcp; ensure LaunchAgent is loaded |
unable to open database file (Things3) | Full Disk Access not granted to uvx | Grant FDA, then restart the things-mcp service |
| Google Calendar: "credentials not found" | Missing google_credentials.json | Place OAuth credentials at backend/config/google_credentials.json |
| Google Calendar: OAuth flow fails | Container can't open browser | Copy the auth URL from container logs and open it in your host browser |