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

# What leaves your machine

> The privacy model: capture is local, upload is launch-gated, and session content is scrubbed before it is sent.

Capture and upload are two different things. Capture reads the session files your coding agents already write to disk, and never leaves the machine. Upload is a separate step with one gate in front of it.

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/outerlayer-stg/M2z6K_ww2sAtqE1w/images/upload-boundary-light.svg?fit=max&auto=format&n=M2z6K_ww2sAtqE1w&q=85&s=f7ec6b16d4793c33914ec1ab34c4be44" alt="On your machine, your agent writes a session file. If the session was not started for a work item with OUTERLAYER_WORK, it never leaves the machine. If it was, secrets and your local identity are always removed, then your chosen tier decides how much is sent: full sends everything, redacted drops message text, metrics sends only counts and costs. On OuterLayer, your organization's limit can remove more, and secrets are removed again before it is stored." width="660" height="500" data-path="images/upload-boundary-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/outerlayer-stg/M2z6K_ww2sAtqE1w/images/upload-boundary-dark.svg?fit=max&auto=format&n=M2z6K_ww2sAtqE1w&q=85&s=940ae519c11d4900c2e656c1c2df8e76" alt="On your machine, your agent writes a session file. If the session was not started for a work item with OUTERLAYER_WORK, it never leaves the machine. If it was, secrets and your local identity are always removed, then your chosen tier decides how much is sent: full sends everything, redacted drops message text, metrics sends only counts and costs. On OuterLayer, your organization's limit can remove more, and secrets are removed again before it is stored." width="660" height="500" data-path="images/upload-boundary-dark.svg" />
</Frame>

## The gate

A session uploads only when it was launched with `OUTERLAYER_WORK` naming the work item it is for. A session started without it never leaves the machine, in any tool, however `sync` is invoked.

The gate covers evidence too. An `outerlayer emit` from a session that was never launched is refused, so the same content cannot leave by a second route.

This applies equally to the three ways a session can reach the network:

| Path | When it runs | What it sends |
| - | - | - |
| `outerlayer sync` | When you run it. It also runs in the background after each agent turn, at most once every five minutes. The background run needs saved credentials: a login with the repository connected, or a factory key. | Launched sessions, incrementally. |
| `outerlayer daemon` | While it runs. | A launched session's new turns, as they land. Nothing else. |
| The session-start, tool-use and session-end hooks | At launch, at most once every five minutes while the session works, and once when it ends. | The work item's number and the session id, through `outerlayer work link-session`, keeping the session's hold on the item current. Never the session's content. The session-start call also asks which repository supplies the instructions in a governed repository — see below. |

Set `"autoSync": false` in `~/.outerlayer/config.json` to leave every automatic upload to your own command. The daemon reads that setting on every send, so it takes effect without a restart.

## What a launched session contains

At the default tier, `full`, a session ships with:

* prompts, agent messages and thinking
* tool inputs and outputs, and images
* file paths, repository and branch names
* models, token counts per model, speed and input size, and costs

Speed is the provider's speed tier for a request, such as `standard` or `fast`. Input size is a band of the request's input tokens, starting at 0, 128,000, 200,000, 256,000, 272,000 or 512,000. The server prices the session from those token counts and stores its own figure. The cost your CLI works out is a local estimate. A Claude Code session also carries the cost Claude Code reports for the session, which the status line records. The server stores it only to compare with its own figure.

Choose a lower tier with `--tier <tier>` on `sync`, the `OUTERLAYER_TIER` variable, or `"tier"` in `~/.outerlayer/config.json`. `--tier` wins over the variable, and the variable wins over the config file.

Two lower tiers strip fields on your machine before anything is sent:

| Tier | Keeps | Strips |
| - | - | - |
| `full` | Everything. | Nothing. |
| `redacted` | Metrics, tool names, and identifiers: your email, the working directory, repository, branch, commit, file paths, error signatures, the session's outcome, hook commands and warning details. | The session title, message text, thinking, images, tool input and output, hook error output, and unclassified agent data. |
| `metrics` | Counts, costs, timings, tool names and statuses. | Everything `redacted` strips, and every identifier it keeps. |

`outerlayer sync --dry-run` prints the exact field paths each tier strips.

Your organization can set a ceiling of its own. Send a richer tier than the ceiling allows and the server strips the extra fields before storing them.

## What is scrubbed before upload

Session content is scrubbed on your machine before it is sent, at every tier. The scrub cannot be disabled.

* **Credentials.** About thirty credential shapes are replaced with `[REDACTED:<type>]`. They include vendor API keys, tokens, PEM private keys, bearer headers, cookies and payment card numbers.
* **Home directory.** A home-directory path is collapsed to `~`.
* **Local identity.** Your OS username, git `user.name`, git `user.email` and host name become `[DEV_USER]`, `[DEV_NAME]`, `[DEV_EMAIL]` and `[HOST]`.

A username or host name shorter than four characters is not replaced outside a home path. A git name shorter than three is not replaced either. Short names are too likely to match ordinary words.

