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

# Local development

> Run r5d.chat on your machine with a local workspace daemon instead of Kubernetes.

Local development uses `WORKSPACE_MODE=local`, which replaces per-user
Kubernetes pods with a single daemon process on your machine. It is the fastest
way to work on the application.

## Requirements

* Bun 1.3 or later
* Docker with Compose, for PostgreSQL
* Optionally `recoll`, `recollindex`, and `rg` for document-search testing

## Set up

<Steps>
  <Step title="Start PostgreSQL">
    ```bash theme={null}
    docker compose up -d postgres
    ```
  </Step>

  <Step title="Configure the environment">
    ```bash theme={null}
    cp .env.example .env
    ```

    Replace the placeholder values for `BETTER_AUTH_SECRET` and
    `SETTINGS_ENCRYPTION_KEY`. Both must be at least 32 characters.

    ```bash theme={null}
    printf 'BETTER_AUTH_SECRET=%s\n' "$(openssl rand -base64 36)"
    printf 'SETTINGS_ENCRYPTION_KEY=%s\n' "$(openssl rand -base64 36)"
    ```
  </Step>

  <Step title="Install and migrate">
    `bash bun install bun run db:migrate `
  </Step>

  <Step title="Run the workspace daemon">
    In its own terminal. The daemon uses a directory in your project as the
    workspace home rather than touching your real one.

    ```bash theme={null}
    mkdir -p .local-workspace
    HOME="$PWD/.local-workspace" \
    R5DCHAT_HOME="$PWD/.local-workspace" \
    R5DCHAT_DAEMON_TOKEN=development-only-token \
    bun run runtime -- daemon
    ```
  </Step>

  <Step title="Run the application">
    In a second terminal:

    ```bash theme={null}
    bun run dev
    ```

    Open `http://localhost:3000` and complete setup. The first successful setup
    request becomes the administrator.
  </Step>
</Steps>

<Warning>
  In local mode every user shares one daemon and one home directory. This is
  fine for development and completely unsuitable for anything multi-tenant.
</Warning>

## Environment reference for local mode

```dotenv theme={null}
DATABASE_URL=postgresql://r5d_chat:r5d_chat@localhost:5432/r5d_chat
BETTER_AUTH_SECRET=replace-with-at-least-32-random-characters
SETTINGS_ENCRYPTION_KEY=replace-with-at-least-32-random-characters
BASE_URL=http://localhost:3000
WORKSPACE_MODE=local
R5DCHAT_LOCAL_DAEMON_URL=http://127.0.0.1:7337
R5DCHAT_LOCAL_DAEMON_TOKEN=development-only-token
ALLOW_PRIVATE_EGRESS=false
ALLOW_INSECURE_HTTP=false
```

<Tip>
  To use a model server on your own machine, such as Ollama or vLLM, set
  `ALLOW_PRIVATE_EGRESS=true` and `ALLOW_INSECURE_HTTP=true`. These weaken the
  outbound URL guard, so only enable them locally.
</Tip>

## Checks before opening a pull request

```bash theme={null}
bun run format:check
bun run lint
bun run typecheck
bun test
bun run test:integration
bun run test:e2e
bun run build
```

`bun test` is hermetic and never touches a database. Database-backed tests are a
separate suite that needs a reachable `DATABASE_URL`:

```bash theme={null}
bun run test:db
```

Validate the chart when you change it:

```bash theme={null}
helm lint charts/r5d-chat \
  --set secrets.betterAuthSecret=abcdefghijklmnopqrstuvwxyz123456 \
  --set secrets.settingsEncryptionKey=abcdefghijklmnopqrstuvwxyz123456
```

## Database migrations

Migrations in `drizzle/` are **written by hand**, not generated. The Drizzle
schema in `src/server/schema.ts` describes the current shape for the query
builder; the SQL files describe how an existing database reaches that shape.

`drizzle/meta/` intentionally carries no per-migration snapshots, so
`bun run db:generate` has no baseline to diff against and emits the *entire*
schema as a new migration. Applying that to a populated database fails with
`relation already exists` and the rollout's migrate init container crash-loops.
Use it only to read a suggested diff, never as the committed file.

To add a migration:

1. Change `src/server/schema.ts`.
2. Write `drizzle/000N_<name>.sql` containing only the incremental change,
   using `IF NOT EXISTS` / `IF EXISTS` so a re-run is harmless.
3. Append a matching entry to `drizzle/meta/_journal.json`. Keep the
   hand-assigned `when` values sequential (`1770000000000` + `idx * 1000`);
   the runner orders by `idx`, and a stable value keeps diffs readable.
4. Apply it: `bun run db:migrate`.

Verify both paths before committing — a fresh database and an upgrade from the
previous migration must end up with the same schema:

```bash theme={null}
createdb mig_fresh && DATABASE_URL=.../mig_fresh bun run db:migrate
# then diff `pg_dump --schema-only` against an upgraded database
```

In Kubernetes, the chart applies migrations from an init container on every
rollout, so a bad migration blocks the deployment rather than corrupting data.

## Project layout

| Path                | Contents                                                  |
| ------------------- | --------------------------------------------------------- |
| `src/routes`        | File-based routes for pages and the API                   |
| `src/server`        | Auth, database, crypto, workspace controller, agent tools |
| `src/components`    | UI components, including AI elements                      |
| `packages/runtime`  | The `r5dchat` workspace daemon and CLI                    |
| `packages/protocol` | Shared types between control plane and workspace          |
| `charts/r5d-chat`   | The Helm chart                                            |
| `images/*`          | Dockerfiles and bundled agent skills                      |
| `docs`              | This documentation site                                   |
