Skip to content

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

ProcessPortStart command
Postgres5439docker compose up -d postgres
API server4400pnpm dev
Vite (Web UI)4401pnpm dev
TUInonepnpm 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:

ModeBehavior
local (default)Single user, no auth, no organizations
multiAuth, 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.

DriverUse
docker (default)Isolated containers. Build: docker compose build sandbox-image
local-processNo isolation. Development and CI only
spriteHosted (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.

VariableDefaultPurpose
BENTO_STATE_DIR/var/tmp/bentoWorktrees, server state
BENTO_REPOScompose working directoryRepository 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:

  1. OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, as written.
  2. OTEL_EXPORTER_OTLP_ENDPOINT plus /v1/logs.
  3. PostHog, when POSTHOG_API_KEY is set in multi mode.
  4. 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

SymptomCause / fix
404 /api/auth/get-session in local modeExpected. The Web UI probes session before mode detection
Empty board, New project inactiveAPI unavailable. Check curl localhost:4401/api/health
EADDRINUSE on 4400/4401Stale process. `lsof -ti:4400
Postgres connection refusedStart postgres: docker compose ps (port 5439)
Relation does not existRun pnpm db:migrate
Container server not reachable on 4400BENTO_HOST=0.0.0.0; published as 127.0.0.1:4400:4400

Source layout

PathResponsibility
apps/web/src/App.tsxRouting, board state, SSE
apps/web/src/components/Board.tsxLanes, Done, search
apps/web/src/components/FeatureDrawer.tsxCard actions, transcript, gates
apps/web/src/components/AgentsPanel.tsxAgent CRUD, YAML
apps/web/src/components/StageConfig.tsxStage config, pipeline YAML
apps/web/src/components/TeamSettings.tsxOrganizations, 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.