Skip to content

Latest commit

 

History

258 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Prax

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.

Prax

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.


Quick Start

Docker Compose

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=unused

Leave 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 boot reconcile_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 has workspace_dir == PRAX_USER_ID are unaffected. make run-local-all also injects PRAX_USER_ID, so the native path is exposed on a fresh identity DB too.

Lite mode (recommended for laptops)

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 --build

2 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.

Full mode (recommended for servers)

docker compose up --build

2 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 sandbox service in docker-compose.yml and docker-compose.lite.yml was health-checked with curl http://localhost:4096/global/health — the OpenCode server the prax-sandbox image removed on 2026-07-20 — and with prax on depends_on: sandbox: condition: service_healthy the sandbox never reported healthy and prax never started. The compose-level check is gone; the image's own HEALTHCHECK (pgrep -x supervisord) is what prax waits on. The same change dropped the sandbox's ANTHROPIC_API_KEY/OPENAI_API_KEY injection and the read-write /source mount of this whole repo (.env included): the sandbox now gets only ${WORKSPACE_DIR}/${PRAX_USER_ID} at /workspace — the keyless posture docs/security/sandbox-execution-boundary.md describes and prax-sandbox's own compose always had. tests/test_compose_sandbox_service.py fails 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 up fails with the 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.

With observability (Grafana + Tempo + Prometheus + Loki)

docker compose --profile observability up --build

This 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/.

GPU mode (NVIDIA)

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 -d

Requires on the host:

  • NVIDIA driver (verify: nvidia-smi)
  • nvidia-container-toolkit installed and registered with Docker (verify: docker info | grep -i nvidia shows nvidia under 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.

Memory, Ollama, and core services

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-text

The configured embedding model is auto-pulled when the Ollama container starts. See Embedding Providers for a comparison of OpenAI vs Ollama vs local options.

Developer mode

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 prax

For 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 reload

Open 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            # both

Remote access (Tailscale / HTTPS)

Accessing 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 config

SSH 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/

Run without Docker

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-status only checks ports; make smoke (after make 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, run make integration: it tears the running stack down, deletes the derived state that masks fresh-download bugs (the built TeamWork SPA, the .local-run markers), brings everything up from scratch, and runs make 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=1 also rebuilds the sandbox image from scratch; SANDBOX_PATH=/nonexistent skips it for a core-only run.) The Fresh-install integration GitHub workflow (.github/workflows/fresh-install.yml, nightly/manual) runs the same make integration on 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, per gh run list --workflow fresh-install.yml). The 2026-09-06 run failed at Sandbox OpenCode /global/health :4096scripts/smoke_test.py still 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 .env still sets no TEAMWORK_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-all builds the UI automatically when npm is present (and run-local-all-dev runs it with hot-reload); if npm is 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 (or choco install nodejs-lts) — or the nodejs.org installer.
  • Linux (Debian/Ubuntu): sudo apt install nodejs npm (Ubuntu's nodejs package omits npm, 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 the make target.

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-sandbox

Docker 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:5

make 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-all starts these in Docker for you (or uses native binaries if Docker is absent). You can skip memory entirely with MEMORY_ENABLED=false (STM still works; LTM degrades silently).
  • Docker — used by run-local-all for 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 docker without sudo (the Makefile calls plain docker). If make run-local-all / make integration reports permission denied while trying to connect to the Docker API at unix:///var/run/docker.sock, add yourself to the docker group, 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.

1. Install Prax

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 3

2. Start the backing services (memory)

Prax 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 :6334

Neo4j — 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 :7474

Prefer 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:5

Or skip memory altogether: set MEMORY_ENABLED=false in .env and skip this step.

3. Configure .env

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_DOCKER unset, prax.utils.shell.run_command runs commands as a host subprocess rather than in the sandbox — its routing keys on settings.sandbox_persistent, which is simply running_in_docker (prax/settings.py), not on whether the sandbox is enabled or reachable. Callers include the six desktop_* tools in prax/agent/sandbox_tools.py (desktop_open passes the model-supplied string to bash -c), plugin caps.run_command (prax/plugins/capabilities.py), and the Mermaid validator. So on this native path (and on make run-local-all, which also leaves the variable unset) those commands execute on the Prax host as the Prax user, not inside the container.

4. Run Prax

uv run python app.py                       # serves the Flask API on http://localhost:5001

That'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.

5. (Optional) Run TeamWork without Docker

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.cli

Then 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).

