# Run402 CLI -- Agent Reference — Repos (encrypted git remotes) > Slice `repos` of the Run402 CLI reference. Index (start here): https://docs.run402.com/llms-cli.txt · Whole reference: https://docs.run402.com/llms-cli-full.txt ### repos — host-blind encrypted git repos `run402 repos` is your source, encrypted before it leaves the machine — a Git remote whose contents are encrypted client-side and stored as a chain of signed, admitted heads (wire protocol `r402s/v0`). One noun, seventeen verbs. KyGit is the brand; a **vault** is the resource (`/vaults/v1`, `VAULT_*` error codes, `vault_policy`); a **repo** is what you have — `run402 repos` on the command line, `r.repos` in the SDK, `repos_*` in MCP. One remote scheme: `run402::/`. **What Run402 claims about it, and how strong each claim is.** These are the entire approved claims vocabulary — three separate promises with three separate strengths: 1. **Run402 cannot decrypt your vault or repository history. Deployment artifacts remain a disclosed plaintext custody boundary.** Cryptographic, against Run402 itself: source payload and repository-history content are ciphertext-only; the substrate retains only enumerated plaintext metadata and holds zero vault keys. The deploy lane is separate — the platform custodially holds the plaintext artifacts of every deploy, and says so. 2. **Activation requires vault admission by default; an explicit, audited override can bypass it.** An operational platform invariant, not a cryptographic one — the platform is the party enforcing it. 3. **Retention is an operational promise of the platform, not a cryptographic guarantee against it** (the host controls timestamps and bytes). Naming law: a verb is either exactly a `gh repo` verb (`create`/`list`/`view`/`rename`/`delete`), exactly a `git` verb meaning what it means in git (`gc`, `fsck`), or a plain-English verb for an operation with no analog (`capture`, `policy`, `mirror`, `access`, `recover`) — never a git verb reused for a different meaning. That rule is why the offline-restore verb is `recover`, not `restore`: `git restore` already means something else. Tiered help — three or four verbs loud, the rest occasional or maintenance: ``` Common: repos create [name], repos view, repos list Then git: git push, git clone run402::/ Occasional: repos capture, repos mirror, repos recover, repos recovery-bundle Handoff: repos handoff, repos resume Invite: repos invite, repos join Lifecycle: repos rename, repos delete Maintenance: repos fsck, repos gc, repos access, repos policy ``` | Verb | What it does | |---|---| | `create [name]` | Provision a project (or, with `--project `, ADOPT an existing one), ALLOCATE its vault (mints key material and, on first allocation, a one-shot recovery receipt), and scaffold the remote — `origin` when free, `run402` when taken. `[name]` is inferred from an existing git remote's basename or the directory name when unambiguous — **never a prompt**; if the directory and an existing remote disagree, or nothing usable can be derived, this is a structured error naming exactly one `next_action`. The response's `next_action` is the exact `git push` to run. Nothing is deployed, ever, unless you separately choose to — a vault-only project is a first-class shape, not a half-configured one. `--byo ` allocates the vault with `storage_profile: "byo"` — payload ciphertext lives in that bucket, which you own (see "BYO primary bucket" below) — taking the same `--profile `/`--ambient`/`--region`/`--endpoint` credential flags `mirror` uses; the destination is probed create-only BEFORE allocation and a probe refusal (`VAULT_BYO_BUCKET_PROBE_FAILED`, naming the failing property) means no vault half-exists. | | `view [--human]` | Side-effect-free: what this machine and the control plane each believe — allocation, policy, the local keystore, the authenticated and materialized pins, the mirror summary when one is configured, and where the keystore lives. NEVER materializes refs or advances a local pin — `refs` reports `{known:false, reason:"not_materialized"}` with a `verify_refs` next_action pointing at `fsck`, which is the one place materialization happens now. Carries the same advisory `compact_advised` `next_action` a publish does when this checkout's learned checkpoint coverage has fallen far enough behind, and echoes the vault's `warnings[]` verbatim — including the progressive `terminal_loss_risk` warning, which trips on generations, source bytes, or age since genesis and never clears (nothing can detect a second principal able to open the vault, so the terminal-loss statement itself never changes — only how prominently it is repeated). Also echoes the standing `vault_unmirrored` finding (vault-mirror-default) until a first mirror write or sync succeeds — informational, never blocking, computed client-side only. Multi-writer vaults (vault-multi-writer rev 47, see below) additionally carry `writer_set` (the chain-verified roster) and `pending_writers[]` (eligible candidates not yet admitted) — `--human` prints one roster line per writer plus a `repos access sync` hint when candidates are waiting, alongside the rest of its short summary. | | `list [--human]` | The organization's vault-bearing repos, via the bulk `GET /vaults/v1?org_id=` read when the gateway has it (one round trip) — gracefully falls back to the older per-project walk when that route 404s. Not every project in the org; ones with no vault are omitted. `--human` renders a compact roster (address, generation, bytes, policy) instead of JSON. | | `rename ` | Claim or rename the repo's per-org-unique, address-form name — the `` half of `run402::/`. Address with `--repo ` or `--project ` (not both). | | `delete` | Deletes a REPO-ONLY project: database, functions, subdomains, mailbox, and secrets must all be absent. When any is materialized, this REFUSES with `PROJECT_HAS_NON_REPO_RESOURCES`, enumerating `refused_resources` and pointing at `run402 projects delete` — the verb whose name says what it destroys. `--force` overrides ONLY the separate vault-history confirmation (the repo holds admitted generations) — it never overrides the non-repo-infra refusal. Success enumerates `deleted_resources`. | | `capture [--allow-dirty] [--manifest-out ]` | Capture the working tree and publish it. Not gated on a deploy — a vault-only repo captures for months without one. Against a project with no vault yet, this ALLOCATES one inline before publishing. Push-to-creates through a slug-form remote (`run402::/`) the same way `git push` does. `--dry-run` previews the real local pipeline without publishing. **A dirty tree (any modified/staged tracked path, or any untracked-not-ignored path) refuses by default** — `SNAPSHOT_DIRTY_TREE`, before any object is created, with `details.modified`/`details.untracked` naming every path and `next_actions` pointing at committing or `--allow-dirty`. `--dry-run` surfaces the same refusal (a preview that hid it would lie). `--allow-dirty` captures the tree as-is; the result discloses exactly what got swept in via `modified_captured`/`untracked_captured` (printed to stderr too) — even the override never captures silently. The same rule applies to the deploy-lane capture (`deploy` against a `vault_policy: required` project) — see `--allow-dirty` there. Both `--dry-run` and a real capture print a SUMMARY by default — `files_total`/`files_changed`/`files_new` counts, total/delta bytes, and up to 200 `changed_paths` (`changed_more` names any overflow) — never the full captured-file inventory (which can run to thousands of entries on a real repo). `--manifest-out ` writes the complete, untouched inventory to a private 0600 file and names it in the result's `manifest_path`; `-v`/`--verbose` inlines the full inventory directly in the JSON, composing with (not replacing) its usual stderr stats line. **Every publish result carries `checkpoint_staleness`** — `{generations_since_checkpoint, advised}`, computed locally from the coverage THIS checkout has learned (unknown coverage reads as `{0, false}`, silence rather than a guess). When `advised` is true the result gains a `compact_advised` `next_action` pointing at `run402 repos gc`, because a cold clone re-verifies every generation since the last checkpoint one at a time. It is advisory only and never gates a push. A vault holding no branch heads (capture-only) additionally carries a `restore_capture_ref` `next_action` with the exact `git fetch`/`git checkout` to run — a plain `git clone` of such a vault reports an empty repository with no hint the captures exist. | | `mirror []` | ONE flag-driven verb for the client-side, customer-owned ciphertext mirror — Run402 never holds a credential to it, and it is the recommended default, not a power-user extra (see "Mirroring — the recommended default" below). No argument: READ the configured destination plus a keyless freshness check. ``: configure (idempotent upsert). `--off`: remove the config only — never touches the mirror's own bytes. `--backfill`: copy every object the mirror is missing (also what completes the FIRST copy and clears the `vault_unmirrored` finding). Exactly one of these per call. State also renders inside `view`; integrity inside `fsck --mirror`. | | `fsck [--human]` | Walks the head chain AND materializes the ref map, advancing BOTH local trust pins — reported explicitly as `local_state_changed` + `pin_before` + `pin_after`, never implied. `--no-write` is a genuine audit mode: the same real walk and decrypt, persisting neither pin. `--budget ` caps heads walked per call. `--mirror` additionally runs the keyless mirror integrity probe — proves the mirror's VALIDITY, never its FRESHNESS. On a `storage_profile: "byo"` vault it ALSO HEAD-checks every object the vault's own signed chain names against your destination bucket, automatically, no flag — adjudicated against run402's chain (what SHOULD exist): a confirmed absence fails fsck with `VAULT_BYO_OBJECT_MISSING` naming each missing `{key, object_kind}` (capped list + count), while a machine with no local BYO credentials reports an explicit `byo_presence: {verified: false, not_checked_reason}` — named as unchecked, never a silent pass, never a hard failure. A managed vault is byte-identical to before (no `byo_presence` key at all). `--human` renders a short summary instead of JSON. | | `gc [--force-headroom]` | `git gc`'s own two halves — checkpoint publication and prune planning — in one verb, **not** described as "exactly `git gc`": the deletion ceremony is stricter. Plans and checkpoints by default; nothing is deleted until `--submit --intent-core --verifier-receipt ` supplies both receipts the two-phase protocol requires. The plan's submit `next_action` carries `destructive: true` / `requires_approval: true` / `safe_to_auto_execute: false` as additive fields. **Storage headroom:** compaction transiently holds BOTH the new checkpoint and the not-yet-pruned history — roughly **2× `source_bytes`**, counted against the org's POOLED tier storage — so this preflights before building or uploading anything and refuses `VAULT_COMPACT_INSUFFICIENT_HEADROOM` (naming `pool_used_bytes`, `pool_limit_bytes`, `projected_transient_bytes`, and the override) when the projection will not fit. Every run carries a `headroom` block with those figures, fitting or not, on both the planning and `--submit` halves. `--force-headroom` proceeds anyway; a preflight that cannot be ANSWERED (tier status unreadable) proceeds after one stderr note rather than blocking maintenance on an advisory read. The platform's own storage-quota enforcement stays authoritative in every branch — this only ever refuses earlier and more legibly, never admits more. | | `access [--human] [--no-reconcile]` | READ-ONLY of the roster (it lists AGENT members' keystore keys too, and `this_keystore` reports YOUR keystore for every principal type — `enrolled`, `publish_state` (`active`/`pending`/`absent`/`rotation_required`), `covered_on_this_vault` — never `null` for a real keystore). One side effect, reported never hidden: on a key-holder's FIRST ordinary vault operation in a process (`view`/`list`/`access` included) the SDK wraps every pending desired recipient (`reconcile_recipients` on the handle); `--no-reconcile` defers that and says so with the pending count; `fsck`, `--no-write` and `recover` never wrap. The org's directory of encryption-key-holding members, which of the vault's current envelope-recipient fingerprints are covered, per-recipient `envelope_state` (`converged`/`pending`/`pending_removal`, from the gateway's desired-recipient-state substrate), and (best-effort, this machine only) each principal's local TOFU pin. `stale_access` names removed members whose access was NOT actually revoked yet — `pending_removal` is honest bookkeeping, not enforcement. States an honest remaining gap rather than inventing one: `history_scope` (which epochs each recipient can read) has no substrate to report — vault protocol v0 pins a single fixed epoch, so there is no per-epoch scope to report. **`member_custody`:** when a sign-in session is cached (`run402 login`), the read also carries YOUR OWN wrapper custody — key identity, custody scheme, each wrapper's kind (`webauthn_prf`/`recovery_code`) and state (`pending`/`active`) — with stderr warnings for a passkey-only key (no offline recovery path) or an unfinished `pending` enrollment; without a session the block is honestly `{ available: false, reason, hint }`, never silently missing and never misleadingly answered as the agent principal. An enrolled teammate's key envelope is wrapped AUTOMATICALLY — no manual step — by the next `git push` or `repos capture` any key-holding client runs (best-effort, non-blocking; there is no manual reconcile verb). `access repair` (a general re-key clearing `stale_access` / a first-ever migration; needs `--recipient-state-version`/`--recipient-revocation-version`), `access revoke-key ` (self-contained, no flags), and `access declare-exposure` are the explicit owner+step-up rotation entry points. `--human` (read form only) renders a compact roster instead of JSON. | | `access retire-previous-owner` | After a project transfer: blocks every writer key whose principal is not a developer+ member of the vault's organization (the previous owner's writers) and commits the `writer_key_revoked` rotation that removes them. Owner + step-up. A transfer never does this on its own; a sender kept on as a `developer` is a member and is not retired. Protects only what is written afterwards. Safe to re-run; nothing to retire means no rotation. Prints `{repo_id, retired[], rotation_required, note, rotation}`. | | `access repin --principal --fingerprint ` | A KEY-HOLDER explicitly accepts a recipient's CHANGED key (vault-agent-envelopes). The session-start wrap refuses `pinned_key_mismatch` and never bypasses it — not even after an owner's `orgs members revoke-key`, because from this machine an owner re-key and a platform substitution look identical. Name the new fingerprint (read it back with the recipient — public data); refuses a fingerprint the org directory does not currently serve. Records the D197 re-pin receipt (best-effort) and moves the local pin; the next ordinary operation wraps. | | `access sync [--human]` | Multi-writer (vault-multi-writer rev 47, see below): on-demand reconcile of `pending_writers[]` — admits every eligible candidate (active org membership at role developer+, a published signing key, not yet a writer) via a real `add_writer_key{"writer"}` head per candidate. Requires THIS machine's own key to already be an active writer — checked locally first, no network call otherwise; `eligible: false` reports "no authority here", distinct from an empty `admitted: []` ("nothing pending"). `skipped[]` names candidates whose signing key could not be resolved, with why. This is the explicit, on-demand form of the SAME reconcile every ordinary `repos` operation already runs automatically when this machine is itself an active writer. `--human` renders a short summary instead of JSON. | | `policy ` | Set the activation policy. Owner + step-up, audited. `grandfathered` needs `--reason`; returning to `required` does not. Allocating a repo never sets this. | | `recover --out [--human]` | `r402s-recover`: rebuild a BARE recovery repository (no working files) straight from a mirrored prefix, with NO SERVER INVOLVED — the offline disaster path. Kept its name (design D10): `restore` was considered and rejected because `git restore` already means something else. The result's `layout` is `"bare"` and its `next_actions` print the exact `git clone -worktree` to run for a working tree — `recover` itself never checks files out. Normal retrieval is plain `git clone run402::/` — there is no `repos clone` verb. Cloning needs a Run402 principal on this machine — a wallet and a keystore holding an envelope for this vault — this is encrypted git, not a shareable link. A fresh clone installs local `refs/r402/retain/` refs for every retained deploy-capture tip no branch reaches, so `git fsck` is silent — `git for-each-ref refs/r402/` lists what is retained. Clones made by a client older than this one (or a checkout whose ref write degraded) may still show dangling commits under `git fsck`; harmless, not corruption — one `run402 repos fsck` run installs the missing refs. A retained ref locally pins that history against `git gc` until the vault prunes the capture, at which point the next fetch retracts it. `--human` renders a short summary instead of JSON. **Human-member path (vault-recovery-custody):** a member under wrapper custody (no keystore) recovers with `--bundle ` (their exported `r402s-member-recovery-bundle/v1` — omit it to use the mirror's own `member-recovery-bundles/` sidecar) + the source recovery code (`--code`, or prompted with hidden input so it never lands in shell history) + `--receipt ` (the vault's one-shot recovery receipt — the trust anchor; key material never substitutes for it; without any pin the result is honestly labeled `unauthenticated_salvage`). `--rp-id ` overrides the seal-time ceremony host bound into the wrapper context (default: the bundle's own `rp_id`, then `console.run402.com`). A raw WebAuthn PRF output is NOT a supported input — the no-server path for a human is the recovery code; a code with no exported bundle refuses as `RECOVERY_BUNDLE_MISSING` (a server-side wrapper row that was never exported is not offline backup). A bundle-decrypted result names it in `member_recovery` (which sidecar, which wrapper, which rp_id). `fsck --mirror` reports any `member-recovery-bundles/` sidecars as UNVERIFIED availability hints in `member_recovery_bundles[]`. | | `recovery-bundle [--out \| --out -]` | Export YOUR member recovery bundle (`GET /agent/v1/source-access/recovery-bundle`, format `r402s-member-recovery-bundle/v1`): key identity + every ACTIVE wrapper ciphertext — the file `recover --bundle` opens with the source recovery code, kept SEPARATELY. A server-side wrapper row alone is NOT offline backup; this export is what makes the code work offline, and the gateway stamps it as recovery-posture evidence. Writes `run402-source-recovery-bundle-.json` (0600) in the cwd unless `--out` says otherwise (`--out -` prints only); the full JSON always goes to stdout. **Principal-scoped, not repo-scoped** — one bundle covers every vault you can read, so there is no `--project`, and auth is your sign-in session (`run402 login` first; without one the request answers for the active WALLET's agent principal, normally no wrappers, with a stderr note saying so). To make the bundle travel WITH a mirror, copy it to `member-recovery-bundles/.json` under the mirrored prefix — `recover` finds it there automatically. Enrollment/activation/revocation/code replacement are browser ceremonies at console.run402.com/account → Source access; requires an active source-access key (`409 NO_ACTIVE_ENCRYPTION_KEY` otherwise). No MCP tool (same sign-in-session rule as `login`). | | `handoff [--note-file ] [--role ] [--ttl ] [--include-sensitive ]... [--list] [--revoke ]` | Capture the ACTUAL working tree — staged, unstaged, AND untracked, the same shape real `git stash push -u` produces (a synthetic 3-parent commit: tracked-worktree-state, an index-tree parent, a parentless untracked-tree parent) — push it into the vault as a retention root (no ref; it never appears in `git log`/branch listings), and mint a single-use bearer key, `kgh1_<69 chars>`, printed to stdout **alone** — nothing else this verb prints, in any mode, ever carries it. Mints at the caller's OWN org role by default (`--role` overrides, never escalates); the key is real authority until redeemed or its TTL expires (default 3600s, 60–86400 range) — the mint result's `warnings[]` says so verbatim and the CLI echoes it to stderr before the key. The Handoff Note (`kygit.handoff-note.v1` — `summary` required; `completed`/`in_progress`/`failing`/`tried`/`next_steps`/`decisions`/`open_questions`/`commands`) comes from `--note-file ` or piped stdin (a non-TTY stdin with nothing on it is a `BAD_USAGE` refusal, not a hang). Sensitive untracked paths (`.env`, `.env.*`, `*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.jks`, `*.keystore`, `id_rsa*`, `id_ed25519*`, `id_ecdsa*`, `.npmrc`, `.netrc`, `.pypirc`, `.git-credentials`, `*.tfstate*`, `*credentials*.json`, `.aws/**`, `.ssh/**`, `.gnupg/**`, `*.secret`, `secrets.*` — 22 globs) are excluded from capture by default; repeatable `--include-sensitive ` re-admits one explicitly, by name, never silently. `--list` reads outstanding handoffs instead of minting; `--revoke ` kills one before it's claimed. No MCP tool — mints a bearer secret. | | `resume \| --key-stdin [--to ] [--no-init]` | Claim a Handoff Key, clone a fresh checkout, and reapply the checkpoint with `git stash apply --index` — bit-for-bit what the sender's working tree looked like, staged/unstaged/untracked distinction included. One-shot: a claim from the SAME principal that already claimed it is a safe, explicit dedup (`deduplicated: true`, the original envelope reused — never a re-mint); any other claim after the first fails. `--to ` picks the target directory (default: derived from the vault address or repo id). On a wallet with no active tier, `resume` first folds the same cold-start chain `create` does — wallet → faucet → one x402 prototype payment, each step announced on stderr — so a resumed agent arrives as a paid-up run402 wallet of its own; `--no-init` opts out. The claim itself needs no tier, so a faucet throttle or payment refusal is reported (`cold_start.error` in `--json`, plus a `renew_tier` next action) and never blocks the resume. Prints the Handoff Note as Markdown by default (`--json` for the raw envelope) followed by the restore location and every `next_actions` command — always `git push origin main`, plus, when the sender minted the key at `owner`, a `remove_member` reminder that the previous agent is still an org owner and names removing it as the (destructive, approval-required) follow-up if that environment is gone for good. No MCP tool — mutates org membership. | | `invite [--room ] [--note-file ] [--role ] [--ttl ] [--include-sensitive ]... [--list] [--revoke ]` | The second redeem kind (kygit-invite): capture the checkpoint exactly like `handoff` — the inviter's own worktree, index, branch, refs, and access are all untouched — register the INVITER's own presence in a coordination room (the project's default room, or `--room ` for a named org room), mint a single-use bearer key, `kgi1_<69 chars>`, printed to stdout **alone**, and post ONE room message naming the checkpoint and the invite id — never the key. A Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. Mints at `developer` by default (`--role` overrides, never wider than the minter's own); the key is real authority until redeemed or its TTL expires (default 3600s, same 60–86400 range as `handoff`) — the mint result's `warnings[]` says so verbatim. A presence-registration or room-message failure is reported (`inviter_presence`/`room_message` in the result) and NEVER voids the mint — the key is still printed and the invite can still be redeemed. The Invite Note (`kygit.invite-note.v1` — same shape as the Handoff Note) comes from `--note-file ` or piped stdin, scanned for bare secrets before the commit is written (no override flag). `--list`/`--revoke ` read/revoke instead of minting. No MCP tool — mints a bearer secret. | | `join \| --key-stdin [--to ] [--no-init]` | Redeem an Invite Key on ANY machine: folds the SAME cold-start chain `resume` does (wallet → faucet → one x402 prototype payment, announced; `--no-init` opts out; a chain failure is reported and never blocks the redemption), clones a fresh checkout, and reapplies the checkpoint with `git stash apply --index` exactly like `resume`. Additionally pins the invite's OWN room locally (`r402.room` — a named room, or the project id when the mint omitted one), adds `.run402/` to `.git/info/exclude` (never `.gitignore`, which is part of the captured tree), registers THIS session's own presence, and posts ONE arrival message. Prints the Invite Note as Markdown by default (`--json` for the raw envelope), then the restore location, the inviter's name/labels/liveness, who else is live in the room, and the last few messages — so your first message needs no lookup. `next_actions` name `run402 messages wait` (block until the other side speaks) and an addressed `run402 messages send "…" --to `. No MCP tool — mutates org membership and writes a working tree. | Every verb above also accepts `-v`/`--verbose` (one stderr summary line of this call's request stats — round trips, wire time, bytes — coexists with `--human`) and always carries a `stats` block in its JSON result. See [Observability](https://docs.run402.com/cli/reference/#observability) below. **After rewriting history (rebase/amend), push with `git push --force` — NOT `--force-with-lease`.** The lease cannot cross git's remote-helper boundary (the helper receives a non-force update and refuses it as non-fast-forward), while plain `--force` through this remote already carries force-with-lease safety: every forced update is compare-and-swapped against the freshly read remote state server-side, so a concurrent push is never silently overwritten. The non-fast-forward refusal itself names this in its `next_actions`. **Address resolution is offline after first contact.** The first successful resolution of a `run402::…` remote pins the vault id plus its project/org ids in the checkout's local git config; every later `git push`/`fetch` on that checkout resolves with zero extra round trips. If the pinned vault is ever deleted and the project re-allocated a new one, the next verb heals itself: it clears the pin, re-resolves once, and retries. ```bash run402 repos create # name inferred from cwd/remote run402 repos create my-notes --org org_1a2b3c # explicit name, new project run402 repos create --project prj_xyz # adopt an existing project run402 repos create --nested --project prj_xyz # the app lives inside another repo: make it its own nested repo + remote git push -u origin HEAD # the printed next_action, verbatim run402 repos view --human run402 repos list --org org_1a2b3c run402 repos rename my-notes --project prj_xyz run402 repos delete --project prj_xyz --force # only once you've read what --force would lose run402 repos capture --dry-run run402 repos capture --dry-run --manifest-out /tmp/capture-plan.json run402 repos mirror s3://acme-vault-mirror --profile acme run402 repos mirror --backfill run402 repos fsck --mirror run402 repos gc run402 repos access run402 repos recover s3://acme-vault-mirror --out ./restored run402 repos handoff --note-file handoff.json # mints kgh1_… — the key is the ONLY thing on stdout run402 repos resume kgh1_… # on the other machine: redeem, clone, restore, print the note run402 repos invite --note-file invite.json # mints kgi1_… — the key is the ONLY thing on stdout run402 repos join kgi1_… # on the other machine: pay in, redeem, clone, restore, print the note run402 messages wait # then: block until the other agent speaks (or the timeout elapses) ``` A working tree inside another repository (a monorepo app) still gets its vault allocated, but the remote scaffold is skipped (`remote.status: "skipped"`, enclosing `toplevel` named) and the response carries a `create_nested_repo` next_action instead of `push_repo`; re-run with `--nested` to make the directory its own repository with the encrypted remote — the enclosing repository only gains one local `.git/info/exclude` line and is otherwise untouched. The inferred `[name]` for a monorepo app comes from the app directory, never from the enclosing repository's `origin`. `resume` and `join` end with the checkout's first git command in mind: when `git-remote-run402` (the helper every `run402::` remote needs) is not on PATH — an `npx -y run402` run keeps it inside the npx cache, where git cannot see it — the result carries an `install_remote_helper` next action naming `npm i -g run402`; nothing else in the redemption is affected. **Agents read each other's vaults with no human step (vault-agent-envelopes).** Custody follows membership, not principal type: every active `human` or `agent` member of the owning org is an envelope recipient. The CLI keystore's own X25519 key is enrolled AUTOMATICALLY on a keystore's first vault operation (`repos …`, `git clone`/`push`) — published as `custody_scheme: keystore_v1` and possession-proven in the same invocation; there is no enrollment verb. **Rotation is never automatic:** if this keystore's key differs from the principal's published key, the operation fails `VAULT_KEY_ROTATION_REQUIRED` — restore the keystore backup, or have an org owner revoke the stale key (`DELETE /orgs/v1/:org_id/members/:principal_id/encryption-key`, owner + step-up), after which the next operation enrolls afresh; a principal that is the sole member of every org it belongs to may republish with `replace_current`. **Cold open:** a member on a fresh keystore that has been wrapped opens a vault straight from its own envelope (`git clone run402::/` just works); the handle's `restored` block says how much was verified — `trust: platform_attested` + `continuity: first_seen|pinned` + `independently_verified: false` is the control plane's signed allocation record vouching for genesis, NOT end-to-end authentication (a creator-held recovery receipt is the `receipt` tier). Not yet wrapped → `VAULT_ENVELOPE_PENDING` naming the key-holders and the next actions: poll `repos access`; any key-holder's next ordinary vault operation wraps you. **MCP.** An MCP host reaches the vault through a `run` snippet against `r.repos`. The verbs that mint or redeem a one-time secret (`handoff`/`invite`'s single-use bearer keys, `resume`/`join`) refuse there with `SECRET_REQUIRES_CLI` and name the `run402 repos` command to hand the person. **Wallet selection, remote naming, id-pinning, named addressing, lazy allocation, the vault-only track, dry-run preview, `--human` rendering, the deploy-time capture lane, `gc`'s two-phase submit ceremony, expiry, terminal loss, the backup reminder, and mirroring to your own storage** are all covered in the sections below. **Cost.** There is no separate repos price. A repo's bytes count against the organization-pooled vault quota (`sourceBytes`: prototype 1 GB, hobby 10 GB, team 50 GB), a SEPARATE pool from the `storage_bytes` your projects share, charged once per unique object, with a 4 KiB per-object accounting floor and a 1 MiB per-vault minimum. **One thing to know before you rely on this: a vault has a writer set, not a single key.** Every admitted member or handoff recipient opens the vault and pushes under its OWN keystore key (`repos access` lists them; a member whose key is not yet admitted gets `VAULT_WRITER_NOT_ADMITTED` until any live writer's next operation admits it). A vault whose only admitted principal is this keystore is exactly as safe as this keystore: back up the keystore directory the moment you have something in it worth keeping, or admit a second principal. **Terminal loss (protocol §0).** In V0-A, **whole-machine or whole-keystore loss is terminal for vault history until human envelopes ship**. `view` prints the full statement verbatim on stderr and carries it in its JSON — read it before you rely on this. The vault protects source history from host-side loss while a principal keystore survives. **Mirroring — the recommended default (vault-mirror-default).** The customer-owned mirror is not a power-user feature: the recommended shape is three copies: your working clone, the platform's replicated vault, and a mirror in storage you own. Until a first mirror write or sync succeeds, doctor and `repos view` carry the standing `vault_unmirrored` finding — named, informational, never blocking, computed entirely client-side (the gateway never learns whether a mirror exists), and cleared by the first successful mirror write or sync (`run402 repos mirror ` to configure, `run402 repos mirror --backfill` to complete the first copy; every later capture is mirrored automatically). `repos create` prints the same one-liner beside the recovery receipt — the two things worth doing in the first minute. The two honesty limits ride every mirror status verbatim: this recovery proves validity, never freshness — a mirror (or the vault itself) can only tell you the newest generation it happens to hold, never that a newer one does not exist elsewhere; and a mirror without the principal keystore (or an equivalent key) recovers nothing — mirroring ciphertext does not create a second key, and the V0 terminal-loss sentence is unchanged. **BYO primary bucket (vault-byo-primary-bucket).** `run402 repos create --byo ` allocates the vault with `storage_profile: "byo"`: payload ciphertext (WAL packs, checkpoint packs/manifests/claim sets, ref state, retention roots, key envelopes) is written by YOUR client straight to a bucket YOU own — for a BYO vault, your source ciphertext never touches our infrastructure — not even encrypted. The gateway stores only the destination ADDRESS (never credential material of any kind); the credential is resolved locally at use time, exactly like `mirror`'s, and no request to run402 ever carries it. run402 still holds and serves the authoritative signed chain — heads, admission records, rotation/prune/maintenance records — for a BYO vault exactly as for a managed one, so admission, rotation, and deploy-lane activation gating are byte-identical regardless of profile; your client additionally dual-writes a non-authoritative copy of that chain into your bucket on every push, in admission order, so the bucket alone is a complete `repos recover` source. Storage is honest about what it can verify: a BYO finalize is client-attested — your client declares the manifest, writes create-only, and submits a per-object hash/size attestation — and the resulting receipts on payload kinds say `storage_verification: "client_attested"` (a managed vault's receipts say `"gateway"`, byte-identical to before); an attestation that disagrees with the declared manifest is refused `VAULT_BYO_ATTESTATION_MISMATCH` (422, gateway-raised) before any receipt is minted. Three more codes are CLIENT-raised, against your own bucket, which run402 has no credential to observe: `VAULT_BYO_BUCKET_PROBE_FAILED` (allocation-time probe, fail-closed — no vault half-exists), `VAULT_BYO_BUCKET_WRITE_REFUSED` (a payload write failed — the primary is never advisory, so the push fails), and `VAULT_BYO_OBJECT_MISSING` (a reader or `fsck` found a chain-referenced object absent from your bucket, adjudicated against run402's own signed chain). **Degraded reads:** on a network-class gateway failure (never a 4xx), `git fetch`/`clone` fall back to the vault's own BYO destination — or, on a managed vault, a configured mirror, same code path — marked `degraded: true` with source provenance and one stderr line; it proves validity, never freshness, local trust pins advance only as far as the copy chain-verifies, and writes still require the gateway in every storage model. **Billing:** BYO payload bytes never enter the org-pooled vault quota (`sourceBytes`); only the gateway-held chain counts against it, and abuse caps stay platform-enforced. **Copy-count honesty, said plainly:** run402 holds no payload copy of a BYO vault — only the small signed chain; your primary bucket is the sole copy of your source until you add a second customer-held location. The `vault_unmirrored` finding applies to BYO vaults exactly as to managed ones, with the BYO remedy: add a second customer-held location: 'run402 repos mirror ' works unchanged against a different destination — for a BYO vault this is your only additional copy, since run402 holds no payload copy of its own. `storage_profile` is chosen at allocation only in v1 — nothing flips it on an existing vault; promote-mirror-to-primary is a tracked follow-up. **Handoff / resume — pass a working tree to another agent (kygit-handoff).** `handoff` and `resume` are a session PASS, not a backup: a working tree, dirty state and all, moved from one agent/machine to another with a single-use bearer key and no shared keystore, no shared wallet, no server-side principal to configure ahead of time. `handoff` captures whatever is actually checked out right now (not the last capture) into the same synthetic-commit shape `git stash push -u` produces, pushes it as a vault retention root, and mints `kgh1_<69 chars>` — HKDF-derived from a fresh `handoff_id` and a random master secret, deriving an `auth_secret` (the gateway sees only its SHA-256 hash) and a `wrap_key` that seals the vault's live epoch key directly to the recipient in a small AEAD envelope, a deliberately different, simpler format than an `r402s/v0` vault object. The key is printed to stdout and NOWHERE else — not in `--json`, not on stderr, not in any log line this CLI writes. `resume` claims it once (a same-principal retry is a safe, explicit dedup — never a re-mint; a different principal's retry after the first claim fails), clones fresh, and reapplies the exact checkpoint with `git stash apply --index`. The claim is a REAL org-membership grant at the sender's role by default — treat the key exactly like a credential, because until claimed or expired, it is one. A resumed agent is also a NEW run402 wallet: on a machine with no active tier, `resume` folds the cold-start chain (wallet → faucet → one x402 prototype payment) before the claim, exactly as `create` does, and `--no-init` opts out — the claim never waits on it. On a multi-writer vault (vault-multi-writer rev 47, see below), claiming also makes the recipient a WRITER — their own key signs future pushes, not the sender's — and `resume` prints `writer: (generation N)` to stderr reporting it; `handoff` prints the same event before minting the key. **Invite / join — bring a second agent into the exact work, dirty tree included (kygit-invite).** The second redeem kind beside handoff/resume, sharing the same crypto/keystore/restore machinery — a Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. `invite` captures the checkpoint exactly like `handoff` and mints `kgi1_<69 chars>` — HKDF-derived from a fresh `invite_id` and a random master secret, domain-separated from a Handoff Key by kind (`kygit/invite/…` info strings, so an invite secret never verifies as a handoff hash or the reverse), sealed in its own `KGI1` envelope frame (distinct from both `r402s/v0`'s object frame and the handoff bridge's `KGH1`). Before minting, `invite` registers the INVITER's own presence in a coordination room — the project's default room, or `--room ` for a named org room — so the row can carry it; after minting it posts ONE room message naming the checkpoint (never the key), never blocking the mint on a presence or message-post failure. `join` redeems it (a same-principal replay dedups safely), clones fresh, reapplies the checkpoint with `git stash apply --index`, pins the invite's OWN room locally (`r402.room`), registers this session's own presence, posts ONE arrival message, and reports the inviter (name, labels, liveness), who else is live, the catch-up cursor, and the last few messages — so the first reply needs no lookup. A joined agent is also a NEW run402 wallet: on a machine with no active tier, `join` folds the SAME cold-start chain `resume` does before the redemption. A `kgh1_…` key handed to `join`, or a `kgi1_…` key handed to `resume`, is refused BY NAME before any network call. From there, `run402 messages wait` is the agent's ear. **Multi-writer vaults (vault-multi-writer rev 47).** A vault's writer set is chain state, not a fixed genesis-creator key — every read/push/rotation re-derives it fresh from the signed chain. Two doors admit a new writer: an ALREADY-active writer can admit any eligible org member (active membership at role `developer`+, a published signing key) with no grant/acceptance ceremony — the admitting head's own signature IS the authorization; `repos handoff`/`repos resume` is the other door — claiming a Handoff Key makes the recipient a writer too, not just a checkout pass (see "Handoff / resume" above). A key that is ever removed can never be re-added — burned, permanently. `repos view` lists the chain-verified roster (`writer_set.writers[]`, each with its admission generation and which door admitted it) and any `pending_writers[]` — eligible candidates not yet admitted; `--human` renders one line per writer plus a `repos access sync` hint when candidates are waiting. `run402 repos access sync` is the explicit, on-demand form of the SAME reconcile every ordinary `repos` operation (`view`, `push`, `capture`, `deploy`, …) already runs automatically whenever this machine's own key is itself an active writer — it distinguishes "I have no authority here" (`eligible: false`) from "nothing is pending" (an empty `admitted: []`), and names any candidate it could not admit in `skipped[]` with why. `run402 doctor`'s vault check reports this machine's own standing as `writer: active|pending|not_admitted|read_only_vault`, each with its own remedy in `gaps[]`. `run402 orgs members add` runs the SAME reconcile across every vault the gateway names in its response — best-effort, warning rather than refusing when the caller isn't a writer anywhere yet. Pushing (or `capture`/`deploy`/any other head-signing operation) from a machine that is not an active writer refuses LOCALLY, before any network call — `VAULT_WRITER_NOT_ADMITTED`, with a `request_writer_sync` next action pointing at the fix: any current writer's next vault operation admits pending writers automatically, or run `repos access sync` yourself once you are one. Removing a writer rides the SAME rotation that would otherwise change the vault's epoch key (a rekey and a removal are one atomic head — a removed writer can never decrypt the epoch it was cut from); removing the vault's LAST writer is refused unless explicitly acknowledged, entering a read-only terminal — the vault keeps serving reads, but nothing can push until a new writer is admitted through a recovery path.