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

# How XORCISE works

> What xorcise up actually starts: one local process with two network planes, the containers behind a run, and where every file lands.

Everything you used in your first run is one long-lived process on your machine plus a handful of Docker containers. Nothing else is installed, and no server is involved.

## What you'll learn

* What `xorcise up` starts, and which piece answers your browser, your CLI and your agent.
* How your agent reaches a deliberately vulnerable mission without that mission reaching your host.
* Where a run's data lives, and which parts of a run leave your machine.

## The local architecture

One process serves two network planes. Docker holds the mission. Your agent runs outside all of it and talks to both.

```mermaid theme={"theme":"css-variables"}
%%{init: {'theme':'base','themeVariables':{'fontFamily':'JetBrains Mono, ui-monospace, SFMono-Regular, Consolas, monospace','fontSize':'12px','primaryColor':'#232323','primaryTextColor':'#f2ead6','primaryBorderColor':'#3a3a3a','secondaryColor':'#1a1a1a','tertiaryColor':'#141414','background':'#1a1a1a','mainBkg':'#232323','nodeBorder':'#3a3a3a','lineColor':'#6e6144','textColor':'#c7bb9f','clusterBkg':'transparent','clusterBorder':'#2f2f2f','edgeLabelBackground':'#1a1a1a','actorBkg':'#232323','actorBorder':'#3a3a3a','actorTextColor':'#f2ead6','actorLineColor':'#6e6144','signalColor':'#6e6144','signalTextColor':'#c7bb9f','labelBoxBkgColor':'#232323','labelBoxBorderColor':'#3a3a3a','labelTextColor':'#f2ead6','noteBkgColor':'#141414','noteTextColor':'#c7bb9f','noteBorderColor':'#3a3a3a','sequenceNumberColor':'#0a0805'},'flowchart':{'padding':26,'nodeSpacing':40,'rankSpacing':46},'sequence':{'useMaxWidth':true}}}%%
flowchart TB
    subgraph host["Your machine"]
        subgraph proc["XORCISE — one process"]
            rest["REST API + web UI<br/>port 3001"]
            otlp["OTLP receiver<br/>port 4318"]
        end

        store[("~/.xorcise<br/>config · SQLite<br/>missions")]
        docker["Docker daemon"]
        hs["Headscale container<br/>network coordination"]

        subgraph net["Per-run isolated network"]
            chal["Mission containers"]
            router["Tailnet router"]
        end

        agent["Your agent"]
    end

    rest <--> store
    otlp --> store
    rest -->|Docker SDK| docker
    docker --> chal
    docker --> router
    docker --> hs
    router <--> hs
    agent -->|run control: artifacts, intel, complete| rest
    agent -->|traces and logs| otlp
    agent <-->|reaches targets by IP| router
```

The process is what `xorcise up` starts in the background. It writes its own errors to `~/.xorcise/serve.log`, holds all durable state in a single SQLite file, and drives Docker over the local socket. It does not reach out to anything on its own.

## The planes

The process listens on two ports, and they answer completely different audiences.

| Plane               | What it serves                        | Who talks to it                                                                                                            | Default port |
| ------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------ |
| REST API and web UI | `/api/*` and the web UI at `/ui`      | Your browser, the `xorcise` CLI, and your agent's run control — submitting artifacts, requesting intel, completing the run | `3001`       |
| OTLP ingest         | `POST /v1/traces` and `POST /v1/logs` | Your agent's harness, exporting OpenTelemetry                                                                              | `4318`       |

Both ports auto-increment when the one they want is busy, scanning up to 50 ports above it and printing each move. Treat the numbers above as defaults rather than facts about your install: the authoritative values are the ones `xorcise up` prints, and `xorcise status` reports them again later.

<Note>
  In the default local topology both planes bind loopback — `127.0.0.1` and `::1` — plus, on native Linux, the Docker bridge gateway, which is how an agent running in a container reaches them through `host.docker.internal`. The bind widens to `0.0.0.0` only if you set `XORCISE_HOST=0.0.0.0`, or on Linux when the bridge gateway cannot be determined at boot. Neither plane has any authentication, so every process on your machine and every container on that bridge can already read your runs and create new ones — and widening the bind extends that to anyone who can reach the port. [Security and isolation](/operate/security) covers what to do about that.
</Note>

## How a run reaches its mission

A mission is deliberately vulnerable software, so XORCISE never puts it on a network your host can route to. Each run gets its own isolated network instead, built from two pieces.

The first is topological. The mission's containers run on a Docker network nested inside a container of their own, so there is no route from your host to a mission address. The second is a private tailnet: XORCISE mints a one-time key per run, and a Headscale policy grants exactly one rule — this run's agent may reach this run's addresses, and nothing else. There is no default-allow rule, so the policy is fail-closed, and XORCISE refuses to apply a policy that does not match that shape. Two runs in flight at once cannot see each other's targets.

You never configure any of this. It is created when the run is created and torn down when the run seals; the connect prompt hands your agent the one command that joins it.

