---
title: "A local environment"
description: "The whole platform on one machine, with data on every page of the web UI, for seeing and changing the web UI."
canonical_url: "https://docs.oz0.ai/about/local-environment"
---
# A local environment

> The whole platform on one machine, with data on every page of the web UI, for seeing and changing the web UI.

For: Contributors developing the web UI.

Run the local development environment to work on the web UI with populated pages. It starts a dev server with embedded Temporal, two nodes, a stand-in model API and the Vite development server, then seeds sample records. No model key or paid model call is needed.

This environment binds to loopback on one machine. Use the [quickstart](https://docs.oz0.ai/getting-started/quickstart) to try an agent first.

## With Tilt

[Tilt](https://tilt.dev) runs each part as a resource with its own log and status, rebuilds the binaries and the runtime bundle when Go or runtime files change, and restarts the server and the nodes after.

```bash [Terminal]
tilt up                 # the Tilt UI: http://localhost:10350
tilt up -- --no-docs    # without the docs' dev server
tilt up -- --no-web     # without the web UI on Vite
tilt down               # stop everything; the data stays
```

The Tilt UI links to the web UI, the Temporal UI and the docs. Tilt's update check and usage statistics are off in the `Tiltfile`.

To install Tilt, run its official install script, or take a pinned release and check it against the release's `checksums.txt`:

```bash [Terminal]
curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash
```

```bash [Terminal]
curl -fsSLO https://github.com/tilt-dev/tilt/releases/download/v0.36.0/tilt.0.36.0.linux.x86_64.tar.gz
echo "9ce610083efc76ffa518ec9b001ddb1711b652adce3333f57ffff6be50ad9719  tilt.0.36.0.linux.x86_64.tar.gz" | sha256sum -c
tar -xzf tilt.0.36.0.linux.x86_64.tar.gz tilt && install tilt ~/.local/bin/tilt
```

## Without Tilt

`make dev-ui` runs the same parts in one terminal, with their logs in `.oz0/dev-env/logs`, and prints the addresses once the seed is done. Ctrl-C stops all of it, the nodes' runtimes and plugin servers too.

```bash [Terminal]
make dev-ui
```

It does not rebuild on changes; stop it and run it again. Vite reloads the web UI either way. `OZ0_DEV_DOCS=1 make dev-ui` also starts the docs' dev server.

## What a build serves, and what it leaves

Each build also rebuilds `dist/` for your machine's platform, the way `make dist PLATFORMS=<os>/<arch> TOOLCHAIN=0` does, whenever the node binary or the runtime changed. The dev server serves `dist/` to its nodes as a cluster's edge does, so the nodes update through the same path a real install uses, and you never run `make dist` by hand.

A build cleans up after itself, so rebuilds do not fill the disk:

- `dist/runtime/` keeps the three newest runtimes. Older tarballs and bundle folders go before the manifest is signed, and older node binaries go when it is published.
- The nodes' installed runtimes, in `~/.cache/orchestrator-zero/dev-env/node/runtimes`, keep the one just built, any a running process still uses, and the three newest. An install in progress is never touched.
- Go's build cache is pruned at most every six hours: entries unused for 72 hours, then the least recently used until it is under 100 GB.
- At the same time, the nodes' uv cache is pruned once it passes 2 GB (`OZ0_DEV_UV_CACHE_MB`); the next runtime install then fetches its packages again.

`OZ0_DEV_KEEP_RUNTIMES` changes how many runtimes stay. `make go-cache-prune` prunes Go's cache at any time; `GO_CACHE_MAX_AGE_HOURS` and `GO_CACHE_MAX_GB` change its limits.

## Two addresses, one web UI

The local environment serves the same web UI at two addresses. Both talk to the same dev server and show the same data; what differs is which build of the app you get.

<table>
<thead>
  <tr>
    <th>
      Address
    </th>
    
    <th>
      What it serves
    </th>
    
    <th>
      Use it to
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        http://127.0.0.1:15173
      </code>
    </td>
    
    <td>
      The app from its sources in <code>
        web/
      </code>
      
      , on Vite's dev server, which reloads the page when you save a file
    </td>
    
    <td>
      Change the web UI
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        https://127.0.0.1:18443
      </code>
    </td>
    
    <td>
      The copy built into the server binary, on the management port, as every install serves it
    </td>
    
    <td>
      Check the app as it ships: the production build, its Content-Security-Policy, HTTPS and the secure session cookie
    </td>
  </tr>
</tbody>
</table>

15173 exists only in a checkout, for working on the web UI. An install has no Vite: its operators open the management port. Without Vite, every change to the UI would wait for a production build, a server build and a restart. If you are not changing the UI, leave Vite out with `tilt up -- --no-web` or `OZ0_DEV_WEB=0 make dev-ui`, and use 18443.

The copy on 18443 is only as new as your last `make web`. The server embeds whatever is in `web/dist` when it is built, and only `make web` fills `web/dist`; `tilt up` and `make dev-ui` build the server but not the web UI, so that a change to Go code does not also wait for a production build of the UI.

When 18443 looks older than 15173, or newer after a build on another branch, it is an earlier build of the same app, not a different one. To bring it up to date:

```bash [Terminal]
make web             # build the web UI into web/dist
tilt trigger build   # under Tilt: rebuild the server with it and restart; without Tilt, run make dev-ui again
```

## Sign in

Open the web UI on Vite at `http://127.0.0.1:15173` and sign in as `admin`. The password is in the server's data folder, and works on 18443 too:

```bash [Terminal]
cat .oz0/dev-env/server/admin-password
```

Vite's dev server passes the API calls, the sign-in and the session cookie on to the dev server, so the app runs from its sources while the data comes from the server.

To see the [chat drawer](https://docs.oz0.ai/getting-started/web-ui/chats#the-chat-drawer) with chats in it, sign in as one of the seed's people, `alex` or `maya`, operators who see content. Their passwords are made by the seed and kept in the data folder, like the admin's (dev only):

```bash [Terminal]
cat .oz0/dev-env/seed/alex-password .oz0/dev-env/seed/maya-password
```

For the CLI, point it at the dev server:

```bash [Terminal]
export OZ0_CONTEXT=.oz0/dev-env/server/operator/context.json
oz0 job list
```

## What the seed makes

<table>
<thead>
  <tr>
    <th>
      Where
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Tenants
    </td>
    
    <td>
      <code>
        default
      </code>
      
      , and <code>
        acme
      </code>
      
       with no node of its own
    </td>
  </tr>
  
  <tr>
    <td>
      Nodes
    </td>
    
    <td>
      <code>
        dev-a
      </code>
      
       (<code>
        pool=general
      </code>
      
      ) and <code>
        dev-b
      </code>
      
       (<code>
        pool=batch
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      Plugins
    </td>
    
    <td>
      <code>
        support
      </code>
      
       (support-triage, support-refunds, support-reporter) and <code>
        hiring
      </code>
      
       (hiring-screener); <code>
        support
      </code>
      
       in <code>
        acme
      </code>
      
       too
    </td>
  </tr>
  
  <tr>
    <td>
      Jobs
    </td>
    
    <td>
      finished ones, a conversation of two turns, one that failed on its step limit, an export that runs for most of a day, a refund that waits for a person's approval, and a job in <code>
        acme
      </code>
      
       that waits for a node
    </td>
  </tr>
  
  <tr>
    <td>
      Findings
    </td>
    
    <td>
      a possible loop, and a job that spent 90% of its budget
    </td>
  </tr>
  
  <tr>
    <td>
      Cost
    </td>
    
    <td>
      the tenant's monthly budget, 85% spent: the CV parser reports what it costs
    </td>
  </tr>
  
  <tr>
    <td>
      Rollouts
    </td>
    
    <td>
      support 0.2.0 on its canary, soaking for a day
    </td>
  </tr>
  
  <tr>
    <td>
      Governance
    </td>
    
    <td>
      the inventory, with hiring-screener as high risk; a kill switch in <code>
        acme
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Chats
    </td>
    
    <td>
      <code>
        alex
      </code>
      
      's pinned chat about an order, one that waits for a person to approve a refund, and one shared with <code>
        maya
      </code>
      
      ; <code>
        maya
      </code>
      
      's chat about an applicant. The seed makes them by signing in as each person, since a chat is a person's and the CLI acts as its operator
    </td>
  </tr>
  
  <tr>
    <td>
      Also
    </td>
    
    <td>
      teams with a quota, a routine with a fire, a reader account <code>
        riley
      </code>
      
      , an API key, model aliases, and the audit entries all of it wrote
    </td>
  </tr>
</tbody>
</table>

A stand-in for the model API, `scripts/fakellm`, answers every model call from a script, `scripts/dev-env/fakellm.json`, and the plugins in `scripts/dev-env/plugins` work on made-up orders and applicants.

The seed only adds what is missing, so it can run again on a seeded server, and does each time the environment starts. What waits and runs ends with time: the approvals are denied after 24 hours (alex's next seed then starts another refund chat), and the export, the wait in `acme` and the rollout's soak end within a day. The next seed starts a new one of each. To seed a running environment again by hand:

```bash [Terminal]
make dev-seed
```

To start over, stop the environment and delete `.oz0/dev-env`. A rollout in progress keeps the tenant's other plugin updates waiting; `oz0 rollout abort <id>` ends it.

## Ports

Every port and the data folder can be changed, with environment variables for both ways of running it, or Tilt's arguments, such as `tilt up -- --web-port 25173`. The defaults stay clear of `make dev`'s ports and of the docs' and website's dev servers.

<table>
<thead>
  <tr>
    <th>
      Variable
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        OZ0_DEV_WEB_PORT
      </code>
    </td>
    
    <td>
      15173
    </td>
    
    <td>
      The web UI on Vite, from its sources, for changing it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_MANAGEMENT_PORT
      </code>
    </td>
    
    <td>
      18443
    </td>
    
    <td>
      Management: the admin API and the web UI built into the server, as of the last <code>
        make web
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_EDGE_API_PORT
      </code>
    </td>
    
    <td>
      17443
    </td>
    
    <td>
      The edge's HTTPS API, for the nodes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_EDGE_TEMPORAL_PORT
      </code>
    </td>
    
    <td>
      17233
    </td>
    
    <td>
      The edge's Temporal proxy, for the nodes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_TEMPORAL_PORT
      </code>
    </td>
    
    <td>
      17234
    </td>
    
    <td>
      The embedded Temporal frontend
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_TEMPORAL_UI_PORT
      </code>
    </td>
    
    <td>
      18233
    </td>
    
    <td>
      The Temporal UI
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_FAKELLM_PORT
      </code>
    </td>
    
    <td>
      18090
    </td>
    
    <td>
      The stand-in for the model API
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_DOCS_PORT
      </code>
    </td>
    
    <td>
      13001
    </td>
    
    <td>
      The docs' dev server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_DIR
      </code>
    </td>
    
    <td>
      <code>
        .oz0/dev-env
      </code>
    </td>
    
    <td>
      The server's and the nodes' data
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OZ0_DEV_NODE_CACHE
      </code>
    </td>
    
    <td>
      <code>
        ~/.cache/orchestrator-zero/dev-env/node
      </code>
    </td>
    
    <td>
      uv, Python and runtimes the nodes install, kept between runs
    </td>
  </tr>
</tbody>
</table>

## What reaches the internet

The seed and the platform call nothing outside the machine: the gateway's model calls go to the stand-in. The first start of a node downloads uv and Python, as every node does, and installs the plugins' Python packages; both are cached. The docs' dev server downloads its fonts once.

## Test it

`make e2e-dev-env` runs `make dev-ui` on free ports and a fresh data folder, checks the seeded data through the API and the web UI through Vite's proxy, stops it with Ctrl-C and checks that nothing is left running. When Tilt is installed, it then runs `tilt ci` on the same data.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
