Skip to content

Latest commit

 

History

1,893 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Phoenix IDE

Web based, self-hosted LLM-powered coding agent.

Forking encouraged, make your own personal IDE that matches your workflow.

Self hosted means you can poke it when it breaks. All data is stored in a single local sqlite db.

Server/client architecture from day 1 means you can run the API half on a remote coding VM and access from anywhere!

Quick Start - LLM Access

Supported Options:

  • API keys -> ANTHROPIC_API_KEY and/or OPENAI_API_KEY
  • Paid ChatGPT / Codex -> in-app login!
    • "We want people to be able to use Codex, and their ChatGPT subscription, wherever they like!" - from OpenAI themselves
    • Browser and device code supported.
  • Custom provider-compatible endpoint -> ANTHROPIC_BASE_URL / OPENAI_BASE_URL

See the env var section for more details (or point your preferred coding agent at the repo)

Quick Start - Run it

# Start everything (backend build + frontend dev server)
./dev.py up

# Other lifecycle commands
./dev.py down        # stop services
./dev.py restart     # restart services
./dev.py status      # show running state
./dev.py check       # pre-commit checks (fmt, clippy, tests)

# Optional: run the dev backend over HTTPS with h2 ALPN enabled
./dev.py up --https

Single-shot CLI

# Runs via uv — no manual dependency install needed
./phoenix-client.py -d /tmp "Create hello.txt with 'Hello World'"
./phoenix-client.py -c <conversation-slug> "Now modify it"

Architecture

Rust backend serves the API and, in production, embeds the React frontend via rust-embed. SQLite persists conversations and messages. A bedrock state machine drives the conversation lifecycle (Idle → Processing → ToolExecuting → …). Tools are modular and LLM-invokable. Multi-provider LLM support: the Anthropic and OpenAI APIs, a ChatGPT/Codex bridge, or provider-compatible base URLs.

Philosophy

State is visible, not inferred. The UI reflects exact system state — which tool is running, how many are queued, what retry attempt you're on, whether your message was sent or is still in the local queue. Nothing is hidden behind a spinner.

Deterministic core. The conversation lifecycle is a pure state machine (Elm architecture): same inputs always produce the same outputs. All I/O is isolated in effect executors. State transitions are property-tested.

Recoverable by default. State is persisted to SQLite on every transition. SSE reconnection replays missed events by sequence ID. Drafts survive tab close. The server restarts to a known-good state.

Subtle, not minimal. The UI is information-dense — it communicates clearly without wasting visual elements. Status is shown inline with symbols and color, not buried in separate screens or modals. Progressive disclosure: essentials visible by default, details on demand.

Tools

Tool Description Spec
bash Shell command execution with wait windows, truncation, and handle-based background observation spec
patch Structured file editing — create, modify, delete with fuzzy matching spec
read_file Read a file, or a line range of one (any path the server process can access)
search grep + glob over a directory tree, ripgrep-style (any path the server process can access)
keyword_search Semantic code search using LLM-filtered results spec
think Reasoning scratchpad with zero side effects spec
browser (browser_*) Headless browser — navigate, eval JS, screenshot, click/type/keypress, resize, console logs spec
read_image Read and encode image files for vision models
tmux Drive a persistent tmux session for long-lived / interactive processes spec
terminal_last_command / terminal_command_history Inspect the in-app terminal's last command and history spec
spawn_agents Parallel task delegation to child agents spec
ask_user_question Ask the user a structured multiple-choice question mid-run spec
skill Invoke a user-defined skill (SKILL.md instruction set) spec
propose_task Propose a task file (task-authoring conversations only)
MCP tools Tools exposed by configured MCP servers, registered dynamically at startup

Production Deployment

Designed to run as a background service: a single static binary that serves the HTTP API with the React UI embedded.

./dev.py prod deploy                    # check/build local HEAD, then deploy transactionally
./dev.py prod deploy --release vX.Y.Z   # deploy a checksummed published release
./dev.py prod deploy --release latest   # resolve and deploy the latest stable release
./dev.py prod status                    # backend, runtime identity, and durable transaction
./dev.py prod stop                      # stop the backend-owned runtime

Optional HTTPS Quick Start

TLS is opt-in. The lowest-toil internal-DNS flow is a local Phoenix private CA that you trust once on your browser machine, then use to issue per-host leaf certificates. The CA private key stays on the machine where you issue certs; the remote host receives only its leaf cert and key.

# On the machine that owns the Phoenix CA, create/show the CA.
./dev.py tls ca

# Trust this CA cert once on the browser machine:
#   ~/.phoenix-ide/tls/phoenix-local-ca.pem

# Issue a bundle for the hostname you will open in the browser.
./dev.py tls issue phoenix-host.internal

