bro v3 — enforced session journal
bro captures the middle layer of state that formal artifacts don't: operator state, live decisions, shortcut vocabulary, open tails, working discipline. In v3 this layer is enforced by hooks, not by model discipline: the harness injects the read-order into every session, blocks the end of a turn while the journal is stale, and denies writes to retired v2 paths.
Storage layout (v3)
~/bro/ ← root; configurable via ~/.claude/bro-config.json "root"
INDEX.md ← registry: one line per workspace (files, last entry)
_principles.md ← GLOBAL principles — the only copy
CONFLICTS.md ← unresolved principle-merge conflicts (delete when resolved)
_rule-candidates.md ← global queue of RULE: markers awaiting operator confirmation
<workspace>/ ← one folder per project; name = lowercased basename of repo dir
_workspace.md ← what this is + people + pointers (thin; registers hold the rest)
decisions.md ← decision register (harvested from DECIDED: markers, ADR-style)
open.md ← open-items register (harvested from TAIL: markers; close by hand)
vocab.md ← vocabulary register (harvested from TERM: markers)
2026-09-06.md ← daily journal — ALL chats of the day write here, in sections
_legacy-v2/ ← preserved v2 thread summaries (read-only history)
_archive/ ← migrated v1/v2 storages, untouched
Five record types, one rule each: the chronicle (journal body) is free-form and append-only; decisions, open items, terms and rule candidates are typed records born as journal markers and harvested into registers by script; views (INDEX.md) are generated, never hand-edited.
The unit is workspace + day, not chat. Parallel chats write sections into the same daily file — nothing to synchronize. A workspace is "enabled" when ~/bro/<workspace>/ exists (created by /bro setup or migration); in projects without it, all hooks stay silent. Resolution walks UP from cwd: the workspaces map in ~/.claude/bro-config.json (cwd, then its ancestors) wins; otherwise the first ancestor directory whose lowercased-basename slug exists in the store — so a session started in project/active/subtask/ still lands in project's workspace.
Command routing
/bro (no argument) → Capture (below).
/bro status → Status.
/bro setup → Setup.
/bro off / /bro on → Per-chat switch.
/bro harvest → Harvest (manual full run over all workspaces).
/bro migrate → Migrate.
/bro update → Update.
Per-chat switch (off / on)
Enablement is per-project (a workspace in the store), but any single chat can opt out:
/bro off — use Bash: mkdir -p ~/.claude/bro/off && touch ~/.claude/bro/off/${CLAUDE_SESSION_ID}. Report: bro is off for this chat only (session-start injection and the stop turnstile skip it; the write guard stays on — it protects data, not discipline). Other chats are unaffected; the switch survives reopening this same chat.
/bro on — use Bash: rm -f ~/.claude/bro/off/${CLAUDE_SESSION_ID}. Report: bro is back on for this chat from the next session start.
Capture (default)
- Resolve workspace as the hooks do:
.workspaces map (cwd, then ancestors), else walk up from cwd taking the first ancestor whose slug exists in ~/bro/. If none, offer /bro setup and stop.
- Re-read
~/bro/_principles.md NOW, always — even if it was read earlier this session: another chat may have changed it, and /bro is the manual "pull the latest rules" button. Then read, if not already in context this session: ~/bro/<ws>/_workspace.md, the registers (decisions.md, open.md, vocab.md), today's and the previous daily.
- Review the conversation since the last journal entry. Classify each piece of material with the temporal test: would this still be true and relevant in a fresh chat tomorrow?
- No → journal free text (states, events, the story of the day).
- Yes, project-scoped → a typed MARKER in the journal:
DECIDED: / TAIL: / TERM: — harvest moves it to the register.
- Yes, universal → a
RULE: marker (lands in the candidates queue); confirm with the operator before it enters _principles.md (max one batched ask per capture).
- Append to
~/bro/<ws>/YYYY-MM-DD.md (create from the format below if missing). Use Edit/Write; never rewrite earlier sections of the day.
- Run
~/.claude/bro/bin/bro-harvest.sh --workspace <ws> --quiet with Bash (also regenerates INDEX.md). Check open.md — close items the session resolved.
- Report in one line what was written and where.
Journal format (daily file)
# bro — 2026-09-06 / cowork
## 14:30 · <work thread> — <topic with a distinguishing detail>
Free text. Operator's verbatim quotes in the language spoken. What happened,
what mattered, current state.
DECIDED d-0906-1: chose X | over: Y | because: Z | revisit-if: W
REJECTED: <what was turned down, in the operator's words; nothing chosen instead>
RULE: <new operator instruction, verbatim>
TAIL: <open item carried forward>
TERM: <term> — <meaning, in the operator's words>
Rules:
- Section time HH:MM comes from the date command — never from your sense of time (the hook hands you NOW at session start; after any pause it is the only truth).
- Header line 1 exactly
# bro — YYYY-MM-DD / <workspace> (the lint checks it).
- One section per sitting; append, don't rewrite. The section header carries an ANCHOR: time · work thread — topic with a detail that distinguishes it («выключатель /bro off», not «доработки»). A cold reader a year later must place the section without any context.
- Markers at line start, single line each:
DECIDED: / REJECTED: / RULE: / TAIL: / TERM:. Russian aliases equally valid: РЕШЕНИЕ: / ОТКАЗ: / ПРАВИЛО: / ХВОСТ: / ТЕРМИН:. An explicit id after the keyword (DECIDED d-0906-1:) is optional — harvest assigns a stable hash id when absent.
- Markers are SEEDS, not final records: the harvest script moves them into the registers (decisions.md / open.md / vocab.md / _rule-candidates.md). Never hand-edit registers to add records — write a marker in the journal instead; hand-edit registers only to change status (close an item, supersede a decision, accept a rule).
- Operator state is journal material. Mood, energy, life context the operator shares — record it plainly, in their own words. It is often the most valuable line for whoever resumes tomorrow.
- Bilingual: write in the language the exchange happened in; verbatim quotes never translated; code/paths/URLs in backticks as-is.
- Never trim or summarize existing entries. The journal is append-only history.
- Outcomes are complete (§36): a finished discussion leaves its typed outcome — chose → DECIDED, turned down → REJECTED, deferred → TAIL, rule born → RULE, word born → TERM.
- Proof lives inside the record (§37): the number, table, exact phrase or link a record rests on goes into the record verbatim — a paraphrase loses the evidence.
- A promise becomes a TAIL the moment it is spoken (§38) — deadlines, callbacks, "I'll check" — not at session end from memory.
- Side work is logged like main work (§39) — especially anything touching security or irreversibly changing data.
Registers and harvest
bro-harvest.sh (run automatically by the session-start hook for the current workspace; /bro harvest runs it over all workspaces) collects markers from journals into registers. It is idempotent (stable ids, append-only) and never closes or edits existing records.
decisions.md — one ### <id> (date) [active] block per decision; rejections land here too as [rejected] (id prefix o-), with the journal section it was born in. To retire a decision, change [active] to [superseded by <id>] — never delete.
open.md — checklist. Close by hand: - [x] … — закрыт YYYY-MM-DD: <чем>. The session-start hook reports the count of unchecked items — review them against the day's work; close what got done.
vocab.md — terms in the operator's words with birth dates.
_rule-candidates.md (global) — every RULE: lands here. Review cadence is enforced: when the queue reaches 10 or 7 days pass since the last review, the session-start hook demands a batched review; after it, stamp date +%F > ~/bro/.last-rule-review. On capture or /bro status, surface pending candidates to the operator; on confirmation, write the rule into _principles.md (category + anchors form) and mark [x] принят; on rejection mark [-] отклонён. Never move a rule into principles without the operator's word.
_workspace.md format (thin)
# <workspace> — bro summary
## What this is
2-4 lines: the project, its goal, key consumers.
## People
- **Name** — role in this workspace.
## Pointers
- <what> → `<path>` — one line per key project file a cold session must find
(runbooks, income logs, canonical specs). Optional but load-bearing: this
block is read at EVERY session start, so it is the reliable bridge from
bro to the project's own knowledge files.
Decisions, vocabulary and open questions live in the registers — do not duplicate them here.
_principles.md (global)
Universal rules that apply in every workspace: working discipline, privacy boundaries, communication norms, people that cross projects. Promote a RULE: here only with operator confirmation. Append-only; supersede with (supersedes YYYY-MM-DD) notes. If CONFLICTS.md exists, remind the operator until it's resolved.
Status
- Use Bash:
~/.claude/bro/bin/bro-harvest.sh --all --quiet && cat ~/bro/INDEX.md; show per-workspace freshness, open-tail counts, and the Reviews due section.
- Report threshold and hook state:
jq '.staleMinutes, .root' ~/.claude/bro-config.json and whether ~/.claude/bro/bin/ scripts are registered in ~/.claude/settings.json.
- Flag stale workspaces (last entry > 7 days), pending
_rule-candidates.md entries (walk the operator through accept/reject), and CONFLICTS.md if present.
- When Reviews due is non-empty, run the review cycle with the operator, one principle at a time: still alive and correct → extend
Пересмотр: with a LONGER interval than the last one (spaced-repetition logic: proven rules get checked less often); needs change → supersede with a new entry referencing the old id; dead → mark superseded, never delete.
Harvest (manual)
Run ~/.claude/bro/bin/bro-harvest.sh --all with Bash; report what was added per register. Use after bulk journal edits or to rebuild INDEX.md.
Setup
- Ensure installation: if
~/.claude/bro/bin/bro-session-start.sh is missing, run scripts/bro-install.sh from the repo (or: curl -fsSL https://raw.githubusercontent.com/balaka/bro/main/scripts/bro-install.sh | bash).
- Create the workspace:
mkdir -p ~/bro/<ws> (name = lowercased basename of cwd; ask the operator only if the name is ambiguous). Add the cwd→name mapping to .workspaces in ~/.claude/bro-config.json when it differs from the default.
- Create
_workspace.md from the format above with what is known from this session. Add the workspace line to INDEX.md.
- Confirm: hooks will now inject read-order and enforce freshness here.
Migrate
Migration is a deterministic script — never improvise it by hand:
- Run
~/.claude/bro/bin/bro-migrate.sh --dry-run with Bash; show the operator the plan.
- On their confirmation, run it without
--dry-run.
- Read the resulting
~/bro/CONFLICTS.md; walk the operator through duplicate-heading conflicts; edit _principles.md accordingly; delete CONFLICTS.md when done.
- Hard rules (enforced by the script, documented in MIGRATION.md): nothing deleted, everything archived with a tar.gz backup; dailies byte-for-byte (same-date threads merged under explicit headers); v2 thread summaries kept verbatim in
_legacy-v2/; principles concatenated with provenance, semantic dedup left to step 3.
Update
git -C <repo> pull if a clone exists, else fetch the repo fresh; then run scripts/bro-install.sh (idempotent — it replaces its own hook entries and bumps ~/.claude/bro/VERSION).
- If the new major >
~/bro/.version, the session-start hook will demand /bro migrate on the next session — follow it.
What the hooks enforce (installed by bro-install.sh)
| Hook |
Event |
Effect |
| bro-session-start.sh |
SessionStart (startup/resume/compact/clear) |
Injects read-order for the workspace; flags storage-version mismatch → /bro migrate |
| bro-stop-turnstile.sh |
Stop |
Blocks end of turn once per prompt when today's journal is missing/stale (> staleMinutes, default 30) or fails lint; the model writes the entry and finishes |
| bro-write-guard.sh |
PreToolUse (Write|Edit) |
Denies writes to retired v2 storage paths (bro/ inside repos), pointing to the central store |
Config ~/.claude/bro-config.json: root (store path), staleMinutes (turnstile threshold), workspaces (cwd→name overrides). All operator-tunable.
Discipline carried from v2 (unchanged)
- No token economy against the operator. Depth is the default; never trim journal content to save tokens.
- Nothing is ever deleted. Append, archive, supersede — never erase history.
- Verbatim over paraphrase. The operator's words are the record; your summary is commentary.
1---2name: bro3description: Session continuity journal with hook enforcement. One central store (~/bro) — global principles, one summary and shared daily journals per workspace, an INDEX over everything. Hooks inject read-order at session start, enforce journal freshness at stop, and guard legacy paths. Use /bro to capture now; also status, setup, off/on per chat, migrate, update.4---56# bro v3 — enforced session journal78bro captures the middle layer of state that formal artifacts don't: operator state, live decisions, shortcut vocabulary, open tails, working discipline. In v3 this layer is **enforced by hooks**, not by model discipline: the harness injects the read-order into every session, blocks the end of a turn while the journal is stale, and denies writes to retired v2 paths.910## Storage layout (v3)1112```13~/bro/ ← root; configurable via ~/.claude/bro-config.json "root"14 INDEX.md ← registry: one line per workspace (files, last entry)15 _principles.md ← GLOBAL principles — the only copy16 CONFLICTS.md ← unresolved principle-merge conflicts (delete when resolved)17 _rule-candidates.md ← global queue of RULE: markers awaiting operator confirmation18 <workspace>/ ← one folder per project; name = lowercased basename of repo dir19 _workspace.md ← what this is + people + pointers (thin; registers hold the rest)20 decisions.md ← decision register (harvested from DECIDED: markers, ADR-style)21 open.md ← open-items register (harvested from TAIL: markers; close by hand)22 vocab.md ← vocabulary register (harvested from TERM: markers)23 2026-09-06.md ← daily journal — ALL chats of the day write here, in sections24 _legacy-v2/ ← preserved v2 thread summaries (read-only history)25 _archive/ ← migrated v1/v2 storages, untouched26```2728Five record types, one rule each: the **chronicle** (journal body) is free-form and append-only; **decisions**, **open items**, **terms** and **rule candidates** are typed records born as journal markers and harvested into registers by script; **views** (INDEX.md) are generated, never hand-edited.2930The unit is **workspace + day**, not chat. Parallel chats write sections into the same daily file — nothing to synchronize. A workspace is "enabled" when `~/bro/<workspace>/` exists (created by `/bro setup` or migration); in projects without it, all hooks stay silent. Resolution walks UP from cwd: the `workspaces` map in `~/.claude/bro-config.json` (cwd, then its ancestors) wins; otherwise the first ancestor directory whose lowercased-basename slug exists in the store — so a session started in `project/active/subtask/` still lands in `project`'s workspace.3132## Command routing3334- `/bro` (no argument) → **Capture** (below).35- `/bro status` → **Status**.36- `/bro setup` → **Setup**.37- `/bro off` / `/bro on` → **Per-chat switch**.38- `/bro harvest` → **Harvest** (manual full run over all workspaces).39- `/bro migrate` → **Migrate**.40- `/bro update` → **Update**.4142## Per-chat switch (off / on)4344Enablement is per-project (a workspace in the store), but any single chat can opt out:4546- `/bro off` — use Bash: `mkdir -p ~/.claude/bro/off && touch ~/.claude/bro/off/${CLAUDE_SESSION_ID}`. Report: bro is off for this chat only (session-start injection and the stop turnstile skip it; the write guard stays on — it protects data, not discipline). Other chats are unaffected; the switch survives reopening this same chat.47- `/bro on` — use Bash: `rm -f ~/.claude/bro/off/${CLAUDE_SESSION_ID}`. Report: bro is back on for this chat from the next session start.4849## Capture (default)50511. Resolve workspace as the hooks do: `.workspaces` map (cwd, then ancestors), else walk up from cwd taking the first ancestor whose slug exists in `~/bro/`. If none, offer `/bro setup` and stop.522. Re-read `~/bro/_principles.md` NOW, always — even if it was read earlier this session: another chat may have changed it, and /bro is the manual "pull the latest rules" button. Then read, if not already in context this session: `~/bro/<ws>/_workspace.md`, the registers (`decisions.md`, `open.md`, `vocab.md`), today's and the previous daily.533. Review the conversation since the last journal entry. Classify each piece of material with the **temporal test**: would this still be true and relevant in a fresh chat tomorrow?54 - **No** → journal free text (states, events, the story of the day).55 - **Yes, project-scoped** → a typed MARKER in the journal: `DECIDED:` / `TAIL:` / `TERM:` — harvest moves it to the register.56 - **Yes, universal** → a `RULE:` marker (lands in the candidates queue); confirm with the operator before it enters `_principles.md` (max one batched ask per capture).574. Append to `~/bro/<ws>/YYYY-MM-DD.md` (create from the format below if missing). Use Edit/Write; never rewrite earlier sections of the day.585. Run `~/.claude/bro/bin/bro-harvest.sh --workspace <ws> --quiet` with Bash (also regenerates INDEX.md). Check `open.md` — close items the session resolved.596. Report in one line what was written and where.6061## Journal format (daily file)6263```markdown64# bro — 2026-09-06 / cowork6566## 14:30 · <work thread> — <topic with a distinguishing detail>67Free text. Operator's verbatim quotes in the language spoken. What happened,68what mattered, current state.69DECIDED d-0906-1: chose X | over: Y | because: Z | revisit-if: W70REJECTED: <what was turned down, in the operator's words; nothing chosen instead>71RULE: <new operator instruction, verbatim>72TAIL: <open item carried forward>73TERM: <term> — <meaning, in the operator's words>74```7576Rules:77- Section time HH:MM comes from the date command — never from your sense of time (the hook hands you NOW at session start; after any pause it is the only truth).78- Header line 1 exactly `# bro — YYYY-MM-DD / <workspace>` (the lint checks it).79- One section per sitting; append, don't rewrite. The section header carries an ANCHOR: time · work thread — topic with a detail that distinguishes it («выключатель /bro off», not «доработки»). A cold reader a year later must place the section without any context.80- Markers at line start, single line each: `DECIDED:` / `REJECTED:` / `RULE:` / `TAIL:` / `TERM:`. Russian aliases equally valid: `РЕШЕНИЕ:` / `ОТКАЗ:` / `ПРАВИЛО:` / `ХВОСТ:` / `ТЕРМИН:`. An explicit id after the keyword (`DECIDED d-0906-1:`) is optional — harvest assigns a stable hash id when absent.81- Markers are SEEDS, not final records: the harvest script moves them into the registers (decisions.md / open.md / vocab.md / _rule-candidates.md). Never hand-edit registers to add records — write a marker in the journal instead; hand-edit registers only to change status (close an item, supersede a decision, accept a rule).82- **Operator state is journal material.** Mood, energy, life context the operator shares — record it plainly, in their own words. It is often the most valuable line for whoever resumes tomorrow.83- Bilingual: write in the language the exchange happened in; verbatim quotes never translated; code/paths/URLs in backticks as-is.84- Never trim or summarize existing entries. The journal is append-only history.85- **Outcomes are complete** (§36): a finished discussion leaves its typed outcome — chose → DECIDED, turned down → REJECTED, deferred → TAIL, rule born → RULE, word born → TERM.86- **Proof lives inside the record** (§37): the number, table, exact phrase or link a record rests on goes into the record verbatim — a paraphrase loses the evidence.87- **A promise becomes a TAIL the moment it is spoken** (§38) — deadlines, callbacks, "I'll check" — not at session end from memory.88- **Side work is logged like main work** (§39) — especially anything touching security or irreversibly changing data.8990## Registers and harvest9192`bro-harvest.sh` (run automatically by the session-start hook for the current workspace; `/bro harvest` runs it over all workspaces) collects markers from journals into registers. It is idempotent (stable ids, append-only) and never closes or edits existing records.9394- `decisions.md` — one `### <id> (date) [active]` block per decision; rejections land here too as `[rejected]` (id prefix o-), with the journal section it was born in. To retire a decision, change `[active]` to `[superseded by <id>]` — never delete.95- `open.md` — checklist. Close by hand: `- [x] … — закрыт YYYY-MM-DD: <чем>`. The session-start hook reports the count of unchecked items — review them against the day's work; close what got done.96- `vocab.md` — terms in the operator's words with birth dates.97- `_rule-candidates.md` (global) — every `RULE:` lands here. Review cadence is enforced: when the queue reaches 10 or 7 days pass since the last review, the session-start hook demands a batched review; after it, stamp `date +%F > ~/bro/.last-rule-review`. On capture or `/bro status`, surface pending candidates to the operator; on confirmation, write the rule into `_principles.md` (category + anchors form) and mark `[x] принят`; on rejection mark `[-] отклонён`. Never move a rule into principles without the operator's word.9899## `_workspace.md` format (thin)100101```markdown102# <workspace> — bro summary103104## What this is1052-4 lines: the project, its goal, key consumers.106107## People108- **Name** — role in this workspace.109110## Pointers111- <what> → `<path>` — one line per key project file a cold session must find112 (runbooks, income logs, canonical specs). Optional but load-bearing: this113 block is read at EVERY session start, so it is the reliable bridge from114 bro to the project's own knowledge files.115```116117Decisions, vocabulary and open questions live in the registers — do not duplicate them here.118119## `_principles.md` (global)120121Universal rules that apply in every workspace: working discipline, privacy boundaries, communication norms, people that cross projects. Promote a `RULE:` here only with operator confirmation. Append-only; supersede with `(supersedes YYYY-MM-DD)` notes. If `CONFLICTS.md` exists, remind the operator until it's resolved.122123## Status1241251. Use Bash: `~/.claude/bro/bin/bro-harvest.sh --all --quiet && cat ~/bro/INDEX.md`; show per-workspace freshness, open-tail counts, and the **Reviews due** section.1262. Report threshold and hook state: `jq '.staleMinutes, .root' ~/.claude/bro-config.json` and whether `~/.claude/bro/bin/` scripts are registered in `~/.claude/settings.json`.1273. Flag stale workspaces (last entry > 7 days), pending `_rule-candidates.md` entries (walk the operator through accept/reject), and `CONFLICTS.md` if present.1284. When **Reviews due** is non-empty, run the review cycle with the operator, one principle at a time: still alive and correct → extend `Пересмотр:` with a LONGER interval than the last one (spaced-repetition logic: proven rules get checked less often); needs change → supersede with a new entry referencing the old id; dead → mark superseded, never delete.129130## Harvest (manual)131132Run `~/.claude/bro/bin/bro-harvest.sh --all` with Bash; report what was added per register. Use after bulk journal edits or to rebuild INDEX.md.133134## Setup1351361. Ensure installation: if `~/.claude/bro/bin/bro-session-start.sh` is missing, run `scripts/bro-install.sh` from the repo (or: `curl -fsSL https://raw.githubusercontent.com/balaka/bro/main/scripts/bro-install.sh | bash`).1372. Create the workspace: `mkdir -p ~/bro/<ws>` (name = lowercased basename of cwd; ask the operator only if the name is ambiguous). Add the cwd→name mapping to `.workspaces` in `~/.claude/bro-config.json` when it differs from the default.1383. Create `_workspace.md` from the format above with what is known from this session. Add the workspace line to `INDEX.md`.1394. Confirm: hooks will now inject read-order and enforce freshness here.140141## Migrate142143Migration is a **deterministic script** — never improvise it by hand:1441451. Run `~/.claude/bro/bin/bro-migrate.sh --dry-run` with Bash; show the operator the plan.1462. On their confirmation, run it without `--dry-run`.1473. Read the resulting `~/bro/CONFLICTS.md`; walk the operator through duplicate-heading conflicts; edit `_principles.md` accordingly; delete CONFLICTS.md when done.1484. Hard rules (enforced by the script, documented in MIGRATION.md): nothing deleted, everything archived with a tar.gz backup; dailies byte-for-byte (same-date threads merged under explicit headers); v2 thread summaries kept verbatim in `_legacy-v2/`; principles concatenated with provenance, semantic dedup left to step 3.149150## Update1511521. `git -C <repo> pull` if a clone exists, else fetch the repo fresh; then run `scripts/bro-install.sh` (idempotent — it replaces its own hook entries and bumps `~/.claude/bro/VERSION`).1532. If the new major > `~/bro/.version`, the session-start hook will demand `/bro migrate` on the next session — follow it.154155## What the hooks enforce (installed by bro-install.sh)156157| Hook | Event | Effect |158|---|---|---|159| bro-session-start.sh | SessionStart (startup/resume/compact/clear) | Injects read-order for the workspace; flags storage-version mismatch → `/bro migrate` |160| bro-stop-turnstile.sh | Stop | Blocks end of turn once per prompt when today's journal is missing/stale (> `staleMinutes`, default 30) or fails lint; the model writes the entry and finishes |161| bro-write-guard.sh | PreToolUse (Write\|Edit) | Denies writes to retired v2 storage paths (`bro/` inside repos), pointing to the central store |162163Config `~/.claude/bro-config.json`: `root` (store path), `staleMinutes` (turnstile threshold), `workspaces` (cwd→name overrides). All operator-tunable.164165## Discipline carried from v2 (unchanged)166167- **No token economy against the operator.** Depth is the default; never trim journal content to save tokens.168- **Nothing is ever deleted.** Append, archive, supersede — never erase history.169- **Verbatim over paraphrase.** The operator's words are the record; your summary is commentary.