Files
gstack/docs/gbrain-sync.md
Garry Tanandcapy-ai[bot] dd2416f4eb v1.92.46.0 fix(brain-sync): never commit a synced JSON file that does not parse (#3763) (#3796)
* test: synced JSON that does not parse is never committed and is reported (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): hold a synced JSON file that does not parse; --profile and doctor report it (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): read JSON index blobs with one cat-file batch, no python subprocess (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): block instead of committing a one-sided merge when a held file is mid-merge (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): merge-block status names the real recovery and waiting files (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(brain-sync): JSON holds in the --status, held-files and two-machine sections (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(brain-sync): #3704 fixtures write valid JSON to developer-profile.json (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(brain-sync): keep the reviewed JSON paragraph wording after the main merge (#3763)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-01 23:20:55 +00:00

252 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cross-machine memory with GBrain sync
gstack writes a lot of useful state to `~/.gstack/` — learnings, retros, CEO
plans, design docs, developer profile. By default, all of that dies when you
switch laptops. **GBrain sync** pushes a curated subset to a private git
repo so your memory follows you across machines and becomes indexable by
GBrain.
## What you get
- Work on machine A, pick up seamlessly on machine B.
- Your learnings, plans, and designs are visible in GBrain (if you use it).
- A clean off-ramp (`gstack-brain-uninstall`) that never touches your data.
- No daemon, no system service, no background process.
## What does NOT leave your machine
By design, these stay local even when sync is on:
- Credentials: `.auth.json`, `auth-token.json`, `sidebar-sessions/`,
`security/device-salt`
- Machine-specific state: Chromium profiles, ONNX model weights,
caches, eval-cache, CDP-profile, one-time prompt markers
(`.welcome-seen`, `.telemetry-prompted`, `.vendoring-warned-*`, etc.)
- Question-preferences: per-machine UX preferences
(`question-preferences.json`, `question-log.jsonl`, `question-events.jsonl`).
The exact allowlist lives in `~/.gstack/.brain-allowlist`. The CLI manages
it; you can append your own entries below the marker line.
## First-run setup (30–90 seconds)
```bash
gstack-artifacts-init
```
The command:
1. Turns `~/.gstack/` into a git repo.
2. Asks for a remote URL (default: `gh repo create --private
gstack-artifacts-$USER`). Any git remote works — GitHub, GitLab, Gitea,
self-hosted.
3. Pushes an initial commit with just the config.
4. Writes `~/.gstack-artifacts-remote.txt` (URL-only, no secrets —
safe to copy to another machine).
5. Prints the `gbrain sources add` hookup command for the brain host
(never auto-executed — run it yourself, or on your own machine
`bin/gstack-gbrain-source-wireup` does the same wiring) so
`gbrain search` can index your synced learnings, plans, and designs.
The old `gstack-brain-reader add --ingest-url ...` HTTP path was
removed in v1.15.1.0 — it depended on a `/ingest-repo` endpoint gbrain
never shipped.
After init, the **next skill you run** will ask you ONE question about
privacy mode:
- **Everything allowlisted (recommended)**: learnings, reviews, plans,
designs, retros, timelines, and developer profile all sync.
- **Only artifacts**: plans, designs, retros, learnings — skip
behavioral data (timelines, developer profile).
- **Decline**: keep everything local. You can turn sync on later with
`gstack-config set artifacts_sync_mode full`.
Your answer is persisted. You won't be asked again.
## Cross-machine workflow
On machine A: run `gstack-artifacts-init` once. That's it — every skill
invocation now drains the sync queue at its start and end boundaries
(~200–800 ms network pause per skill).
On machine B:
1. Copy `~/.gstack-artifacts-remote.txt` from machine A to machine B
(password manager, dotfile repo, USB stick — your call; the legacy
`~/.gstack-brain-remote.txt` name is still recognized).
2. Run any gstack skill. The preamble sees the URL file and prints:
```
BRAIN_SYNC: brain repo detected: <url>
BRAIN_SYNC: run 'gstack-brain-restore' to pull your cross-machine memory
```
3. Run `gstack-brain-restore`. That clones the repo, rehydrates your
learnings/plans/retros, and re-registers the git merge drivers.
4. Next skill: your yesterday-on-machine-A learning surfaces. That's the
magical moment.
## Status, health, and queue depth
```bash
gstack-brain-sync --status
```
Shows: the status (`ok`, `idle`, `held`, `collision`, `blocked`,
`push_failed`, `error`), files held back by the secret scan or because
they are not valid JSON, with their fixes, files held because their path differs only by letter case from
another path (`case_held`, `case_names`; see
[troubleshooting](troubleshooting.md#artifact-case-collision)), the
`drainable` count, the last sync and push times, pending queue depth, and
the current privacy mode.
Every skill run prints an `ARTIFACTS_SYNC:` line near the top of the
preamble output. When the sync is stuck it adds an
`ARTIFACTS_SYNC: attention:` line; the table of those lines and their fixes
is in [gbrain-sync-errors.md](gbrain-sync-errors.md#artifacts_sync-attention--at-skill-start).
## Privacy modes in detail
| Mode | What syncs |
|------|------------|
| `off` | Nothing (default). |
| `artifacts-only` | Plans, designs, retros, learnings, reviews. Skips timelines + developer-profile. |
| `full` | Everything in the allowlist, including behavioral state. |
Change anytime with:
```bash
gstack-config set artifacts_sync_mode full
gstack-config set artifacts_sync_mode off
```
## Removed and moved files
By default the sync never publishes a removal: delete a synced design doc,
or move a project directory (a project slug migration does), and the old
path stays on the remote, where the brain keeps indexing it next to the new
copy. To publish removals too:
```bash
gstack-config set artifacts_sync_removals on
```
Each sync then commits the removal of synced files that are gone from disk.
Transcript pages are left alone, and a decision log and its active snapshot
go only together. A sync that would remove more than 20 files at once
publishes none of them: that looks like a loss, not a cleanup. Review and
publish those yourself, whatever the setting:
```bash
gstack-brain-sync --publish-removals # list them
gstack-brain-sync --publish-removals --yes # commit and push their removal
```
## Secret protection
Every commit is scanned for credential-shaped content before it leaves
your machine. Blocked patterns include:
- AWS access keys (`AKIA…`)
- GitHub tokens (`ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`, `github_pat_`)
- OpenAI keys (`sk-…`)
- PEM blocks (`-----BEGIN …-----`)
- JWTs (`eyJ…`)
- Bearer tokens in JSON (`"authorization": "…"`, `"api_key": "…"`, etc.)
If a scan hits, only the flagged file is held back (with any file coupled
to it); everything else syncs. The held file stays queued and is re-scanned
at every sync, and skill start prints:
```
ARTIFACTS_SYNC: attention: status=held held=1. Sync is holding back 1 file(s) that the secret scan flagged or that are not valid JSON; everything else still syncs. See which files and how to fix them: <bin> --status
```
To remediate:
1. Run `gstack-brain-sync --status` (the line prints its absolute path). Each
entry under `held` names the file, the scanner rule and both fixes.
2. If the match is a false positive on content you never want synced, run
`gstack-brain-sync --skip-file <path>` to permanently exclude that path;
`gstack-brain-sync --unskip-file <path>` undoes it.
3. Otherwise, edit the file to remove the secret; the next skill run syncs it.
There's a defense-in-depth hook at `~/.gstack/.git/hooks/pre-commit` that
runs the same scan if you manually `git commit` against the repo.
Separately (v1.63.0.0+), every push writes a tamper-evident receipt to the
egress ledger (`~/.gstack/security/egress.jsonl`) *before* anything is
sent, fail-closed: if the receipt can't be written, the push is refused
and the queue is preserved. Inspect the ledger with `gstack-egress list`
and verify its hash chain with `gstack-egress verify`.
## Two-machine conflicts
If you write on machine A and machine B the same day, both will push
append commits. Git's default would conflict at the file tail, but the
`.jsonl` and markdown files are registered with custom merge drivers:
- JSONL files use a sort-and-dedup driver that orders appends by ISO
timestamp (falls back to SHA-256 hash of each line for determinism).
- Markdown artifacts (retros, plans, designs) use a union merge driver
that concatenates both sides.
You shouldn't see conflict prompts. If you do (a real semantic conflict,
like two machines editing the same plan), git will stop and prompt.
When a push is rejected, brain-sync fetches and merges the other machine's
commits itself. Local edits to files that merge would change are set aside
and put back afterward, and a failed merge is aborted, so `~/.gstack` never
holds conflict markers. If a local edit conflicts with the other machine's
change, the other machine's version stays in the working tree and your edit
is kept in a git stash; `gstack-brain-sync --status` names the stash and the
`git -C ~/.gstack --literal-pathspecs restore --source=<stash> -- <path>`
command that brings it back.
JSON files such as `developer-profile.json` have no merge driver, so a
merge can leave conflict markers in them. The sync parses every staged
`.json` file and holds back one that does not parse (status `held`, rule
`invalid-json`) until you repair it, and `gstack-doctor` warns when a synced profile or
decision snapshot holds conflict markers.
## Cross-machine pull cadence
The preamble runs `git fetch` + `git merge --ff-only` once per 24 hours
(cached via `~/.gstack/.brain-last-pull`). You don't need to think about
this — it happens automatically at the first skill invocation each day.
Historical note (#2516): that daily pull refreshed only `~/.gstack` itself —
NOT the detached worktree at `~/.gstack-brain-worktree` that gbrain actually
indexes, so the brain silently served stale pages until the next
setup-gbrain/sync-gbrain run. Since this fix, the daily sync also advances
the brain worktree (`gstack-gbrain-source-wireup --advance-only`, throttled
via `~/.gstack/.brain-worktree-last-advance`); a failed advance warns instead
of failing silently, and never force-resets a dirty worktree.
## Uninstall
```bash
gstack-brain-uninstall
```
This:
- Removes `~/.gstack/.git/` and all `.brain-*` config files.
- Clears `artifacts_sync_mode` in `gstack-config`.
- Does NOT touch your learnings, plans, retros, or developer profile.
Add `--delete-remote` to also delete the private GitHub repo (GitHub only,
uses `gh repo delete`).
Re-init anytime with `gstack-artifacts-init`.
## Troubleshooting
See [gbrain-sync-errors.md](gbrain-sync-errors.md) for an index of every
error message gstack-brain may print, with problem / cause / fix for each.
## Under the hood
The architectural decisions behind this feature: allowlist over denylist
(unknown files stay local by default), preamble-boundary sync over a daemon
(no background process to babysit), a JSONL merge driver so concurrent
machines union their queues instead of conflicting, and a privacy stop-gate
that asks once before anything syncs.