# Copy only the bundle to the remote host.
scp ~/.phoenix-ide/tls-bundles/phoenix-host.internal.tar.gz ssh-host:~/

# On the remote host, from its phoenix-ide repo checkout:
./dev.py tls install ~/phoenix-host.internal.tar.gz
./dev.py prod deploy

After install, ./dev.py prod deploy reads .phoenix-ide.env and serves https://phoenix-host.internal:8031. For local development, ./dev.py up --https uses the same default CA directory, keeps the dev UI on the Vite URL, and proxies API requests to Phoenix over HTTPS.

Publishing a Release

Merge a version-bump PR to main. The release workflow tags that main commit and publishes checksummed native assets for:

  • aarch64-apple-darwin
  • x86_64-apple-darwin
  • aarch64-unknown-linux-musl
  • x86_64-unknown-linux-musl

Each stable asset URL follows:

https://github.com/scottopell/phoenix-ide/releases/latest/download/phoenix_ide-<target>

Environment Variables

Everything Phoenix reads. The server reads its config from the environment at startup; ./dev.py and the production deploy paths populate most of these for you (prod reads .phoenix-ide.env from the repo root of the checkout you deploy from).

Core server

Variable Purpose Default
PHOENIX_PORT HTTP(S) listen port 8000
PHOENIX_DB_PATH SQLite database path. If the path contains prod, startup logs "production mode" $HOME/.phoenix-ide/phoenix.db
PHOENIX_PASSWORD Optional auth password (REQ-AUTH-001). Empty/unset disables password auth — (disabled)
RUST_LOG tracing filter (info, debug, phoenix_ide=debug, …) env-default filter
HOME / USERPROFILE Home dir — used for the default DB path, built-in-skill extraction, working-dir resolution, tmux sockets OS home; /tmp fallback

LLM providers and backend-compatible endpoints

Variable Purpose Default
ANTHROPIC_API_KEY Direct Anthropic API key
ANTHROPIC_BASE_URL Override the Anthropic API base URL
OPENAI_API_KEY Direct OpenAI API key
OPENAI_BASE_URL Legacy OpenAI endpoint override; routed only when its path matches the selected OpenAI wire format
OPENAI_RESPONSES_BASE_URL Exact endpoint for OpenAI Responses-compatible models
OPENAI_CHAT_COMPLETIONS_BASE_URL Exact endpoint for OpenAI Chat Completions-compatible models
DEFAULT_MODEL Preferred default model ID (used only if it actually registers) first registered model
PHOENIX_LLM_MODELS Inline JSON array of additional model specs to add to the built-in registry
LLM_API_KEY_HELPER Command that prints a fresh API key/token on stdout (e.g. claude OAuth helper)
LLM_API_KEY_HELPER_TTL_MS How long a helper-produced credential is cached 7200000 (2 h)
LLM_CUSTOM_HEADERS Extra request headers — newline-separated Key: value (literal \n accepted); a provider header is auto-injected
LLM_REQUEST_TAGS Comma-separated key=value request tags
LLM_AUTH_HEADER bearer → send the key as Authorization: Bearer …; anything else → provider's native API-key header api-key style
PHOENIX_ENABLE_MOCK_MODEL 1 → register the deterministic mock provider (testing only) off

If none of ANTHROPIC_API_KEY, OPENAI_API_KEY, LLM_API_KEY_HELPER, or a ChatGPT/Codex credential is present, the server starts with no models and logs a warning.

PHOENIX_LLM_MODELS is additive. It does not override built-in model IDs; a configured duplicate ID is ignored and Phoenix logs a warning while keeping the built-in definition. The value is parsed at startup, and invalid JSON or invalid fields are logged and ignored without removing built-in models. Each configured model object has this shape:

[
  {
    "id": "provider-compatible/model-id",
    "api_name": "optional-wire-model-name",
    "backend": "anthropic",
    "family": "anthropic",
    "description": "Human-readable picker text",
    "context_window": 200000,
    "max_output_tokens": 16384,
    "recommended": false,
    "supports_tool_search": false
  }
]

api_name may be omitted; when absent Phoenix sends id as the wire model name. backend selects both route/auth family and wire protocol. Supported values are anthropic (Anthropic Messages-compatible), openai_responses (OpenAI Responses-compatible), and openai_chat_completions (OpenAI Chat Completions-compatible). family controls the user-facing provider label and defaults from backend; set it when model ownership differs from wire format. max_output_tokens is optional and defaults to a context-safe cap.

Example Anthropic-compatible provider POC:

ANTHROPIC_API_KEY=provider-api-key
ANTHROPIC_BASE_URL=https://provider.example/v1/messages
LLM_CUSTOM_HEADERS=source: my-provider-poc
DEFAULT_MODEL=example/provider-model
PHOENIX_LLM_MODELS=[{"id":"example/provider-model","backend":"anthropic","description":"Example Anthropic-compatible POC","context_window":200000,"recommended":false,"supports_tool_search":false}]

Example Chat Completions-compatible provider POC:

OPENAI_API_KEY=provider-api-key
OPENAI_CHAT_COMPLETIONS_BASE_URL=https://provider.example/v1/chat/completions
DEFAULT_MODEL=example/chat-model
PHOENIX_LLM_MODELS=[{"id":"example/chat-model","api_name":"provider/example/chat-model","backend":"openai_chat_completions","family":"Google","description":"Example Chat Completions-compatible POC","context_window":128000,"max_output_tokens":8192,"recommended":false,"supports_tool_search":false}]

TLS

Variable Purpose Default
PHOENIX_TLS HTTPS mode: auto/on/true/1, manual, or off/none/false/0 off
PHOENIX_TLS_HOSTS Comma-separated extra DNS/IP SANs for PHOENIX_TLS=auto localhost,127.0.0.1,::1
PHOENIX_TLS_DIR Managed local-CA + auto-issued leaf certificate directory parent of PHOENIX_DB_PATH + /tls
PHOENIX_TLS_CERT_PATH Manual TLS certificate PEM path; together with the key path, enables manual TLS even if PHOENIX_TLS is unset
PHOENIX_TLS_KEY_PATH Manual TLS private-key PEM path; required alongside the cert path

TLS is opt-in. PHOENIX_TLS=auto creates a private Phoenix CA in PHOENIX_TLS_DIR if one is not already present, then rotates the server leaf certificate on startup. PHOENIX_TLS=manual serves the cert/key paths exactly as configured; this is what ./dev.py tls install writes for remote production hosts. See TLS.md for the complete trust and deployment workflow.

Tools and runtime

Variable Purpose Default
PHOENIX_DATA_DIR Base dir for runtime data — currently the per-conversation tmux socket dir ($PHOENIX_DATA_DIR/tmux-sockets/) $HOME/.phoenix-ide
PHOENIX_CHROME_EXECUTABLE Explicit Chrome/Chromium path for the browser tool; falls back to auto-detection auto-detect
PHOENIX_PARENT_TOOL_CYCLE_CAP Max tool cycles a parent agent may run before yielding (non-negative int; 0 disables the cap) built-in default
PATH Used to locate tmux, Chrome, and shell binaries; inherited into the in-app terminal inherited
SHELL Shell the in-app terminal launches /bin/bash

The in-app terminal is given a deliberately minimal environment (it never inherits the server's env, to avoid leaking secrets): TERM, COLORTERM, HOME, USER/LOGNAME, SHELL, PATH, LANG, plus shell-integration hints (TERM_PROGRAM=phoenix-ide, ITERM_SHELL_INTEGRATION_INSTALLED=Yes). USER falls back to LOGNAME.

Zero-downtime restart (set by socket activation, not by you)

Variable Purpose
LISTEN_FDS / LISTEN_PID systemd-style socket passing — the listening socket survives an in-place binary swap (./dev.py restart, prod deploy). Managed automatically; the binary clears them after adopting the fd.
launchd Sockets / Listeners macOS launchd socket passing — ./dev.py prod deploy installs an IPv4/v6 dual-stack listener named Listeners, and the binary adopts it with launch_activate_socket.

Tests only (cargo test)

Variable Purpose
PHOENIX_SKIP_BROWSER_TESTS 1 → skip browser-tool tests (no Chrome available)
PHOENIX_SKIP_NETWORK_TESTS 1 → skip tests that need outbound network

Dev/build tooling only (read by ./dev.py / Vite, not the server)

Variable Purpose
PHOENIX_PUBLIC_URL Display URL shown in ./dev.py prod status / deploy output
PHOENIX_VERSION Version string ./dev.py bakes into the systemd/prod env (the server reports its own CARGO_PKG_VERSION)
VITE_API_PORT / VITE_API_SCHEME / VITE_API_PROXY_SECURE How the Vite dev server proxies /api to Phoenix

API Endpoints

  • GET /api/conversations - List all conversations
  • POST /api/conversations/new - Create new conversation
  • GET /api/conversations/:id - Get conversation details
  • POST /api/conversations/:id/messages - Send a message
  • GET /api/conversations/:id/stream - SSE stream for real-time updates

Documentation

  • specs/ — Per-tool and subsystem specs using the spEARS methodology
  • LAUNCHD.md — macOS launchd deployment and socket activation
  • TLS.md — HTTPS, HTTP/2, private CA, and deployment workflow
  • AGENTS.md — Agent architecture and conventions

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages