* feat(core): execution-environment detection — local | cloud-sandbox | ephemeral-container detectExecutionEnvironment() + isCredentialInjectingProxy() with injected signals (CLAUDE_CODE_REMOTE, cse_ session-id prefix, proxy-injected token placeholder, anthropic-egress proxy JWT, container markers). binaryOnPath moves here as the canonical PATH probe. autopilot's detectInstallTarget ephemeral branch now routes through the shared detector. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bootstrap): repo-visibility ladder + per-turn Stop push + same-session failure banner The durability lane. One repo-visibility verdict for every consumer (REST first — never GraphQL, which sandbox proxies pin; authed ls-remote + an attributed anonymous probe as the git-protocol fallback), replacing three drifted probes. A 200 counts as public only with advertisement proof; a 401/404 counts as private-signal only with an auth challenge — fail-closed in BOTH directions. Private verdicts cache 1h (private-only, per origin). gbrain hook stop now spawns a debounced detached push per turn (per-root state; cloud-sandbox defaults to every turn, elsewhere 5 min; a failing status bypasses the debounce), closing the /exit and VM-reclaim gaps. Push status is per workspace root, read through one shared reader by the user-prompt banner (additionalContext + systemMessage — visible to the human, not just the model), the SessionStart note, and doctor. Escape hatches for self-hosted git: flag > env > file-plane config key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bootstrap): environment-aware install — honest cron skip, cloud repo-create guard, execution_env surfaces installDurabilityCron probes for crontab before writing anything (containers and cloud sandboxes ship without one — expected, reported as an honest skip naming the event-driven pushes that still cover persistence). The repo phase installs the container-friendly harden half (post-commit hook, no scheduler) outside local machines. createPrivateRepo fails fast in cloud sandboxes with the flow that works (create outside, open the session ON the repo, attach). bootstrap verify gains a never-gating execution_env check; bootstrap status --json carries execution_environment for installing agents to branch on. Also fixes a live-PATH resolution class: binaryOnPath and the crontab execs now pass the current env explicitly (Bun resolves against the startup snapshot otherwise). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bootstrap): lifecycle hygiene — uninstall teardown, doctor job liveness, .mcp.json out of the repo, honest persistence copy uninstall now tears down the durability wiring it installed (launchd/cron job, untracked post-commit hook, credential wiring — the committed helper and AGENTS rules stay). doctor gains bootstrap_durability_job: presence + LIVENESS (launchctl load-state, crontab line, pull-log freshness) — a plist on disk with a dead job no longer reads as healthy. Rendered .gitignore now covers .mcp.json (absolute machine paths must not land in the private repo); verify warns on pre-fix installs that committed it; the never-built state/mcp.json promise is gone from GITHUB.md. Persistence copy tells the truth everywhere: event-driven pushes do the durability work, the 30-min job is a multi-machine pull freshener. Post-commit hook install/removal is worktree-safe (git-path resolution; the git marker is a FILE there) and the cron wrapper's self-disable tests the repo dir, not its git marker. Two follow-up TODOs filed (plugin hook distribution; Channels push lane). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bootstrap): committed hook carrier for cloud + cloud-setup-script emitter + cloud runbook Cloud sessions clone fresh and snapshot hook config at session start — the gitignored settings.local.json never exists there, so hooks never fired in cloud at all. Cloud installs now write the repo-COMMITTED .claude/settings.json with PATH-resolved, fail-open commands (no machine paths; a host without the binary no-ops); local installs keep settings.local.json; the writers enforce that one event never fires from both carriers, and removal cleans both. New: gbrain bootstrap cloud-setup-script prints the paste-ready environment setup script (npm transport — bun fetching is proxy-incompatible in cloud; never the unrelated npm-registry package). Runbook gains a NEVER FABRICATE TOOLING hard rule, a cloud-sandbox section (expected degradations as facts to relay, the attach-first flow), and failure-table rows for the proxy-403 and missing-crontab signatures. Codex lane stated honestly: AGENTS.md Gate 2 now has the pull-side push-health check. Guide documents the new knobs and the cloud contract; llms bundles regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(bootstrap): per-turn push e2e chain, cloud-sandbox sim, per-root status reader migration + registry regen bootstrap-persistence e2e gains the Stop-hook chain (one stop banks the turn to the real bare remote; the debounce holds across stops; debounce-0 lands consecutive turns) and fixes a pre-existing post-#4024 break: repoPhaseComplete required a github-parseable origin, so session-end pushes deferred FOREVER for self-hosted/file-transport origins — non-github repo_urls now bind by exact URL equality (redirect protection preserved). Degraded-modes e2e gains the cloud-sandbox simulation (status reports the environment; repo creation refuses with the attach flow). workspace-push tests read per-root status through the shared reader. Flag registry regenerated (new git argv literals from the git-path/ls-files calls — the accepted argv-bleed class). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(doctor): categorize bootstrap_durability_job (categories drift guard) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bootstrap): adversarial-review hardening — fail-closed privacy, no push-storm, no exfil paths Both Claude and Codex adversarial passes ran; 11 findings fixed (union): - Non-github 401+WWW-Authenticate no longer reads as private (RFC 7235 makes the header mandatory on every 401, so a middlebox 401s identically) — the top exfil path both models flagged; now unverifiable/fail-closed, operator confirms via the escape hatch. github.com still needs x-github-request-id. - Escape hatches downgrade ONLY 'unverifiable' — a PROVEN-public origin still refuses (hatches never authorize a public push). - treeNeedsPush measures against origin/<branch> (the push's own ref), not @{u}: a no-upstream branch no longer reports a committed-but-unpushed tree as push_clean and silently strands it. - [D20] failing-retry uses a fixed 60s floor, not min(debounce,60s) — cloud debounce=0 no longer re-runs the network ladder every turn. - Committed hook carrier: dedupe/suppress only on the EXACT portable-command shape, not a 'gbrain hook' substring (blocks the suppress-local-run-evil supply-chain vector); GBRAIN_HOME refused in the committed carrier. - push-status reason sanitized (charset+length) at every surface (banner, doctor, status blob) so remote git stderr can't inject via the remediation. - Per-root state: ghost-root records (deleted workspaces) filtered so a dead failing record can't re-fire the banner forever; uninstall removes them. - statusReport support blob reads push status through the shared per-root reader; visibility cache strips URL userinfo (no PAT persisted). - anonProbe strips userinfo + redirect:manual + SSRF flags + --end-of-options on ls-remote; cron self-disable uses git rev-parse (worktree-safe both ways); cloud-setup-script fails loud on a broken update; durability liveness won't certify a never-run crontab as live; config get/unset resolve the dotted file-plane keys; .bak/.broken gitignored; typed config fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * v0.45.8.0 feat(bootstrap): first-class cloud-sandbox install + per-turn persistence + fail-closed privacy ladder VERSION + package.json + CHANGELOG + TODOS + llms bundles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: sync KEY_FILES + README for the cloud-DX wave (v0.45.8.0) New KEY_FILES entries for execution-env.ts and repo-visibility.ts; updated the bootstrap repo/hooks, workspace-push, brain-repo-durability, and hook.ts entries to current behavior (ladder verification, committed cloud hook carrier, per-root push status, crontab probe + liveness, per-turn stop push + banner). README's Claude Code line now states per-turn + cloud persistence honestly. llms bundles regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bootstrap): doc-review pass — attach records repo_url (enables cloud persistence), verify reads per-root push status, honest docs Cross-model doc review (Codex) caught that the headline cloud-persistence claim was hollow and two docs over-claimed: - attach now records repo_url from the adopted origin, so the no-daemon push gate (repoPhaseComplete) recognizes the repo phase as done — WITHOUT this, the per-turn/session-end pushes deferred forever after an attach, which is the ONLY install path in a cloud sandbox (repo is refused there). Privacy is still enforced at push time by the ladder. - bootstrap verify's push_probe reads the shared per-root reader [D8], not the legacy single file — a fresh v0.45.8 install no longer reports 'no push recorded' when per-root status exists. - Docs corrected to match code: runbook stamp → 0.45.8.0; README states the per-turn cadence honestly (debounced local, next-turn failure notice); GITHUB.md qualifies auto-push as Claude Code (Codex is pull); KEY_FILES says repo/status use REST (not the full ladder) and drops the stale --push; bootstrap.md hooks-location covers both carriers; README verbs surface says seven. llms bundles regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bootstrap): regenerate vendored template-repo tree + isolate env-mutating tests (verify gate) The verify CI gate caught two things the piecemeal local runs missed: - templates/bootstrap/template-repo/ is a GENERATED tree; the source-template edits (AGENTS.md Gate 2, CLAUDE.md cloud note) plus the version stamp had drifted. Regenerated from source (check:bootstrap-templates green). - test/bootstrap-repo.test.ts + test/durability-cron.test.ts newly mutated process.env in non-serial files; converted to withEnv() (check:test-isolation green) — no .serial rename needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(ambient-recall): pin a keyless gateway so delta/context_pack writes never fire a real embed Root cause of the shard-2 CI red after the 0.45.9.0 re-bump: adding this wave's test files reshuffled the weight-packed shards, moving ambient-recall.test.ts next to a neighbor that leaks CI's dummy OPENAI_API_KEY (sk-test-*) into the gateway singleton (the bunfig preload configures with env:{...process.env}, and a present-but-invalid key turns remember's keyless-degrade embed into a hard 401). The delta/context_pack tests exercise cursor + budget logic, not embedding quality, so this pins a keyless gateway (env:{}) in beforeAll — isAvailable('embedding') is false, writeSingleFact degrades (degraded_dedup) with no HTTP call, and the file is deterministic regardless of shard bin-packing. Verified: passes with OPENAI_API_KEY=sk-test-* set (the CI condition). Master's own comment in legacy-embedding-preload.ts already warns 'adding any test file reshuffles the mines'; this makes the file immune. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
13 KiB
GBrain Bootstrap — your harness as your agent
gbrain bootstrap turns a Claude Code or Codex session into a persistent personal
agent: identity files rendered from your own answers, a local PGLite brain,
per-turn context, session-triggered schedules, and a private GitHub repo as the
agent's durable, portable body. This guide is the full contract — what gets
installed, what runs when, what it can and cannot do, and how to undo all of it.
Normative design docs: AGENT_BOOTSTRAP_DESIGN.md
(scope) and AGENT_BOOTSTRAP_PLAN.md
(implementation). The paste block lives in the README; the runbook your agent
follows is BOOTSTRAP_FOR_AGENTS.md at the repo root, fetched at the
latest-stable ref.
What gets installed, exactly
| Piece | Where | Runs when |
|---|---|---|
| Identity files (SOUL/USER/MEMORY/AGENTS/CLAUDE/HEARTBEAT/ACCESS_POLICY/GITHUB) | your workspace folder | loaded at session start |
agent.json manifest + brain/, memory/, skills/, state/ |
workspace | — |
| Local brain (PGLite) | ~/.gbrain/ (never in the repo) |
while a session's MCP serve is open |
MCP registration (gbrain serve) |
Claude Code: project scope by default; Codex: user-global (no scope flag) | spawned by your harness per session |
| Hooks (Claude Code, ON by default) | local installs: .claude/settings.local.json (gitignored); cloud sandboxes: the COMMITTED .claude/settings.json (PATH-resolved, fail-open commands) |
each prompt; fail-open; --no-hooks opts out at install, GBRAIN_HOOKS=0 disables at runtime |
| Per-turn persistence | Stop hook → debounced, detached scan-gated push (per workspace; 5 min default, every turn in cloud sandboxes) | after each assistant turn; GBRAIN_STOP_PUSH=0 disables; GBRAIN_STOP_PUSH_DEBOUNCE_MIN / config hooks.stop_push_debounce_min tune it |
| Session persistence | SessionEnd hook → scan-gated commit+push | at session end (note: the harness never fires SessionEnd on /exit — the per-turn push is what covers that) |
| Push-failure visibility | next turn's context + a user-visible notice; re-announces every 30 min while failing | whenever a background push fails |
| Optional background job (consent-gated) | git post-commit auto-push + launchd/cron 30-min pull (pull job skipped honestly on hosts without a scheduler) | while logged in |
| Private GitHub repo | your account, created by bootstrap repo (or an empty repo you made yourself, adopted) |
privacy verified via API |
| Machine receipt | ~/.gbrain/bootstrap/receipt.json |
uninstall is keyed to it |
What does NOT run: anything while the harness is closed. Session-triggered schedules fire at turn/session boundaries only. True 24/7 operation is what a hosted brain provides — this is the honest desktop contract.
Cloud sandboxes (claude.ai/code and similar)
Cloud sessions run in a reclaimed-after-inactivity VM behind a
credential-injecting egress proxy. gbrain bootstrap status --json reports
execution_environment: "cloud-sandbox" there, and the install adapts:
- Hooks live in the committed
.claude/settings.jsonwith PATH-resolved, fail-open commands (no machine paths). The gitignored local settings file never survives into the next session's fresh clone, and hook config is snapshotted at session start — so hooks written mid-session go live on the NEXT session. Commit and push the file. - The per-turn push runs every turn (debounce 0) — a reclaimed VM's tail loss is permanent, so each turn banks to the private repo.
- Repo-privacy verification falls back to pure git protocol when the proxy blocks the GitHub API (GraphQL is always pinned there; REST reaches only session-attached repos). Confirmed-public origins still always refuse.
- Repo creation is refused in cloud with the flow that works: create the
private repo from a normal machine or github.com, open the cloud session ON
that repo, run
gbrain bootstrap attach. - The gbrain binary installs via the environment setup script — print it
with
gbrain bootstrap cloud-setup-scriptand paste it into the environment config (npm-based; bun's package fetching is proxy-incompatible there). - No scheduler exists — the consent-gated pull job is skipped with an honest message; event-driven pushes cover persistence.
Escape hatch for self-hosted git you trust (every use warns loudly):
the CLI flag on sources push, GBRAIN_ALLOW_UNVERIFIED_REMOTE=1, or
gbrain config set push.allow_unverified_remote true (file-plane — the only
form that reaches detached hook children inside a sandbox).
Bring your own repo (create-repo-first)
By default bootstrap creates the private GitHub repo for you. If you prefer to own
that step — pick the name/org-under-your-account, or just work the familiar way —
create a new empty private repo under your own GitHub account (no
README/.gitignore/license), clone it, open the clone in your harness, and run the
bootstrap block. gbrain bootstrap repo detects the empty repo you created and
adopts it: it verifies the repo is private, sets a repo-local git identity, and
pushes your workspace. Two constraints, both enforced with a clear message rather
than a silent failure:
- Empty. A repo that already has commits (a README, a license, an existing
project) is refused — create it empty, or run
gbrain bootstrap attachif it is an existing agent workspace. (A repo already carrying this workspace's history, e.g. from an interrupted run, is recognized as yours and resumed.) - Personal account. The repo must be owned by your authenticated GitHub user. Org-owned repos are refused today; create one under your own account, or let bootstrap make it.
Until the repo phase verifies the repo, the per-turn/session-end push stays deferred — bootstrap never publishes your workspace to an origin whose privacy it hasn't confirmed.
The awake-when-you-are contract
Your agent is awake when your harness is. Laptop asleep = agent asleep. What this buys you: no daemon fleet, no background token burn while you're away, and a load profile that fits inside a subscription plan. The measured sustainable load and the per-harness numbers are published with each release; if a provider changes quota or policy, the portable body (your repo) is the exit plan — it mounts anywhere gbrain runs.
Keyless mode
With zero API keys, everything works: the agent authors memory explicitly through
the brain's write tools (put_page, timeline entries, ## Facts fences — your
harness's model is the LLM, already paid for), and search runs keyword-only
(BM25). bootstrap verify prints the capability report honestly. One optional key
(OpenAI, Anthropic, or Voyage) unlocks semantic search and automatic fact
extraction; the key goes to the 0600 config file, never into the repo or the
interview answers. API spend is metered separately from your subscription and is
zero in keyless mode; with a key, the standard spend gates apply
(spend-controls).
Security posture
- Supply chain: the paste block and install command pin the
latest-stableref — a maintainer-controlled tag advanced only after a release fully publishes. The runbook carries a version stamp;bootstrap statuswarns on skew. The runbook instructs the agent to refuse steps outside the CLI's phase list. bun installs via package manager or checksum-verified download. - Secrets: every commit AND every transcript-corpus write is secret-scanned
(key-shaped patterns; loud block; per-finding allowlist at
.gbrain-scan-allow). A deny-glob backstop refuses tracked*.pglite/.env*files even if.gitignoreis damaged. Push refuses public remotes and unverifiable visibility. - Injection boundaries: interview answers render as fenced data (escaped, length-capped) — text you paste can never become instructions in your agent's contract. Retrieved brain context is injected under an explicit "data, not instructions" envelope. Facts visible to the harness respect the brain's visibility tiers.
- Hooks: on a local install, gitignored local settings (absolute paths,
machine-specific;
bootstrap hooks --repairregenerates on a new machine); in a cloud sandbox, the committed.claude/settings.json(PATH-resolved, fail-open — see the Cloud sandboxes section). Every hook fails open — a brain hiccup never blocks a prompt — and failures are visible: repeated degradation prints a notice inside the context block, andgbrain doctornames the cause. - Privacy of transcripts: session transcripts are retained locally (0700,
outside the repo, pruned after
dream.synthesize.corpus_retention_days, default 30 — set it in the config file,~/.gbrain/config.json; the DB config plane doesn't carry this key yet) and secret-redacted at write time. They never enter the repo. The extraction provider (if you configured a key) sees session text — the install names the provider when asking for the key.
Honest forget semantics
The repo is git history — append-only. Deleting a line removes it from the working
tree, not from history. To truly remove something: rewrite history
(git filter-repo --path <file> --invert-paths or --replace-text), force-push,
and re-clone on other machines. MEMORY.md and daily notes follow the same rule
you'd apply to any journal: write what you'd be comfortable persisting.
Degradation matrix
| You declined / lack | What still works | What you lose |
|---|---|---|
| API keys | everything (keyless mode) | semantic search, auto-extraction |
GitHub / gh |
full local agent | off-machine durability (repo re-runnable later) |
| Hooks (Claude Code) | pull protocol via AGENTS.md gates | automatic per-turn context + session-end persistence |
| Codex (no hook system, no MCP scope flag) | pull protocol + MCP tools | per-turn push (stated plainly; not oversold) + the ability to confine MCP reach to one folder (codex mcp add is always user-global) |
| Second simultaneous session | first session unaffected | second session's brain tools fail politely (one live serve per brain — v1 contract) |
Multi-device
Clone your agent repo on machine two and run gbrain bootstrap attach — it
validates the manifest, wires this machine (source registration, hooks repair,
MCP), and verifies. The brain database is derived state, rebuilt from brain/ +
re-ingestion; hot facts extracted only on machine one arrive via the repo's pages
and fences. Simultaneous editing from two machines is ordinary git conflict
territory — sources push pulls divergence-safely (commit first, rebase pull,
loud on conflicts).
Uninstall
gbrain bootstrap uninstall removes exactly what this machine's install receipt
records: hook wiring, MCP registrations (surgically — foreign servers and hooks
survive), and bootstrap-created state. Your repo is never touched — the body
remains yours. The brain database is KEPT by default; --delete-brain is offered
only when bootstrap created the brain, offers a facts export first, and enumerates
what it is about to remove. It refuses to run while a session's serve is live.
If something seems broken
One command: gbrain doctor. It covers hook health, push staleness, serve/lock
collisions, schema state, and prints fixes. gbrain bootstrap status --json emits
a support blob (versions, harness, last verify/push, hook failure rate) your agent
can relay verbatim when you report a problem.
Real-agent e2e
Most bootstrap tests drive the dispatcher with PATH-shimmed claude/codex
recorders — fast, hermetic, no API cost. Two additional "door" tests drive the
ACTUAL binaries end to end so we catch real-world drift (a codex mcp add flag
that changed shape, a harness that stopped calling our MCP server):
test/e2e/bootstrap-real-claude.serial.test.ts— realclaude -pover MCP.test/e2e/bootstrap-real-codex.serial.test.ts— realcodex exec. It runs the keyless-init→ interview → render →gbrain bootstrap hooks --harness codexpath (executing the realcodex mcp addinto a hermetic~/.codex/config.toml), asserts the renderedAGENTS.mdcarries the Gate-3 brain-first pull protocol (Codex has no hook system, so the pull protocol is its per-turn seam), then spends one livecodex execturn to prove real codex → gbrain MCP → brain → a seeded, brain-only fact (falling back to a shellgbrain queryif headless stdio-MCP is unavailable).
These pay real API cost and take 30s–2min per turn, so they are NOT in the PR
shard. Everything is hermetic (temp HOME / CODEX_HOME / CLAUDE_CONFIG_DIR /
GBRAIN_HOME per test — the operator's real ~/.claude, ~/.gbrain, ~/.codex
are never touched; auth is copied read-only). Each file self-SKIPS via
describe.skipIf when its binary or auth is absent, so on a machine without the
tool it is a clean no-op that never fails. CI wires them into the real-agent-e2e
job in .github/workflows/heavy-tests.yml (nightly + the real-agent-e2e /
heavy-tests label); on a stock runner they self-skip. To actually exercise the
binaries you need a runner with authed claude/codex and the provider creds
(GSTACK_ANTHROPIC_API_KEY/ANTHROPIC_API_KEY, VOYAGE_API_KEY) exported.
Run locally (where both are installed + authed):
bun test test/e2e/bootstrap-real-codex.serial.test.ts