To scrub more, add your own strings or patterns under `scrub` in the [config file](/reference/config-file).

The server runs a smaller set of credential patterns again on arrival, plus the home-path collapse.

The patterns match known formats, so a credential in an unusual shape can still get through. They favour precision: a missed token can be revoked, but a scrubber that mangles code makes sessions unreadable.

The scrub applies to session content and to what a runner reports about a build. Files and text you pass to other commands are sent as you wrote them. That includes `outerlayer emit artifact` files, `outerlayer emit` bodies, findings and criteria, and `outerlayer work open-pr` and `outerlayer work comment` text.

## See it before you send it

```bash theme={"system"}
outerlayer sync --dry-run
```

It prints:

* the destination and the tier
* the exact field paths stripped at that tier
* image byte totals and a breakdown by repository
* a row per session, for the first twenty

It makes no network calls. Here it is at the `redacted` tier, on a machine with no launched sessions:

```text theme={"system"}
DRY RUN — nothing left this machine.

  destination  https://gateway.example.com (app 7f3c2a10-0000-4000-8000-000000000001)
  tier         redacted
  stripped     events[].errorText, title, vendor, turns[].text, turns[].thinking, turns[].images, turns[].vendor, turns[].toolCalls[].input, turns[].toolCalls[].output
  sessions     0 (of 0 scanned) in 0 batch(es)
  images       0 blob(s), 0 B
  repos        0 distinct:
```

Add `--json` to inspect the literal session payloads. For a queued artifact, the JSON lists its file name, kind and session, not its bytes.

The dry run shows the tier your machine applies. Your organization's ceiling is applied on the server, so it does not appear.

## Other commands that reach the network

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/outerlayer-stg/vtkewxrpx_NzS0BQ/images/system-map-light.svg?fit=max&auto=format&n=vtkewxrpx_NzS0BQ&q=85&s=e3a543a80cae9053e8249cc2077ee811" alt="What crosses between your machine, a host, OuterLayer, GitHub and Anthropic. On your machine, the agent session sends its model requests to Anthropic with your own login, and pushes and opens its pull request on GitHub with your gh login. The hooks send OuterLayer the item number and session id, and the CLI sends the pull request number, the launched sessions after scrubbing, and evidence. On a host, every request a build makes goes through the runner, which signs with the host key. The runner sends OuterLayer claims, heartbeats, releases and the runner log, plus the build's sessions, evidence and pull request text, and gets back the item key and repository tokens. It adds the host's Claude credential to the build's model requests to Anthropic, and fetches and pushes on GitHub with tokens limited to one repository. OuterLayer's gateway, through the GitHub App, opens a build's pull request, posts the evidence comment and the OuterLayer evidence check, and merges from the Work page. GitHub sends OuterLayer webhooks." data-path="images/system-map-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/outerlayer-stg/vtkewxrpx_NzS0BQ/images/system-map-dark.svg?fit=max&auto=format&n=vtkewxrpx_NzS0BQ&q=85&s=4a80b655cd1d7f7274eb855d6470f32b" alt="What crosses between your machine, a host, OuterLayer, GitHub and Anthropic. On your machine, the agent session sends its model requests to Anthropic with your own login, and pushes and opens its pull request on GitHub with your gh login. The hooks send OuterLayer the item number and session id, and the CLI sends the pull request number, the launched sessions after scrubbing, and evidence. On a host, every request a build makes goes through the runner, which signs with the host key. The runner sends OuterLayer claims, heartbeats, releases and the runner log, plus the build's sessions, evidence and pull request text, and gets back the item key and repository tokens. It adds the host's Claude credential to the build's model requests to Anthropic, and fetches and pushes on GitHub with tokens limited to one repository. OuterLayer's gateway, through the GitHub App, opens a build's pull request, posts the evidence comment and the OuterLayer evidence check, and merges from the Work page. GitHub sends OuterLayer webhooks." data-path="images/system-map-dark.svg" />
</Frame>

None of these sends session content. This list names the commands people run most. The README in the `@outerlayer/cli` package names every part of the CLI that opens a connection, and a test checks that list against the code.

### Signing in and connecting

* **`outerlayer login`, without a piped key,** sends the dashboard two things:

  * this machine's host name, cut to letters, digits, spaces and `_ - . ( )`
  * a one-time public key, made on the spot

  While it waits, it polls the dashboard with the login's id and nothing else.

  The dashboard returns a token for your account, encrypted to that public key, plus the gateway address and your email. It names no factory.

  The private key stays on your machine and decrypts the token once. The token goes into `~/.outerlayer/config.json`.

  A login returned to a script keeps the private key in `~/.outerlayer/login-pending/` until `login --check` finishes it or the link expires.

  If this machine already held a login token, `login` sends that old token to the dashboard once, to revoke it. A key piped on stdin is saved locally and sends nothing.