Sandbox 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.

Ports

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)

Channels

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 :5001 for Twilio. A port tunnel such as ngrok http 5001 (what the Docker image's supervisord runs when NGROK_AUTHTOKEN is set — scripts/ngrok-launch.sh — and what docs/security/configuration.md shows 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 (no before_request / API-key check in app.py or prax/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): with TWILIO_AUTH_TOKEN unset, 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 spoofing From; with it set, the signature is checked against request.url with no X-Forwarded-Proto handling, so behind an HTTPS tunnel that forwards plain HTTP the computed http://… URL does not match the https://… 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 :5001 on the public internet (see docs/security/network-exposure.md).

Remote access

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's serve-config.json map to the Docker ports; point them at :8000/:5001 instead before using them without Docker.

Deploying the suite to a fresh server (keyless, single-tenant)

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.

1. Host prep

# 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 nodejs

2. Clone the siblings

They 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
done

3. Configure credential isolation

Follow 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 .envPROXY_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.

4. TeamWork: frontend build + the credential pair

cd ~/PRAX/teamwork/frontend && npm ci && npx vite build   # else / 404s

TeamWork'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 it

Generate 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.

5. Start everything

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:8000

Never expose the sandbox ports (:9223 CDP, :6080 noVNC, :6090 clipboard). 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.

6. Verify

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          # 200

7. Survive a reboot

Started 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 prax

Two 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 .env from its working directory and exports HTTPS_PROXY itself (app.py_export_proxy_env_from_dotenv). systemd's EnvironmentFile parser does not handle quoted values the way a shell does, so pointing it at .env would 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.1

Setting 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.

⚠️ Running a second Prax alongside an existing one

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.

Known limitation — single-tenant only

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.


Features

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 Web UI

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.


Documentation

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.


Memory System

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.

Full documentation →


Agent Improvement Loop

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.

Full documentation →


Coding Agents

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 (default claude-code) still exists in prax/settings.py and is read by prax/agent/claude_code_tools.py, which builds a claude / codex / opencode command line and runs it with cd /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.
  • /source no 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 stale docker-compose.dev.yml overlay still adds /source/* to the sandbox. prax-sandbox's own compose — what make run-local-all uses — 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.


Agent Autonomy

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.pybrowser_click, browser_fill, schedule_create, plugin_write, plugin_import, self_improve_deploy, …) is reached only through an ungoverned sub-loop — a spokes/ agent (prax/agent/spokes/_runner.py binds tools without the wrapper), the self-improvement sub-agent (prax/agent/self_improve_agent.py, for self_improve_deploy) or the gpu_power plugin (gpu_power_on/gpu_power_off, registered only when GPU_POWER_BROKER_URL is 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=balanced

If 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.


Roadmap

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).

Shipped

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=sqlite needs langgraph-checkpoint-sqlite, which is not a declared dependency, so without installing it prax/agent/checkpoint.py logs 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.py builds the loop straight from bind_tools_user_context(tools)), and every tool classified HIGH in prax/agent/action_policy.py (browser_click, schedule_create, plugin_write, self_improve_deploy, …) is reached only through such an ungoverned sub-loop — a spokes/ agent, the self-improvement sub-agent (prax/agent/self_improve_agent.py, for self_improve_deploy) or the gpu_power plugin — 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.py uses safe_request; tests/test_workspace_download_ssrf.py). Closed 2026-09 (scheme): every agent-driven browser navigation — browser_navigate, sandbox_browser_act navigate, browser_verify goto, browser_page_screenshot, the interactive-login opener — passes check_navigation_url (prax/services/browser_service.py, called from prax/agent/cdp_tools.py), which unconditionally refuses non-http(s) URLs (file://, javascript:, data:, about:) and hostless URLs; tests/test_browser_navigate_scheme.py pins 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's arxiv_fetch_papers (prax/plugins/tools/arxiv_reader/plugin.py) use plain requests.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 eval quality gate
  • Nightly continuous-eval job publishing prax_eval_quality gauges 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)

Planned

  • 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

License

Apache License 2.0

About

Prax, a dynamic agent harness that can improve itself and accept plugins via github

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages