diff --git a/setup-gbrain/memory.md b/setup-gbrain/memory.md
index 16935b523..23a2c3485 100644
--- a/setup-gbrain/memory.md
+++ b/setup-gbrain/memory.md
@@ -25,13 +25,15 @@ answer does.
Curated memory (learnings, timeline, plans, designs, retros, eureka, builder
profile) syncs on every `/sync-gbrain`. Session transcripts sync only after
you choose to share them. The choice is the `transcript_ingest_mode` config
-key:
+key, optionally narrowed by the `transcript_repos` allowlist:
| Value | What `/sync-gbrain` ingests |
|---|---|
| `recent` | Sessions from the last 90 days |
| `all` | All history (`--full` walks every session file) |
+| `new@2026-10-03T17:00:00Z` | Only sessions whose first record is at or after that UTC time. A session that started earlier stays out even when it is appended to later. A session with no start timestamp stays out. |
| `off` | No transcripts; other memory still syncs |
+| `recent+repos`, `all+repos`, `new@…+repos` | The same window, only for the repos in `transcript_repos`. gstack adds and removes `+repos` itself. |
Both sources are covered: Claude Code sessions (`~/.claude/projects/`) and
Codex sessions (`~/.codex/sessions/`), from every project on this machine
@@ -43,38 +45,79 @@ Set or change it:
```bash
gstack-config set transcript_ingest_mode recent # or all, or off
+gstack-config set transcript_ingest_mode "new@$(date -u +%Y-%m-%dT%H:%M:%SZ)" # only sessions from now on
+gstack-config set transcript_repos github.com/acme/app,github.com/acme/api # only these repos
+gstack-config unset transcript_repos # every repo again
gstack-config has transcript_ingest_mode && gstack-config get transcript_ingest_mode
+bun run lib/transcript-consent.ts --describe # the effective consent in words
```
-`gstack-config set` rejects any other value and keeps the stored one.
+`gstack-config set` rejects any other value and keeps the stored one. The
+cutoff takes only the `Z` (UTC) form, to the second. `transcript_repos`
+accepts any git remote spelling (`https://…`, `git@…:…`, with or without
+`.git`) and stores its canonical form; an empty list is rejected. Setting
+or clearing the allowlist updates the `+repos` marker in the same write.
+`gstack-config set transcript_ingest_mode ` keeps the marker while an
+allowlist exists, and `off` never carries it.
+
+**Several config roots.** When gstack reads more than one state root (for
+example `GSTACK_HOME` plus `~/.gstack`), the most restrictive answer wins:
+`off` in any root means off, the latest cutoff and any 90-day window both
+apply, and only repos in every root's allowlist count. The resolved root
+must hold the consent itself.
**Not set.** Until you choose, transcripts are skipped. Each sync prints one
line saying so, even with `--quiet`, and the next interactive `/sync-gbrain`
asks once. Older values from previous gate versions (`A`-`E`, `incremental`)
-also count as not chosen and get the question again. A stored `off` prints
-`transcripts off (your choice)` without `--quiet` and is never asked again.
+and values this version does not recognize also count as not chosen and get
+the question again. A stored `off` prints `transcripts off (your choice)`
+without `--quiet` and is never asked again.
**One-run override.** A `--sources` list (or `GSTACK_MEMORY_INGEST_SOURCES`)
that names `transcript` ingests transcripts for that run whatever the mode;
-the sync still prints the mode notice. `--sources all`, an empty list, or a
-list with no valid types does not override.
+the sync still prints the mode notice. An override never widens a scoped
+consent: a `new@` cutoff and the `transcript_repos` allowlist still apply.
+`--sources all`, an empty list, or a list with no valid types does not
+override.
```bash
bun run bin/gstack-gbrain-sync.ts --incremental --sources transcript
```
+**What each sync tells you.** Without `--quiet` the sync prints the consent
+in words (window or cutoff, up to three repos, the config roots it came
+from). The ingest summary counts sessions left out by reason: before the
+cutoff, no start time, not in the allowlist, and repo policy deny. A sync
+under `new@` that ingests nothing says so and says new sessions will appear
+on the next sync.
+
**Interrupted imports.** If a sync was interrupted mid-import and your
-transcript choice changed before the next run, the next sync restages memory
-from scratch once instead of resuming, and prints a line saying so.
+transcript choice changed before the next run (the mode, the cutoff or the
+allowlist), the next sync restages memory from scratch once instead of
+resuming, and prints a line saying so.
-**Staged pages wait.** Transcript pages already staged under
-`~/.gstack/transcripts/` (remote-http mode) stay on disk and are not pushed
-by `gstack-brain-sync` until the mode is `recent` or `all`. Other artifacts
-keep pushing.
+**Staged pages.** Transcript pages already staged under
+`~/.gstack/transcripts/` (remote-http mode) wait while the mode is not
+`recent`, `all` or `new@…`: they stay on disk and are not pushed by
+`gstack-brain-sync`. Other artifacts keep pushing. When you narrow a scoped
+consent (a later cutoff, a smaller allowlist), the next sync removes the
+staged pages that are now out of scope and prints `removed N staged
+transcript pages outside the new scope`; widening again re-stages them from
+the original sessions. Before every push, `gstack-brain-sync` also checks
+its own unpushed commits and rewrites them without any page outside your
+consent (`removed N excluded transcript pages from unpublished sync
+commits`). The removal covers staged and unpushed pages only.
-**Already-ingested transcripts stay.** Switching to `off` stops new ingests;
-it does not remove pages already in the brain. Delete them there (see
-"Delete a page" below).
+**Already-ingested transcripts stay.** Switching to `off` or narrowing the
+scope stops new ingests; it does not remove pages already in the brain or
+already pushed. Delete them there (see "Delete a page" below).
+
+**Downgrading gstack.** A gstack that predates `new@` and `transcript_repos`
+reads those values as unrecognized and ingests no transcripts
+automatically. It does honour an explicit transcript override without any
+scope, so before downgrading: remove `--sources transcript` and
+`GSTACK_MEMORY_INGEST_SOURCES=transcript` from scripts, and let pending sync
+publish, or run `gstack-config set transcript_ingest_mode off`.
## What stays local
diff --git a/setup-gbrain/sections/transcript-gate.md.tmpl b/setup-gbrain/sections/transcript-gate.md.tmpl
index 3751a1e23..422a5baeb 100644
--- a/setup-gbrain/sections/transcript-gate.md.tmpl
+++ b/setup-gbrain/sections/transcript-gate.md.tmpl
@@ -3,24 +3,27 @@ config (Step 8), ask whether this machine's coding-agent session
transcripts may go into gbrain. Curated `~/.gstack/` artifacts (learnings,
timeline, plans, designs, retros) sync whatever the answer; this question
covers transcripts only. Transcripts are ingested only after the user picks
-`recent` or `all`.
+`recent`, `all` or `new` (only sessions that start from now on).
Check whether the user already chose. The gate keys on `has`, because `get`
prints the `off` default for a key that was never set:
```bash
_TIM=$(~/.claude/skills/gstack/bin/gstack-config get transcript_ingest_mode 2>/dev/null || true)
if ~/.claude/skills/gstack/bin/gstack-config has transcript_ingest_mode; then
+ _T='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]t[0-9][0-9]:[0-9][0-9]:[0-9][0-9]z'
case "$(printf '%s' "$_TIM" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')" in
- recent|all|off) echo "TRANSCRIPT_MODE: $_TIM" ;;
- *) echo "TRANSCRIPT_MODE: ask (stored value '$_TIM' is from an older version)" ;;
+ recent|all|off|recent+repos|all+repos|new@$_T|new@$_T+repos)
+ echo "TRANSCRIPT_MODE: $_TIM (repos: $(~/.claude/skills/gstack/bin/gstack-config get transcript_repos 2>/dev/null || true))" ;;
+ *) echo "TRANSCRIPT_MODE: ask (stored value '$_TIM' is not recognized by this version)" ;;
esac
else
echo "TRANSCRIPT_MODE: ask (not set)"
fi
```
-- `TRANSCRIPT_MODE: recent`, `all` or `off`: the user already chose. Do not
- ask again; continue to Step 8.
+- `TRANSCRIPT_MODE:` with a value (`recent`, `all`, `off`, `new@