Your personal AI assistant — on the web, Discord, SMS, and voice.
A hub-and-spoke agent with built-in tools, extensible via self-modifying plugins. Git-backed memory. Runs on your own server.
Designed to work with the optional TeamWork — a Slack-like web UI with real-time chat, Kanban board, file browser, terminal, and browser screencast.
Designed to work with the optional prax-sandbox — a plug-and-play code-execution sandbox: a long-running Docker container with a shell/Python execution surface, a headless + desktop Chromium (CDP + noVNC), code-server, and a full toolchain (TeX, ffmpeg, pandoc, hugo, …). The coding-agent CLIs it used to bundle (OpenCode / Claude Code / Codex) were removed in 2026-07 so the container needs no model API key; Prax codes with its own tools.
Warning — Active Development & API Cost Risk
This project is under heavy, rapid development. Interfaces, tool names, and behaviors may change without notice. Use at your own risk.
Prax runs agentic workflows that can chain many LLM calls per user message (tool calls, sub-agent delegation, revision loops, sandbox sessions, etc.). If you are using a non-local LLM provider (OpenAI, Anthropic, Google, etc.), you must set up spending limits and cost monitoring on your provider account. A stuck or misconfigured workflow can burn through API credits quickly. Built-in guardrails (recursion limits, round budgets, auto-abort on consecutive failures) reduce this risk but cannot eliminate it entirely. The maintainers are not responsible for any API charges incurred.
git clone https://github.com/praxagent/prax.git && cd prax
git clone https://github.com/praxagent/teamwork.git ../teamwork # web UI
git clone https://github.com/praxagent/prax-sandbox.git ../prax-sandbox # build input
cp .env-example .env # configure (see below)Required .env settings for direct OpenAI access with the example's default provider and models:
| Variable | What | Example |
|---|---|---|
OPENAI_KEY |
OpenAI API key | sk-... |
PRAX_USER_ID |
Workspace directory used by the shared sandbox. This is not per-request tenant isolation. Pick any slug. | usr_alice, myworkspace |
FLASK_SECRET_KEY |
A strong random secret for session signing | Generate a unique value |
TS_AUTHKEY |
Current Compose requires a value even without the Tailscale profile | unused for local use only |
OPENAI_KEY=sk-...
PRAX_USER_ID=usr_alice
FLASK_SECRET_KEY=<strong-random-secret>
TS_AUTHKEY=unusedLeave COMPOSE_PROFILES unset for this local example. Replace TS_AUTHKEY with a real key before enabling Tailscale. For Anthropic or a proxy, configure the provider, model names, and credentials together; follow Setup. Stock Compose grants Prax host Docker access (the sandbox gets only your workspace at /workspace — no repo mount and no model keys since 2026-09); use it only in a trusted environment. See Deployment topology before relying on credential isolation.
Prax will refuse to start without PRAX_USER_ID when running in Docker (app.py fails fast). On first boot it creates workspaces/<PRAX_USER_ID> (the service-state path is derived from the variable before any user exists — prax/services/state_paths.py) and, on later starts, points your identity at it.
Known gap (2026-09): the user record created by your first message gets its own
usr_<id8>workspace (identity_service._canonical_workspace), not<PRAX_USER_ID>. On the next bootreconcile_workspace_dir()(prax/services/identity_service.py) repoints that user to<PRAX_USER_ID>and only symlinks the old directory when the new one does not already exist — which it does, from boot one — so nothing is migrated and notes/files written in the first session drop out of view after the first restart. Deployments whose primary user already hasworkspace_dir == PRAX_USER_IDare unaffected.make run-local-allalso injectsPRAX_USER_ID, so the native path is exposed on a fresh identity DB too.
docker compose -f docker-compose.lite.yml up --build
# …or with remote access over Tailscale (opt-in profile; set TS_AUTHKEY +
# TS_HOSTNAME in .env first — see "Remote access" below):
COMPOSE_PROFILES=tailscale docker compose -f docker-compose.lite.yml up --build2 containers. Bundles Prax + TeamWork + Qdrant + Neo4j + ngrok into a single image alongside the sandbox. Uses ~2-3GB RAM total. Best for local development and resource-constrained machines. The Tailscale sidecar is opt-in: it only starts with COMPOSE_PROFILES=tailscale and TS_AUTHKEY set, never by default.
docker compose up --build2 containers by default (prax + sandbox). Uses the full Dockerfile (JDK 21, glibc Qdrant binary) — more memory headroom for Neo4j's JVM and faster GC, suited to servers. Same bundled layout as lite: Prax + TeamWork + Qdrant + Neo4j + ngrok all run inside the prax container. Opt-in profiles add services alongside: --profile local-llm starts Ollama, --profile observability starts Grafana + Tempo + Prometheus + Loki (see below).
Closed 2026-09: both compose files now bring Prax up. Until then the
sandboxservice indocker-compose.ymlanddocker-compose.lite.ymlwas health-checked withcurl http://localhost:4096/global/health— the OpenCode server the prax-sandbox image removed on 2026-07-20 — and withpraxondepends_on: sandbox: condition: service_healthythe sandbox never reported healthy andpraxnever started. The compose-level check is gone; the image's ownHEALTHCHECK(pgrep -x supervisord) is whatpraxwaits on. The same change dropped the sandbox'sANTHROPIC_API_KEY/OPENAI_API_KEYinjection and the read-write/sourcemount of this whole repo (.envincluded): the sandbox now gets only${WORKSPACE_DIR}/${PRAX_USER_ID}at/workspace— the keyless posturedocs/security/sandbox-execution-boundary.mddescribes and prax-sandbox's own compose always had.tests/test_compose_sandbox_service.pyfails CI if any of it comes back. The native path,make run-local-all(see Run without Docker), remains the dev-loop default.
Both modes expose the same UI at http://localhost:3000 and publish the same host ports (3000, 5001, 8000, 4040). Qdrant and Neo4j run inside the prax container and are not published to the host by default — if you want direct access, add a ports: entry (6333:6333, 7474:7474) to docker-compose.yml or use docker compose exec prax ....
Older Ubuntu? If
docker compose upfails withthe classic builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit, install the buildx plugin:sudo apt install docker-buildx. BuildKit then becomes the default and the build proceeds. As a one-shot alternative, prefix the command:DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker compose up --build.
docker compose --profile observability up --buildThis adds the full observability suite alongside the core services:
| Service | Port | Purpose |
|---|---|---|
| Grafana | localhost:3002 | Dashboards — traces, logs, metrics (login: admin / prax) |
| Tempo | 4318 | Distributed tracing backend (receives OTLP spans from Prax) |
| Prometheus | localhost:9090 | Metrics scraping and storage |
| Loki | 3100 | Log aggregation (fed by Promtail) |
| Promtail | — | Ships Docker container logs to Loki |
OBSERVABILITY_ENABLED=true is the default in .env. If you run without --profile observability, Prax detects that Tempo is unreachable at startup and silently disables the OTEL exporter — no retries, no memory accumulation, no OOM risk.
Grafana comes pre-provisioned with Tempo, Loki, and Prometheus datasources plus two dashboards (Agent Overview, LLM Performance). Config lives in observability/.
If you have an NVIDIA GPU and want the sandbox to use it (local LLM inference via vLLM, faster Whisper transcription, ML/AI experimentation in sandbox_shell, etc.), layer in the GPU override:
make sandbox-gpu # one-shot, with smoke test
# or persistently — add to .env so plain `docker compose` always uses it:
echo 'COMPOSE_FILE=docker-compose.yml:docker-compose.gpu.yml' >> .env
docker compose up -dRequires on the host:
- NVIDIA driver (verify:
nvidia-smi) nvidia-container-toolkitinstalled and registered with Docker (verify:docker info | grep -i nvidiashowsnvidiaunder Runtimes).
The override (docker-compose.gpu.yml) reserves all GPUs for the sandbox container and sets the env vars the toolkit needs to inject CUDA libraries at runtime — no CUDA install in the image. To pin to a specific GPU instead of all, change count: all to device_ids: ["0"] in the override file. Inside the sandbox, nvidia-smi works immediately and pip install torch --index-url https://download.pytorch.org/whl/cu124 (or any cu12x wheel) picks up the GPU automatically.
Only the sandbox gets the GPU — Prax itself stays CPU-only by default. If you want CUDA in the prax container too (for embeddings etc.), add the same deploy.resources block to the prax service in the override.
Qdrant and Neo4j are bundled inside the prax container and start automatically on docker compose up — data persists to workspaces/<PRAX_USER_ID>/.services/{qdrant,neo4j,teamwork} so it survives restarts and rebuilds, and is scoped per user. Prax talks to them over localhost inside the container; they're not published to the host by default. Set the memory options to match the services you intend to run.
Ollama is opt-in: start it with docker compose --profile local-llm up to run a separate Ollama container that Prax reaches at http://ollama:11434 inside the Docker network.
| Service | How it runs | Host port | Purpose |
|---|---|---|---|
| Qdrant | embedded in prax container |
— (internal) | Vector store for semantic memory retrieval (dense + sparse) |
| Neo4j | embedded in prax container |
— (internal, login: neo4j / prax-memory) | Knowledge graph for entity/relation memory |
| Ollama | separate container, profile local-llm |
localhost:11434 | Local LLM and embedding inference (model auto-pulled on first start) |
MEMORY_ENABLED=true is the default. Set to false to disable memory even when the services are running. See Memory System for details.
For local embeddings (no data sent to OpenAI), set in .env:
EMBEDDING_PROVIDER=ollama
EMBEDDING_MODEL=nomic-embed-textThe configured embedding model is auto-pulled when the Ollama container starts. See Embedding Providers for a comparison of OpenAI vs Ollama vs local options.
The default docker-compose.yml volume-mounts ./prax, ./app.py, and ./scripts into the prax container. Python/prompt changes take effect with a container restart — no image rebuild needed:
# Edit prax/ or prompts locally, then:
docker compose restart praxFor frontend (TeamWork) changes, run the Vite dev server locally instead of rebuilding the TeamWork image:
cd ../teamwork/frontend # or wherever your teamwork repo lives
npm run dev # starts Vite on :5173 with hot reloadOpen http://localhost:5173 instead of :3000. Vite proxies API calls (/api/*, /ws/*) to the TeamWork backend at localhost:8000, which is already exposed by Docker Compose. The architecture:
Browser → Vite :5173 (HMR, serves React)
↓ /api/*, /ws/*
TeamWork :8000 (exposed to host via Docker)
↓ internal webhook
Prax app :5001 (Docker-internal)
The frontend never talks to Prax directly — TeamWork is the middleman. So Vite works with the full Docker stack with no extra config.
When you need a full rebuild (dependency changes in pyproject.toml or package.json):
docker compose up --build prax # Prax image only (TeamWork, Qdrant, Neo4j are bundled into it)
docker compose up --build sandbox # Sandbox image only
docker compose up --build # bothAccessing Prax from another machine works fine, but the Desktop and Browser tabs in TeamWork need a WebSocket to noVNC / CDP, and browsers only allow those from a secure context — HTTPS or localhost. Opening http://<remote-host>:3000 directly will fail with noVNC requires a secure context (TLS) in the console and Connection closed (code: 1006) from rfb.js.
Three easy fixes, in order of recommendation:
Tailscale sidecar (Docker) — recommended. Runs tailscaled inside the Compose stack. It adds a private route but does not close the host-published application ports; restrict those separately. State is persisted in a Docker volume so the node keeps its identity across restarts:
# 1. Get a reusable, NON-ephemeral, pre-approved key from
# https://login.tailscale.com/admin/settings/keys
# Use persisted state for this long-running service; see the
# Tailscale configuration guide for identity and plan considerations.
# 2. Add to .env:
# TS_AUTHKEY=tskey-auth-...
# TS_HOSTNAME=prax # whatever name you want on the tailnet
# COMPOSE_PROFILES=tailscale # without this the sidecar is skipped
# 3. Up:
docker compose up -d
# Visit: https://prax.<tailnet>.ts.net/ (TeamWork)
# https://prax.<tailnet>.ts.net:3001/ (Grafana, if observability is up)The sidecar uses kernel TUN mode (NET_ADMIN and /dev/net/tun), reads its serve config from tailscale/serve-config.json, and proxies :443 → prax:8000 and :3001 → grafana:3000 over the tailnet. HTTPS must be enabled on your tailnet (admin console → DNS → HTTPS Certificates). With COMPOSE_PROFILES unset, the sidecar does not start. Current Compose still requires a nonempty TS_AUTHKEY during interpolation; the local Quick Start uses unused only while this profile is disabled.
Tailscale on the host — fallback if you already run tailscaled on the server and don't want a sidecar. The Makefile keeps the original mappings:
make tailscale-up # serves :443→:3000 and :3001→:3002 from the host
make tailscale-down # tears them down
make tailscale-status # show current serve configSSH tunnel — works without touching Tailscale at all, since browsers treat localhost as a secure context even over plain HTTP:
ssh -L 3000:localhost:3000 <remote-host>
# Visit: http://localhost:3000/The Docker image bundles Prax + TeamWork + Qdrant + Neo4j into one container. To run without Docker you start each of those pieces yourself. This is the path for local development and for machines where you'd rather not run Docker.
Shortcut — make targets. Once the prerequisites below are in place, you don't have to start each piece by hand:
make run-local-min # Prax core only, foreground — memory/sandbox/TeamWork all OFF (Ctrl-C to stop)
make run-local-all # full local stack in the background: Qdrant + Neo4j + TeamWork + sandbox + Prax
make run-local-all-dev # same as run-local-all but DEBUG=true — Prax restarts on code change (Werkzeug reloader)
make run-local-all-tail-dev # run-local-all-dev + a Tailscale serve exposing the TeamWork UI over HTTPS
make local-status # probe each service's port, report up/down
make smoke # connectivity smoke test — verify everything is CONNECTED, not just up
make integration # FROM CLEAN: tear down, clear derived state, bring the stack up, run smoke (pre-PR)
make local-logs # tail -F every .local-run/*.log
make shutdown # stop everything run-local-all started (processes, containers, and the Tailscale serve)Verify a fresh install.
make local-statusonly checks ports;make smoke(aftermake run-local-all) asserts the cross-service wiring a fresh clone needs — TeamWork serves its built SPA, TeamWork→Prax proxy works, the sandbox CDP/desktop WebSocket upgrades succeed, and Prax reaches memory + the sandbox. To prove this on your own box before opening a PR, runmake integration: it tears the running stack down, deletes the derived state that masks fresh-download bugs (the built TeamWork SPA, the.local-runmarkers), brings everything up from scratch, and runsmake smoke— its exit code is the pass/fail signal. (It's disruptive — it stops your live local stack — and heavy: it rebuilds the SPA and starts the Chrome+desktop sandbox, so budget ~4GB free RAM.REBUILD_SANDBOX=1also rebuilds the sandbox image from scratch;SANDBOX_PATH=/nonexistentskips it for a core-only run.) TheFresh-install integrationGitHub workflow (.github/workflows/fresh-install.yml, nightly/manual) runs the samemake integrationon a clean runner that clones all three repos — the intent is that "works on a fresh download," not just on a machine you've been hacking on, is proven every night. Known gap (2026-09): it is not proving that today. The workflow last passed on 2026-06-29 and every run since 2026-06-30 has failed (69 consecutive failures as of 2026-09-06, pergh run list --workflow fresh-install.yml). The 2026-09-06 run failed atSandbox OpenCode /global/health :4096—scripts/smoke_test.pystill probed the OpenCode endpoint the sandbox image removed in 2026-07 (that probe was removed 2026-09; the next scheduled run will show whether it was the only blocker) — and the workflow's boot.envstill sets noTEAMWORK_API_KEY, which TeamWork's external API now requires.
Prerequisite — Node.js (for the TeamWork web UI). The TeamWork UI is a React app that must be compiled (or run via the Vite dev server). Without Node.js + npm on the host, TeamWork's backend still runs but
/returns{"detail":"Not Found"}(no UI).make run-local-allbuilds the UI automatically whennpmis present (andrun-local-all-devruns it with hot-reload); ifnpmis missing it warns and serves API-only. Node 18+ is enough (TeamWork uses Vite 5). Install it:
- macOS:
brew install node— or download the LTS installer from nodejs.org.- Windows:
winget install OpenJS.NodeJS.LTS(orchoco install nodejs-lts) — or the nodejs.org installer.- Linux (Debian/Ubuntu):
sudo apt install nodejs npm(Ubuntu'snodejspackage omitsnpm, so install both) — or, for a newer Node, the NodeSource repo / nvm.- Any OS via nvm:
nvm install --lts.Verify with
node --version && npm --version, then re-run themaketarget.
run-local-all brings up the whole stack — memory on, TeamWork on, sandbox on. Prax and TeamWork run as plain host processes; Qdrant, Neo4j and the sandbox run in Docker (Prax connects to their published ports — note the Makefile publishes Qdrant and Neo4j with plain -p 6333:6333 / -p 7474:7474 -p 7687:7687, i.e. on all host interfaces, with Qdrant unauthenticated and Neo4j on the committed default password prax-memory; it does not bind them to loopback, so keep the host behind a firewall or security group). Everything persists under the user's workspace (default PRAX_USER=local): Qdrant/Neo4j data in workspaces/$PRAX_USER/.services/{qdrant,neo4j}, and the sandbox's /workspace is bind-mounted to workspaces/$PRAX_USER — so memory and sandbox files survive restarts rather than vaporizing with the containers. On this path the sandbox container gets no provider keys: the Makefile exports .env's ANTHROPIC_KEY/OPENAI_KEY as ANTHROPIC_API_KEY/OPENAI_API_KEY into the shell that runs docker compose, but prax-sandbox's docker-compose.yml declares no environment: block, so they never reach the container. That is the keyless posture the security docs describe — and since 2026-09 this repo's own compose files match it (see the Quick Start note). PIDs and logs land in .local-run/. If the sandbox is expected (Docker + checkout present) but fails to start, run-local-all hard-fails instead of silently disabling it.
Optional backing services may be skipped with an install hint when unavailable; an expected sandbox that fails to start is a hard failure — Qdrant and Neo4j prefer Docker (falling back to a native qdrant/neo4j binary), plus a sibling TeamWork checkout and a sibling prax-sandbox checkout (Docker-only). Override locations/owner with make run-local-all TEAMWORK_PATH=/path/to/teamwork SANDBOX_PATH=/path/to/prax-sandbox PRAX_USER=alice. By default the sibling repos are expected next to this one:
git clone https://github.com/praxagent/teamwork ../teamwork
git clone https://github.com/praxagent/prax-sandbox ../prax-sandboxDocker images. The first run-local-all pulls qdrant/qdrant and neo4j:5 automatically (via docker run) and builds the sandbox image from its checkout — the first run is therefore slow. To pre-warm the cache (or just to watch progress), pull them yourself first:
docker pull qdrant/qdrant
docker pull neo4j:5make deliberately does not run docker pull for you: auto-pull happens through the Docker daemon, which honours its own proxy/registry settings. Behind a proxy, configure Docker itself (daemon HTTP_PROXY/HTTPS_PROXY or ~/.docker/config.json, see Docker's proxy docs) — not make. Neo4j takes ~20–40s to accept Bolt connections on a cold start; run-local-all waits for it (via cypher-shell "RETURN 1") before starting Prax, so the reported status reflects reality.
For live-reload development use make run-local-all-dev — Prax runs under the Werkzeug reloader (DEBUG=true) and restarts when you edit its source. (Tailscale is a separate concern: make tailscale-up.) The manual steps below are exactly what those targets automate, in case you want to run a piece yourself.
Prerequisites
- Python 3.13 and uv (the package manager — not pip)
- For memory (on by default): Qdrant and Neo4j.
run-local-allstarts these in Docker for you (or uses native binaries if Docker is absent). You can skip memory entirely withMEMORY_ENABLED=false(STM still works; LTM degrades silently). - Docker — used by
run-local-allfor Qdrant, Neo4j, and the code-execution sandbox. Not required if you run memory natively and don't need the sandbox.-
Your user must be able to run
dockerwithoutsudo(the Makefile calls plaindocker). Ifmake run-local-all/make integrationreportspermission denied while trying to connect to the Docker API at unix:///var/run/docker.sock, add yourself to thedockergroup, then start a fresh login shell (group changes only apply to new sessions):sudo usermod -aG docker "$USER" # then log out and back in # …or activate it in the current shell without re-login: newgrp docker # (or run a single command: sg docker -c 'make integration')
-
- Optional: Ollama (only if you set
EMBEDDING_PROVIDER=ollama).
Installing the dev toolchain (Ubuntu)
uv runs the app and tests; actionlint is needed by make ci. Both install into ~/.local/bin — make sure it's on your PATH.
# uv — the package manager (run/sync/test)
curl -LsSf https://astral.sh/uv/install.sh | sh
# actionlint — GitHub-workflow linter, required by `make ci`
# Option A — build from source (needs Go ≥ 1.25):
GOBIN="$HOME/.local/bin" go install github.com/rhysd/actionlint/cmd/actionlint@latest
# Option B — no Go? grab a prebuilt binary (see https://github.com/rhysd/actionlint/blob/main/docs/install.md):
# bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) latest "$HOME/.local/bin"With both on your PATH, make ci (actionlint + ruff + the layer linter + pytest) is the pre-commit gate — green locally means green in CI. To run the sandbox with or without it, see SANDBOX_ENABLED.
git clone https://github.com/praxagent/prax.git && cd prax
uv sync --python 3.13 # install deps into a local venv
mkdir -p static/temp
cp .env-example .env # then edit — see step 3Prax defaults already point at localhost (QDRANT_URL=http://localhost:6333, NEO4J_URI=bolt://localhost:7687), so you just need the two datastores listening on those ports.
Qdrant — single static binary, no dependencies (releases):
./qdrant # serves HTTP on :6333, gRPC on :6334Neo4j — Community Edition 5.x, requires a JDK 21 on the host (download). Set the password Prax expects and enable the APOC plugin:
neo4j-admin dbms set-initial-password prax-memory # matches NEO4J_PASSWORD default
# enable APOC: copy the bundled apoc jar from labs/ into plugins/, then:
neo4j console # Bolt on :7687, browser UI on :7474Prefer not to install these directly? You can run just the two datastores as standalone containers and still run Prax itself without Docker:
# bind to loopback: Qdrant has no auth by default and this Neo4j password is public docker run -p 127.0.0.1:6333:6333 -p 127.0.0.1:6334:6334 qdrant/qdrant docker run -p 127.0.0.1:7474:7474 -p 127.0.0.1:7687:7687 -e NEO4J_AUTH=neo4j/prax-memory -e NEO4J_PLUGINS='["apoc"]' neo4j:5Or skip memory altogether: set
MEMORY_ENABLED=falsein.envand skip this step.
At minimum:
| Variable | Required? | Notes |
|---|---|---|
FLASK_SECRET_KEY |
Yes | Hard requirement — Prax won't import settings without it. Use any strong random string. |
OPENAI_KEY |
Yes (or ANTHROPIC_KEY) |
LLM provider. OPENAI_KEY also covers the default embeddings. |
PRAX_USER_ID |
No (without Docker) | Only required in Docker. Without it, Prax defaults to a local workspace. |
RUNNING_IN_DOCKER |
Leave unset | Setting this flips on Docker-only code paths (the PRAX_USER_ID guard, the persistent-sandbox sidecar). Keep it out of your .env. |
NEO4J_PASSWORD |
No | Defaults to prax-memory — match whatever you set in step 2. |
Known gap (2026-09): with
RUNNING_IN_DOCKERunset,prax.utils.shell.run_commandruns commands as a host subprocess rather than in the sandbox — its routing keys onsettings.sandbox_persistent, which is simplyrunning_in_docker(prax/settings.py), not on whether the sandbox is enabled or reachable. Callers include the sixdesktop_*tools inprax/agent/sandbox_tools.py(desktop_openpasses the model-supplied string tobash -c), plugincaps.run_command(prax/plugins/capabilities.py), and the Mermaid validator. So on this native path (and onmake run-local-all, which also leaves the variable unset) those commands execute on the Prax host as the Prax user, not inside the container.
uv run python app.py # serves the Flask API on http://localhost:5001That's Prax's API up. Discord answers once DISCORD_BOT_TOKEN (+ DISCORD_ALLOWED_USERS) is set; SMS / voice additionally need the Twilio setup and a public HTTPS endpoint for Twilio's webhooks (see Channels — and read the tunnel warning there first). The TeamWork web UI is a separate process — see below.
TeamWork is off until TEAMWORK_URL is set — the URL is the switch (teamwork_active in prax/settings.py; TEAMWORK_ENABLED is deprecated and only still honoured as an explicit false opt-out). To use the web UI without Docker, run its repo separately:
git clone https://github.com/praxagent/teamwork.git ../teamwork && cd ../teamwork
# build the frontend (Node 18+, per TeamWork's README):
cd frontend && npm ci && npm run build && cd ..
# start the backend (FastAPI/uvicorn on :8000):
DATABASE_URL="sqlite+aiosqlite:///./vteam.db" \
WORKSPACE_PATH="$(pwd)/../prax/workspaces" \
PRAX_URL="http://localhost:5001" \
CORS_ORIGINS='["http://localhost:3000","http://localhost:5173"]' \
python -m teamwork.cliThen point Prax at it — add to Prax's .env and restart app.py:
TEAMWORK_URL=http://localhost:8000
TEAMWORK_API_KEY=<the shared key — see below>The shared key is required. TeamWork's external API refuses requests (503) when no credential is configured, so Prax can't post without it. Generate one:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"Put the same value in both repos — Prax sends it, TeamWork checks it:
| File | Variable |
|---|---|
prax/.env |
TEAMWORK_API_KEY=<value> |
teamwork/.env |
EXTERNAL_API_KEY=<value> |
If they drift apart Prax gets 401 Invalid API key; if neither is set, 503.
(TeamWork also supports a per-agent credential registry — one token bound to one
agent identity plus its own capability set — which is what you want once more
than one agent is posting. See TeamWork's README.)
Branch channels. TeamWorkClient.ensure_branch_channel(branch) gives a git
branch its own channel, so the conversation about it, the patches and whatever CI
reported live in one place instead of interleaving with another branch in
#general. It's idempotent, so it's safe to call at the start of every turn that
touches a branch, and it returns None (rather than raising) when TeamWork is
disabled or unreachable — a branch channel is a convenience and must never block
the actual work. post_branch_update(branch, content) ensures the channel and
posts in one step.
For frontend hot-reload during development, run npm run dev in teamwork/frontend (Vite on :5173, proxies /api and /ws to the backend on :8000) instead of building static assets.
Note: TeamWork's in-browser terminal
docker execs into the sandbox container, so it expects Docker. Running TeamWork without Docker gives you chat, Kanban, file browser, and execution graphs; the terminal/desktop/browser tabs need the sandbox (next caveat).
The code-execution sandbox is itself a Docker container — and it now lives in its own repo, prax-sandbox (a sibling directory; Prax depends on it as prax_sandbox_client). So "fully Docker-free" means no sandbox: set SANDBOX_ENABLED=false (or simply don't run the sandbox container) and Prax runs as a pure harness — sandbox features (package auto-install, sandbox_shell, the in-browser terminal, the noVNC desktop, the Chrome screencast, run_python, and the delegate_sandbox / delegate_desktop spokes) are unavailable, and no sandbox tools are registered. Core Prax (chat, memory, notes, scheduling, channels) runs fine without it. To run a sandbox locally, build its image from the sibling repo (make run-local-all does this on first run; or cd ../prax-sandbox && make build). This repo's own docker compose up also builds it (the stale :4096 healthcheck that used to block prax was removed 2026-09 — see the Quick Start note). To run it on a remote box, see providing Prax a sandbox and the prax-sandbox repo's docs/remote.md.
| Port | Service | Started by |
|---|---|---|
| 5001 | Prax Flask API | uv run python app.py |
| 6333 / 6334 | Qdrant HTTP / gRPC | you (step 2) |
| 7687 / 7474 | Neo4j Bolt / browser UI | you (step 2) |
| 8000 | TeamWork API (+ Swagger at /docs) |
TeamWork backend (step 5) |
| 3000 | — (Docker port mapping only) | nothing listens here without Docker; the UI is served by TeamWork itself on :8000 (or Vite on :5173) |
| 5173 | TeamWork Vite dev server | npm run dev (frontend dev only) |
| 11434 | Ollama | you (only if EMBEDDING_PROVIDER=ollama) |
Set up a channel in Prax's .env:
- TeamWork web UI: run it separately (step 5), then open http://localhost:8000
- Discord (free):
DISCORD_BOT_TOKEN+DISCORD_ALLOWED_USERS - Twilio (paid):
TWILIO_ACCOUNT_SID+TWILIO_AUTH_TOKEN+NGROK_URL(Twilio's webhooks need a public HTTPS URL that reaches Prax's:5001)
Read before exposing
:5001for Twilio. A port tunnel such asngrok http 5001(what the Docker image's supervisord runs whenNGROK_AUTHTOKENis set —scripts/ngrok-launch.sh— and whatdocs/security/configuration.mdshows for host installs) forwards every Flask route to the internet, not only the Twilio webhooks. Prax registers no request authentication on its own HTTP surface (nobefore_request/ API-key check inapp.pyorprax/blueprints/): the/teamwork/*routes (including/teamwork/webhook, which runs a full agent turn, tools included, as the configured TeamWork user),/plugins/import, and/api/users/*accept anonymous callers, so anyone who learns the tunnel hostname can drive them. Only the Twilio routes carry@validate_twilio_request, and it has two limits (prax/blueprints/twilio_auth.py): withTWILIO_AUTH_TOKENunset, validation is skipped (logged once), leaving only per-route checks such as the SMS sender allowlist (sms_service._ensure_authorized), which a caller satisfies by spoofingFrom; with it set, the signature is checked againstrequest.urlwith noX-Forwarded-Protohandling, so behind an HTTPS tunnel that forwards plain HTTP the computedhttp://…URL does not match thehttps://…URL Twilio signed and genuine requests are rejected with 403. Until the tunnel is path-restricted and the non-Twilio routes are authenticated, do not put:5001on the public internet (seedocs/security/network-exposure.md).
The Tailscale / HTTPS options below also apply without Docker, with one adjustment: the Docker-oriented commands assume the container's host ports (:3000, :3002), but here Prax serves on :5001 and TeamWork on :8000. Substitute accordingly.
- SSH tunnel — works unchanged; just forward those ports:
ssh -L 8000:localhost:8000 -L 5001:localhost:5001 <remote-host> # then open http://localhost:8000 (TeamWork) — localhost is a secure context, so the Desktop/Browser tabs work
- Tailscale — the
make tailscale-*targets and the sidecar'sserve-config.jsonmap to the Docker ports; point them at:8000/:5001instead before using them without Docker.
A native-service deployment recipe based on an earlier Ubuntu VM installation. Provider credentials can be kept out of the agent process using the proxy; filesystem and administrative isolation must be configured separately. This recipe has not been rerun from a clean machine for the September 4 docs revision. For the container path, use Setup.
Target shape. Prax + TeamWork + sandbox + Qdrant + secrets-proxy. Neo4j and the LGTM observability stack are left off: they cost ~600 MB together and are what make the difference between 4 GB working and not. You keep Qdrant vector memory; you lose the graph layer.
Historical idle footprint: approximately 1 GB with an inactive browser and the reduced service set above. This does not establish peak memory requirements; measure the intended workload and leave room for browser, model, and service use.
# Swap FIRST — a 4 GB box running Chromium will OOM without it.
sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-prax-swap.conf # swap late, not eagerly
sudo apt-get update && sudo apt-get install -y make build-essential git jq ffmpeg
# ^^^^^^ audio/transcription
curl -fsSL https://get.docker.com | sh && sudo usermod -aG docker "$USER"
curl -LsSf https://astral.sh/uv/install.sh | sh # provides Python 3.13; the system python is NOT used
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejsThey are consumed side by side, never vendored:
mkdir -p ~/PRAX && cd ~/PRAX
for r in prax teamwork prax-sandbox prax-secrets-proxy; do
git clone https://github.com/praxagent/$r.git
doneFollow the canonical secrets-proxy guide for reverse-proxy authentication, TLS, and deployment boundaries. The forward-mode procedure covers generated credential maps, CA trust, and its different access controls.
Do not put real provider keys in a sibling .env readable by the same user and
call that isolation. Stock Docker socket access also permits administration of
other containers on that daemon. An isolated proxy needs a separate administrative
boundary. Each proxy half authenticates callers only when its token is set in the
proxy's .env — PROXY_AUTH_TOKEN for the reverse half (:8785),
PROXY_FORWARD_AUTH_TOKEN for the forward half (:8786, which answers 407
without it) — and empty means open: set both, and still keep both ports
loopback-only. One credential cannot move at all: TWILIO_AUTH_TOKEN is also the
inbound webhook-signature secret, which a proxy cannot inject —
prax/blueprints/twilio_auth.py skips validation when Prax's own copy is empty,
so keyless Prax has no inbound Twilio validation (see the
credential matrix).
After configuring the intended path, verify unauthorized requests are rejected where authentication is required, then make one small authorized request. A successful completion establishes that path's wiring, not filesystem isolation. Local session, integration, and SSH credentials remain sensitive even when model keys are proxied; see the credential matrix.
cd ~/PRAX/teamwork/frontend && npm ci && npx vite build # else / 404sTeamWork's external API fails closed — with no credential it returns 503,
and a wrong one 401. The same value must appear on both sides under different
names:
KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))")
echo "EXTERNAL_API_KEY=$KEY" >> ~/PRAX/teamwork/.env # TeamWork checks this
echo "TEAMWORK_API_KEY=$KEY" >> ~/PRAX/prax/.env # Prax sends itGenerate a fresh pair per deployment; don't reuse another box's. Check for
duplicate TEAMWORK_API_KEY= lines afterwards — a stale empty one later in the
file silently wins under dotenv "last one wins" semantics.
docker run -d --name prax-qdrant -p 127.0.0.1:6333:6333 --restart unless-stopped \
-v ~/PRAX/qdrant-data:/qdrant/storage qdrant/qdrant:latest
cd ~/PRAX/teamwork && nohup uv run --python 3.13 python -m teamwork.cli &
cd ~/PRAX/prax-sandbox && WORKSPACE_DIR=~/PRAX/workspaces/<user_id> docker compose up -d --build
cd ~/PRAX/prax && nohup uv run --python 3.13 python app.py &Expose only the UI, and only on your tailnet:
sudo tailscale serve --bg --https=443 http://localhost:8000Never expose the sandbox ports (
:9223CDP,:6080noVNC,:6090clipboard). They are unauthenticated by design and are safe only because they bind to loopback. (:4096/OpenCode no longer exists — removed 2026-07.) Once the tailnet works, close public SSH.
curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/health # 200
curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/ # 200 (UI built)
curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/api/external/projects # 401 = fail-closed works
curl -s -o /dev/null -w '%{http_code}\n' localhost:5001/health # 200Started by hand, Prax and TeamWork are ordinary foreground processes — a reboot (or an instance resize) leaves you with a box that answers on no ports. Most of the stack already self-heals and only these two need units:
| Restarts itself? | Why | |
|---|---|---|
| Tailscale | ✅ | tailscaled is a systemd unit, enabled on install |
| Swap | ✅ | the /etc/fstab line from step 1 |
| Qdrant, secrets-proxy, sandbox | ✅ | Docker restart policies |
| Prax, TeamWork | ❌ | nothing supervises them — install the units below |
sudo cp deploy/systemd/{prax,teamwork}.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now teamwork praxTwo things in deploy/systemd/ are deliberate rather than
incidental:
- TeamWork starts before Prax. Prax's startup reconnects to (or creates) its TeamWork project and silently skips if the UI is unreachable — which leaves you staring at an empty workspace wondering what broke. The ordering is the difference between a wired-up workspace and a confusing one.
- No
EnvironmentFile. Prax reads.envfrom its working directory and exportsHTTPS_PROXYitself (app.py→_export_proxy_env_from_dotenv). systemd'sEnvironmentFileparser does not handle quoted values the way a shell does, so pointing it at.envwould silently mis-set variables.
Restart=always with a 10s backoff means a crash recovers too, not only a
reboot. Adjust User= and the paths if your layout differs from ~/PRAX.
Running the sandbox? The terminal, browser and desktop panels all reach into the sandbox container, and TeamWork needs to be told where it is. The unit file ships those three lines commented out — uncomment them:
Environment=DESKTOP_VNC_URL=http://127.0.0.1:6080
Environment=SANDBOX_CONTAINER=prax-sandbox-sandbox-1
Environment=CHROME_CDP_HOST=127.0.0.1Setting DESKTOP_VNC_URL in .env alone is not enough on older builds: it was
read from the environment rather than declared as a setting, and pydantic loads
.env into the settings object, not into os.environ — so the line looks
right and does nothing.
After configuring the service units, verify recovery across a planned reboot and check the UI, model, and sandbox surfaces. Process health alone does not establish that the terminal, browser, and desktop panels work:
sudo systemctl reboot
# then, once it is back:
./deploy/update.sh --check # every surface, including the sandbox
sudo tailscale serve status # UI still published--check changes nothing — it verifies the cross-service settings that fail
silently when unset, then probes each surface a user can actually open, and
exits non-zero if any of them is not serving.
DISCORD_ENABLED defaults to ON. Two Prax instances sharing a
DISCORD_BOT_TOKEN both connect to the gateway and both reply to every
message. The same applies to Twilio/SMS. Exactly one instance may own each
channel — set DISCORD_ENABLED=false (and omit TWILIO_*) on the other, and
restart it: an .env edit alone does not take effect.
The Discord token is one of the credentials that remains local in the current integration: the gateway
carries it inside a WebSocket IDENTIFY payload rather than an HTTP header, so
there is nothing for a header-injecting proxy to rewrite. See its entry in
credential_registry.py for the full
rationale and the blast-radius assessment.
The sandbox bind-mounts one workspace at a fixed path
(WORKSPACE_DIR:/workspace) into one shared container, and nothing scopes
execution per user. Every tenant's run_shell/run_python therefore shares one
filesystem. Deploy this as single-tenant until per-tenant sandbox isolation
lands.
Prax is a multi-channel AI assistant powered by a LangGraph ReAct agent. It connects through the TeamWork web UI and/or Discord and/or Twilio (voice + SMS), remembers everything in SQLite, and can modify its own tools at runtime.
| Category | Highlights |
|---|---|
| Channels | TeamWork web UI (Slack-like chat, Kanban, terminal, browser, file browser, execution graphs, live agent output), Discord bot (free, WebSocket), Twilio voice + SMS (webhooks), cross-channel mirroring (Discord/SMS → TeamWork #discord/#sms channels) |
| Agent | LangGraph ReAct loop, built-in tools on the orchestrator plus domain spokes (extensible via plugins), dedicated sub-agents (self-improvement, plugin engineering, content authoring, research, coding), watchdog supervisor, automatic checkpoint & retry on failures |
| Memory | Two-layer human-like memory: STM scratchpad + LTM with Qdrant vector store (semantic recall) and Neo4j knowledge graph (entity relations, multi-hop reasoning). Hybrid retrieval via RRF fusion, Ebbinghaus-inspired forgetting curve, automatic consolidation. Plus: SQLite conversations, git-backed workspaces, user notes, to-do lists |
| Notes | Conversation-to-note publishing (Hugo pages with KaTeX, mermaid, syntax highlighting), iterative updates, searchable index, shareable URLs, bidirectional knowledge graph links |
| Documents | PDF extraction (arXiv, URLs, attachments), web page summaries, YouTube transcripts, LaTeX compilation, URL-to-note / PDF-to-note / arXiv-to-note pipelines |
| Research | Research projects (group notes, links, sources), RSS/Atom feed subscriptions, conversation history search across sessions |
| Code | Docker execution sandbox (separate prax-sandbox repo): sandbox_shell / run_python, package auto-install. The image ships no coding-agent server or CLI (OpenCode / Claude Code / Codex removed 2026-07) — Prax codes with its own tools |
| Linux Desktop | Graphical desktop in the sandbox (Xvfb + XFCE + noVNC). Prax can launch GUI apps (Chromium, code-server, xterm), interact with them programmatically (screenshot, click, type), and install software. Users see everything through TeamWork's Desktop tab |
| Self-Upgrading | Prax auto-escalates his intelligence tier when stuck. If he doesn't have a tool, he writes Python. If that fails, he uses the desktop. Never gives up |
| Scheduling | Cron jobs (YAML), one-time reminders, timezone-aware delivery |
| Browser | Playwright automation, persistent login profiles, VNC for manual login, credential management |
| Self-improvement | Hot-swappable plugin system (sandbox + auto-rollback), self-modifying code via PRs, QLoRA fine-tuning on conversation history |
| Models | OpenAI, Anthropic, Google Vertex, Ollama, local vLLM — per-component routing |
| Plugins | Folder-per-plugin, git submodule imports from public repos, security scanning, workspace push to private remote, auto-generated catalog |
| File sharing | Opt-in file publishing via ngrok (shareable links for videos, PDFs), Twilio media serving |
TeamWork is an agent-agnostic collaboration shell — a Slack-like web interface that gives Prax a visual frontend. With this repo's Docker Compose files it is bundled inside the prax container (there is no separate teamwork service — scripts/supervisord-prax.conf runs it); without Docker it is a separate process. Either way it connects via the External Agent API.
What you get:
- Real-time chat — public channels (#general, #engineering, #research) and private DMs with Prax, with typing indicators and WebSocket updates
- Channel mirroring — Discord and SMS conversations are mirrored to dedicated #discord and #sms channels in TeamWork, so you can follow cross-channel conversations in one place
- Kanban board — task management with drag-and-drop columns (pending / in progress / review / completed). Prax creates, assigns, and completes tasks automatically as it works through plans
- Execution graphs — real-time visualization of agent delegation trees. Watch LangGraph execution as it happens: see which spokes are running, tool call counts, timing, and status. Click any node to inspect its details and live output
- Live agent output — terminal-style view of each agent's real-time execution output (Observability > Live Agents tab). Select any agent to watch its work stream
- In-browser terminal — full PTY shell into the sandbox container (the sandbox image no longer ships coding-agent CLIs such as Claude Code)
- Browser screencast — live view of the headless Chrome running in the sandbox, with mouse/keyboard passthrough
- File browser — browse and manage workspace files
- Multi-agent status — see which role agents (Planner, Executor, Researcher, etc.) are active
TeamWork is built into the prax image by the default docker-compose.yml (the teamwork build context). After docker compose up --build, open http://localhost:3000 (subject to the compose known gap in Quick Start). API docs (Swagger) are at http://localhost:8000/docs.
For standalone use or integration with other agents, see the TeamWork repository.
Detailed documentation is organized in a hub-and-spoke structure under docs/:
System overview, five-layer design, request flows (SMS, Discord, TeamWork), workspace layout, and the hub-and-spoke orchestrator pattern.
Agent delegation (spoke agents, sub-hubs), self-improving fine-tuning (vLLM + Unsloth + LoRA), self-modification via PRs, and LangGraph checkpointing.
How to provide Prax a sandbox (local or remote — the sandbox itself lives in the separate prax-sandbox repo), Playwright browser automation, Grafana observability stack (Tempo traces, Prometheus metrics, Loki logs — --profile observability), two-layer memory system (STM + LTM with Qdrant, Neo4j, hybrid retrieval), and Docker Compose configuration.
Plugin trust tiers, subprocess isolation for imported plugins, capabilities proxy, tool risk classification, supply chain hardening, and full configuration reference.
Setup and prerequisites, extending the agent (plugin system, manual tool registration), testing (unit, e2e, integration, A/B), channel setup (TeamWork, Discord, Twilio), and troubleshooting.
Academic foundations for agentic workflow design — covering planning, reflexion, orchestration, anti-hallucination, tool overload, content pipelines, plugin sandboxing, Thompson Sampling, Active Inference, and external benchmarking. Each finding is empirically validated and mapped to Prax's implementation.
Prax has a two-layer, research-grounded memory system inspired by human cognition: a fast short-term memory (STM) for immediate context, and a scalable long-term memory (LTM) for durable recall across conversations.
- STM — per-user JSON scratchpad, always available (no infra needed), auto-injected into context
- LTM — Qdrant vector store (dense + sparse embeddings) + Neo4j knowledge graph (entities, relations, temporal events, causal links), fused via query-adaptive weighted RRF
- Consolidation — scheduled pipeline extracts entities/relations/facts via LLM, validates with confidence gate (≥0.6 → LTM, below → STM pending review), applies dual decay (Ebbinghaus time + interaction-based)
- Embedding providers — OpenAI (default), Ollama (local/offline), or fastembed (in-process) — no data leaves your machine if you don't want it to
- 10 agent tools — STM read/write/delete, LTM remember/recall/forget, entity lookup, graph query, consolidation, stats
MEMORY_ENABLED=true is the default. Memory infrastructure (Qdrant, Neo4j) runs as first-class services. Set MEMORY_ENABLED=false to disable. Without memory, STM still works — LTM degrades silently.
Prax implements a trace-centered feedback loop that turns production failures into permanent regression guards:
feedback → failure journal → eval runner → verified fix
- Feedback capture — Users rate agent messages (thumbs up/down) via TeamWork. Negative feedback automatically creates a failure journal entry with the full execution trajectory.
- Failure journal — Stores observed failures in JSONL (always available) + Neo4j (graph queries, tool relationships) + Qdrant (semantic similarity search). Auto-classifies failures: wrong tool, hallucination, incomplete, too slow, asked instead of acting.
- Eval runner — Replays failure cases through the current agent and uses an LLM judge to score whether the failure has been fixed. Produces pass/fail verdicts with 0.0–1.0 scores.
- Self-improve integration — The self-improve agent reads the failure journal, proposes targeted fixes, and runs the eval suite to verify before deploying.
Resolved failures stay as permanent regression guards — every fix adds a test case.
Status (2026-09): the sandbox image no longer ships coding-agent CLIs. Claude
Code, Codex and OpenCode were removed from the prax-sandbox
image on 2026-07-20 (prax-sandbox #4 — see the comment block in its sandbox/Dockerfile)
so that the container needs no model API key. Prax now codes with its own governed
tools (run_python, workspace_save / workspace_patch with the syntax linter,
source_read / source_grep, sandbox_shell) and modifies itself through the
self_improve_* tools in prax/agent/codegen_tools.py (isolated git worktree in a
staging clone → verify: tests, lint, startup check → hot-swap via self_improve_deploy;
self_improve_submit exists but is disabled — git push is not allowed — and only returns
a message telling the agent to use deploy or hand the change to the user). All of that
stays behind SELF_IMPROVE_ENABLED=true (default off).
What remains of the old wiring:
SELF_IMPROVE_AGENT(defaultclaude-code) still exists inprax/settings.pyand is read byprax/agent/claude_code_tools.py, which builds aclaude/codex/opencodecommand line and runs it withcd /source && …in the sandbox. That can only work if you install the CLI in the container yourself, with a dedicated, spend-capped key — the image does not provide it./sourceno longer exists in either of this repo's compose files: the read-write repo mount was removed 2026-09 (see the Quick Start note), and only the staledocker-compose.dev.ymloverlay still adds/source/*to the sandbox. prax-sandbox's own compose — whatmake run-local-alluses — mounts only/workspace.- code-server (browser-based VS Code) is installed in the sandbox image.
Warning: any coding-agent CLI you install yourself uses provider API tokens and costs money per invocation. Monitor your API spend when self-improvement is enabled.
Control how independently Prax operates via PRAX_AUTONOMY in .env:
| Level | Behavior |
|---|---|
guided |
(default) All safety gates active. HIGH-risk tools require user confirmation. Prescriptive workflow rules enforced. Most conservative. |
balanced |
(recommended) Removes prescriptive workflow rules — Prax uses judgment. HIGH-risk tools still gated but smart auto-approve kicks in when intent is clear. Agent decides its own approach. |
autonomous |
Also relaxes recursion limits, allows self-tier-upgrade (agent can switch to a more capable model mid-task), and earned trust can downgrade browser tool risk levels. Most independent. |
Known gap (2026-09): the "HIGH-risk tools require user confirmation" gate is enforced by the governance wrapper on the orchestrator's tool set, and that set currently contains no HIGH-classified tool: every name in
_HIGH(prax/agent/action_policy.py—browser_click,browser_fill,schedule_create,plugin_write,plugin_import,self_improve_deploy, …) is reached only through an ungoverned sub-loop — aspokes/agent (prax/agent/spokes/_runner.pybinds tools without the wrapper), the self-improvement sub-agent (prax/agent/self_improve_agent.py, forself_improve_deploy) or thegpu_powerplugin (gpu_power_on/gpu_power_off, registered only whenGPU_POWER_BROKER_URLis set) — and none is in the hub registry. In the shipped topology the confirmation gate therefore does not fire; see Roadmap → Security & governance.
PRAX_AUTONOMY=balancedIf Prax keeps asking "should I proceed?" or "do you want me to...?" when the answer is obviously yes — switch from guided to balanced. The guided mode is designed for initial setup and untrusted environments. For daily use, balanced is the right default.
The code-execution sandbox is now its own repo — its roadmap lives in prax-sandbox. This list covers the harness itself. The harness keeps only thin bridges to the sandbox (
prax/services/sandbox_bridge.py,prax/agent/sandbox_tools.py).
Agent core & models
- LangGraph hub-and-spoke ReAct agent (orchestrator + domain spokes)
- Multi-provider LLM factory with mid-session switching, local vLLM, and multi-model consensus
- Agent task planning (multi-step decomposition) + instruction persistence
Reliability & resilience
(flag-gated; recommended settings are pre-flipped in .env-example per the
2026-07-08 eval-gate run —
every flag A/B'd against baseline, including the ones that measured WORSE and
stay off)
- Cross-provider LLM failover (rate-limit / overload / breaker-aware)
- Per-dependency circuit breakers for external services
- Checkpoints with automatic retry-from-last-good-state — in-memory by default;
CHECKPOINT_BACKEND=sqliteneedslanggraph-checkpoint-sqlite, which is not a declared dependency, so without installing itprax/agent/checkpoint.pylogs a warning and silently falls back to in-memory - User-initiated resume of a failed/timed-out turn from saved checkpoints
- Within-turn recovery-context injection on retry after a tool-chain failure
- Multi-perspective (4-angle) error recovery + tool-call loop detection
- Health watchdog + append-only telemetry with self-repair advisories
Security & governance
- Governance wrapper (
prax/agent/governed_tool.py: risk classify + arg scrub + confirm gate + audit + budget) applied to the orchestrator's tool set (tool_registry.get_registered_tools) and to the MCP server's pool. Known gap (2026-09): spoke-internal tools are bound without it (prax/agent/spokes/_runner.pybuilds the loop straight frombind_tools_user_context(tools)), and every tool classified HIGH inprax/agent/action_policy.py(browser_click,schedule_create,plugin_write,self_improve_deploy, …) is reached only through such an ungoverned sub-loop — aspokes/agent, the self-improvement sub-agent (prax/agent/self_improve_agent.py, forself_improve_deploy) or thegpu_powerplugin — and none is in the hub registry, so the HIGH-risk confirm gate does not fire in the shipped topology - Scoped HIGH-risk confirmation (
HIGH_RISK_SCOPED_CONFIRM, default off; subject to the gap above). Unknown tools classify MEDIUM — a deny-by-default variant (unknown → HIGH) was measured in the 2026-07-08 campaign, regressed, and was removed rather than left switchable (see flag-audit.md) - SSRF egress guard (
prax/utils/ssrf.py: blocks internal/metadata addresses, per-redirect-hop revalidation) on the plugin capability gateway, the URL reader and, since 2026-09,workspace_download(prax/agent/workspace_tools.pyusessafe_request;tests/test_workspace_download_ssrf.py). Closed 2026-09 (scheme): every agent-driven browser navigation —browser_navigate,sandbox_browser_actnavigate,browser_verifygoto,browser_page_screenshot, the interactive-login opener — passescheck_navigation_url(prax/services/browser_service.py, called fromprax/agent/cdp_tools.py), which unconditionally refuses non-http(s) URLs (file://,javascript:,data:,about:) and hostless URLs;tests/test_browser_navigate_scheme.pypins it. Known gap (2026-09): the host leg of that gate — refusing private / loopback / link-local hosts — is opt-in (BROWSER_NAVIGATE_SSRF_GUARD, default off,prax/settings.py), and two model-controlled fetchers still bypass the guard entirely:pdf_service.download_pdf(prax/services/pdf_service.py) and the arXiv plugin'sarxiv_fetch_papers(prax/plugins/tools/arxiv_reader/plugin.py) use plainrequests.get - Hard plugin-activation gate for security-flagged imports
- Earned-trust relaxation + configurable autonomy profiles (guided / balanced / autonomous)
- Deterministic claim-auditor (numeric + grounding checks) over final responses
Memory, knowledge & retrieval
- Two-layer memory: bounded auditable STM + Qdrant/Neo4j LTM with bi-temporal consolidation + dual decay
- Hybrid retrieval (weighted RRF over dense + sparse + graph) with neighbourhood expansion
- Retrieval precision: LLM relevance rerank + query expansion (paraphrase/HyDE variants)
- Hybrid (dense+sparse) semantic search over knowledge-graph concepts
- OKF (Open Knowledge Format) export/import bridge for portable interchange
- Durable structured-memory ledger that works without vector/graph infra
- Dynamic user notes (per-user preferences and personality)
Evaluation & observability
- Reference-free live-traffic eval, decomposed into grounding / relevancy / correctness axes
- Failure-replay eval runner + regression suite (LLM judge);
make evalquality gate - Nightly continuous-eval job publishing
prax_eval_qualitygauges to Prometheus - OpenTelemetry / Prometheus metrics + tracing; observability-as-tools (agent queries its own health)
- Semantic search over past execution traces ("have I solved this before?")
Prompting & routing
- Selective system-prompt assembly (drops unneeded topic sections on simple turns)
- Thompson Sampling tier bandit + difficulty-driven routing
- Metacognitive failure profiles injected as prompt warnings + self-verification of outputs
Plugins & extensibility
- Plugin system — folder-per-plugin, hot-swap, subprocess isolation, capabilities proxy, auto-rollback
- Auto-generated plugin catalog (import-free metadata parse)
- Separate git repo for agent-authored plugins (push + cherry-pick workflow)
- Self-authored-tool registry tracking rationale / state / performance
Interop (MCP)
- MCP server exposing a curated, governed tool subset to other agents
- Per-caller identity + per-caller allowlist; fail-closed bearer endpoint
Channels, identity & sharing
- Unified UUID identity with provider linking across SMS / Discord / TeamWork
- Twilio SMS + voice, Discord bot, configurable agent name
- TeamWork web-UI integration; public share registry (per-file/course/note via ngrok URL)
Tasks, scheduling & Library
- Background task runner — auto-executes Kanban + todo items assigned to the agent
- Library: hierarchical Project → Notebook → Note knowledge base with Kanban
- Per-space rolling progress log (hard char cap + LLM compaction)
- Recurring schedules (timezone-aware cron) + one-time reminders + user to-do list
Workspace, documents & readers
- Git-backed per-user workspace with context injection
- PDF extraction + document pipelines (LaTeX render, Mermaid validate, Hugo publishing)
- Content readers (arXiv, YouTube, audio transcription, news) + lightweight URL fetch
- Browser automation (Patchright: navigate, forms, stored-credential login)
Teaching & self-improvement
- Faculty of professor personas teaching adaptive one-lesson-at-a-time courses
- Self-improving fine-tuning (trajectory export + QLoRA/Unsloth + vLLM adapter hot-swap)
- Self-modification (staging clone → verify → deploy / PR) — executed via the sandbox
Deployment
- Docker Compose (lite / full / GPU) + Kubernetes Helm chart + operator CRDs (
PraxInstance/PraxWorkspace)
- Apple Silicon (MLX) local-inference backend
- Schedule firing with workspace file attachments (e.g. daily PDF digest)
- Discord voice channel support (join, listen, speak)
- Multi-step browser workflows (recorded / replayable recipes)
- Adapter A/B testing (champion / challenger LoRA evaluation)
- SSRF guard DNS-rebinding hardening (pin the resolved IP into the socket)
- MCP Streamable-HTTP streaming / SSE responses
- Durable cross-restart checkpoints by default + automatic "resume?" prompt