# Strata — public agent guide

You are an agent asked to build, deploy, or debug an Unreal Engine project on
**SimplyStream** (WebGPU game streaming), or publish it to **GameGhost** (the
public store at play.gameghost.gg). **Strata** is the platform that does this: a
content-addressed VCS + cloud build fabric. You push a commit; Strata cooks it
in the cloud, deploys it, and proves it on a real GPU. You never build locally
and you never need Unreal installed.

**`GET /v1/agent/surface` is the machine-readable half** (tokenless): routes,
job kinds, capabilities, verdict codes, limits, artifact names, install
commands. Fetch it at session start and trust it over anything you remember
about the API. This page carries only what a schema cannot — how to get a token,
and the judgment calls.

## 1. Get a token

Every request carries `Authorization: Bearer <token>`. Agent tokens look like
`strata_agent_*` and are **scoped**: to specific repos, and to capabilities
(`read`, `push`, `doctor`, `escalate`, `publish`). They are minted by a Strata
admin or by a signed-in repo owner (`POST /v1/agents/tokens {"name", "repos",
"caps"}`).

**No token? Bootstrap from just the human's email** — declare your exact grant
up front (scope-at-approve; least privilege by construction):

1. `POST /v1/auth/email/start {email, agent: {name, repos: ["<repo>"],
   caps: ["read","push","doctor"], provision: true}}` → `{authId}`. The email
   shows the human EXACTLY what approving grants — repo, caps, project
   provisioning — next to an "Approve this grant" button and a 6-digit fallback
   code.
2. Tell the human to approve; poll `POST /v1/auth/email/poll {email, authId}`
   every ~3s (202 = pending), fallback `POST /v1/auth/email/verify {email,
   code}`.
3. Approval does EVERYTHING atomically server-side: their account, the repo
   (created and owned by them), and the SimplyStream project (ClientID sealed
   into the vault — you never see a credential). `agentToken` in the response is
   your repo-scoped token; no account session is ever created. Add `"publish"`
   to caps only if the human wants store publishing.

`GET /v1/whoami` echoes your identity and effective scope — call it first on any
unexpected 403; the usual cause is a scope mismatch, not an outage. Never print
the token, and a sudden 401 means re-ask the human rather than retry.

## 2. Optional setup for LATER sessions

Nothing here blocks today's job: the raw HTTPS routes need no install, no write,
and no shell.

```sh
# The skill loads on demand in future sessions. Never clobber an existing one —
# if the path exists in any form, this machine is already set up and you skip.
[ -e ~/.claude/skills/strata ] || \
  { mkdir -p ~/.claude/skills/strata && \
    curl -fsSL https://api.stratavcs.com/v1/agent/skill \
    -o ~/.claude/skills/strata/SKILL.md; }
```

The surface carries the rest: the hosted MCP URL (typed tools — the only path
for shell-less harnesses), the local MCP package, and the CLI manifest with
per-platform installers. Windows uses `install.ps1`; **do not install WSL to get
around a missing binary**.

**A blocked install is not a blocked task.** Sandboxed harnesses routinely refuse
writes into `~/.claude/` and outbound `curl` — fall through to the raw HTTP
routes, finish the job, and mention the denial once at the end. Only §1 is
load-bearing.

## 3. The loop

**Done means** a deployed build whose verify reads `0`, with the play URL in the
human's hands. A cook → deploy → verify is minutes of waiting and that wait IS
the job: report the `eta` once (step 7), then work this loop until step 4 reads
`0`. Do not hand back mid-flight to say "queued".

**Hand back only when you cannot proceed without them**: a 429 whose
`retryAfterSeconds` outlasts your session, a `423` lock only they can release, an
escalation whose fix is a future engine release, or intent only they have (step
2). A failed verify is not one of those — read the proof, fix, re-push.

0. **Orient**: `GET /v1/repos/:repo/onboard` — a typed checklist from "empty
   repo" to "listed on GameGhost", each incomplete step carrying the exact next
   call. Start here.
1. **Read remotely**: `/v1/repos/:repo/tree|file` inspects head with no
   workspace.
2. **Write — pick the path by SIZE, before you start:**

   | What you have | Path |
   |---|---|
   | A few files (caps are in the surface) | `POST /v1/repos/:repo/edit` — whole-file replacements committed server-side. Pass `expectedHead` (the commit you read at) for optimistic concurrency, `skipCook: true` for docs/config-only commits. |
   | An existing project of any real size (thousands of files, GBs of `Content/`) | the **CLI**: `strata init` / `pull` / `push`. No HTTP route uploads a tree — 413 is what trying earns. |
   | Nothing yet, and you want a starter | `POST /v1/repos/:repo/fork` from an existing repo. |
   | An Epic/Fab **marketplace** asset the human already owns | `POST /v1/jobs/import` — a MARKETPLACE lane keyed by Epic `appName` or Fab listing id. Not a project or git import: it cannot upload what is on your disk. |

   A push triggers cook → SimplyStream deploy → GPU verify automatically; there
   is nothing to "start". When you want the whole pipeline as one checklist
   instead, `POST /v1/repos/:repo/ship` drives [engine?] → cook (client ∥
   server) → verify → doctor? and returns an id you poll — it obeys the same
   capacity gates as any cook.

   **Strata is its own VCS.** `strata push` snapshots the working tree and
   ignores `.git` outright: a dirty branch, staged edits, a detached HEAD — none
   of it changes what deploys. Never git-commit on the human's behalf to tidy up
   first. If you cannot tell whether their uncommitted edits are meant to ship,
   ask — that is a question about intent, not about git.
3. **Watch**: `GET /v1/repos/:repo/pipeline?commit=<prefix>` aggregates every
   job for your commit with normalized states, verdict strings, and proof URLs.
   Use it instead of polling `/v1/jobs`, and no faster than every ~10s —
   verdicts take minutes. Webhooks exist if your harness can receive them
   (subscriptions are operator-managed today).
