Installation & Runtime Setup
What to install, which environment variables to set, and how to start every process.
Hard prerequisites
- Python 3.10+ (tokenizer + dataclasses +
asyncio.to_thread). - A running Docker daemon. The sandbox uses the Docker SDK; on macOS it auto-detects OrbStack / Docker Desktop sockets (
sandbox/docker.py:13-54). - An OpenAI-compatible LLM endpoint reachable from the agent (llama.cpp, vLLM, Ollama). Default expectation:
http://127.0.0.1:8080. - Required: a Tor SOCKS proxy for outbound traffic (
TOR_PROXY=socks5h://127.0.0.1:9050). Ghost Agent fails closed if it is unreachable — see Anonymity & Tor routing. - Optional: a Postgres database for the
postgres_admintool; GPU servers for image generation / vision.
Python dependencies
Pinned in requirements.txt at the repo root. Headline packages:
| Package | Used by |
|---|---|
fastapi, uvicorn | API + lifespan |
httpx[socks], PySocks, curl-cffi, brotlicffi | LLM client, web fetcher, Tor passthrough |
chromadb, sentence-transformers, tiktoken | Vector memory and embeddings |
sqlalchemy, psycopg2-binary | Postgres admin tool |
docker | Sandbox |
PyMuPDF, pypdf, beautifulsoup4 | Document ingest, vision OCR fallback |
duckduckgo-search | Web search |
transformers, torch, sentencepiece, huggingface-hub | Tokeniser, embeddings, image-gen server |
apscheduler | Scheduled background tasks |
qwen-agent, soundfile | Optional Qwen ReAct bridge (core/agent_qwen.py → tools/qwen_bridge.py). soundfile is a transitive dep of qwen-agent (its utils/utils.py does import soundfile at module load); pinned explicitly so fresh installs don't hit a cryptic ModuleNotFoundError when the Qwen variant is used. On Linux also install the system package libsndfile1 (apt: sudo apt-get install libsndfile1); macOS / Windows wheels ship libsndfile bundled. The default agent path (ghost_agent.main) does NOT need qwen_bridge. |
slack-bolt, slack-sdk | Slack interface |
dspy-ai | Prompt optimisation (ghost_agent.optim, scripts/run_gepa.py). _require_dspy() surfaces a clear install error if the transitive chain (litellm / gepa / asyncer) is broken. |
black, pylint, pytest, pytest-asyncio | Dev tooling |
Environment variables
The CLI reads arguments first, then falls back to environment variables. See CLI Reference for argument-by-argument detail.
| Variable | Default | Effect |
|---|---|---|
GHOST_API_KEY | ghost-secret-123 | Value matched against the X-Ghost-Key header on every authed route. Mandatory for interface/server.py; agent itself ships a default but you should override. |
GHOST_HOME | ~/ghost_llamacpp | Base directory; agent creates sandbox/ and system/memory/ beneath it. |
GHOST_MODEL | qwen-3.6-35b-a3 | Default model name advertised on Ollama-compatibility routes. |
GHOST_SANDBOX_DIR | derived from GHOST_HOME | Host directory bind-mounted into the container at /workspace. |
GHOST_DEFAULT_DB | postgresql://ghost@127.0.0.1:5432/agent | DSN used by postgres_admin tool when no override given. |
GHOST_SANDBOX_MEM | 4g | Memory cap for the sandbox container. |
GHOST_SANDBOX_CPU_QUOTA | 200000 | CPU quota in microseconds (200000 µs / 100000 µs period ≈ 2 vCPU). |
GHOST_INTERFACE_STREAM_CAP | 52428800 (50 MB) | Per-task stream buffer cap inside interface/server.py. |
TOR_PROXY | unset (but required) | socks5h://127.0.0.1:9050. All outbound HTTP from the agent is routed through this SOCKS proxy; identity rotation uses the companion Tor control port. Leaving it unset causes outbound tools to refuse to fire — fail-closed by design. |
SLACK_BOT_TOKEN / SLACK_APP_TOKEN | unset | Slack bot bot/app credentials. Required by interface/externals/slack_bot/main.py. |
PI_VOICE_URL | http://raspberrypi.local:8000 | Base URL for the TTS/STT server reached by server.py. |
Filesystem layout at runtime
$GHOST_HOME/
├── sandbox/ # bind-mounted into container as /workspace
│ ├── acquired_skills/ # generated tool .py files
│ │ ├── <name>.py # (name strictly validated — see tools/acquired_skills)
│ │ ├── retired/ # auto-archived degraded skills
│ │ └── skills_registry.json
│ └── ... user/agent files
├── trajectories/ # Stage-1 distill log (day-partitioned JSONL)
│ └── YYYY-MM-DD/
│ └── session-<sid>.jsonl # redacted turn-level traces
└── system/
├── ghost-agent.log
├── eval/
│ └── baseline.json # frozen SuiteResult (scripts/eval_baseline.py)
├── optim/
│ └── <signature>.json # tuned GEPA instructions (scripts/run_gepa.py)
└── memory/
├── chroma.sqlite3 # ChromaDB persistent store
├── knowledge_graph.db # Graph triplets (SQLite)
├── episodic_memory.db # Episodes (SQLite)
├── projects.db # Project / task / artifact / event log
├── memory_journal.json # Short-term work queue
├── user_profile.json # Profile facts
├── skills_playbook.json # Lessons (verified + utility-ranked)
├── adaptive_threshold.json
├── contradiction_log.json
├── self_play_frontier.json + .lock
├── library_index.json # Names of ingested documents
└── scratchpad.db # LRU + TTL working memory
Boot sequence
From main.py:399-440 and the lifespan async context (main.py:140-398):
- Parse CLI flags + env vars.
- Make sure
GHOST_HOME/sandboxandGHOST_HOME/system/memoryexist. setup_logging()wires colourised structured logs (utils/logging.py).load_tokenizer()grabs / downloads the Qwen3 tokenizer for token budgeting.- Build
GhostContextwithScratchpad,MemoryJournal,SkillMemory,FrontierTracker. - Construct the FastAPI app and attach the lifespan coroutine.
- Lifespan startup:
LLMClientwith foreground + swarm/worker/visual/coding/image_gen pools.DockerSandbox.ensure_running()in a worker thread.ProjectStorealways (for user-driven multi-session projects).- Memory stores (vector, graph, profile, episodes, contradiction log, adaptive threshold) — skipped under
--no-memory. - APScheduler for user-defined cron tasks.
MemoryBus,Verifier,UncertaintyTracker.- Optional
MCTSReasoner+HypothesisTesterwhen--deep-reason. GhostAgent+ biological watchdog daemon task.
- Lifespan shutdown: cancel watchdog, stop scheduler, close LLM client, stop sandbox container (kept around with
remove=Falsefor fast resume). uvicorn.run(app, host, port, log_config=None).
Process commands
# core agent
python -m src.ghost_agent.main \
--upstream-url "http://127.0.0.1:8080" \
--host 0.0.0.0 --port 8000 --verbose
# web UI proxy (separate port)
python interface/server.py --agent-log /var/log/ghost-agent.log
# Slack bot (Socket Mode)
SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... \
python interface/externals/slack_bot/main.py \
--log-file /var/log/ghost-slack-bot.err
# Voice server (Jetson / RPi GPU box)
python interface/externals/tts_stt/voice_server.py
# Image gen server (GPU box)
python interface/externals/image_generation/img_gen_server.py
remove=False so subsequent restarts resume in seconds. Pass --remove-on-shutdown equivalent only if you need a clean image.Pre-provisioned sandbox image (optional, recommended)
The runtime sandbox wrapper can provision Chromium + apt deps + the full pip stack on first boot, but this takes 2–3 minutes the first time and can silently rot if committed before --with-deps was added. The repeatable path is to build a proper image:
scripts/build_sandbox_image.sh
# → builds ghost-agent-base:latest (~2 GB first run; ~5 min on warm cache)
# → runs a Chromium launch smoke test at the end
The image bakes apt system packages, the deep-learning pip stack, and playwright install --with-deps chromium at build time — self-play + browser tasks start instantly. The runtime gate checks both the /root/.supercharged.v2 marker AND that the Chromium binary is actually on disk; if the marker is present but the binary is missing (the exact silent-failure mode the v2 bump exists to catch), a full reinstall fires on next ensure_running.
If you skip this step, the fallback inline provisioning still produces a correct image; it just takes longer on the first turn.
Test runner
pytest # full suite (asyncio_mode=auto)
pytest tests/test_agent_planning.py # single file
pytest -k "memory and not slack" # filter
black src interface tests
pylint src/ghost_agent