The gate
A session uploads only when it was launched withOUTERLAYER_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:
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
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:
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, gituser.emailand host name become[DEV_USER],[DEV_NAME],[DEV_EMAIL]and[HOST].
scrub in the 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
- 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
redacted tier, on a machine with no launched sessions:
--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
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
~/.outerlayer/config.json. A login returned to a script keeps the private key in~/.outerlayer/login-pending/untillogin --checkfinishes it or the link expires. If this machine already held a login token,loginsends that old token to the dashboard once, to revoke it. A key piped on stdin is saved locally and sends nothing. - this machine’s host name, cut to letters, digits, spaces and
-
outerlayer connect, and the first step ofouterlayer 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 withinit --local, no request is made. -
outerlayer work build,list,status,removeandpr, andouterlayer emit artifact,criteria,finding,findingsandouterlayer emit <name>, send the sameGET /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 wayconnectdoes. They skip the second request. With a factory key,--app-idorOUTERLAYER_APP_ID, they send nothing extra. -
outerlayer logoutsends the saved token to the dashboard, as the bearer credential ofPOST /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.outerlayer initrunsdoctoras its last step. On a runner host,doctoralso asks the npm registry for the latest Claude Code version. Withbuiltin:containerhooks it also asks the gateway which repository governs each included repository (GET /v1/context/source). It then runsgit ls-remoteagainst that repository with the host’s own credentials.
Work items and evidence
outerlayer work build,remove,status,list,link-session,pr,open-pr,claim,renew,release,commentandthreadsstart, read and update work items.work link-sessionsends the item’s number and the session id.work prsends the session id and the pull request number and repository.work open-prsends the pull request’s title and the text of the body file you name. A host build sends them to the gateway. In your own session the same text goes to GitHub throughgh, andwork open-prthen sends the pull request number and repository the waywork prdoes.work commentsends the comment’s text, andwork threadsreads comments back.work claim,renewandreleaserecord a host’s hold on an item. Run inside a session with no--host,releaseinstead sends the session id and releases that session’s own lease.
outerlayer emit artifactsends 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 nextouterlayer syncuploads it with the session id.outerlayer emit <name>sends one check’s outcome, and the sentence you give it.outerlayer emit finding,emit findingsandemit criteriasend the findings or criteria you write, for a work item.outerlayer mcp serveforwards 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. - The session-start hook, when the repository’s git hooks are missing, runs that repository’s own
preparescript. What that script does is the repository’s business.
The runner on a build host
-
outerlayer runner, releasing a claim, sends the host, the outcome, the step the attempt ended in (stage), the exit code of the process that ended it, a reason that is scrubbed and cut to 200 characters, and what the attempt ran in (environment, listed below).- The reason comes from the runner itself or from a host’s cleanup or report hook, never from the command’s output.
- The runner’s own reasons include a failed image build, an out-of-memory kill, the item’s evidence verdict and GitHub refusing a workflow change.
-
The
environmenta release sends holds:- the isolation (
container,vmorshared-user) - the recipe’s source commit, key and path, and the image id
- the recipe properties the runner ignored
- the CLI and agent versions
- who authored the commits (the GitHub App or the host), and whether the person who asked for the build was credited
- for a container build: the hosts the build reached (up to 200, with a count of the rest), and the first 200 addresses the tunnel refused with their range (with a count of the rest)
- the variable names the runner’s config passed in, the names of the destinations the build called, and which kinds of repository token the build used (
read,push)
- the isolation (
-
outerlayer runner init --vmon a Mac runslimactl. Lima downloads a Linux VM image, and inside the VMaptdownloads Node andnpmdownloads the CLI. -
outerlayer runner checkasks the gateway (GET /v1/runner/host-key) whether the runner key can claim work. -
outerlayer runner imagefetches the repository’s default branch with the host’s own git credentials. Docker pulls base images and features with the host’s owndocker login. The build also downloads pinnedghandtinireleases from GitHub. -
outerlayer runner, on every poll, sends a heartbeat. It holds the host name (runner.host, which defaults to this machine’s hostname), the CLI version, the runner protocol (which version of the gateway contract the CLI was built for), the poll interval, and the slots in use and the total. It holds the result of that poll’s own request for work — success, or an error code and message. The error is only what the platform already returned to this host, or a fixed sentence when no error came back from the platform. Once the runner has updated itself, it addslastUpdate: the two CLI versions, how it ended (updated,rolled_back,verification_failed,not_publishedorretrying), a short reason and the time it ended. It adds one field only while the runner holds off new work for want of disk,lowDisk: the free and needed disk space in bytes. While a check of the runner’s own stops it claiming work, it addsblocked: one sentence of at most 500 characters that names the cause. The causes are a missing/dev/kvmfor a microVM build, no Claude credential, a command still set to the placeholderrunner initwrites, builds that do not fit in memory, and an image builder that is not ready or not usable. Depending on the cause, that sentence can include the host’s usable memory in GiB, the/dev/kvmpath and Docker’s own error text. It is not scrubbed. Nothing else about the work item or the machine is sent. -
outerlayer session-senderruns only inside a runner’s build. It streams the sessions that build launched to its own work item, with the build’s item key. It sends no other session on the machine. -
outerlayer runner, before a build’s command starts, runsgit ls-remoteandgit fetchin the job’s workdir with the host’s own git credentials, to put it on the branch the claim named. Nothing about the job is sent to OuterLayer that way. -
outerlayer runner, for a container build, asks the gateway for a read token and a push token limited to the item’s repository (POST /v1/work-items/{workItemId}/claim/tokens). It sends git’s fetch and push requests for the build to github.com itself, with those tokens. The build’s git traffic goes from the runner, not from the build. When the attempt ends, the runner revokes each token at GitHub. No token goes to OuterLayer. The read token is written to the build’sgh/hosts.ymlon your machine soghcan use it, and is deleted with the build. The push token stays in the runner’s memory. -
outerlayer runner, for a container build, asks the gateway which repository governs the item’s repository (GET /v1/context/source). It fetches that repository on the host with the host’s own git credentials. The request names the item’s repository and nothing else. The build receives that repository’s files, never a credential for it. -
outerlayer runner, for a build it provisions itself, serves two built-in destinations:gatewayandclaude. The build sends its requests to your gateway and to Claude’s API (api.anthropic.com) through the runner. The runner adds the attempt’s item key and the host’s Claude credential on the way out. The credential goes to Anthropic only, and is read from the runner’s own environment. A container or microVM build never has it in its environment or files. Abuiltin:processbuild runs as the runner’s user, so it can read the credential from the runner’s files. -
outerlayer runner, for a microVM build (builtin:vm), downloads a pinned Firecracker release, a guest kernel and a small guest helper from github.com the first time it needs them. It checks each against a sha256 pinned in the CLI. The runner sends nothing in those requests. The build’s connections leave the microVM over vsock to the runner’s tunnel, which applies the same refusals as for a container build. The microVM has no network device of its own. -
outerlayer runner, when a build’s command exits 0, runs the repository’s host checks. It sends each one’s result (POST /v1/emitted-results): the check’s name, pass or fail, and for a fail the exit status and the last line of its output, scrubbed. It then reads the work item once more, for its evidence verdict, and sends nothing but that request. - On a runner host, any command that uses the saved runner key signs each request with the host key. The signature covers the request’s method, path, query and body, and adds nothing else.
-
outerlayer runner start, when the gateway names a higher CLI version than the one running and no build is in flight, calls the public npm registry (registry.npmjs.org). It asks for@outerlayer/cli: that version’s metadata, its tarball and its provenance attestation. Those requests carry no credentials and nothing about the host or the factory. It then runsnpm installandnpm audit signatures. These fetch the package’s dependencies too, and they use this user’s npm configuration, including any registry token in~/.npmrc. Setrunner.autoUpdatetofalseto stop them.
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: anOUTERLAYER_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.