# Hallvi agent reference

How a coding agent, such as Codex, operates an existing Hallvi application through the `hallvi` CLI or the Hallvi MCP adapter. This file describes Hallvi 0.1.1-alpha.12. Read the rules first, then the section you need.

## Rules

1. Work only on an application that the owner already set up in Hallvi, and only by sending requests to its main operator, Pi. The CLI and the adapter create no applications and connect no accounts.
2. Name the controller with a loopback address, such as `http://127.0.0.1:4747`. Hallvi refuses any other address and never guesses one.
3. Run `inspect` before you send work, and check `main` and `attention`. A missing worker or interrupted work needs the owner, not another request.
4. Keep the `handle` and the `requestKey` that `exec` returns. A timeout stops only your watching. Pi keeps working.
5. If you do not know whether Pi accepted a request, send the identical text again with the same `--request-key`. Never send it again under a new key, because that can duplicate the work.
6. Never try to get past a wait. You cannot approve a command, answer an input card, change the permission mode or continue interrupted work. Send the owner to `attention.page`, then follow the same handle.
7. `completed` means Pi finished answering. It does not mean the work succeeded. Read the answer and the evidence of the operation that took your request key.
8. Put no credentials, secrets or contributor instructions in a request. Send Pi only the work and its scope.
9. Report what you observed, when you observed it, and what failed or remains unknown.

## Quick start

The owner has installed Hallvi, set up an application and given you the controller address. On the computer running Hallvi:

```sh
export HALLVI_CONTROLLER_URL=http://127.0.0.1:4747
hallvi apps --json                     # pick the application's id
APP_ID='<id from applications[]>'
hallvi inspect "$APP_ID" --json        # confirm it; check main and attention
REQUEST_KEY=$(uuidgen | tr '[:upper:]' '[:lower:]')
hallvi exec "$APP_ID" - --request-key "$REQUEST_KEY" --background --json < request.txt > accepted.json
HANDLE='<handle from accepted.json>'
hallvi wait "$HANDLE" --timeout 120 --json > outcome.json
```

On Linux without `uuidgen`, use `cat /proc/sys/kernel/random/uuid`. Write the request to `request.txt` first. A bounded, read-only request looks like this:

```text
Read-only check: GET <application URL>/api with the header
X-Hallvi-Verification: <unique marker>. Report the HTTP status, the application
name against <expected name>, and whether that same marker was echoed.
Report the running commit or immutable image identity if it can be established,
and which evidence establishes it. Do not redeploy, restart, change configuration,
create resources or alter application data. Report missing or failed checks.
```