4. **Read the verdict** (semantics are exit codes, not prose):
   - `0` — the deployed build booted and presented ≥1 real GPU frame. Verified.
   - `1` — fatal/timeout: a real failure of this build. Read the proof.
   - `2` — WebGPU unavailable on the GPU node: **node health, never your
     change**. The fleet retries on a replacement automatically.
5. **Read proofs, bounded**: `verify.json` digest first; `console.log` next.
   `fatals: 0` does **not** mean clean — grep the console for
   `WGPUCreatePipelineAsyncStatus|Invalid BindGroup|validation error|device lost`.
   Never pull `console.raw.log` whole; never inline image bytes (use the
   artifact URLs).
6. **Rendering wrong** (washed, black, missing draws)? Enqueue a doctor capture
   with `vision: true` — a real-GPU capture with per-pass / per-draw WebGPU
   Vision snapshots, persisted as artifacts.
7. **Tell the human how long.** `pipeline` and `GET /v1/jobs/<id>` embed a
   `queue` block for whatever is gating them (shape in the surface). Relay `eta`
   verbatim the first time you report, and again when it moves materially.
   `GET /v1/queue` answers the same question BEFORE you commit to a build.

## 4. Capacity: what a 429 means

Every account gets **one build at a time** (all flavors run in parallel), a
**short waiting line**, and a **daily budget of builder minutes**; all free
accounts together hold only part of each pool, with the rest reserved so
operator work never queues behind a crowd. `GET /v1/queue` reports both.

- **429 is an answer, not an outage.** Report what the body says (`reason`,
  `detail`, `retryAfterSeconds` — vocabulary in the surface) and wait. Retrying
  in a loop gets no slot sooner, and on `daily-budget` nothing succeeds until
  00:00 UTC. Never mint a second token to get around it: quota is charged to the
  account, not the token.
- **A push always lands.** With no build capacity the commit is stored and only
  its automatic cook is held back — the response carries `buildDeferred`. Say
  so, then cook later with `POST /v1/repos/:repo/rebuild`.
- **`queue.blocked` means waiting, not broken.** Report `blocked.detail` rather
  than re-enqueuing. `concurrency` and `daily-budget` are your account's limits;
  `fleet-capacity` is everyone's.
- **Spend the budget on builds that matter.** Batch edits into one commit; pass
  `skipCook: true` on docs/config-only commits.

Estimates come from the median of recent runs on this fleet: guidance, not a
schedule — phrase them the way the API does ("about 12 minutes").

## 5. Deploy targets

- **SimplyStream** — automatic. Every green push IS a deploy; the play URL and
  per-mode verdicts are at `GET /v1/repos/:repo/latest`. Credentials are vaulted
  per project and injected at cook time — never put a ProjectID/ClientID in the
  tree, and never ask the human for one.
- **GameGhost** — a public listing on top of a deploy.
  `POST /v1/repos/:repo/publish` promotes a deployed client revision to the
  launch profile, verify-gated (green es31+sm5) unless you pass an explicit
  older `revision` (rollback) or `force`; `dryRun: true` previews. `{publish:
  true}` flips the listing live — a first listing needs
  `store: {slug, name, description}`, and that metadata must come from the
  human, never invented. Gated by the `publish` capability (distinct from
  `push`) or repo ownership. Never call SimplyStream backend routes directly.

`latest`, `onboard`, and `publish` responses also carry a `links` block — the
play URL, dashboard page, SimplyStream project page, and GameGhost portal + slug
when listed. End your work by reporting status, verdicts, and those links.
**Never construct URLs yourself.**

## 6. The engine is operator-only

You cannot patch, read, or build the SimplyStream engine, and no route returns
engine source. If a fault is genuinely in the ENGINE — an RHI/WebGPU validation
error, a cook that fails inside engine code, a render fault a project change
cannot fix — **escalate it** rather than guessing:

```
POST /v1/repos/:repo/escalate { "description": "what failed and what you tried",
                                "refs": [{ "jobId": "..." }, { "treeHash": "..." }] }
```

It lands on a queue the Strata operator drains. **The response tells you where
you stand** — a `queue` block with `{position, eta, operator}`:

- `operator: "active"` — someone is draining; `eta` estimates the wait from
  recent resolution times. Relay it.
- `operator: "idle"` or `"none"` — **no operator is working the queue right
  now.** There is no honest ETA; tell the human the report is queued but may not
  get a timely reply, rather than implying one is coming.

Then poll `GET /v1/repos/:repo/escalate`, or **wait without polling** by
subscribing to the `escalation.resolved` webhook. You get back a **concise,
code-free resolution** — typically "fixed in a new engine release, re-cook" —
never a diff, and you should not ask for one. When the operator attached the
fix's release, the resolved report carries `engineRev`: re-cook against it and
the fix is applied. Fix everything on YOUR side first. You may hold only a
bounded number of OPEN reports at once (re-filing the SAME report is always
allowed); a 429 with `reason:"escalation-cap"` means wait for one to resolve.

## 7. Ground rules

- Repos are `[A-Za-z0-9][A-Za-z0-9._-]{0,63}`, case-insensitive, and each is
  pinned server-side to an engine line (5.8 or 6.0) — you do not manage engines;
  the onboard checklist says if it needs setting.
- A brand-new repo needs no setup call: minting a token scoped to an unused name
  creates it, owned by the human; your first edit (or CLI push) writes the root
  commit.
- 401/403 → `GET /v1/whoami` first. A 423 on push means a human holds a lock on
  a path you changed — surface it, don't force.
- Provenance: your token id is recorded on every lock and push. Act like it.