> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oao.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Local development

> Install, start, verify, stop, and troubleshoot the complete OAO development stack.

OAO's default development profile runs the console, API, runtime worker, and PostgreSQL on your machine. Development authentication is local, but executing an agent requires an OpenRouter, OpenAI, Anthropic, or xAI connection.

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

## 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
* `curl` for health checks and `jq` for the API walkthroughs in these docs

Check the installed versions from the repository root:

```bash theme={null}
node --version
pnpm --version
docker --version
docker info
```

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

```bash theme={null}
colima start
docker context ls
docker info
```

If more than one Docker context exists and the active context does not reach Colima, select it and check again:

```bash theme={null}
docker context use colima
docker info
```

OAO does not start or stop Docker Desktop or Colima. The current launcher invokes the `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](/getting-started/quickstart). The steps below describe the manual development workflow.

From the repository root, install dependencies:

```bash theme={null}
pnpm install
```

The checked-in defaults are enough for local development. Copy the environment template only if you want a visible file to edit:

```bash theme={null}
cp .env.example .env
```

Do not commit `.env`; it is for local values and may contain provider credentials.

Start the complete stack:

```bash theme={null}
pnpm dev:local
```

On every start, the launcher:

1. Starts PostgreSQL 17 if it is not already running.
2. Builds the workspace packages required by the source watchers.
3. Applies all PostgreSQL migrations.
4. When `AUTH_PROVIDER=development`, idempotently seeds the development
   organization, project, and principal.
5. Starts the API, runtime worker, and Vite console.
6. Waits up to 30 seconds for the API, worker, and console to become ready.

Wait for this message before opening the application:

```text theme={null}
OAO is ready at http://127.0.0.1:8080
Press Ctrl-C to stop.
```

The default services are:

| Service | Address | Purpose |
| - | - | - |
| Console | `http://127.0.0.1:8080` | React management and debugging UI |
| API | `http://127.0.0.1:3000` | REST, authentication, and SSE endpoints |
| Runtime worker | `http://127.0.0.1:8788` | Durable run execution and readiness endpoint |
| PostgreSQL | `127.0.0.1:5432` | Canonical state, control state, read models, and wake queue |