<Warning>
  Be clear about what this does not do. It is not an egress firewall and it is not a Docker `internal` network. The mission containers sit behind ordinary NAT, so **traffic from a mission target out to the internet is not blocked**. The isolation guarantee is that the mission is unreachable from your host and from other runs — not that the mission is sealed off from the world.
</Warning>

## What happens from create to score

One path, from the moment you create a run to the moment a number exists.

```mermaid theme={"theme":"css-variables"}
%%{init: {'theme':'base','themeVariables':{'fontFamily':'JetBrains Mono, ui-monospace, SFMono-Regular, Consolas, monospace','fontSize':'12px','primaryColor':'#232323','primaryTextColor':'#f2ead6','primaryBorderColor':'#3a3a3a','secondaryColor':'#1a1a1a','tertiaryColor':'#141414','background':'#1a1a1a','mainBkg':'#232323','nodeBorder':'#3a3a3a','lineColor':'#6e6144','textColor':'#c7bb9f','clusterBkg':'transparent','clusterBorder':'#2f2f2f','edgeLabelBackground':'#1a1a1a','actorBkg':'#232323','actorBorder':'#3a3a3a','actorTextColor':'#f2ead6','actorLineColor':'#6e6144','signalColor':'#6e6144','signalTextColor':'#c7bb9f','labelBoxBkgColor':'#232323','labelBoxBorderColor':'#3a3a3a','labelTextColor':'#f2ead6','noteBkgColor':'#141414','noteTextColor':'#c7bb9f','noteBorderColor':'#3a3a3a','sequenceNumberColor':'#0a0805'},'flowchart':{'padding':26,'nodeSpacing':40,'rankSpacing':46},'sequence':{'useMaxWidth':true}}}%%
sequenceDiagram
    participant You
    participant UI as CLI or web UI
    participant X as XORCISE
    participant C as Mission
    participant A as Your agent
    participant M as Judge model

    You->>UI: create a run (agent, mission, budget)
    UI->>X: POST /runs
    X->>C: start the containers on a new per-run network
    C-->>X: environment ready
    X-->>UI: connect prompt + launch profile
    You->>A: launch with the prompt
    A->>X: join the run's network
    A->>C: work the objective, by IP
    A->>X: OpenTelemetry traces and logs
    A->>X: submit artifacts, request intel
    A->>X: complete the run
    X->>X: seal the evidence, run the mission's checks
    X->>M: distilled transcript + rubric
    M-->>X: a score and a reason per criterion
    X-->>You: one overall score
```

Two details are worth carrying forward. The evidence is sealed at completion — telemetry that arrives after that point is rejected rather than folded in, which is why the launch profile has to be exported before the agent starts. And the judge call is the only step *in this sequence* that leaves your machine; pulling a mission and attributing terrain also reach off the host, and the table below accounts for all three.

## Where things are stored

Everything lives under `~/.xorcise`, or wherever `XORCISE_HOME` points.

| Path                 | What it holds                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `config.toml`        | Configuration, plus a managed block XORCISE rewrites for the local network coordinator                                            |
| `.env`               | Your model API keys and everything the Settings page writes. Mode `0600`                                                          |
| `xorcise.db`         | Every agent, run, result and raw OpenTelemetry record — all durable state                                                         |
| `missions/`          | Installed missions, one directory each, with their attachments                                                                    |
| `runs/<run_id>/`     | A derived event export for that run, rebuildable from the database                                                                |
| `cache/`             | The pinned network client, one archive per architecture                                                                           |
| `headscale/`         | Config, compose file and TLS material for the local network coordinator, plus an ownership marker you should never delete by hand |
| `serve.log`          | The process's own error output. Boot failures land here                                                                           |
| `runtime-ports.json` | The ports actually bound, so the CLI can find a relocated instance                                                                |
| `logs/`              | Empty. Nothing writes to it                                                                                                       |

That last row matters when something goes wrong: `logs/` exists but has no writer anywhere in XORCISE, so a stuck start leaves no trace in it. Read `serve.log` instead.

## What runs where

| Where            | What runs there                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| On your host     | The XORCISE process and its two planes, the SQLite database, and everything under `~/.xorcise`                            |
| In Docker        | The mission containers on their per-run network, that run's tailnet router, and the Headscale container coordinating them |
| Off your machine | Pulling a mission from the XORCISE library, the judge model call, and terrain attribution                                 |

The third row is short but real. Pulling a mission contacts the hosted library and downloads its image; it needs no account, no API key and no sign-in, and it sends nothing about your runs. Judging and terrain attribution both send a distilled transcript of the run — what your agent did — to the model provider you configured, using your key. Everything else stays on the machine, and there is no analytics of any kind. [Security and isolation](/operate/security) sets out exactly what each of those three sends.

## Next steps

The next five pages take the pieces apart in the order a run uses them: agents, missions, runs, traces, and grading.

<Card title="Agents" icon="bot" href="/concepts/agents">
  The agent record, and the one decision it forces.
</Card>
