PostgreSQL is the only application database in the local profile. Daytona is
needed only for sandbox-enabled agents. WorkOS, S3-compatible storage, and an
OTLP collector are optional for a sandbox-disabled first session.
Prerequisites
Install the following before starting OAO:- Node.js 22.19.0 or newer
- pnpm 10.27.0, which is the version pinned by the workspace
- The Docker CLI and a running Docker-compatible daemon
curlfor health checks andjqfor the API walkthroughs in these docs
docker info must reach the Docker daemon. The local scripts use Docker Compose when it is available, fall back to the legacy docker-compose command, and otherwise manage the PostgreSQL container with the Docker CLI directly. A Compose plugin is therefore helpful but not required.
On macOS, start Docker Desktop, Colima, or another Docker-compatible daemon before running OAO. For example, if Colima is installed:
docker CLI; Apple Container is not implemented as a local runtime for these scripts.
First start
For the recommended interactive path, use the guided quickstart. The steps below describe the manual development workflow. From the repository root, install dependencies:.env; it is for local values and may contain provider credentials.
Start the complete stack:
- Starts PostgreSQL 17 if it is not already running.
- Builds the workspace packages required by the source watchers.
- Applies all PostgreSQL migrations.
- When
AUTH_PROVIDER=development, idempotently seeds the development organization, project, and principal. - Starts the API, runtime worker, and Vite console.
- Waits up to 30 seconds for the API, worker, and console to become ready.
Open http://127.0.0.1:8080 in a browser.
Disable the development identity
Authentication is selected at process startup. To disable deterministic development login and use WorkOS AuthKit locally, set these values in.env:
http://127.0.0.1:8080 sign-out URI in the
WorkOS Staging application. Restart pnpm dev:local after changing the provider;
the launcher does not switch authentication in a running API process.
WorkOS login does not create OAO authorization. Before switching a fresh local
database, create the local tenant in development mode, then use the
provision:workos command documented in apps/api/README.md to link the WorkOS
user and organization to that existing OAO principal and membership.
Normal start and stop
After the first installation, use the same command for normal development:- If this
dev:localinvocation started PostgreSQL, it stops and removes the PostgreSQL container during shutdown. - If PostgreSQL was already running, it leaves PostgreSQL running.
- In both cases, the named data volume remains intact.
pnpm db:up before pnpm dev:local when you want PostgreSQL to remain running after you stop the application processes. pnpm db:down stops and removes the OAO PostgreSQL container but does not delete its data volume.
Use pnpm dev:local, rather than the generic pnpm dev, when you need the complete local environment. The local launcher owns database startup, migration, seeding, dependency builds, readiness checks, and coordinated shutdown.
Verify the stack
WithAUTH_PROVIDER=development, the browser loads the console without a
WorkOS sign-in and shows the deterministic development identity. With
AUTH_PROVIDER=workos, an unauthenticated browser is redirected to AuthKit and
the development identity is unavailable. In both modes, the console uses the
HTTP API through the Vite /v1 proxy.
The launcher gives the selected provider to the console at startup. Development
mode establishes its deterministic local session before loading protected
context, while WorkOS mode goes directly to hosted sign-in when no session is
present. It does not probe the disabled development-login route, so normal
authentication startup is not reported as a missing API route.
The console renders in a single light appearance and has no theme toggle. Its
neo-brutalist system uses a white ground, ink borders and hard offset shadows,
and three flat brand hues with fixed jobs: action red for primary buttons and
destructive confirmation, signal yellow for live and completed states, and
accent blue for links and focus. Dark surfaces appear compositionally — an ink
block for a failed run or a crashed worker — never as a separate theme.
Check the services directly:
200 response. /healthz reports process liveness. /readyz additionally checks whether the service can use the required PostgreSQL state; the runtime response also identifies the active model profile and sandbox adapter.
For repository-wide validation, use:
0041_tool_publication_lock.sql narrows the replay lock without changing the
approval policy or claim/result fencing.
test:postgres:fresh waits for TCP readiness so the temporary initialization
server cannot trigger a premature test start. It creates a PostgreSQL container on a random host port,
applies migrations twice, and runs the database and runtime integration suites.
Automated tests use explicit provider doubles at adapter boundaries; those
doubles are never selectable in the runnable API or console. Both fresh checks
remove their temporary container after success, failure, or interruption and do
not use the persistent oao-postgres-data volume.
Persistent local data
Both the Compose path and the Docker CLI fallback store PostgreSQL data in the named Docker volumeoao-postgres-data. Agents, sessions, transcripts, and debugging history therefore survive dev:local stop/start cycles and pnpm db:down.
Inspect the volume without changing it:
pnpm db:down remains the non-destructive database stop command: it removes the
container but preserves the named volume and all local records.
Change local ports
The launcher reads.env when it exists. Keep dependent values aligned when changing ports. This example moves all four services:
pnpm dev:local after editing .env. The console uses Vite’s strict-port mode, so it exits instead of silently choosing a different port when the configured port is occupied.
Configure runtime providers
Runtime execution requires an organization-shared model provider and a organization-shared Daytona provider. There is no credential-free fake execution profile.OpenRouter, OpenAI, Anthropic, and xAI models
Generate one platform encryption key and set it for both the API and runtime:Daytona sandboxes
Use the same platform encryption key configured for hosted model providers, restart the API and runtime, then open Sandbox providers in the console. Select Add sandbox provider, choose Daytona, and add the API key, an optional target preference, and the domain/CIDR allowlist agents may use withnetwork: "restricted". The key is a write-only input encrypted in PostgreSQL
for the current project.
Choose the connection, one of the active snapshots returned by Daytona, the
network policy, and exact sandbox capabilities while creating an agent or
before publishing a later version. No process restart is needed for later
connection, credential, target, or allowlist changes.
The connection’s optional target is only a provider placement preference. OAO
does not verify residency or treat the target as a residency guarantee. A
restricted egress policy must contain at least one allowed domain or CIDR;
network: "none" blocks sandbox network access.
See Daytona sandboxes for the API and tool-capability
contracts.
S3-compatible attachments and workspace backups
To submit run attachments or preserve Daytona files after a sandbox is deleted, open Storage providers and add an organization-shared S3-compatible connection. The API and runtime use the sameOAO_CREDENTIAL_ENCRYPTION_KEY to protect its
write-only credentials. The first connection becomes the organization default
automatically; you can explicitly select another default later. Attachment
bytes go directly to this object store and are never persisted in PostgreSQL.
OAO asks Daytona for the thread sandbox’s active workdir, archives that directory
after each completed agent run, and restores the latest verified archive into a
new replacement sandbox’s workdir. No restart is required after adding or
rotating a storage connection. See
Workspace backups for retention, encryption, size,
and failure behavior.
Event webhooks to a local receiver
Event webhooks require public HTTPS endpoints. To deliver to a receiver on your machine, such as a local Convex backend, add this to.env and restart pnpm dev:local:
http:// and private-network endpoints in both the API and the
runtime worker, and both refuse to start with it unless NODE_ENV=development.
Event commits wake the worker immediately. It also polls for undelivered
events every second as a fallback; set OAO_EVENT_WEBHOOK_POLL_MS to change
that interval.
OTLP telemetry
To export vendor-neutral telemetry, set the collector endpoint:pnpm dev:local reads AUTH_PROVIDER and NODE_ENV from .env. Development
remains the credential-free default. Local WorkOS testing requires
NODE_ENV=development; hosted WorkOS deployments should use production mode
with HTTPS origins, callbacks, and cookies.Troubleshooting
Cannot connect to the Docker daemon
Symptoms includeCannot connect to the Docker daemon, a missing socket, a Docker context error, or pnpm db:up failing before PostgreSQL starts.
-
Check the selected context and daemon:
-
Start Docker Desktop or, when using Colima, run:
-
Run
pnpm dev:localagain only afterdocker infosucceeds.
colima context is missing, starting Colima normally creates or restores it; then recheck the active Docker context.
pnpm install reports ignored build scripts
pnpm 10 may finish successfully while warning that dependency lifecycle scripts were ignored. In this checkout, the warning can name @google/genai, esbuild, and protobufjs. The warning by itself is not a failed installation; pnpm dev:local immediately runs the workspace build, which is the relevant functional check.
Do not approve an unfamiliar dependency automatically. If startup later fails specifically because a reviewed dependency’s build script did not run, use pnpm’s interactive approval flow and reinstall:
git diff before committing any result.
A port is already in use
The default ports are5432, 3000, 8788, and 8080. Find the process holding a port:
.env. For PostgreSQL, change both OAO_POSTGRES_PORT and DATABASE_URL. For the API, keep OAO_API_PORT, API_ORIGIN, and VITE_OAO_API_PROXY_TARGET aligned. For the console, keep OAO_CONSOLE_PORT and APP_ORIGIN aligned.
PostgreSQL does not become healthy
Inspect the container and its health output:oao-postgres-local; use the PostgreSQL container name shown by docker ps -a with docker logs.
Check that the host port in DATABASE_URL matches OAO_POSTGRES_PORT. Do not delete the volume as a first troubleshooting step, because it contains all persistent local OAO data.
A service never becomes ready
The launcher stops the stack if the API, runtime worker, or console does not become ready within 30 seconds. Read the first service error printed above the timeout, then check:- PostgreSQL is healthy and reachable through
DATABASE_URL. - No other process is using an application port.
- API and runtime use the same canonical 32-byte
OAO_CREDENTIAL_ENCRYPTION_KEY. - The project has a configured model provider, an approved model preset, and a Daytona connection.
- The selected immutable agent version references those project provider keys.
pnpm dev:local if the platform
encryption key changed, and rerun the health checks.
Codex worktrees
The repository’s.codex/environments/environment.toml defines the OAO local
Codex environment. Its setup script copies .env from the checkout on main
when a new worktree has no .env, chooses available Console, API, and runtime
ports, updates their local URLs, and installs dependencies with the lockfile.
Existing worktree credentials are preserved on reruns. The Run action starts
pnpm dev:local. Setup requires Python 3.9+, pnpm, and a checkout of main with
an existing .env.
This changes application ports, not database isolation: worktrees retain the
source database URL and workers consume the same run queue. Port availability
is checked during setup; if another process takes a chosen port before startup,
rerun setup or adjust the worktree’s .env. Environment files remain untracked
and are written with permissions restricted to the current user.