* **`outerlayer connect`, and the first step of `outerlayer init`,** send a request with your login's token: `GET /v1/me/factories?repository=<host/owner/name>`, naming this checkout's remote. The gateway answers with the factories you can use, and whether this repository is linked to each. Once a factory is chosen, a second request, `GET /v1/apps/<factory id>/git/links`, asks which repositories that factory has linked. The choice is saved in the repository's git directory, never in a tracked file. With a factory key, and with `init --local`, no request is made.
* **`outerlayer work build`, `list`, `status`, `remove` and `pr`, and `outerlayer emit artifact`, `criteria`, `finding`, `findings`, `step` and `outerlayer emit <name>`,** send the same `GET /v1/me/factories?repository=<host/owner/name>` request before their own. They do this when you are logged in and the repository has no saved connection. If exactly one factory fits, they save it the way `connect` does. They skip the second request. With a factory key, `--app-id` or `OUTERLAYER_APP_ID`, they send nothing extra.
* **`outerlayer logout`** sends the saved token to the dashboard, as the bearer credential of `POST /api/cli/logout`, so it is revoked. It sends nothing else.
* **`outerlayer doctor`**, with a factory key saved, asks the gateway (`GET /v1/repositories/access`) which GitHub App permissions each connected repository's installation has not accepted. When the credential check passes, it also asks which control plane governs the checkout's repository (`GET /v1/context/source?repo=`), which sends the repository's name. `outerlayer init` runs `doctor` as its last step. On a runner host, `doctor` also asks the npm registry for the latest Claude Code version. With `builtin:container` hooks it also asks the gateway which repository governs each included repository (`GET /v1/context/source`). When the gateway has a GitHub App (`GET /v1/repositories/access` does not answer `repository_tokens_unavailable`), `doctor` reports that builds read the control plane through the App and checks nothing on the host. Without one, it runs `git ls-remote` against that repository with the host's own credentials.

### What the CLI reads from the gateway

Gateway answers to the CLI carry an `OuterLayer-CLI-Version` header. It names the CLI version that the factory's runners use. The CLI keeps that version in `~/.outerlayer/cli-version.json` and compares it with its own version. The CLI sends no request to learn it. A machine with no login makes no gateway request and gets no header.

### Work items and evidence

* **`outerlayer work build`, `remove`, `status`, `list`, `link-session`, `pr`, `open-pr`, `claim`, `renew`, `release`, `comment`, `threads` and `logs`** start, read and update work items.
  * `work link-session` sends the item's number and the session id.
  * `work pr` sends the session id and the pull request number and repository.
  * `work open-pr` sends the pull request's title and the text of the body file you name. A runner's build sends them to the gateway. In your own session the same text goes to GitHub through `gh`, and `work open-pr` then sends the pull request number and repository the way `work pr` does.
  * `work comment` sends the comment's text, and `work threads` reads comments back.
  * `work logs` sends the item number and optionally an attempt id, and reads the runner's log back.
  * `work claim`, `renew` and `release` record a runner's hold on an item. Run inside a session with no `--host`, `release` instead sends the session id and releases that session's own lease.
* **`outerlayer emit artifact`** sends the file you name, its caption, and any criterion id. It also sends the file's name, media type, size and sha256, and where it came from: the repository, branch, commit, pull request number, CI run and parsed test results. Inside a recorded session it queues the file instead, and the next `outerlayer sync` uploads it with the session id.
* **`outerlayer emit <name>`** sends one check's outcome, and the sentence you give it.
* **`outerlayer emit step`** sends the step's name, its start time (the time you ran the command), a random id for the step and the work item number. If the gateway cannot be reached, the same values are kept, with the factory id, the working directory and the time of writing, in `~/.outerlayer/spool/steps.jsonl` and `outerlayer sync` sends them later. Nothing from the session itself is sent.
* **`outerlayer emit finding`, `emit findings` and `emit criteria`** send the findings or criteria you write, for a work item.
* **`outerlayer mcp serve`** forwards the JSON-RPC messages your editor sends to the gateway.

### Hooks in your own sessions

* **The session-start hook, in a governed repository,** asks which repository supplies this checkout's instructions (`GET /v1/context/source`), and sends the repository name to do it. The files themselves are fetched over git with your own credentials, from your git host, not through OuterLayer. See [Share instructions across repositories](/share-instructions-across-repositories).
* **The session-start hook, when the repository's git hooks are missing,** runs that repository's own `prepare` script. What that script does is the repository's business.

### The runner on a build host

What a runner and its builds send, and to whom, is in
[What a host sends](/build-security#what-a-host-sends).

### Paths that make no network call

Three paths are proved to make no network calls at all, each tested with every outbound call replaced by a stub that throws:

* `outerlayer daemon --once`
* a session start in a repository nothing governs
* the daemon's handling of a session that was never launched

## Failures you cannot see in the session

Two failures never show up in the session itself: an `OUTERLAYER_WORK` value that was refused, and a `work link-session` that could not reach OuterLayer. Both are appended to `~/.outerlayer/spool/hook-errors.log`, and the next session start tells you so.


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