Then read `outcome.json` as [Evidence](#evidence) describes. Keep request and output files in a task folder of their own, because output can contain private application data.

## Prerequisites

| Requirement | Detail |
| --- | --- |
| A computer running Hallvi | Hallvi runs on an Apple-silicon Mac or on Ubuntu 24.04 x64. Its controller answers only on that computer. |
| A running controller | `hallvi status` shows the service and the Pi worker as `running`, and prints the controller address. |
| An application | The owner creates it, connects its accounts and deploys it in Hallvi's browser page. The CLI and the adapter cannot. |
| Your process | Your agent runs on the computer running Hallvi. To work from another computer, use the MCP adapter over SSH. |
| For the MCP adapter only | You need Node.js 22 and a source checkout of the Hallvi release in use. The standard installer does not install the adapter. |

## Install and connect

The owner installs Hallvi once, on the computer that will run it, as their normal user and without `sudo`:

```sh
curl -fsSLo ./install-hallvi.sh https://github.com/lustoykov/hallvi/releases/latest/download/install-hallvi.sh && sh ./install-hallvi.sh
```

The installer puts the `hallvi` command in `~/.local/bin` and brings the Node.js it needs, so the CLI needs no source checkout. If your shell cannot find `hallvi`, use `~/.local/bin/hallvi`. Check the service:

```sh
hallvi status
```

```text
Hallvi <installed version> (<revision>)
  service    running; starts when you log in to this Mac
  interface  http://127.0.0.1:4747
  Pi worker  running
  state      /Users/you/.local/share/hallvi
  logs       hallvi logs
```

The `interface` line is the controller address. The owner finishes setup in a browser at that address: the accounts, then an application and its first deployment. The people docs cover those steps in [Install Hallvi](https://hallvi.com/docs/people/install.html) and [Deploy your first application](https://hallvi.com/docs/people/first-deployment.html).

## Choose the controller

Every request command needs a controller, and Hallvi never looks for one, starts one or tries another. A command uses the first of these:

1. `--controller <url>` on the command line.
2. The `HALLVI_CONTROLLER_URL` environment variable.
3. Nothing. The command contacts nothing and fails with `controller-missing`.

The address must be a plain `http` origin at `localhost`, `127.0.0.1` or `[::1]`, with a port and nothing else, such as `http://127.0.0.1:4747`. The CLI refuses anything else before it connects (`controller-invalid`), including `https`, a path and credentials. It also refuses a redirect instead of following it (`redirected`).

Use the address the installation reports. `hallvi status` prints it on its `interface` line, and `hallvi url` prints it alone:

```sh
export HALLVI_CONTROLLER_URL=$(hallvi url)
```

An installation set up for use from another computer answers at port 5747, not 4747. Spell the address the same way everywhere, because the CLI compares it with the controller inside a handle.

Every result names its controller in `controller`. Check it, especially when a development checkout and an installation run side by side. A handle names its own controller, and a `--controller` passed beside a handle must be the same one (`controller-conflict`).

Loopback is a local trust assumption, not a login. Any process on that computer can reach the controller, as it can reach Hallvi's page. A request's origin label says where it was written and grants nothing. Never expose the controller or the adapter on a public address or through an unauthenticated tunnel.

## CLI commands

The request commands are `apps`, `inspect`, `exec` and `wait`. Every one takes `--json`, and then stdout carries exactly one JSON object and stderr stays empty. Without `--json`, progress goes to stderr and a compact result to stdout. Read the JSON even when a command exits nonzero. The service commands, such as `start`, `stop`, `status` and `logs`, are separate and belong to the owner.

### `hallvi apps`

```sh
hallvi apps --json
```

Returns `{ controller, applications }`. Each application has `id`, `name`, `source`, `condition`, `stack`, `address`, `permissionMode` and `mainChatId`. It reads recorded state only. It calls no model and looks at no server.

### `hallvi inspect`

```sh
hallvi inspect <app> --json
hallvi inspect <app> --execution <execution-id> --json
```

Returns `{ controller, applicationId, application, condition, conversations, main, attention, deployment, records, recordsOmitted, executions, executionsOmitted }`. The result holds the 25 records Pi presented most recently and the 20 newest executions, with counts of the rest. It reads recorded state only, so it is not a fresh check of the server.

- `application` holds `repositoryUrl`, `permissionMode` and `host`. Confirm them before you send work.
- `main.status` is `idle`, `working` or `interrupted`, and null when no worker answered. It never shows an approval wait.
- `attention` is a list of `{ kind, reason, page, executionId?, requestedAt? }`, where `kind` is `approval`, `input` or `interrupted`. Here `page` is a path on the controller, such as `/applications/<app>?chat=<chat>`.

With `--execution`, it returns `{ controller, applicationId, execution }`: one execution of that application in full, as far as the executor kept it. That is the last 100,000 characters, redacted.

### `hallvi exec`

```sh
hallvi exec <app> "<request>" --json
hallvi exec <app> - --request-key <uuid> --background --json < request.txt
```

| Argument | Meaning |
| --- | --- |
| `<app>` | An application `id` that `hallvi apps` printed, a lowercase UUID. There is no lookup by name. |
| `"<request>"` or `-` | The request text, or `-` to read it from stdin. It can be up to 5,000 characters. |
| `--request-key <uuid>` | The request's key, a lowercase UUID. Without it, `exec` makes one and returns it as `requestKey`. Reuse it only to retry the same text. |
| `--background`, `--bg` | Return once Pi has durably taken the request, without watching it. Combining it with `--timeout` is a usage error. |
| `--timeout <seconds>` | Stop watching after this long. `0` reads the state once. Without it, `exec` watches until the request settles. It never limits the work. |

`exec` sends the request to the application's main conversation as a follow-up (`next`), never as a steer, so Pi reads it when the current work ends. Hallvi labels it *CLI* instead of *You* in the conversation. The request meets the same Pi, permission mode, approvals and evidence as a message typed in the page. `accepted` turns true only once Pi has durably taken it. If the controller's answer is lost, `exec` sends the same request under the same key up to three times.

### `hallvi wait`

```sh
hallvi wait '<handle>' --timeout 120 --json
hallvi wait '<handle>' --timeout 0 --json      # read the state once
```

`wait` only reads. It never sends, retries or continues work, and it can follow a request that another process sent. A handle is the address of the request's outcome:

```text
http://127.0.0.1:4747/api/applications/<app>/chats/<conversation>/requests/<key>
```

It names the controller, the application, the conversation and the request key, and holds no credential. Following is a poll about once a second. If a read fails, `wait` never takes that for idle, finished or cancelled. It asks again for about fifteen seconds while a controller or worker restarts, then stops with the state not known and exit code 1.

## Output

`exec` and `wait` return this object with `--json`:

```jsonc
{
  "controller": "http://127.0.0.1:4747",
  "applicationId": "...",
  "chatId": "...",           // the main conversation
  "requestKey": "...",
  "handle": "http://127.0.0.1:4747/api/applications/.../requests/...",
  "accepted": true,          // false: refused; null: not known
  "status": "completed",     // null when not known, and with --background
  "timedOut": false,
  "stoppedByUser": false,    // Ctrl-C
  "background": false,
  "operation": {
    "id": "...",             // Pi's own; the request key when this request began it
    "status": "completed",   // open, completed, failed, aborted
    "startedAt": "...",
    "endedAt": "...",
    "requestKeys": ["..."]   // every request this operation took
  },
  "answer": "...",           // Pi's final words, once the operation ended
  "answerTruncated": false,
  "failure": null,           // a bounded, redacted reason and advice
  "attention": null,         // { kind, reason, page, executionId? }
  "evidence": [],            // the operation's calls, see Evidence
  "evidenceOmitted": 0,
  "error": null              // { code, message } when the command failed
}
```

With `--background`, `accepted: true` and `status: null` mean Pi has the request, not that anything passed. `answer` holds the first 20,000 characters, and `answerTruncated` says when Hallvi cut it. Here `attention.page` is an absolute address on the controller. `operation.id` is usually a request key, but it is `queue:<id>` when the operation began by reading Pi's queue after Continue. When `exec` or `wait` fails after the request is identified, this object carries `error` beside whatever was known. Any other failure returns `{ controller, applicationId, error: { code, message } }`, with null for what was not chosen yet.

## Request lifecycle

```text
exec --> queued --> working --+--> completed             exit 0
                              +--> failed                exit 1
                              +--> waiting-for-approval  exit 2
                              +--> waiting-for-input     exit 2
                              +--> interrupted           exit 4
queued or working ---------------> cancelled             exit 4

waiting-for-approval --> working   after the owner approves or declines
```

| Status | Meaning | Exit | Next step |
| --- | --- | --- | --- |
| `queued` | Pi holds the request and has not read it yet. | 3 on timeout | Follow the handle again. |
| `working` | The operation that took the request is running. | 3 on timeout | Follow the handle again. |
| `waiting-for-approval` | A call waits for the owner's decision and has not run. | 2 | Send the owner to `attention.page`, then follow the same handle. |
| `waiting-for-input` | The operation ended by asking the owner for something through one of Hallvi's cards, and the card is still open. | 2 | Send the owner to `attention.page`. The answer starts new work. |
| `completed` | Pi finished answering. | 0 | Read the answer and the evidence before you report anything. |
| `failed` | Pi could not finish. | 1 | Read `failure` and the evidence, and report them. |
| `cancelled` | Someone stopped it in Hallvi, or Stop dropped it before Pi read it. | 4 | Report it. Do not send it again without the owner. |
| `interrupted` | The worker went away mid-operation, or before reading the request. Nothing runs until someone chooses Continue or Stop in Hallvi. | 4 | Send the owner to `attention.page`, then follow the same handle. |

Other exit codes: 0 for `--background` once Pi accepted the request and for `apps` and `inspect`, 1 for a validation, transport or refusal failure or for acceptance not known, and 130 for Ctrl-C.

When Pi reads several waiting messages in one operation, each of those requests gets that operation's whole result, and `operation.requestKeys` lists them all. A later request, or another application's, never becomes part of it.

## Approvals and input

- The CLI and the adapter never approve or decline a call, answer a card, change the permission mode, or continue or stop interrupted work. Those stay in Hallvi's page, with the owner.
- A wait carries `attention`, shaped `{ kind, reason, page, executionId? }`. Give the owner the reason and `attention.page`. After the owner approves, run `wait` on the same handle. The call then runs and the request is `working` again. If the owner declines, the call does not run and Pi continues without it.
- Detect an approval wait from the request's `status` and `attention`, or from `inspect`'s `attention` list. The conversation-level status, `main.status` in `inspect` and `status` in `hallvi_conversation`, reads `working` during an approval wait. This is a known issue in 0.1.1-alpha.12.
- Hallvi recognises an input wait only from its own cards: where to run, DNS access for a domain, how to deploy, and a secret value. A question Pi asks in prose reads as `completed`, and the answer carries the question.
- Once the owner answers a card, the work goes on in a new operation that Hallvi's own message starts. The original handle keeps reporting its original operation. To see the later work, read `inspect` or `hallvi_conversation`.
- The application's permission mode decides which calls wait, and only the owner changes it. In *Always ask*, every command, file change and provider request waits. In *Hallvi decides*, a call waits only when Pi asks for approval. In *Bypass*, nothing waits. Never ask for a looser mode to get past a wait. [Permission modes](https://hallvi.com/docs/people/permissions.html) lists what waits in each mode.
- A request is not a security boundary. Name the target and the expected result, forbid changes you do not want, and ask for evidence for each claim.

## Retries

The request key makes a retry safe. Pi never takes one key twice, and it refuses the same key with different text.

| Situation | What to do |
| --- | --- |
| `accepted: null`, or error `acceptance-unknown` | Send the unchanged request with the same `--request-key`. That settles acceptance without duplicating work. |
| The wait timed out, or you pressed Ctrl-C | Pi still has the request. Run `wait` on the same handle. |
| Your MCP session disconnected | Pi still has the request. Call `hallvi_wait` with the same handle. |
| You want different work | Send a new request with a new key. |

```sh
# The retry is the same command, with the same file and the same key.
hallvi exec "$APP_ID" - --request-key "$REQUEST_KEY" --background --json < request.txt
```

`exec` already sends the same request again, under the same key, up to three times when the controller's answer is lost. A refusal settles acceptance only when `exec` sent nothing before it. After a lost answer, a refused retry says nothing about the first send. Never send a request again under a new key to find out.

## Evidence

Find your request key in `operation.requestKeys`, then use that operation's `answer` and `evidence`. Do not use the newest execution from `inspect`, because it can belong to other work.

Each evidence item is one tool call as Hallvi recorded it, never interpreted:

| Field | Meaning |
| --- | --- |
| `toolCallId` | Pi's id for the call. |
| `executionId` | The execution record to read in full, or null when the executor keeps no record of that call. |
| `tool`, `target` | What was called, and on what. |
| `input` | The call's input, redacted, first 2,000 characters. |
| `status` | How the call stands: `awaiting-approval`, `running`, `succeeded`, `failed`, `declined` or `interrupted`. |
| `exitCode` | The exit code, present only where a server command reported one. A missing exit code is not zero. |
| `startedAt`, `finishedAt` | When the call ran. |
| `output` | The call's output, redacted, last 2,000 characters. |
| `inputTruncated`, `outputTruncated` | True when Hallvi cut the text above. |

An outcome carries the operation's last 40 calls, and `evidenceOmitted` counts the rest. Omitted evidence is not a clean run. When a relevant excerpt is truncated and has an `executionId`, read the full record:

```sh
hallvi inspect "$APP_ID" --execution '<executionId from the evidence>' --json
```

There is no `verified` flag. Design the request so that the evidence can prove the result, for example a unique marker that the application must echo, and check it. An HTTP 200 alone proves little. Read `answer`, `answerTruncated`, `failure`, `attention` and `evidenceOmitted` even when the status is `completed`, because a completed answer can say the objective was not achieved. Report what was observed, when, and what failed or remains unknown.

## Failures and recovery

| Error code | Meaning | What to do |
| --- | --- | --- |
| `usage` | The command line is wrong. | Fix the arguments. The CLI sent nothing. |
| `controller-missing` | You named no controller. | Pass `--controller` or set `HALLVI_CONTROLLER_URL`. The CLI contacted nothing. |
| `controller-invalid` | The address is not a loopback origin. | Use the `interface` address from `hallvi status`. The CLI contacted nothing. |
| `controller-conflict` | `--controller` differs from the handle's controller. | Pass the handle alone. |
| `handle-invalid` | The handle is not a request address. | Pass the `handle` from the `exec` output unchanged. |
| `unreachable` | Nothing answered at the address in time. | Run `hallvi status` on that computer. If the service is stopped, tell the owner. |
| `redirected` | The address answered with a redirect. | Use the exact controller origin. The CLI never follows a redirect. |
| `not-hallvi` | Something other than a Hallvi controller answered. | Check the port against `hallvi status`. |
| `controller-failed` | The controller answered with a server error. | For a read, run `wait` again. For a send, the CLI already retried, and reports `acceptance-unknown` if every attempt failed. |
| `not-found` | This controller has no such application. | Run `hallvi apps` and use an `id` it prints. |
| `request-not-found` | This controller has no request under the handle's key. | Pi never accepted it, or Stop dropped it while it waited. Ask the owner before you send it again. |
| `refused` | The controller refused the request. The messages below are the common ones. | Read `message`, then act on it. |
| `worker-unavailable` | No Pi worker is running. At a send, the CLI sent nothing and `accepted` is false. During a read, the CLI asks again for about fifteen seconds first. | Tell the owner. `hallvi status` shows the worker, and `hallvi restart` starts it again. |
| `acceptance-unknown` | Whether Pi has the request is not known. | Send the identical request with the same key. |
| `stopped` | Ctrl-C came before the CLI handed anything over. | The CLI sent nothing. |
| `failed` | Any other failure of the command. | Read `message`. |

These `refused` messages come from the controller:

- *This request key was already used for a different message.* Send the original text under that key, or new work under a new key.
- *This conversation was interrupted. Continue or stop it before sending something new.* The owner chooses Continue or Stop in Hallvi.
- *Hallvi is installing an update, so it is not taking new work. Try again once it has restarted.* Wait for the restart, then send the same request with the same key.
- *Keep this message under 5,000 characters.* Shorten the request, and give it a new key.

When the request itself ends badly:

- `failed`: read `failure` and the evidence, report them, and ask the owner before you retry the work.
- `interrupted`: the owner chooses Continue or Stop in Hallvi. Continue checks what happened instead of repeating a started command. Then follow the same handle.
- `cancelled`: the owner or Stop ended it. Do not send it again without the owner.
- A timeout (exit 3): the work goes on. Follow the handle again.

## MCP adapter

The adapter is a proof of concept that gives an MCP host, such as Codex, tools for an existing controller. It calls the same client as the CLI and keeps the CLI's origin label. The standard Hallvi installer does not install it, so build it from the source of the Hallvi release you run. `hallvi status` prints that version on its first line.

### Build

```sh
git clone --depth 1 --branch v0.1.1-alpha.12 https://github.com/lustoykov/hallvi.git
cd hallvi
npm ci
npm run plugin:build -- --controller http://127.0.0.1:4747
```

Use Node.js 22 and the real controller port. The build writes `dist/hallvi-plugin/` with a bundled `server.mjs`, a self-contained `panel.html`, a product skill and local plugin metadata. It contacts no controller and copies no state, keys or logins. At runtime the adapter needs Node.js 22, but no `node_modules` and no checkout.

### Register it in Codex

The build prints an exact `codex mcp add` command with your Node path and the bundle's absolute path. Run it, or add the equivalent to Codex's `config.toml`:

```toml
[mcp_servers.hallvi]
command = "/absolute/path/to/node22"
args = ["/absolute/path/to/dist/hallvi-plugin/server.mjs", "--controller", "http://127.0.0.1:4747"]
startup_timeout_sec = 20
tool_timeout_sec = 60
```

Start a new Codex chat to load the tools. Keep the bundle directory while the connection is configured. Remove the connection with `codex mcp remove hallvi`. That leaves Hallvi's service, applications and history intact.

### Hallvi on another computer

Copy only `server.mjs` and `panel.html` to the computer running Hallvi, for example to `/opt/hallvi-plugin/`, with Node.js 22 there. Confirm that noninteractive SSH to it works with its established host key, then let SSH start the adapter:

```toml
[mcp_servers.hallvi]
command = "ssh"
args = ["-T", "-o", "BatchMode=yes", "hallvi-host", "/usr/bin/node /opt/hallvi-plugin/server.mjs --controller http://127.0.0.1:4747 --ui-url http://127.0.0.1:8474"]
startup_timeout_sec = 30
tool_timeout_sec = 60
```

Keep host-key checking on, and keep the remote shell's stdout clean for MCP messages. `--ui-url` changes only the browser links. The adapter still talks to the controller on that computer, so handles contain its loopback address. Pass them back to `hallvi_wait`, never to a browser or an HTTP client on your laptop. Use a different MCP server name for a second controller.

Approvals still need Hallvi's page. The owner keeps an SSH forward open on the laptop and opens `http://127.0.0.1:8474`:

```sh
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:8474:127.0.0.1:4747 hallvi-host
```

### Tools

IDs and request keys are lowercase UUIDs.

| Tool | Input | What it does |
| --- | --- | --- |
| `hallvi_apps` | None. | Lists applications and their recorded condition, with a Hallvi page for each. It probes no server. |
| `hallvi_inspect` | `application_id`, and optionally `execution_id`. | Reads identity, permission mode, attention, bounded evidence and the last day's stored traffic totals. With `execution_id`, it returns that execution's fields at the top level, not under `execution` as the CLI does. |
| `hallvi_conversation` | `application_id`, `turns` from 1 to 20 (default 3), and optionally `known`. | Reads the latest turns of the main conversation. With a previous `revision` as `known`, it only says whether anything changed. |
| `hallvi_exec` | `application_id`, `message` of 1 to 5,000 characters, and `request_key`. | Sends one request to Pi and returns its handle, with `accepted` true, false or null. It does not follow the request. |
| `hallvi_wait` | `handle`, and `timeout_seconds` from 0 to 20 (default 0, one read). | Reads the handle's outcome, waiting up to that long. It never sends or restarts work. |
| `hallvi_open` | None. | Lists the applications and attaches the optional panel for hosts that support MCP Apps. |
| `hallvi_reload_ui` | None. | Rereads the installed `panel.html` and notifies the connection. It runs no Pi work and restarts nothing. |

`hallvi_exec` makes one send attempt and watches for acceptance for up to 40 seconds. Retrying is your job. If acceptance is unknown (`acceptance-unknown`), keep the handle and send the exact message again with the same `request_key`. In `hallvi_wait` results, the top-level `page` is an absolute address for the browser, and `attention.page` is a path on the controller. A failed tool call returns `error` with a code: the CLI's codes, `adapter-error` or `read-stopped`. Disconnecting Codex or stopping a wait does not stop Pi. The controller, not the adapter, keeps the work and decides its outcome. Outcomes follow the same [statuses](#request-lifecycle) and [evidence](#evidence) rules as the CLI.

### Adapter limits

- Native Codex use has been observed. Native rendering in ChatGPT, and the updated panel over SSH, are not verified. Tools work without the panel.
- `hallvi_conversation` reports `working`, not an approval wait, while a call waits for the owner. Use `hallvi_wait` or `hallvi_inspect` to see approval waits.
- The panel never approves, declines, continues or stops. It opens Hallvi's page for an HTTPS address, and offers **Copy address** for an HTTP one, because the installed Codex host ignores requests to open HTTP links.
- Over SSH, the panel and the tools depend on that SSH connection. A `.local` host name works only on the local network, so check SSH first when the tools vanish. The owner's browser forward must also be running before copied Hallvi addresses work.
- Tool discovery, or a successful `hallvi_reload_ui` response, does not prove that the host renders the panel or refreshed it. A panel that is already open does not replace itself. Open Hallvi again, or start a new chat.
- For ChatGPT developer-mode testing, run OpenAI's Secure MCP Tunnel client on the computer running Hallvi. The adapter's HTTP transport, `--http 4748`, binds only to loopback and rejects foreign Host and Origin headers. Never publish it through an unauthenticated tunnel or reverse proxy.
- The adapter's tests run against a stand-in controller. They do not prove native host support or a remote SSH deployment.

## Limits

- The CLI reaches no controller on another computer and creates, sets up or starts nothing.
- Following is a poll about once a second. There are no streaming events, no lookup by name, and no `attach` or `resume` commands.
- A waiting request that Stop drops leaves no record in Pi. A command that saw it waiting reports `cancelled`, and a fresh `wait` finds nothing.
- Repository workspace commands record no numeric exit code. A failure there is `failed`, with its output.
- `apps` and `inspect` read what Hallvi recorded. Only a request makes Pi look at the server again.

Hallvi is an alpha, and it runs commands with the access the owner gave it. The owner's precautions are in [beta precautions](https://github.com/lustoykov/hallvi/blob/main/docs/beta-safety.md). Working on Hallvi's own source is a different job. Contributors use the repository's [development setup](https://github.com/lustoykov/hallvi/blob/main/docs/development.md) and [verification workflow](https://github.com/lustoykov/hallvi/blob/main/docs/verification.md), not this reference.