Open [http://127.0.0.1:8080](http://127.0.0.1:8080) in a browser.

<Warning>
  Development authentication trusts local requests and seeds a deterministic
  principal with full local permissions. Use this profile only in a trusted
  development environment; it is not a production authentication mode.
</Warning>

### 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`:

```dotenv theme={null}
AUTH_PROVIDER=workos
NODE_ENV=development
APP_ORIGIN=http://127.0.0.1:8080
API_KEY_PEPPER=generate-a-random-secret
WORKOS_API_KEY=sk_test_...
WORKOS_CLIENT_ID=client_...
WORKOS_COOKIE_PASSWORD=at-least-32-random-characters
WORKOS_WEBHOOK_SECRET=whsec_...
WORKOS_CALLBACK_URL=http://127.0.0.1:8080/v1/auth/callback
```

Register the exact callback and `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:

```bash theme={null}
pnpm dev:local
```

Use <kbd>Ctrl</kbd>+<kbd>C</kbd> in that terminal to stop. The launcher terminates the console, API, and runtime worker. Its PostgreSQL behavior depends on who started the database:

* If this `dev:local` invocation 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.

To manage the persistent database separately:

```bash theme={null}
pnpm db:up
pnpm db:migrate
pnpm db:down
```

For example, run `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

With `AUTH_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:

```bash theme={null}
curl -fsS http://127.0.0.1:3000/healthz
curl -fsS http://127.0.0.1:3000/readyz
curl -fsS http://127.0.0.1:8788/healthz
curl -fsS http://127.0.0.1:8788/readyz
curl -fsSI http://127.0.0.1:8080/
```

A healthy default stack returns:

```json theme={null}
{"status":"ok"}
{"status":"ready"}
{"status":"ok"}
{"status":"ready","profile":"project-providers","sandbox":"daytona"}
```

The final console request should return an HTTP `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:

```bash theme={null}
pnpm check
pnpm test:postgres:fresh
pnpm test:stack:fresh
```

CI runs these checks with Node.js 22.19.0; use that version when reproducing
CI-only failures. The long runtime scenario runs in an isolated process with a
three-minute deadline. Its cleanup retains the original error, and the parent
terminates the scenario process group so background workers cannot hang the
test runner. CI also caps the complete PostgreSQL step at ten minutes.

The restart test allows 65 seconds for recovery with pinned Flue 2.0.3:
a 30-second ownership lease, up to two 15-second scan intervals (the scan has
its own elapsed-time gate), and five seconds for startup/completion. Lease
expiry is not an immediate-recovery guarantee. The test still verifies that
recovery reuses the original submission and instance, completes on the second
attempt, and commits the caller's result.

The database suite also holds a tool-publication replay transaction open while
another connection denies or expires its approval. These operations must not
block each other; the test checks the resulting tool stage and claim fence,
and verifies conflicting publication requests are still rejected. Migration
`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 volume `oao-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:

```bash theme={null}
docker volume inspect oao-postgres-data
```

To deliberately reset the complete local environment, first stop the stack and
use the confirmation-gated CLI command:

```bash theme={null}
pnpm oao reset
```

<Warning>
  Reset permanently deletes `oao-postgres-data`, `.env`, and `.oao`. It refuses
  to run while OAO services are active. The next `pnpm oao setup` creates a
  fresh volume, reapplies migrations, seeds the development tenant, and guides
  you through provider and starter-agent creation again.
</Warning>

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

```dotenv theme={null}
OAO_POSTGRES_PORT=55432
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:55432/oao

OAO_API_PORT=3100
API_ORIGIN=http://127.0.0.1:3100
VITE_OAO_API_PROXY_TARGET=http://127.0.0.1:3100

OAO_RUNTIME_PORT=8790

OAO_CONSOLE_PORT=8081
APP_ORIGIN=http://127.0.0.1:8081
```

Restart `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:

```bash theme={null}
openssl rand -base64 32
```

```dotenv theme={null}
OAO_CREDENTIAL_ENCRYPTION_KEY=replace-with-generated-base64-value
```

Restart the API and runtime, then add an OpenRouter, OpenAI, Anthropic, or xAI provider on the
console **Models** page. Enter provider API keys there; OAO encrypts them before
storing them in PostgreSQL for the current organization and project. Add a
model preset linked to that connection. The model selector loads the live
catalog available to the selected provider key; for direct providers it offers
the returned models supported by OAO's runtimes. xAI uses its live
language-model catalog so newly listed text-output Grok IDs can be selected
without an OAO catalog update. OpenAI also discovers new supported GPT and
o-series text/reasoning models from its live account list, including GPT-6
Astra, without a library update. See [model discovery limitations](/models/adding-models#a-model-is-not-in-the-catalog)
for models with unknown metadata. No process restart is needed for later provider
or preset additions. Direct OpenAI presets also pin
plain-text output, reasoning mode and effort, verbosity, and reasoning-summary
settings. Direct Anthropic presets pin adaptive or disabled thinking, a total
output-token ceiling, and a supported effort level. Supported direct xAI
reasoning presets pin text output and a documented Grok reasoning effort. The
runtime applies these settings to every session using that preset.

<Warning>
  Do not set `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or
  `XAI_API_KEY` in `.env`. Provider keys are write-only console/API inputs and
  are never returned after encryption.
</Warning>

### 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 with
`network: "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](/sandboxes/daytona) 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 same `OAO_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](/storage/workspace-backups) for retention, encryption, size,
and failure behavior.

### Event webhooks to a local receiver

[Event webhooks](/integrations/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`:

```dotenv theme={null}
OAO_EVENT_WEBHOOKS_ALLOW_PRIVATE_NETWORK=true
```

It allows `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:

```dotenv theme={null}
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
```

Leave it empty when no collector is running.

<Note>
  `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.
</Note>

## Troubleshooting

### Cannot connect to the Docker daemon

Symptoms include `Cannot connect to the Docker daemon`, a missing socket, a Docker context error, or `pnpm db:up` failing before PostgreSQL starts.

1. Check the selected context and daemon:

   ```bash theme={null}
   docker context show
   docker context ls
   docker info
   ```

2. Start Docker Desktop or, when using Colima, run:

   ```bash theme={null}
   colima start
   docker context use colima
   docker info
   ```

3. Run `pnpm dev:local` again only after `docker info` succeeds.

Starting OAO does not start the daemon for you. If a `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:

```bash theme={null}
pnpm approve-builds
pnpm install
pnpm dev:local
```

Select only dependencies whose install scripts you have reviewed and intend to trust. The approval can update tracked pnpm policy files, so inspect `git diff` before committing any result.

### A port is already in use

The default ports are `5432`, `3000`, `8788`, and `8080`. Find the process holding a port:

```bash theme={null}
lsof -nP -iTCP:8080 -sTCP:LISTEN
```

Stop the conflicting process or change the corresponding values in `.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:

```bash theme={null}
docker ps -a
docker logs oao-postgres-local
docker volume inspect oao-postgres-data
```

When Compose is active, its generated container name may differ from `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.

Correct the provider configuration, restart `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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.