Documentation
Web UI
Run the Web UI from source or Docker, local and multi mode, sandbox drivers, log export.
Web UI source: apps/web. Development: Vite on port 4401. Production: built assets served by the API server. See the repository README for the minimal setup.
Processes
| Process | Port | Start command |
|---|---|---|
| Postgres | 5439 | docker compose up -d postgres |
| API server | 4400 | pnpm dev |
| Vite (Web UI) | 4401 | pnpm dev |
| TUI | none | pnpm dev |
pnpm dev starts all packages with a dev script via turbo. Do not start @bento/web separately.
Server and Web UI only:
pnpm --filter @bento/server --filter @bento/web dev
Setup
Source (hot reload)
docker compose up -d postgres
cp .env.example .env
pnpm install
pnpm db:migrate
pnpm dev
Open http://localhost:4401.
Re-run pnpm install when dependencies change. Re-run pnpm db:migrate after new migrations.
Docker (full stack)
cat >> .env <<'EOF'
BENTO_REPOS=/Users/you/code
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat-...
EOF
docker compose up --build
Open http://localhost:4400. No Node or pnpm required on the host.
BENTO_REPOS: required when checkouts are outside the compose working directory. Claude token may be set in Agents instead. Commit author: Settings, GitHub.
Migrations and sandbox image build complete before the server starts. Migrations are idempotent (advisory lock).
pnpm docker:logs
pnpm docker:down # retain database volume
docker compose down -v # delete database volume
Code changes require docker compose up --build. No hot reload.
Do not run both setups concurrently (port 4400 conflict):
docker compose stop server # before switching to pnpm dev
lsof -ti:4400 | xargs kill # before switching back to compose
Development port layout
Vite (4401) proxies /api to the API server (4400) for same-origin cookies and OAuth.
Production serves apps/web/dist from the API server when BENTO_WEB_DIR is set.
Local production test:
pnpm build
BENTO_WEB_DIR="$PWD/apps/web/dist" pnpm --filter @bento/server dev
Web UI: http://localhost:4400.
Modes
BENTO_MODE in .env:
| Mode | Behavior |
|---|---|
local (default) | Single user, no auth, no organizations |
multi | Auth, organizations, encrypted personal and shared credentials |
Multi mode requires:
BENTO_MODE=multi
BENTO_SECRET_KEY=... # openssl rand -hex 32
BETTER_AUTH_SECRET=... # openssl rand -hex 32
BETTER_AUTH_URL=http://your-bento-url..
Server startup fails in multi mode without BENTO_SECRET_KEY.
Sandbox driver
The board loads without a sandbox. Agent runs require one.
| Driver | Use |
|---|---|
docker (default) | Isolated containers. Build: docker compose build sandbox-image |
local-process | No isolation. Development and CI only |
sprite | Hosted (Fly) deployments. Needs SPRITES_TOKEN |
With sprite as the driver and MODAL_TOKEN_ID and MODAL_TOKEN_SECRET also set, new sandboxes try a Fly Sprite first and fall back to a Modal sandbox when the sprite cannot be made. A team with the network lock on runs on Modal, because a sprite cannot restrict egress. A docker or local-process deployment keeps its driver even when it holds those tokens.
Web and worker processes
One process serves the console and drives agent runs by default (BENTO_ROLE=all). To split them, run the same image twice: BENTO_ROLE=web serves HTTP and drives no runs, BENTO_ROLE=worker drives runs and serves no console. Set the slot count (BENTO_MAX_CONCURRENT_RUNS) on the worker; a web process that sets it is refused at boot. When DATABASE_URL is a transaction mode pooler, set BENTO_DIRECT_DATABASE_URL to a direct connection for the event bus and the migration lock.
Mac app
The Mac app's start screen picks the Agent sandbox type:
- Docker sandbox (default): each agent is isolated in a container.
- Local process: the agent runs directly on your Mac, with your permissions. Development only.
Advanced settings, Model proxy is on by default: model calls go through Bento, so a Docker sandbox contains a proxy token rather than your key (see Security). It is greyed out with Local process, which has no sandbox. Change one while local Bento runs and Start becomes Restart to save these settings, which restarts it.
What a sandbox can reach when a team blocks its network, and what a Docker deployment must allow, is in Security.
Subscription credential sharing
Local mode only. Ignored in multi mode.
When enabled, Bento mounts local tool config directories read-only into the sandbox (BENTO_SHARE_AGENT_AUTH, Web UI Agents, or bento setup). Credentials remain readable by the agent process; not a confidentiality boundary.
local-process inherits the host environment regardless of this setting.
Docker-hosted servers cannot access host Keychain or ~/.claude. Connect your subscription under Agents, Your personal subscriptions (local and hosted), or use claude setup-token → Agents or CLAUDE_CODE_OAUTH_TOKEN in .env.
Docker compose constraints
Agents run in sibling containers via the host Docker socket. Mounted paths must resolve identically on host and server container.
| Variable | Default | Purpose |
|---|---|---|
BENTO_STATE_DIR | /var/tmp/bento | Worktrees, server state |
BENTO_REPOS | compose working directory | Repository checkouts |
Example:
BENTO_REPOS=$HOME/code docker compose up --build
BENTO_STATE_DIR=$HOME/.bento-docker BENTO_REPOS=$HOME/code docker compose up --build
On macOS, default BENTO_STATE_DIR is inside Docker's VM and not visible in Finder.
Server image includes git. Sandbox image is built on up --build. Without it, the board loads but runs fail.
Pass agent credentials via .env:
ANTHROPIC_API_KEY=sk-ant-...
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat-...
Runs fail at start if no credential is configured.
Multi mode in Docker
BENTO_MODE=multi \
BENTO_SECRET_KEY=$(openssl rand -hex 32) \
BETTER_AUTH_SECRET=$(openssl rand -hex 32) \
docker compose up --build
Log export
Every server log line also ships as an OpenTelemetry log record over OTLP/HTTP. Console output never depends on the export.
Destination, first match wins:
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, as written.OTEL_EXPORTER_OTLP_ENDPOINTplus/v1/logs.- PostHog, when
POSTHOG_API_KEYis set in multi mode. - Nowhere.
Any OTLP backend works (a collector, Loki, Datadog, Honeycomb). Put credentials in OTEL_EXPORTER_OTLP_HEADERS as comma separated key=value pairs:
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=abc123
The standard OTEL_EXPORTER_OTLP_* timeout, compression and certificate variables are honoured. OTEL_SERVICE_NAME sets service.name (default bento-server). Records also carry bento.mode and environment (from BENTO_ENVIRONMENT). The boot summary prints log export: ....
Troubleshooting
| Symptom | Cause / fix |
|---|---|
404 /api/auth/get-session in local mode | Expected. The Web UI probes session before mode detection |
Empty board, New project inactive | API unavailable. Check curl localhost:4401/api/health |
EADDRINUSE on 4400/4401 | Stale process. `lsof -ti:4400 |
| Postgres connection refused | Start postgres: docker compose ps (port 5439) |
| Relation does not exist | Run pnpm db:migrate |
| Container server not reachable on 4400 | BENTO_HOST=0.0.0.0; published as 127.0.0.1:4400:4400 |
Source layout
| Path | Responsibility |
|---|---|
apps/web/src/App.tsx | Routing, board state, SSE |
apps/web/src/components/Board.tsx | Lanes, Done, search |
apps/web/src/components/FeatureDrawer.tsx | Card actions, transcript, gates |
apps/web/src/components/AgentsPanel.tsx | Agent CRUD, YAML |
apps/web/src/components/StageConfig.tsx | Stage config, pipeline YAML |
apps/web/src/components/TeamSettings.tsx | Organizations, credentials |
Updates via SSE: /api/board/:id/events.
Implementation notes
Completed lane: status-driven, not a stored stage. Mark completed and drag-to-Completed skip remaining stages. Reopen restores prior stage.
Search: filtered in Board.tsx via matchesQuery (apps/web/src/search.test.ts). Punctuation-normalized matching.
Icons: hashed URLs in index.html and site.webmanifest (Vite plugin) to invalidate browser favicon cache.
Changelog: apps/web/src/changelog.ts. Entry id values are stable anchors; do not rename published ids.
Development workflow
docker compose up -d postgres --build sandbox-image
cp .env.example .env
pnpm install && pnpm dev
CLI: pnpm -C apps/tui link --global (symlink to dist/cli.js). TUI development: pnpm dev:cli. Watch build: pnpm watch.