Open Second Brain
Memory server
First committed by hermes-vps-agent
Published in itechmeat/open-second-brain. Publisher page
550 GitHub stars
What it is
Second brain for AI agents using Obsidian-compatible Markdown vaults. Works with Hermes, Claude Code, Codex, and OpenClaw.
AutoGineer overview, from the brain’s own files
Open Second Brain acts as a memory provider for AI agents, enabling them to read and write persistent knowledge directly into an Obsidian-compatible Markdown vault. It employs a deterministic 'dream' pass to convert repeated signals into actionable rules, while maintaining a hash-chained log for auditability. The system supports various agent clients through MCP servers, lifecycle hooks, and a dedicated CLI, ensuring that agent-learned information remains accessible, versioned, and searchable as standard Markdown files.
How it works
Memory model: Hybrid
AutoGineer overview, from the brain’s own files
The system organizes data into a Brain folder containing Markdown files. Agents interact with this vault via MCP servers, lifecycle hooks, or the o2b CLI. Signals from agents are stored in an inbox, processed through a dream pass to generate preferences, and stored as rules in active.md. A search index is maintained in a hidden directory and can be rebuilt from the source files. The system also supports truth ledgers, decision models, and staged review gates for agent writes.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 7 |
| bin | scripts | 1 |
| docs | docs | 774 |
| hooks | hooks | 35 |
| scripts | scripts | 22 |
| skills | agent-instructions | 6 |
| src | source-code | 1496 |
| templates | templates | 23 |
| tests | tests | 2101 |
| .agents | folder | 1 |
| .ai-notes | folder | 1 |
| .apb | folder | 87 |
| .claude-plugin | folder | 2 |
| .codex-plugin | folder | 1 |
| .githooks | folder | 2 |
| install | folder | 16 |
| openclaw | folder | 1 |
| openspec | folder | 33 |
| plugins | folder | 21 |
| schemas | folder | 5 |
The agent reads
- install/codex.md evidence: README.md
- search_iterative_recall_enabled evidence: README.md
- search_pool_max_per_session evidence: README.md
- docs/decision-models.md evidence: README.md
The agent writes
- package.json evidence: CLAUDE.md
- CHANGELOG.md evidence: CLAUDE.md
- docs/brain-store.md evidence: README.md
- docs/updating.md evidence: README.md
Requirements
Runtimes: Node.js, Python >=3.11, Bun
Databases: SQLite
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)
Set it up
This brain runs code automatically: 54 hooks (codex), 3 scripts. Review .githooks/pre-commit, .githooks/pre-push, hooks/hooks.json, package.json, plugins/codex/hooks/hooks.json before opening the folder in Claude Code. No step below turns a hook on for you.
.githooks/pre-commit:runs on git pre-commit.githooks/pre-push:runs on git pre-pushhooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" post-write-reminder; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook post-write-reminder; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-agenda; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-agenda; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" backlog-nudge; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook backlog-nudge; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" log-catchup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook log-catchup; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" view-render; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook view-render; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" recall-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook recall-inject; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" nav-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook nav-inject; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" counterparty-brief; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook counterparty-brief; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-orient; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-orient; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-host-backup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-host-backup; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" stop-log-guardrail; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook stop-log-guardrail; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" answer-quote-check; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook answer-quote-check; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" hygiene-digest; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook hygiene-digest; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-promote; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-promote; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0package.json:bun scripts/prepare.ts || exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" post-write-reminder; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook post-write-reminder; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-agenda; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-agenda; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" backlog-nudge; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook backlog-nudge; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" log-catchup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook log-catchup; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" view-render; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook view-render; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" recall-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook recall-inject; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" nav-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook nav-inject; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" counterparty-brief; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook counterparty-brief; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-orient; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-orient; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-host-backup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-host-backup; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" stop-log-guardrail; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook stop-log-guardrail; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" answer-quote-check; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook answer-quote-check; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" hygiene-digest; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook hygiene-digest; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-promote; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-promote; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0plugins/codex/hooks/hooks.json:r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0
- Not scanned by AutoGineerThe author’s MCP configuration (.claude-plugin/plugin.json) also starts it in a way AutoGineer does not offer. Read that file: AutoGineer did not scan that path; what it runs is listed under “This brain runs”. https://github.com/itechmeat/open-second-brain/blob/233b91d1bdc7cf74c47ea1c1fd72e949887e1fc4/.claude-plugin/plugin.json
First committed by hermes-vps-agent. Published in itechmeat/open-second-brain. Licence: MIT License.
Usage
AutoGineer overview, from the brain’s own files
- Initialize a new vault for agent memory
- Connect agents like Claude Code or Codex via MCP
- Manage agent preferences and rules through automated dream passes
- Audit agent activity using hash-chained logs
- Perform semantic or keyword searches across agent-generated notes
- Enforce write-path integrity with staged review gates
- “o2b brain today”
- Displays the current day's view and active memory markers for the agent session.
- “o2b brain rollback”
- Restores the vault state from the last snapshot, reversing recent brain mutations.
Documentation by hermes-vps-agent
Open Second Brain
Image: Open Second Brain - your knowledge, amplified by AI
Open Second Brain is a memory layer for AI agents that lives in an Obsidian vault. Agents read and write it through MCP servers, lifecycle hooks and the o2b CLI. Everything the agent learns is a Markdown file under Brain/, so you can open, edit, grep and version it like any other note. A deterministic dream pass turns repeated signals into rules and retires the ones nothing uses; no model runs inside that algorithm.
How it fits
flowchart LR
subgraph Clients["Agent clients"]
Plug["Plugins: Claude Code, Codex,<br/>Hermes, opencode, Grok Build"]
Mcp["MCP config: Cursor, kiro, Copilot CLI,<br/>Gemini CLI, ZCode, any MCP host"]
Claw["OpenClaw<br/>in-process plugin"]
Cliu["Aider, Pi<br/>CLI and context file"]
end
subgraph Product["Open Second Brain"]
Srv["MCP servers<br/>open-second-brain, open-second-brain-writer"]
Hooks["Lifecycle hooks<br/>o2b-hook"]
Cli["CLI<br/>o2b"]
Core["Core"]
end
subgraph Vault["Your vault"]
Brain["Brain/<br/>Markdown the agent writes"]
Notes["Your notes"]
Index[(".open-second-brain/brain.sqlite<br/>rebuildable search index")]
end
Plug --> Srv
Plug --> Hooks
Mcp --> Srv
Claw --> Core
Cliu --> Cli
Srv --> Core
Hooks --> Core
Cli --> Core
Core --> Brain
Core --> Index
Core -. reads, writes a named note on request .-> NotesEach client reaches the same core through a plugin, an MCP registration or the CLI. The core keeps its own records under Brain/ and a generated search index under .open-second-brain/ that it can rebuild from the files. Your notes are read; the note tools write one only at a path the caller names.
flowchart LR
Agent["Agent"] -->|brain_feedback| Inbox["Brain/inbox/<br/>signal"]
Inbox -->|dream pass| Pref["Brain/preferences/<br/>rule"]
Pref -->|rendered into| Active["Brain/active.md"]
Active -->|read at session start| Agent
Agent -->|brain_apply_evidence| Log["Brain/log/"]
Log -->|next dream pass| PrefThe learning loop: signals become rules, rules reach the next session through Brain/active.md, and applied or violated evidence confirms, quarantines or retires them. Details: docs/how-it-works.md.
Install
Install Bun 1.1 or later:
install/prerequisites.md.Install the plugin for your client (guides below), or get the
o2bCLI:install/prerequisites.md.Create the vault files:
o2b init --vault /path/to/vault --name "My Second Brain" o2b brain init --vault /path/to/vaultConnect the client (table below), then check the result:
o2b doctor --vault /path/to/vault o2b install --check
The full router with readiness criteria is install.md; native Windows is covered in install/windows.md.
Supported clients
| Client | Integration | Guide |
|---|---|---|
| Claude Code | Marketplace plugin: MCP servers and hooks | install/claudecode.md |
| OpenAI Codex | Marketplace plugin, then o2b install --target codex --apply | install/codex.md |
| ChatGPT desktop | Reads the shared Codex config: o2b install --target codex --apply | install/codex.md |
| Hermes Agent | Plugin and memory provider | install/hermes.md |
| OpenClaw | Native plugin, no MCP | install/openclaw.md |
| opencode | o2b install --target opencode --apply: MCP servers and a native plugin | install/opencode.md |
| Grok Build | o2b install --target grok --apply: MCP servers and hooks | install/grok.md |
| Cursor | o2b install --target cursor --apply | install/cursor.md |
| kiro | o2b install --target kiro --apply | install/kiro.md |
| GitHub Copilot CLI | o2b install --target copilot-cli --apply | install/copilot-cli.md |
| Gemini CLI | o2b install --target gemini-cli --apply | install/gemini-cli.md |
| ZCode | MCP servers added to its config by hand | install/zcode.md |
| Aider | o2b install --target aider --apply: context file, no MCP | install/aider.md |
| Pi | o2b install --target pi --apply: skill over the CLI | install/pi.md |
| Any other MCP host | o2b install --target generic --apply --out - prints the server entries | install/generic.md |
o2b uninstall --target <name> --apply removes what o2b install wrote; the vault is never touched (install.md).
Capabilities
Preferences that learn and expire. Signals, a deterministic dream pass, confidence and retirement: how it works.
Reversible changes. Brain mutations take a snapshot first;
o2b brain rollbackrestores it: snapshots.Attributed writes.
o2b brain writeslists every note write by agent and device,writes revertplans a restore,o2b brain freezestops all writers, ando2b brain log verifychecks the hash-chained log: Brain CLI.Search. Keyword search with an optional semantic lane, result explanations, recall profiles and compact cards; an opt-in iterative loop (
search_iterative_recall_enabled, default off) keeps querying when coverage gaps survive, pausing with a plan the caller answers throughbrain_search's round trip; and an opt-in per-source-session cap (search_pool_max_per_session, default off) keeps one long imported session from filling the candidate window, so diversity spreads across sessions: search CLI, iterative recall, pool cap, ranking.Session recall. Import agent transcripts already on disk, search them, or select whole-thread coverage under a character budget (
o2b brain session-grep --operation coverage --char-budget <n>, with--entity-keyseeding the fan-out from mined entity mentions): session logs, coverage.Hygiene.
o2b brain hygiene scanfinds contested, duplicate, stale and unused memories;applyruns only the findings you pick: maintenance.Claim ledger.
o2b brain truth ingestrecords an entity claim with an optional validity window (--valid-from/--valid-until; with the flags absent, a window on the source record's frontmatter is adopted and frozen at ingest),truth eventsrecalls a windowed slice of the ledger, andtruth stategrounds an agent-stated claim with an anchoring verdict - committed, or reported back with reason codes.truth stateaccepts an optional opaque--sample-idnaming the context-pack sample the claim drew on, and claim reads report the receipt and outcome provenance behind it at the caller's reach - "receipt known, outcome unknown", never an invented verdict: entity truth CLI.Corrections.
o2b brain lifecycle correct <target>reports a correction's blast radius and retires the affected records - validity-closed by default, tombstoned with--flatly-wrong, dry run by default and--applyto write. A retired record that recall can still serve is served only beside its correction, never alone: belief lifecycle CLI.Sharing and backup. Knowledge packs export a chosen subset;
o2b brain bank-exportwrites a one-file backup bundle: knowledge packs.Secret custody. Credentials live encrypted in the vault's state dir behind a capability-gated store;
$secret:NAMEreferences resolve at the use sites,o2b brain secret unlockwraps the keyfile under a passphrase, andsecret export/importmove the store between installs as one passphrase-encrypted bundle: custody CLI.Today view and note markers.
o2b brain today,@osb loopand@osb setmarkers: Brain CLI.Self-inspection.
o2b version,o2b install --frictionando2b state statusreport what is installed and where state lives: CLI reference.MCP surface. Tools, tool profiles for hosts with tool limits, and the always-loaded writer server:
docs/mcp.md.
Optional features
Semantic search: an embedding provider plus
sqlite-vec; theembeddings-setupskill walks through it:skills/embeddings-setup/SKILL.md.Decision models: a typed judgment model that can rerank search and filter candidates, off by default per use:
docs/decision-models.md.Deep relational recall: a fourth search arm over typed links, off by default. Its traversal runs under width budgets - 8 seeds, 4 edges per node, 16 nodes in total, and a hub above 12 walked edges is reached but not expanded - each overridable through an
OPEN_SECOND_BRAIN_SEARCH_TRAVERSAL_*environment variable or asearch_traversal_*config key; entity co-occurrence bridges join the walk by default and switch off separately (OPEN_SECOND_BRAIN_SEARCH_ENTITY_BRIDGES): retrieval quality.Staged review for agent writes, off by default. With no key set every write publishes exactly as before.
write_approval.notes/OPEN_SECOND_BRAIN_WRITE_APPROVAL_NOTES_ENABLEDandwrite_approval.ingest/OPEN_SECOND_BRAIN_WRITE_APPROVAL_INGEST_ENABLED(each falling back to thewrite_approval.enabledmaster, default off) stage note creates and ingest summary pages intoBrain/pending/beside the staged signals, where they stay out of the search index until an operator runso2b brain pending listand applies or rejects them; write-path integrity.A permissions document, absent by default.
Brain/_permissions.yaml(an operator-edited vault file) resolvesallow/ask/denyper agent, role and target for the write, ingest and owner-write actions; with the file absent every check behaves exactly as today.askstages the write,denyrefuses with thewrite-refusedtoken and the next commando2b brain permissions show, and every ask/deny verdict lands in a queryable decision ledger underBrain/logs/decisions/;o2b brain permissions showdry-runs the decision table,ledgerreads the rows; Brain CLI.The owner-write gate, off by default.
integrity.owner_scope_writesinBrain/_brain.yaml(off|warn|fail, defaultoff) refuses a caller-named owner that differs from the caller's resolved identity on the preference and note lanes (warnallows and records one decision-ledger row); a documentowner_writeverdict composes most-restrictive-wins with the gate; write-time integrity.Named MCP tokens, optional.
o2b mcp token mint|rotate|revoke|listkeeps a hash-at-rest token per agent (.open-second-brain/secrets/mcp-tokens.json; material shown exactly once). Over HTTP a valid token authenticates as its agent per request, the shared--api-keystays valid as the operator master credential, andmcp_tokens_required/OPEN_SECOND_BRAIN_MCP_TOKENS_REQUIRED(defaultfalse) makes a non-empty token map refuse credential-less requests.o2b bootstrap --target <harness> [--token] [--rotate] [--check]provisions MCP registration, token and receipt in one idempotent command, ando2b bootstrap --remove <harness>tears a provision down again; the minted token authenticates HTTP clients configured by hand, while the registered stdio harness presents no credential and keeps its config-derived identity; core CLI.Ambient capture consent, opt-out.
guardrails.ambient_writeback: falsesuppresses the ambient extraction lane with a countedambient-withheldevent (absent keeps today's behavior), andguardrails.ambient_ttl_daysstamps anexpiration_dateon ambient-extracted signals so reads drop them after the window (absent stamps nothing); a TTL-stamped signal still stages when the review gate is on and survives apply verbatim.Open decisions.
o2b brain decision open --title <t> --question <q> --option <o> [...]parks a question with enumerated options atBrain/decisions/open-<slug>.md;resolvemints the realtype: decisionpage,discardcloses without deciding, and the morning brief renders up to five open questions: belief lifecycle.
What is new
Open Second Brain 1.98.0 is the agent-presence and self-improvement release. o2b install --target <host> --apply now writes the brain-usage block into the agent-instruction file the installed host reads at the vault root (AGENTS.md for codex, cursor, opencode and aider; GEMINI.md for gemini-cli) under the same consent prompt as the config writes, preserving every byte outside the block and recording its ownership so uninstall removes exactly the pair the tool wrote; the dream pass gains an operator-approved exit into those same files, where a lesson promotes into a managed lessons block only with a fresh approval pinned to the approved text, and the doctor grades every block state it can name. The skill lane gains a benchmark-gated rewrite loop: a skill is rewritten only after a candidate beats the incumbent on scored dev tasks with full coverage and survives the one held-out check - Open Second Brain never calls a model, the scorer reads structural predicates and nothing the agent claims about its own work, and the accept stays an operator step on the command line. And ChatGPT desktop is named where its operators look for it: it reads the shared Codex config, so one codex registration serves both. Every release is described in the CHANGELOG.
Documentation
| Topic | Page |
|---|---|
| Mental model, vault layout, dream pass, safety properties | docs/how-it-works.md |
| Every CLI verb and flag | docs/cli-reference.md |
| MCP protocol, tools, writer split | docs/mcp.md |
| Brain store: setup, sync, migration, the human view | docs/brain-store.md |
| Architecture and configuration model | docs/architecture.md |
Updating (o2b update) and upgrade notes | docs/updating.md |
| Observability events | docs/observability.md |
| Metrics data contract | docs/metrics.md |
| Cross-project pointer | docs/cross-project-pointer.md |
| Scheduled Brain digest (Hermes cron) | docs/hermes-cron.md |
| Stability policy | docs/stability.md |
| Origin of the idea | docs/idea.md |
Project
CHANGELOG · Security policy · Source on GitHub · MIT License
docs/how-it-works.md
How Open Second Brain Works
A working guide for engineers and agents to the mechanics of the observing memory layer. Read this when you want to understand what the system does, not what to configure.
Mental model
Open Second Brain accumulates preferences and learns from real usage. Three responsibilities:
Capture. Agents and humans drop taste signals into
Brain/inbox/.Accretion. A deterministic
dreampass turns repeat signals into rules.Application. Agents record whether they applied or violated each rule when producing durable artifacts.
The LLM lives outside the system: agents use it to detect signals in conversation and to apply rules during work. The system uses counters, thresholds, and atomic file operations — no LLM inside the algorithm, no surprise, no hallucinated memory.
Vault layout
The agent owns one top-level directory for its own artefacts: Brain/.
Everything it generates unprompted lives there. The one surface that
writes user space is the note-lifecycle family, which acts on a path the
caller names and can move a note to a second top-level directory,
Archive/ — see architecture.md.
<vault>/
├── Brain/ # agent-writable
│ ├── _brain.yaml # schema, thresholds, retention, vault scope, notes.read_paths
│ ├── _BRAIN.md # operating manual for agents
│ ├── active.md # derived: confirmed + quarantine + recently retired
│ ├── inbox/ # raw taste signals
│ │ ├── sig-<date>-<slug>.md
│ │ ├── processed/ # signals already folded into rules
│ │ └── archived/ # unconsumed signals past the contradiction window
│ ├── preferences/ # active rules
│ │ └── pref-<slug>.md # status: unconfirmed | confirmed | quarantine
│ ├── retired/ # archived rules
│ │ └── ret-<slug>.md # retired_reason: stale-no-evidence | expired-unconfirmed | rebutted | user-rejected | quarantine-violated | superseded-by-context
│ ├── log/ # daily event log
│ │ └── YYYY-MM-DD.md # append-only, typed events
│ └── .snapshots/ # pre-dream snapshots
│ └── dream-<run-id>.tar.zst
│
├── .open-second-brain/ # derived search index (rebuildable)
│ └── brain.sqlite # SQLite + FTS5 + optional sqlite-vec
│
└── Daily/ # OPERATOR-OWNED user notes (any folder name)
└── 2026-05-14.md # listed in notes.read_paths for @osb scanningBrain/active.md and .open-second-brain/brain.sqlite are derived —
both can be deleted and rebuilt at any time (o2b brain dream and
o2b search reindex respectively). They are excluded from the agent's
write contract.
flowchart LR
Agent[Agent / MCP client]
subgraph Vault
Brain[Brain/]
Notes[("Operator notes<br/>(any folder)")]
end
Agent -- "brain_* (read + write)" --> Brain
Agent -- "scan-inline (read-only)" --> NotesBrain/ is the only area where the agent writes. User-authored notes
(daily journals, weekly notes, etc.) live in folders the operator
names — wherever they want them. List those folders under
notes.read_paths in _brain.yaml to opt the agent into scanning
them for @osb markers.
A preference's lifecycle
A preference moves between five states from first signal to retirement:
stateDiagram-v2
[*] --> Inbox: brain_feedback
Inbox --> Unconfirmed: dream (≥candidate_threshold<br/>same-sign signals on topic)
Unconfirmed --> Confirmed: brain_apply_evidence applied<br/>(first such event)
Unconfirmed --> Retired_Expired: dream (now > unconfirmed_until,<br/>no applied evidence)
Confirmed --> Quarantine: dream (violated_count ≥ applied_count<br/>AND applied_count > low_max_applied)
Quarantine --> Confirmed: dream (applied_count > violated_count)
Quarantine --> Retired_Quarantine: dream (one further violated)
Confirmed --> Retired_Stale: dream (last_evidence_at<br/>older than stale_evidence_days)
Confirmed --> Retired_Rebutted: dream (≥candidate_threshold<br/>opposite-sign signals collected)
Confirmed --> Retired_Superseded: dream (one apply-evidence with<br/>result: outdated)
Quarantine --> Retired_Superseded: dream (one apply-evidence with<br/>result: outdated)
Confirmed --> Retired_UserRejected: o2b brain reject
Quarantine --> Retired_UserRejected: o2b brain reject
Unconfirmed --> Retired_UserRejected: o2b brain reject
Retired_Expired --> [*]
Retired_Stale --> [*]
Retired_Rebutted --> [*]
Retired_Quarantine --> [*]
Retired_Superseded --> [*]
Retired_UserRejected --> [*]
note right of Confirmed
Pinned preferences (pinned: true)
skip the three "natural decay"
retire transitions (stale,
expired, rebutted) and the
quarantine downgrade — dream
emits retain-pinned instead.
A context-driven outdated still
retires a pinned pref; only
o2b brain reject and outdated
evidence can move it.
end noteInbox is not really a state of the preference — it is the staging
area for the signals that will eventually create one. The first real
state is Unconfirmed: the rule exists but has not yet been applied
in real work.
Quarantine (added in v0.9.1) is a probation state for confirmed rules
whose recent evidence has turned dominantly negative without yet
crossing the rebuttal threshold. A quarantined rule is still active —
the agent reads it — but one further violated retires it with
quarantine-violated, and recovery happens automatically when applied
events overtake the violated count.
End-to-end signal → rule flow
A typical sequence from a user remark to a confirmed preference:
sequenceDiagram
autonumber
participant User
participant Agent
participant MCP as MCP layer
participant Core as Brain core
participant Vault
User->>Agent: do not use internal abbreviations
Agent->>MCP: brain_feedback (topic, signal negative, principle, agent)
MCP->>Core: writeSignal
Core->>Vault: atomic write Brain/inbox/sig file
Core->>Vault: append Brain/log/today.md feedback event
MCP-->>Agent: signal_path and signal_id
Agent-->>User: confirmation
Note over Vault: days pass and two more matching signals arrive on the same topic
Note over Core: scheduled dream pass (cron or manual)
Core->>Vault: scan inbox, preferences, log
Core->>Core: group signals by topic
Core->>Vault: createSnapshot to .snapshots/dream-run.tar.zst
Core->>Vault: write Brain/preferences/pref file status unconfirmed
Core->>Vault: move inbox signals into inbox/processed
Core->>Vault: append log dream event
Core->>Vault: pruneSnapshots to retention_count
Note over Agent,User: later the agent produces a blog draft
Agent->>Agent: write durable artifact
Agent->>MCP: brain_apply_evidence (pref_id, artifact, result applied, agent)
MCP->>Core: appendApplyEvidence
Core->>Vault: append log apply-evidence event
MCP-->>Agent: logged_at and log_path
Note over Core: next dream pass
Core->>Vault: scan log and count applied or violated per pref
Core->>Vault: pref file status unconfirmed becomes confirmed
Core->>Vault: pref file applied_count plus one, last_evidence_at, confidence
Core->>Vault: append log dream event with confirmed listTwo important properties of this flow:
The
dreampass is the only writer of state transitions (unconfirmed → confirmed, anything → retired). Signals and apply-evidence events are append-only side inputs.Every state change is durable on disk before
dreamreturns. There is no in-memory buffer that could be lost on crash.
Capture surfaces
Four independent paths land a signal in Brain/inbox/:
Live - the agent calls
brain_feedback(MCP) oro2b brain feedback(CLI) the moment the rule is formulated. This is the path the end-to-end sequence above documents.Inline - the user (or agent) writes an
@osbmarker into any vault Markdown file.o2b brain scan-inlinefinds every marker, creates the corresponding signal, and annotates the source file with@osb✓ [[sig-...]]so a re-run is a no-op. Two marker shapes: a single line@osb feedback negative topic=... principle="..."or a fenced```osbblock with YAML inside.Session import -
o2b brain import-session <path>reads a Claude Code / Codex CLI / Hermes session JSONL and extracts both@osbmarkers from message text and replays ofbrain_feedbacktool-use calls. Useful when MCP was not available at recording time or the agent did not make the call live. Since v1.50.0 the same verb also sweeps the machine:--statusreports per-runtime coverage over the session roots this build declares,--discoverlists the logs that have never been imported, and--discover --allimports them through the same code path a named path takes. A content-digest ledger under<vault>/.open-second-brain/keeps a re-run from re-parsing a log whose bytes have not changed.Lifecycle hook - Claude Code / Codex hooks call
o2b brain session-hookthroughhooks/session-capture.ts.UserPromptSubmitprompt markers andPostToolUsebrain_feedbackinputs are captured immediately through the same signal dedup hash, while SessionStart / Stop / SessionEnd / PostCompact observations append non-blocking lifecycle audit/log rows.
Session capture is role-filterable (since v0.37.0): session_capture_roles
in the config (comma-separated subset of user,assistant,system,tool,meta)
supplies the default --filter-role set for brain import-session; an
explicit flag wins, an absent key captures every role, and an unknown role
name fails fast.
SessionEnd carries two more lifecycle duties (since v0.37.0). It clears the
ending session's bound search focus (search-focus/<scope>.json) so a
finished session's steering never leaks into the next one - cleanup runs
regardless of the capture boundary. And when session_handoff is "true"
(default off), it writes an operator-readable handoff note to
Brain/handoffs/<date>-<scope>.md - request, completed work, changed files,
learned context, next steps - extracted from the recorded transcript by
deterministic regex (o2b brain handoff <session-file> is the manual
equivalent). Alongside the scope-free Brain/pinned.md scratchpad, scoped
current-intention chains live at Brain/intentions/<scope>.md
(o2b brain intention set|show|list|move, MCP brain_intention): every
update bumps version and appends the superseded text to an in-file history
trail, and move retires the chain into Brain/intentions/history/.
All four paths share a normalised payload hash so the same rule captured twice from different surfaces dedups automatically. Pinned preferences are exempt from automatic retire (stale-no-evidence, expired-unconfirmed, rebutted); only o2b brain reject can retire them.
Session mining has a memory of its own (since v1.86.0). When o2b brain extract-signals (or the brain_extract_signals tool) commits a mine that ends with nothing written and nothing staged - an empty answer, every item already deduped, every item refused by the durability gate, or any mix - it records a zero-yield tombstone for the session in a mining ledger beside the import ledger, keyed on a SHA-256 of the mined turn material (turn id, text, timestamp). The next plan over that session returns the recorded breakdown instead of an envelope, so an unchanged transcript is never re-offered to the model; a re-import that changes the turns changes the hash and re-opens the session with no clearing step. Because removing a downstream preference does not change a session's turns, the tombstone never lifts on its own: --force (MCP force) bypasses the gate for exactly one plan call when the session should be re-proposed. A commit that succeeds but cannot record its tombstone still succeeds and names the failure, so a ledger that stopped being written is never a silent fact.
o2b brain watchdog is the recovery surface for this layout. It checks the
Brain config, required directories, and rebuildable search index, emits
exponential backoff metadata for schedulers, and writes an audit record. By
default it only reports a remediation plan; --remediate may create missing
Brain directories, but search repair remains a recommendation (o2b search reindex)
and snapshot restore is refused unless --restore <run_id> and --force-restore
are both explicit.
Cross-project setup
When coding work happens in a project directory that is not the vault itself, add a pointer snippet to your project's CLAUDE.md or AGENTS.md so the agent knows where to read preferences from. The canonical snippet, the rules for multi-device Syncthing setups, and the o2b brain set-primary invocation are in cross-project-pointer.md.
A vault shared across hosts should declare a single primary_agent in Brain/_brain.yaml - the runtime that owns the dream cron. Dream runs from a different agent emit a non-fatal warning (stderr for CLI, warnings array for MCP) and tag the dream summary log with non_primary_agent: <caller>.
The dream pass in detail
A single dream invocation is a deterministic pipeline:
flowchart TD
Start([o2b brain dream]) --> Load[Load _brain.yaml]
Load --> Scan[Scan inbox/, preferences/, retired/, log/]
Scan --> Intent[Build read-only intent_reviews<br/>for active signal clusters]
Intent --> Plan[Plan transitions:<br/>new unconfirmed, promotions,<br/>quarantine in/out, retires, signal moves]
Plan --> Changed{Any changes planned?}
Changed -- no --> NoOp([regenerate active.md<br/>return changed: false])
Changed -- yes --> Snap["createSnapshot to<br/>.snapshots/dream-RUNID.tar.zst"]
Snap --> SnapErr{Snapshot OK?}
SnapErr -- fail --> Abort([throw; no state changed])
SnapErr -- ok --> Apply
subgraph Apply [Apply transitions]
direction TB
A1[Per topic: signals → unconfirmed pref<br/>if threshold met] --> A2
A2[Per pref: count log evidence,<br/>compute applied_count / violated_count]
A2 --> A3{Unconfirmed with applied ≥ 1?}
A3 -- yes --> A4[Promote to confirmed,<br/>stamp confirmed_at]
A3 -- no --> A5
A4 --> A5[Recompute confidence]
A5 --> A6{outdated evidence?}
A6 -- yes --> A6r[Move to retired/<br/>reason: superseded-by-context]
A6 -- no --> A7{Pinned?}
A7 -- yes --> A7l[Skip auto-retire + quarantine,<br/>log retain-pinned]
A7 -- no --> A8{Quarantine conditions?}
A8 -- enter --> A8q[status: quarantine]
A8 -- exit --> A8c[status: confirmed]
A8 -- already-quarantine + violated --> A8r[Move to retired/<br/>reason: quarantine-violated]
A8 -- none --> A9{Retire conditions?}
A9 -- stale --> A10[Move to retired/<br/>reason: stale-no-evidence]
A9 -- expired --> A11[Move to retired/<br/>reason: expired-unconfirmed]
A9 -- rebutted --> A12[Move to retired/<br/>reason: rebutted]
A9 -- none --> A13[Leave in preferences/]
A6r --> A14
A7l --> A14
A8q --> A14
A8c --> A14
A8r --> A14
A10 --> A14
A11 --> A14
A12 --> A14
A13 --> A14
A14[Move consumed signals to processed/]
A14 --> A15[Archive unconsumed signals older than the window to archived/]
end
Apply --> LogEvent["Append dream event to log/today.md"]
LogEvent --> ActMd[regenerate Brain/active.md]
ActMd --> Prune[pruneSnapshots to retention_count]
Prune --> Done([return changed: true with summary])Key rules baked into the pipeline:
Threshold by dominant sign. New unconfirmed preferences are created only when
candidate_threshold(default 3) same-sign signals on one topic appear withincontradiction_window_days. Mixed signals cancel and the rule does not form.Inbox archive. A signal outside
contradiction_window_dayscounts toward nothing, and never will again, so a signal the run did not consume and that is older than the window moves byte for byte toBrain/inbox/archived/(archived_signalsin the summary and the log event;--dry-runpreviews it). A stale file stays in the inbox only when it is tombstoned, does not parse as a signal (including an unparseablecreated_at), or its name is already taken inarchived/(a warning names it); a pastexpiration_datedoes not archive a signal still inside the window, because the pass still counts it. The rule runs against the earlier of--nowand the real time, so a pass with a future--nowarchives nothing still inside the window today. A staged bundle does not treat newly archivable signals as drift. Archived signals stay readable: the pass reads them as history (a preference evidenced by one keeps its sign), and query, backlinks, sources, expiration, forget, the claim graph and the doctor record checks include them. They also stay in the capture dedup index, so re-importing an old session does not re-create them.dream.archive_stale_signals: falsekeeps them in the inbox; raisingcontradiction_window_dayslater does not bring archived signals back, move them by hand if that is wanted.o2b brain doctorreportsinbox-archivablewhile a pass is due, counting the signals the pass selects for the archive (a--dry-runarchives the same set less any signal it consumes instead).Intent review is audit data. Each run computes
intent_reviewsbefore mutation so operators can see topics that are ready, weak, or conflicted. The review is exposed bydream,brain_review_candidates,o2b brain intent-review, andbrain_intent_review; it preserves the existing transition outcomes by default.Pre-run snapshot before any mutation. If snapshot fails, the run aborts without writing anything; safety net cannot be bypassed.
Idempotency. A second dream run on unchanged inputs is a no-op: no snapshot, no log entry, no file modifications.
Corrupted YAML is tolerated. A single unparseable signal or preference is logged as a
skip-corrupted-frontmatterevent; the rest of the run proceeds.
Lifecycle suite (v0.21.0)
The same pipeline is named as five explicit ordered phases - close (scan) -> reconcile (contradictions) -> synthesize (promote / confirm) -> heal (auto-retire stale, optional enrichment) -> log
each emitting a workrun checkpoint and a structured entry in
DreamRunSummary.phases. The internals are unchanged; the phases are labels over the existing seams, so every invariant above still holds.Reconcile classification. Each contradiction is bucketed by structural signal shape (claims / entity / decisions / source-freshness). Only source-freshness with a decisive recency gap auto-resolves - and even then it is recorded as a
reconcilelog event, never a sub-threshold state mutation. Everything else surfaces inopen_questionsfor operator review rather than being force-merged.Per-preference audit. Every mutation (create / promote / update / retire / merge) appends one line to
Brain/log/pref-audit/<pref-id>.jsonlwith the agent, reason, and revision + content-hash before/after. Counter-only churn is suppressed, so a no-op run writes no audit line.Temporal extraction. On promotion an empty
valid_from/valid_untilis filled from the source signal - explicit bi-temporal fields first, else formal ISO-8601 tokens parsed from the signal text (no localized month/day names).Heal enrichment (opt-in via
dream.heal_enrich_enabled, default off). The heal phase links exact title/alias mentions across user vault pages outside the Brain root. Disabled by default so the install stays byte-identical.Salience gate (opt-in via
dream.salience_threshold, unset by default). The pass's only model delegation is the count-triggered rollup ladder, so the gate governs exactly one thing: which facts are counted into the ladder's fold set. The score is a deterministic weighted sum of the decayed outcome mass, the Wilson-bound confidence and the observed-reuse rate - no model, no cache. Unset, the gate is open, scores nothing, reads nothing, and the run is what it always was; set, every held-back fact is named with its score in the run summary.
Vault portability + session economy (v0.22.0)
A portability/ subsystem of deterministic primitives, all opt-in or
no-op by default:
Session codec. A pure lossless
compress/expand(expand(compress(x)) === xfor all input). Token savings come from reversibly collapsing whitespace/blank-line runs behind a Private-Use-Area marker; code and structured tokens are preserved byte-for-byte. Opt-in on the signal store (gated by a_raw_codecmarker the reader keys off) and exposed aso2b brain codec.Sources dashboard.
o2b brain sources/brain_sourcesaggregate the brain's signals by (agent, source_type) - a read-only projection.Vault-map tokens.
{{role}}tokens resolve to user content folders via an optionalBrain/_vault-map.yaml(defaults otherwise), wired into scan-inline read paths and graph import. The fixedBrain/*layout is never routed through the resolver.Multi-vault profiles. A
profiles.jsonregistry beside the config;resolveVaultprefers the active profile over the barevaultkey. Activation is a pointer (no symlinks - they sync inconsistently).Graph export/import. A stable
graph.jsonof the user's pages (wikilinks + typed relations) and an importer with skip/overwrite/merge modes, every write guarded byensureInsideVault.
Confidence formula
Confidence is computed for every active preference on every dream pass:
flowchart LR
Inputs[applied_count, violated_count,<br/>last_evidence_at, age] --> Q1
Q1{applied ≤ low_max_applied<br/>OR<br/>violated ≥ applied?}
Q1 -- yes --> Low[low]
Q1 -- no --> Q2{applied ≥ high_min_applied<br/>AND violated = 0<br/>AND fresh?}
Q2 -- yes --> High[high]
Q2 -- no --> Medium[medium]Defaults from _brain.yaml:
low_max_applied: 2— rules with two or fewer applications staylowuntil they prove themselves.high_min_applied: 10— high confidence requires ten clean applications.high_freshness_factor: 0.8— "fresh" meansnow - last_evidence_at < stale_evidence_days * 0.8.stale_evidence_days: 90— the boundary for fresh / stale.medium_min: 0.40,high_min: 0.75— derived-band thresholds on the numericconfidence_value(see below).
All six are tunable per vault in Brain/_brain.yaml.
Numeric confidence_value
Each preference also carries a continuous
_confidence_value: 0.0–1.0 field, computed alongside the band on
every dream refresh. The value is the Wilson 95% lower bound on
applied / (applied + violated) modulated by freshness decay
that runs linearly from 1.0 at age 0 to 0.0 at
stale_evidence_days. The band is the maximum of the legacy
step-function and a numeric-threshold view (medium_min,
high_min) — so legacy boundaries stay intact and numeric tuning
can only lift bands, never demote them. The digest's
## Confidence shifts section uses the value to spot drops between
runs; the MCP brain_query response and Brain/active.md both
expose the number alongside the band for inspection.
Active preferences injection
A confirmed rule is useless if the agent does not see it during work.
Four cooperating surfaces close that gap (the first three since
v0.9.1, the fourth — brain_context — since v0.10.10), all derived
from the same source of truth (Brain/preferences/):
flowchart LR
Dream[dream pass] -- regenerate --> Active[Brain/active.md]
Pin[o2b brain pin / unpin] -- regenerate --> Active
Active -- "SessionStart hook<br/>(startup | resume | clear | compact)" --> ClaudeStart["Claude Code / Codex<br/>session start + after /compact"]
Active -- "osb://preferences/active" --> MCPHost["MCP host (Hermes, others)"]
Active -- "brain_context tool" --> PullRT["Cursor / Aider / raw API<br/>(no SessionStart hook)"]
Pinned[Brain/pinned.md] -- "brain_context tool" --> PullRTBrain/active.mdis a derived Markdown digest: confirmed preferences (id, scope, confidence, principle), aMost-applied (Nd)section ranking confirmed/quarantine rules byapply-evidence (result: applied)events in the trailing window (defaults to 30 days / top-10; configurable viaactive.most_applied_window_days,active.most_applied_limit, and the SessionStart injection budgetactive.inject_budget_charsin_brain.yaml), quarantined preferences with their applied / violated counters, and the three most recently retired entries. The writer is idempotent — if the rendered body matches the file on disk, no I/O happens. The same window / limit drive a mirroredMost-applied (Nd)section inbrain_digestoutput.Brain/lessons.mdis a second derived digest that folds the positive-knowledge (preferences + theirapply-evidence appliedrows) and negative-knowledge (violated/outdatedrows andBrain/dead-ends/) into ONE outcome-tagged corpus scored by a signed, recency-decayed weight (shared 30-day half-life from working-memory continuity; tunable vialessons.half_life_days). Each lesson lands in a tier: preferred (net-positive and corroborated by ≥N distinct results —lessons.corroboration_min, default 2), tentative (net-positive but under the corroboration floor), contested (both positive and negative recent evidence — the shown stance follows the sign of the decayed score, so recency wins), or avoid (dead-ends and rules whose recent evidence is net-negative). Rendered top-N (lessons.limit, default 20), ranked by decayed salience. Regenerated at the tail of everydreampass alongsideactive.md, with the same idempotent write.Brain/standing-rules.mdis the one preamble lane the operator writes by hand. It is injected first, ahead of the runtime notices and every recalled body, under a header stating that it takes precedence over them. It is exempt from the injection budget and capped by its ownactive.standing_rules_max_chars(default 4,000); a capped block says how many lines and characters survived out of how many, and names the file. Because it is read outside the fail-open memory load it survives a memory-layer failure and is never written to the inject cache, so a stale copy can never be served in its place. If the file cannot be read, the block says so and states that no standing rules are in force - it never degrades to silence. Two mechanisms, not one, keep it out of an agent's reach, and the claim is exactly as wide as they are: the file sits underBrain/, whose first path segment the note-target resolver refuses for the four caller-named note-write tools (brain_create_note,brain_update_note,brain_append_note,brain_write_batch), andbrain_labels- which takes a caller-named path but resolves it through the weaker containment-only resolver - is refused by a named guard that knows this one file (and, since v1.70.0, theBrain/standing-rules/directory beside it). Nothing generates or rewrites it.SessionStart hook (
startup | resume | clear | compact) injects the body asadditionalContextso the agent sees current rules at the start of every session and again after/compact- thecompactmatcher replaced the former PostCompact injection, whose event no longer exists in current Claude Code. The injected body is budgeted (active.inject_budget_chars, default 8,000 chars): sections first degrade to headline tiers - a section below the keep-guard priority (the preamble and the confirmed rules are exempt) keeps its heading, its lead-in lines and its top three bullets, ranked by the inline application/confidence tags (a section whose lines carry none of the known tags keeps its first three in render order) - and then drop deterministically (recently retired first, then quarantine, then most-applied). A one-line notice names the sections that were reduced to headlines and the sections that were dropped, how many characters survived out of how many, and points the agent atbrain_contextfor the full set. WhenBrain/lessons.mdexists, its (separately budgeted) body is appended so the unified lessons corpus loads on the same surface. Fails soft - every error path in the memory lane exits 0 and the runtime proceeds unaffected, emitting no memory context. The standing-rules block is the one exception, by design: it is read outside that boundary, so a vault with an operator rules file still speaks even when the memory layer is down, and a rules file that cannot be read is stated rather than skipped. The hook emits nothing at all only when there are NEITHER standing rules NOR memory. Asubagent-injectcarrier closes the one gap this session-start event cannot: a delegated sub-agent turn never saw the SessionStart payload, so the carrier delivers the same composition - the operator standing-rules block, the scoped rules and the budgeted active/lessons digest - on theSubagentStartevent, before the sub-agent's first prompt, and again if the sub-agent compacts; on runtimes without that event the same hook falls back to the write-shaped PostToolUse call, once per sub-agent per session (the delivery ledger keeps the most recent 200 agent ids, so a session that spawns more than that re-delivers to an evicted id) and only when the composed payload is non-empty - the agent doing the writes is the one agent that must not run without them.End-of-turn hygiene digest (opt-in): with
hygiene_digest_enabledin the machine config (~/.config/open-second-brain/config.yaml, the same discovery thereground_parts_enabledflag reads; it has no_brain.yamltemplate entry), default off, env overrideOPEN_SECOND_BRAIN_HYGIENE_DIGEST_ENABLED, the Stop hook runs the default hygiene detector sweep plus the index-backed dangling-link count after any turn that wrote an artifact and folds the warning- and action-severity findings into ONE line, so small maintenance does not wait for an explicitbrain_hygienecall. A hash ledger beside the hook-state directory keeps an unchanged finding set silent - once per change, not once per turn; zero eligible findings and flag-off installs emit nothing. The line rides Claude Code's non-blocking Stop feedback channel only; on runtimes where a Stop block would force a continuation turn the hook stays silent.MCP Resources expose the same content for hosts that prefer pull access (
osb://preferences/activeand friends in the table above). The MCPinitializereply advertises theresourcescapability so clients know to enumerate them.brain_contextMCP tool (v0.10.10) lives in the always-loaded writer-scope MCP server. Runtimes that lack aSessionStarthook and do not auto-load MCP resources (Cursor, Aider, raw Claude API) can fetch the sameactive.mdbody, pinned current-task context, and active-preference counts with a single tool call. The standing-rules block leads that content too, and rides an optionalstanding_ruleskey (path, content, truncated) omitted when the file is absent.Brain/pinned.mdis a transient scratchpad for current-task facts. Agents update it throughbrain_pinned_contextwhen a fact should survive context rotation but should not become a durable preference. Clearing it leaves the permanent Brain history untouched.
active.md is not edited by hand — o2b brain dream and pin /
unpin re-derive it from preferences/ and retired/. Deleting the
file is harmless: the next dream regenerates it.
Scoped operator rules
Since v1.70.0 the operator can also write rules that hold for one
project, one harness or one machine, one file per scope value, beside
the vault-wide Brain/standing-rules.md:
| File | Matches when |
|---|---|
Brain/standing-rules/project/<key>.md | the session runs in a project linked to the vault whose key is <key> |
Brain/standing-rules/harness/<harness id>.md | the MCP server was launched for that harness |
Brain/standing-rules/host/<device id>.md | the session runs on the device with that id |
The scope identity is resolved by the server from facts the operator or the packager wrote, never from anything a caller names about itself:
Project: the directory holding the nearest
.o2b-vault.jsonpointer found from the workspace directory (the pointero2b brain project linkwrites), by its basename. The hook uses the payload'scwd(else its own working directory), the MCP server the directory it was started in. Only a pointer that names the vault serving the session counts: a pointer naming another vault, or one that cannot be read, is skipped and the walk continues upward. No such pointer matches no project file.Harness: the
--harness <id>option ofo2b mcp, falling back to--host-target. The Claude Code plugin registers both of its servers with--harness claude-codeand the Hermes plugin launches its bridge with--harness hermes; install adapters already write--host-target. A server launched with neither matches no harness file.Host: the device id (
device_idin the config, orO2B_DEVICE_ID). An empty device id is the explicit opt-out and matches no host file. A device id that cannot be read applies no host file either, and whenBrain/standing-rules/host/holds a file the block ends with one sentence saying so and namingo2b brain doctorfor the cause.
A project or device name becomes a key by lowercasing it in Unicode
normal form NFC and turning every run of characters that is not a
letter, a combining mark or a digit, in any script, into one -,
without leading or trailing - and at most 64 characters: a project
folder My_Repo.v2 keys to my-repo-v2, and a name written with vowel
signs or accents keeps them, so Café and café share the key
café.
The matching files are joined under one ## Scoped operator rules
header, which states the precedence: the operator standing rules above
take precedence over them, and they take precedence over every recalled
preference, lesson and context pack that follows. Each file gets a
### Project: <key>, ### Harness: <key> or ### Host: <key>
subheading, and its own text follows as written. A file that cannot be
read is replaced by one line, UNAVAILABLE: <vault-relative path> could not be read (<error code>)., and so is a file or an axis folder that is
a symbolic link leading out of the vault, with the code ESCAPE; an
empty or missing file is no rule.
Where each layer renders:
SessionStart hook: project and host files, right after the operator standing rules and before the active body. Harness files do not render in the hook in this release, because the hook has no launch-time harness of its own.
brain_context: project, harness and host files, after the standing-rules block and before the memory body, plus an optionalscoped_ruleskey with the resolved scope and the matched files (seemcp.md).Reach: the layer renders only for a local caller. Below local reach the server's working directory and device are not the caller's, so neither the block nor the key appears and the device id is not read.
The block's sections are capped by active.scoped_rules_max_chars
(default 2,000, at least 200); the cap covers the sections only, not the
header or the notices. When the cap cuts anything the host file goes
first, then the harness file, then the project file, and the block ends
with a notice of the characters kept and the files dropped; a file whose
rules cannot fit at all is dropped whole rather than left as a bare
subheading. Unlike the vault-wide file, the block is charged against the
injection budget in the SessionStart hook: there the header and room for
the notices ride inside active.inject_budget_chars, so the section cap
is the smaller of scoped_rules_max_chars and what the budget leaves
after them (never below zero), the rendered block is subtracted from
inject_budget_chars before the active body is budgeted, and the
block's length is recorded as scoped_rules_chars on the hook's receipt
(see observability.md). The files are read on every render and never cached, so one
project's rules are never replayed in another.
The directory is as far out of an agent's reach as the vault-wide file:
every write path that refuses Brain/standing-rules.md refuses any path
inside Brain/standing-rules/ too, compared as written and after
resolving symbolic links, and nothing generates or rewrites the files.
CLI / MCP surface
The same operations are reachable through two channels. Read columns are mirrored in MCP; destructive operations are CLI-only by design.
| Operation | CLI verb | MCP tool | Side effect |
|---|---|---|---|
| Bootstrap layer | o2b brain init | — | creates Brain/ skeleton |
| Record taste signal | o2b brain feedback | brain_feedback | writes signal + log event (sanitised) |
| Consolidation pass | o2b brain dream | brain_dream | mutates preferences/retired, atomic snapshot, regenerates Brain/active.md |
| Record application | o2b brain apply-evidence | brain_apply_evidence | appends log event; result ∈ {applied, violated, outdated}; artifact supports [[file:120-145]] ranges |
| Record milestone | o2b brain note <text> | brain_note | appends one note log event to Brain/log/<today>.md plus the JSONL sidecar. Multi-line collapses to one line. CLI mirror exists for cron / shell. |
| Pin current-task context | — (use MCP) | brain_pinned_context | writes/appends/clears Brain/pinned.md; transient context, not a learned preference |
| Pull active context | — (use MCP) | brain_context | read-only; returns Brain/active.md body, pinned context + counts. Always-loaded on the writer MCP server so runtimes without a SessionStart hook (Cursor, Aider, raw Claude API) can fetch it at session start. |
| Render summary | o2b brain digest [--window Nd] | brain_digest | read-only; includes top_applied, top_referenced, and agent_summary sections |
| Pre-dream intent review | o2b brain intent-review | brain_intent_review | read-only; classifies active signal clusters as ready, needing evidence, or conflicted before the next dream |
| Lifecycle retention review | o2b brain retention | brain_retention | read-only; recommends keep / improve / park / prune for retired preferences and processed signals; no artifact is deleted or moved |
| Monthly synthesis | o2b brain monthly | brain_monthly_review | read-only; month-level counters for events, status transitions, retirements, contradictions, and neglected areas |
| Inspect state | o2b brain query | brain_query | read-only |
| Computed backlinks | o2b brain backlinks <id> | brain_backlinks | read-only; inverted reference map across preferences/, retired/, log/ |
| Validate invariants | o2b brain doctor | brain_doctor | read-only; structural lints + semantic-health findings; --remediate [--dry-run] applies auto-safe content-hash re-stamps |
| Semantic health | o2b brain health | brain_health | read-only; contradictions, concept gaps, stale claims + clean/watch/investigate verdict |
| Preference edit-history | o2b brain history <slug> | — (CLI-only) | read-only; one entry per content mutation (principle / scope / status before -> after) |
| Full-text search | o2b search "<query>" | brain_search | FTS5 + optional semantic; write-classified MCP tool (persists access and opt-in reinforce/telemetry ledgers under Brain/search/) |
| Manage search index | o2b search index | reindex | status | check | — (CLI-only) | builds / inspects <vault>/.open-second-brain/brain.sqlite. search check ends with a recommendations: block on missing pieces (key, sqlite-vec, first reindex) |
| Cron template for reindex | o2b search reindex --cron-template [--interval N] | — (CLI-only) | prints a watchdog script, native crontab line, and hermes cron create recipe to stdout (writes nothing) |
| Operational snapshot | o2b status | second_brain_status | read-only; brain.* + search.* blocks |
| Retire manually | o2b brain reject | — (CLI-only) | requires --reason "<text>"; subsequent signals on the same topic are suppressed |
| Merge near-duplicate prefs | o2b brain merge <keep> <drop> | — (CLI-only) | folds evidenced_by and counters into keep; drop retires with reason merged-into; surfaced as candidates in brain_digest |
| Toggle pin | o2b brain pin / unpin | — (CLI-only) | flips pinned field; regenerates Brain/active.md |
| Protect Brain/ | o2b brain protect / unprotect | — (CLI-only) | machine-enforced deny rules for claudecode / codex runtimes; sidecar manifest at .open-second-brain/protect.lock.json |
| Restore snapshot | o2b brain rollback | — (CLI-only) | overwrites Brain/ from snapshot; from v0.10.6 aborts on drift unless --force-rollback, see Snapshots and rollback |
| Upgrade managed files | o2b brain upgrade | — (CLI-only) | migrates the three release-owned files (_brain.yaml, _BRAIN.md, _OPEN_SECOND_BRAIN.md) forward. _brain.yaml is text-merged additively (user values, comments, and ordering preserved); the other two are byte-compared and overwritten. --dry-run prints a per-file plan; --check exits 2 on pending updates (CI-friendly); --apply --yes applies the plan it showed: a file edited after the plan was read is refused before the upgrade-<ts> snapshot, and again at each write, instead of being overwritten. After a plugin update the MCP server and the SessionStart hook run the same upgrade in a detached worker, off the start-up path, and back off after a failure (see docs/updating.md). |
| Export active prefs | o2b brain export --format json|llms-txt | — (CLI-only) | read-only dump of confirmed | unconfirmed | quarantine preferences from Brain/preferences/. --out <path> writes a file (refuses to overwrite without --force); default sink is stdout. Retired and signal artifacts are deliberately excluded. |
| Export a transcript corpus | o2b brain export --format transcripts-jsonl --transcripts <file|dir> | — (CLI-only) | since v1.50.0: one JSON object per line, one per CONVERSATION, built from recorded session logs. Reads no vault. Messages carry role, text and the NAMES of the tools a turn called, never the tool inputs. --runtime filters by adapter; --since/--until select whole conversations by their start. Guarded record by record; a secret-shaped identifier refuses the whole export and writes nothing. Full contract in cli-reference.md. |
| Force-directed explorer | o2b brain explorer [--port | --export] | — (CLI-only) | live HTTP on 127.0.0.1 (default :7777) or single-file HTML at <path>; renders preferences + retired as a graph; zero backend. Keyboard-accessible <ul role="listbox"> mirror of visible nodes (ArrowUp/Down/Home/End/Enter/Escape); layout + filter state persisted to localStorage under osb-explorer-layout:<vault_basename>; "Reset layout" button clears the key. |
| Import Claude memory | o2b brain import-claude-memory | — (CLI-only) | imports metadata.type=feedback entries from a Claude Code memory directory into Brain/preferences/. --dry-run prints a per-file plan; --apply --yes takes an import-claude-memory-<ts> snapshot before writing. Sidecar manifest Brain/.imports/claude-memory.json keys idempotency on body sha256. UPDATE preserves the eight evidence-related frontmatter fields. CONFLICT (existing pref outside the manifest) lands the safe writes and exits 2. |
| Daily discipline report | o2b discipline {report|install|uninstall} | — (CLI-only) | renders a deterministic Telegram MarkdownV2 block comparing per-agent brain-events vs runtime-agnostic activity (git/mtime/vault delta) plus a complexity-to-thinking ratio. install writes a Hermes cron entry (--telegram-target required; --weekly installs a Monday digest; uninstall --weekly removes only the weekly job). alert fires when taste events (feedback+apply_evidence) are zero while activity is non-zero, or when structural churn grows much faster than recorded thinking. |
Operations that change the protected set (pin, unpin,
reject, rollback) and the index lifecycle (search index,
reindex, check) are kept off the MCP surface — protected-set
moves so an autonomous agent cannot quietly alter what is shielded
from automatic retire, and index management because it is operator
business, never agent business.
In addition to the tools above, MCP hosts can pull structured Brain content as resources (added in v0.9.1):
| Resource URI | Body |
|---|---|
osb://preferences/active | Brain/active.md (auto-regenerated on first read if missing) |
osb://digest/latest | most recent brain_digest rendering |
osb://status | Markdown form of the second_brain_status.brain block |
osb://preference/{id} | one pref- / ret- file |
osb://topic/{slug} | every signal + active/retired pref for a topic |
osb://log/{date} | one day's Brain/log/YYYY-MM-DD.md (a parallel YYYY-MM-DD.jsonl sidecar carries the same events as one JSON row per line for machine consumers) |
osb://backlinks/{id} | inverted reference map for <id> |
Resources are pure read; mutating verbs stay on tools/call.
Snapshots and rollback
A snapshot is taken before any state-changing dream run:
flowchart TD
Trigger[dream wants to mutate] --> CS[createSnapshot]
CS -- failure --> Abort([abort; nothing changed])
CS -- success --> Mutate[apply mutations]
Mutate --> Log[append dream log event]
Log --> Prune[pruneSnapshots to retention_count]
RB["o2b brain rollback RUNID"] --> Restore[restoreSnapshot]
Restore --> Extract[extract .tar.zst into tmp]
Extract --> Replace[replace Brain/ entries<br/>excluding .snapshots/]
Replace --> LogR[append rollback log event]A snapshot captures every file under Brain/ except .snapshots/
and .artifacts/ — the first because rollback would otherwise erase
any snapshots taken after this one, the second because it is TTL'd MCP
tool output documented as never backed up (archiving and hashing it
made unrelated cache churn trip the drift gate). Retention defaults to
ten newest archives.
The archive is streamed: tar writes a staging file and the compressor
reads it, so the size of Brain/ sets no memory ceiling. The compressor
writes a partial file in .snapshots/, and the archive appears under its
final <run_id>.tar.zst name only once it is complete, published by an
exclusive hard link that refuses a name already taken; the manifest is
written after that. A snapshot interrupted during compression therefore
leaves no recovery point behind, only a <run_id>.tar.zst.partial-* file
that nothing lists; retention removes it once it is a day old.
From v0.10.6 every snapshot ships with a SHA-256 sidecar manifest
(Brain/.snapshots/<run_id>.manifest.json) listing every regular
file the snapshot covers — the same tree, minus the two excluded
entries named above — and its hash. Hashing a file the archive does
not contain and the restore does not replace would fire the drift
gate on churn no rollback could ever undo. o2b brain rollback reads the
sidecar back, rebuilds a fresh manifest from the live tree, and
compares: any added / removed / changed entry aborts the rollback
with exit code 2 and a compact drift report on stderr. The intent
is to refuse silent overwrites of Syncthing-delivered edits made on
another device between snapshot and rollback. Pass
--force-rollback to override; the resulting rollback log row
records drift_overridden: true. Snapshots predating v0.10.6 have
no sidecar — rollback emits a stderr warning, skips the drift
check, and falls through to the legacy direct-restore path so old
archives still recover cleanly. The same sidecar primitive backs
o2b brain upgrade --dry-run's per-file diff: both features share
src/core/brain/manifest.ts as the single source of truth for
"what does Brain/ look like right now".
Derived-store coverage
The derived SQLite store (<vault>/.open-second-brain/brain.sqlite)
is a sibling of Brain/, not a member of it, and it used to be in no
snapshot, no manifest and no rollback — with nothing saying so, so a
pre-restore diff rendered a complete-looking picture while the
embeddings silently stayed at whatever the live store held.
What coverage protects is spend, not information. Feedback,
activation and tuning are replayable JSON folds inside Brain/; only
the embeddings and a tier baseline are database-only, so a lost store
costs an embedding bill and a reindex, never a fact. Against that:
retention keeps ten archives and .snapshots/ rides the same
peer-to-peer replication as the rest of the vault, so every retained
copy lands on every device. Hence the three-part answer:
Opt-in.
snapshots.include_derived_storedefaults tofalse.A ceiling that refuses.
snapshots.derived_store_max_bytes(256 MiB by default) is compared against the live store and the snapshot throws when it is exceeded, naming the measured size. Half a database is not a recovery point, so it is never truncated.The live store size in every manifest, included or not, so an operator who has never enabled coverage still sees what it would cost.
When coverage is on, createSnapshot resolves the store path through
the search layer's resolver, refuses when the file is absent, takes the
same writer lock every indexer serialises on, runs the structural
integrity scanner and refuses a condemned store rather than archiving
it over a good snapshot, checks the ceiling, then VACUUM INTOs a
temporary file and compresses it to
Brain/.snapshots/<run_id>.store.sqlite.zst. VACUUM INTO rather than
a file copy because the store is a live SQLite database - WAL mode on a
local backing, DELETE on a remote-backed file - and the runtime exposes
no online-backup API. The archive sits beside the tar rather than
inside it: the extractor requires a Brain/ root and the restore is
defined as "live Brain/ equals archive Brain/ minus the excluded
entries", so a second top-level tar member would break both contracts.
Keeping it in the same directory keeps listing and retention one
family with one loop.
Any of those steps failing is a refusal, not a skip: createSnapshot
throws and leaves no tar behind, so the destructive-snapshot gate —
which already aborts before the operation when the snapshot throws —
simply does not run the mutation it could not fully protect.
The manifest records what happened under an additive derived_store
key at the existing schema version (bumping it would make every
older peer silently lose drift detection on new snapshots): whether it
was included, the resolved source path, the archive name, the digest
over the archived bytes, the compressed size, the live store size
always, and one of four named exclusion reasons —
not-requested, absent, integrity-fault, over-size-ceiling.
A sidecar with no derived_store key predates the feature and
renders as unknown, never as excluded.
Retention deletes the store archive alongside the tar and the sidecar.
rollback replaces the store only when the manifest says it was
included, decompressing to a sibling temp file and renaming it into
place under the writer lock — the same swap discipline the indexer
uses — and prints in every case which answer applied: replaced,
not restored with the recorded reason, record missing when there
is no record but a store archive is sitting beside the tar (the sidecar
write is non-fatal, so coverage can run and leave nothing behind saying
so), or unknown when there is neither a record nor an archive. The
third and fourth used to share one sentence that named the feature as
absent while the feature's own archive was on disk.
Why a recovery point was taken
Two histories existed here and did not join. The Brain event log is
richly typed, per-device sharded and machine-primary, and it recorded a
rollback; the snapshot family is the only revertible history, and
every entry in it was an opaque run id plus an mtime. The reasons existed
de facto — as run-id prefixes at five call sites, three of them inline
string literals — and nothing parsed them back, so an operator could not
ask which recovery point covers a given boundary, could not filter the
revertible history by why it happened, and could not walk from a logged
event to the archive that would undo it.
Every snapshot now carries a typed reason from one closed vocabulary
(BRAIN_SNAPSHOT_REASON): dream, upgrade, import-claude-memory,
delete-by-source, entity-prune, note-delete, note-revert,
payload-gc for the eight destructive
call sites, plus manual for the recovery point a rollback takes of the
live tree it is about to discard (restoreSnapshotWithRecoveryPoint in
snapshot-gate.ts, reached from o2b brain rollback). Three members have
no producer in this release — session-boundary, plan-boundary and
decision-boundary, for boundaries a later release may snapshot at. No
CLI verb and no MCP tool mints a recovery point on demand: the
take-snapshot entry point is reached only by the destructive-operation
gate, the rollback's own recovery point included. The reason is required by
createSnapshot, doubles as the run-id prefix so the filename and the
recorded provenance can never disagree, and is stamped into the manifest
sidecar as an additive snapshot_reason key at the existing schema
version — same reasoning as the derived-store key: bumping it would make
every older peer silently lose drift detection on new snapshots.
A sidecar with no reason reads as unknown and the reason is never
reconstructed from the run-id prefix that happens to spell it. That
inference would look correct on almost every archive this project writes,
and would manufacture provenance for an unstamped, hand-named or
third-party archive at exactly the moment an operator is deciding whether
to overwrite their live tree with it. An unregistered reason fails the
whole manifest closed, as one malformed file entry already does.
Each created recovery point also emits one snapshot log event carrying
the run id, the reason and the archive size — the counterpart the log has
been missing since rollback shipped, which recorded the restore while
the point it restores to left no trace but a filename. The append is
best-effort: createSnapshot runs before the mutation the
destructive-snapshot gate protects, so a throw would turn a lost audit
line into a refused mutation.
Taking snapshots at session, plan and decision boundaries is
deliberately not shipped. That changes how often snapshots happen,
which interacts with retention and with the optional derived-store
archive. An on-demand snapshot verb is not shipped either, for its
own reasons: every recovery point this build takes exists to protect a
specific mutation that is about to run, and an operator-triggered
snapshot is a separate decision about retention pressure and about what
an operator does with a point nothing was going to overwrite. The
vocabulary carries all four members anyway so this build can read a
sidecar a later release writes and replicates back — which also means
snapshot log --reason manual is a valid filter over the recovery points
o2b brain rollback takes of the tree it replaces. Those are local
archives and the operator's way back out of a rollback, not foreign ones.
Read-only inspectors over the snapshot family
With snapshot log the family is complete: log / diff / revert.
o2b brain snapshot log— newest-first listing of every recovery point (by archive mtime, because a hand-named run id carries no timestamp to sort on): run id, created-at, reason, archive size, whether a drift manifest is present, and the derived-store record.--reason <reason>filters by why it happened and rejects an unregistered value with a usage exit (2) rather than an empty listing, which would say "you have no such snapshots" when the truth is "that is not a reason".--limit <n>caps the listing,--jsonyields the structured rows. An empty snapshots directory exits 0; a snapshots directory that exists and cannot be read exits non-zero naming the path, because "no snapshots available" over an unenumerable directory is the same substitution one line up.
Two further surfaces share the same diff renderer over the snapshot
extraction primitive (extractSnapshotToTemp), so previewing and
auditing stay byte-equal:
o2b brain rollback <run_id> --dry-run— extract the archive into a sibling tmp dir, compute the live → snapshot diff, print it, drop the tmp dir. Mutually exclusive with--yessince preview-vs-execute is contradictory. No live writes; no log entry.o2b brain snapshot diff <run_id_a> [<run_id_b>]— same renderer, but compares snapshot ↔ snapshot when both ids are supplied (and snapshot ↔ live when only one is).--jsonyields the structuredBrainTreeDiffpayload for scripting.
rollback --list, the confirmation prompt and the --json result all
name the reason too — it is the one fact that distinguishes two archives
minted seconds apart.
The diff classifies every file under each root into six artifact
kinds (preference, retired, signal, log, config, other). Preference
and retired files receive a typed field-level diff for the derived
counters (_status, _applied_count, _violated_count,
_confidence, _confidence_value, pinned, and a few identity
adjuncts); other kinds compare by byte equality and surface as
(body changed).
The log kind covers every shape the log path helpers write —
<date>.md, <date>.jsonl and their per-device <date>.<deviceId>.…
shards — and it decides that through the recogniser in log-jsonl.ts,
the module whose contract is that the shard layout lives in exactly one
place. The differ used to carry a pattern of its own, which is how it
came to predate sharding and classify both the per-device markdown and
the machine-primary JSONL as other, mislabelling the surface a
rollback --dry-run is most likely to be showing. Ledgers and
subdirectories under log/ (capture-decisions[.<deviceId>].jsonl, dream-runs/,
pref-audit/) are not days of the log and stay in the catch-all class.
Every append-only ledger that several synced machines write keeps one
file per device, named <stem>[.<deviceId>].jsonl: the fixed-name
Brain/log/ ledgers (query-demand, recurrence-support,
capture-decisions), the verifier-rejection ledger under
Brain/skill-proposals/, each preference's pref-<slug>.history, each
repository's git commits and the maintenance journal
.open-second-brain/maintenance-runs (merged by row time on read). The
ISO-week audit directories (session-lifecycle/, hygiene/,
secret-custody/, schema-mutations/, watchdog/ and the hook and
watchdog audit roots under .open-second-brain/) write
<YYYY-Www>[.<deviceId>].jsonl. O2B_DEVICE_ID / device_id select the
shard; the empty device id writes the legacy un-suffixed name, and a
legacy file stays readable without any migration. The full contract is in
observability.md.
Primary agent declaration
Brain/_brain.yaml carries an optional primary_agent: <name> | null
key. When set, dream runs invoked from a different agent emit:
a stderr warning of the form
warning: non-primary-dream-run: …,a
warningsarray entry on the MCPbrain_dreamresponse,a
non_primary_agent: <caller>payload row in the dream summary log event.
The dream pass still completes — the declaration is observability,
not access control. Set / clear via o2b brain set-primary <name>
or o2b brain set-primary --clear. A vault initialised with
o2b brain init --primary-agent <name> writes the value into the
fresh _brain.yaml. The full multi-device walkthrough is in
docs/cross-project-pointer.md.
Hygiene: sanitisation and lints
Two cheap, deterministic layers keep the data clean.
Input sanitisation (v0.9.1). Every Brain writer routes text fields
through src/core/redactor.ts plus a
normaliseTextField / sanitiseTextField pair:
C0 control characters are stripped (except
\tand\n);U+2028andU+2029collapse to\n; text is NFC-normalised.Length caps per field:
principle≤ 512 (single-line),scope≤ 128,raw≤ 4096,source[]items ≤ 512,artifact≤ 512 (single-line),note≤ 4096.Secret-shaped substrings (
api_key=…,token: …,bearer …,authorization: …,password=…,private_key=…, etc.) are masked with***REDACTED***. Therawfield is the only verbatim-quote surface and is intentionally not redacted — it carries the original user phrasing.Explicit
<private>...</private>regions are stripped before secret assignment redaction. An unclosed<private>tag strips from the tag to the end of the field, which is conservative for accidental paste leaks.Inputs that sanitise down to empty fall into the existing "missing field" error branch, so the writer never persists a placeholder signal.
Doctor lints (o2b brain doctor, brain_doctor MCP). All are pure
functions over the on-disk Brain state — no LLM, no network. The first
group are structural; the last three (v0.14.0) are semantic and also
populate the semantic_health report (see brain_health below):
| Code | Triggers when |
|---|---|
broken-backlinks | a [[pref-…]] / [[ret-…]] / [[sig-…]] reference exists in preferences/, retired/, inbox/, or log/, but the target file does not |
duplicate-preferences | pairwise Jaccard ≥ 0.7 on principle tokens within the same (topic, scope) bucket |
low-evidence-confirmed | status: confirmed with applied_count ≤ low_max_applied AND confirmed_at older than unconfirmed_window_days |
pinned-without-recent-evidence | pinned: true with no evidence or evidence older than stale_evidence_days |
malformed-evidence-range | apply-evidence artifact uses [[file:…]] range syntax but fails validation (:abc-def, :120-100, bare :) |
orphan-evidence | apply-evidence artifact wikilink does not resolve to any file in the vault |
contradictory-preferences | two confirmed preferences share a subject (principle Jaccard ≥ health.contradiction_jaccard) but carry an opposite sign of record |
concept-gap | an entity recurs across ≥ health.concept_gap_min_frequency corpus entries with no covering preference topic |
stale-claim | a confirmed preference's newest evidence is older than health.stale_claim_max_age_days |
That table is a selection, not the registry. Later releases added codes
that are documented alongside the feature that produces them in
docs/cli-reference.md - for example symlink-escape,
entity-label-malformed, brain-root-absent, and the v1.46.0
recall-channel-silent / recall-channel-unmeasured pair. The complete
enumeration lives in the source (src/core/brain/diagnostics.ts for codes
with a registered next command, src/core/brain/doctor-exits.ts for those
without) and is CI-enforced by tests/core/brain/doctor-exit-census.test.ts,
which fails on a code that appears in neither table.
Since v1.69.0 the orphan-evidence and malformed-evidence-range
findings carry sources (the preference the evidence is about) and
removed-tool-reference carries path (vault-relative on the MCP
wire), so every finding names its record structurally and brain_doctor
leaves out a finding about a record the caller cannot read at its reach.
With --strict, warnings demote ok to false so CI can gate on
hygiene. brain_doctor itself stays read-only — auto-modifying state on
a plain doctor run would break the "explicit-driven" invariant. The
explicit opt-in o2b brain doctor --remediate [--dry-run] is the only
writer: it plans a dependency-ordered repair and applies the auto-safe
fixes (currently the lossless content-hash re-stamp), bounded by
health.remediation_step_cap.
flowchart LR
BrainRoot[Brain/]
BrainRoot --> Yaml[_brain.yaml]
BrainRoot --> Manual[_BRAIN.md]
BrainRoot --> Inbox[inbox/]
BrainRoot --> Prefs[preferences/]
BrainRoot --> Ret[retired/]
BrainRoot --> LogD[log/]
BrainRoot --> Snaps[.snapshots/]
Yaml --> Archive((snapshot.tar.zst))
Manual --> Archive
Inbox --> Archive
Prefs --> Archive
Ret --> Archive
LogD --> Archive
Snaps -. excluded .-> ArchiveIntegration with agent runtimes
The same MCP tools are advertised to every runtime; only the wiring differs:
graph LR
Hermes -- "mcp_servers.yaml" --> Stdio["o2b mcp (stdio)"]
ClaudeCode["Claude Code"] -- "plugin.json mcpServers" --> Stdio
Codex -- "codex mcp add" --> Stdio
GrokBuild["Grok Build"] -- "config.toml [mcp_servers]" --> Stdio
OpenClaw -- "native JS plugin" --> InProc["in-process tools"]
Stdio --> Vault[(Brain/ on disk)]
InProc --> VaultHermes loads the MCP server via
mcp_servers:in~/.hermes/config.yaml. The agent surface also needs thebrain-memoryskill enabled in the active profile (viahermes-skills-sync enable <profile> brain-memory) so the LLM recognises preference triggers in conversation.Claude Code picks up the
mcpServersdeclared in the bundled.claude-plugin/plugin.jsonand the plugin-shippedbrain-memory/SKILL.mdautomatically.Codex registers the MCP server with
codex mcp add; the same skill bundle is loaded automatically.Grok Build registers MCP in
~/.grok/config.toml[mcp_servers.*](grok's primary source) and lifecycle hooks in~/.grok/hooks/(its native hooks dir), both with an absolutebun run …/main.tscommand, viao2b install --target grok. Verified against live grok 0.2.45: the bareo2bcommand does not resolve on grok's session-spawn PATH, so the absolute form is required. grok sessions (~/.grok/sessions/.../updates.jsonl, an ACP stream) import via thegroksession adapter. Seeinstall/grok.md.OpenClaw runs tools natively in the plugin's Node.js process (no subprocess, by security-scanner requirement).
Full-text search
v0.10.0 adds a deterministic search layer over the entire vault — not
just Brain/. The index lives at <vault>/.open-second-brain/brain.sqlite
(SQLite + FTS5) and is fully rebuildable from the Markdown files.
Semantic search is optional and pluggable: sqlite-vec + any
OpenAI-compatible /v1/embeddings endpoint when configured.
flowchart LR
subgraph Build [o2b search index]
direction LR
Walker[walker<br/>vault-relative paths] --> Chunker[chunker<br/>structural split + token packer]
Chunker --> Store[(SQLite store<br/>documents / chunks / links / embeddings)]
Chunker --> Links[extract wikilinks /<br/>markdown links / tags]
Links --> Store
Store --> FTS[(chunk_fts<br/>FTS5 BM25<br/>content + heading_path)]
Store -.optional.-> Vec[(chunk_vec<br/>sqlite-vec)]
Chunker --> Ents[extract entities<br/>structural / Unicode]
Ents --> Store
Texts[chunk content] -. on --embeddings .-> Provider[OpenAI-compat<br/>embeddings provider]
Provider --> Vec
end
subgraph Query [search]
direction LR
Q["o2b search '<q>'<br/>brain_search"] --> KW[FTS5 keyword<br/>limit × 3 candidates]
Q -. semantic on .-> SemQ[provider.embed query]
SemQ --> SemS[vec_search<br/>limit × 5 candidates]
KW --> Ranker[ranker<br/>BM25 + cosine + link<br/>+ recency + entity boost<br/>+ why_retrieved]
SemS --> Ranker
Ranker --> Trav[link-graph traversal<br/>retrieve-then-walk]
Trav --> MMR[MMR diversity rerank]
MMR --> Results[ranked results]
endKey behaviours, all driven from Brain/_brain.yaml-free search_* /
embedding_* config keys:
Chunker. Two passes: a structural split (headings, fenced code, lists, tables, frontmatter), then a token-budget packer with overlap. YAML frontmatter becomes synthetic
chunk_index: 0sotags:and other front-matter fields are searchable through FTS5 alongside the body.Store boundary.
src/core/search/store.tsis the single SQL home; every other module talks to it through a typed surface. WAL mode for concurrent reads - journal mode DELETE instead, announced once on the write open, when the index file sits on a remote filesystem (an index already in WAL keeps it while another connection has the file open, since leaving WAL needs exclusive access: the read open stays silent, the write open says so once and retries on its next open) -proper-lockfileon the index path for writer exclusivity (three attempts, 1 s backoff, thenINDEX_LOCKED).Embedding providers (v0.36.0).
embedding_providerselectsopenai-compat(remote/v1/embeddings),local(an offline feature-hashing embedder: token unigrams + character trigrams hashed into a fixed unit-normalised vector, no cloud / key / model download),disabled, or a name registered througho2b search provider add(which resolves toopenai-compatconfig after the built-ins). Onesignature.tskernel canonicalises<provider>:<model>:<dimension>and prices it;embedding_cost_gate_usdrefuses an over-budget run unless--force-cost.Embedding spend (v1.72.0). A price quote names its source:
builtin(the frozen table; the local embedder is builtin and free),operator(theembedding_price_model/embedding_price_usd_per_mtokpair, which wins over the table for the model it names) orunknown. One spend plan owns the pending census (optionally scoped by path prefix), the model resolution, the token estimate, the quote and the gate verdict. The embedding phase, the maintenance preview, the backfill dry run and thesearch statusrefresh estimate all read it, so what is announced, refused, spent and receipted is one computation. An unknown price stays unknown (null, printedprice unknown) rather than reading as $0, and under a positive gate it refuses withEMBEDDING_COST_UNPRICEDunless forced. Price is never part of the embedding identity.Vector carry-over (v1.72.0). Replacing a document's chunks keeps the stored vector of every new chunk whose content hash matches an old chunk of the same document (duplicates match as a multiset), as long as the old row's model and dimension equal the identity
index_staterecords; nothing is carried while that identity is unrecorded. Only the vectors not carried are purged, and the index run reports the count asembeddingsReused.Fusion modes (v0.36.0).
search_fusion_modeislinear(default, the weighted sum below) orrrf- Reciprocal Rank Fusion, which scores each candidateΣ w_lane/(search_rrf_k + rank_in_lane)across the keyword and semantic lanes, min-max-normalised to[0,1]. RRF ignores the configuredsearch_keyword_weight/search_semantic_weight- those calibrate two score magnitudes and this fusion reads none - butw_lanedoes carry the per-query intent profile (and the learned recall weights), so a quoted phrase favours the lexical lane in both fusion modes. A query that declares no intent weights both lanes 1, which is the classic weightless term. RRF replaces only the relevance term; every boost below still applies, andlinearis bit-identical to pre-v0.36.0 ranking.Ranking.
final_score = clamp01(keyword_weight·norm_BM25 + semantic_weight·cosine + link_boost + recency_boost + entity_boost)(linear mode; inrrfmode the first two terms are replaced by the fused reciprocal-rank relevance). BM25 is min-max-normalised within the candidate set; cosine is1 - L2² / 2on unit-normalised vectors; link boost rewards candidates that other candidates reference via[[wikilink]]/ markdown link (capped at 0.03) or share a tag with (capped at 0.02); recency is a configurable Weibull decay curve on the content's authoring instant —authored_atwhen the document declares one the reading clock can already have observed, and storagemtimeotherwise, so a batch of historical conversations imported today ages from when it was said rather than from when it was written to disk (search_recency_shape/search_recency_scale/search_recency_amplitude, v0.20.0), defaulting close to the prior step function and flooring effectively-stale content to zero; entity boost (capped at 0.04) rewards proper-noun overlap between query and chunk. Every result carries awhy_retrievedlist of the layers that fired.Recall layers (v0.13.0). After ranking, link-graph traversal walks outbound wikilinks from the top hits and merges in related documents scored
parent·hop_decay^hop(bounded bysearch_max_hops/search_max_expansion_per_hit); then MMR reranks the pool with a deterministic token-set similarity so near-duplicates do not crowd the top (search_mmr_lambda,1disables). An opt-in cap on the pool keeps one group from filling the window the caller draws from (search_pool_max_per_session, default0- off; since v1.83.0): a row's group is its source import session (session_refon imported signal notes,session_idon handoff notes) or, when a note names neither, its own path - all chunks of one note form one group, so bench session notes and multi-chunk notes are capped too. The cap runs after the caller's own filters, so only filter-passing rows consume a group's quota and the cap never surfaces a row those filters would withhold; it widens the pool rather than truncating it; a value at or above the query's resolved pool width behaves as off; and when its drops leave the window under-filled, one wider assemble pass re-runs so the window refills from the groups the cap did not exhaust. A vault whose notes each fit in one chunk is byte-identical with the knob on or off. Header-anchored chunking indexes each chunk's heading breadcrumb in a dedicated FTS column (weighted below content) so a mid-document chunk keeps its topical anchor. Entity and heading layers populate on the next reindex.Optional cross-encoder rerank (opt-in). A learned final reader step that re-scores the top-K fused candidates jointly against the query, appended after every heuristic rerank. Off by default (
search_rerank_enabled); when on, it resolves an OpenAI-compatible/rerankendpoint (search_rerank_base_url/search_rerank_model/search_rerank_env_key, or a profile registered viao2b search rerank-provider add) and re-orders thesearch_rerank_top_kcandidates by the returned relevance, promoting a genuinely-relevant hit the heuristic ranker placed deep. Zero HTTP cost and byte-identical ordering when disabled; a request-time endpoint error degrades to the heuristic ordering (arerank_degraded:warning, never a throw). Since v1.66.0 the same failure is also recorded in the retrieval trail asrerank-provider-unavailablewith a typeddetail.category, and such an answer is never written to the query cache, so a transient endpoint failure is retried on the next identical query. A configured model whose announced decommission date has passed is not called at all (rerank-model-sunset). Most valuable on thethoroughprofile.Query plan + recall economy (v0.20.0). A pure structural pass classifies each query's intent (neutral / exact / entity / broad) from its shape - quoted phrases, FTS wildcards, wikilinks, entity-token share, token count, no language word lists - and emits a bounded weight profile that re-weights the ranking layers (on by default,
search_intent_enabled; neutral is bit-identical). Opt-in synonym expansion (search_synonym_enabled) broadens recall by OR-ing in terms that co-occur across the top candidates' own content - language-agnostic, suppressed for exact intent. An opt-in persistent query cache (search_cache_enabled,search_cache_ttl_seconds) serves an identical request while the corpus generation (embedding model + dimension + schema + a content-reindex revision) is unchanged; cache reads and writes are best-effort.brain_context_packadditionally accepts per-memory and total character caps, andbrain_pre_compress_packreturns a budgeted top-preferences-plus-active.md addendum for pre-compression injection.Score-aware tail trim (config-gated). With
tail_trim_context_pack: "true"in the machine-local main config (or the env overrideOPEN_SECOND_BRAIN_TAIL_TRIM_CONTEXT_PACK; the default is off),brain_context_packruns one score-aware trim over the unified tier-and-budget walk whenever the call carries a query: the top three delivered items are immune, any later item whose query-term coverage - the fraction of the query's tokens it carries - is at least 0.8 of the top delivered score is kept (it restates the question's terms nearly as fully as the top hit), and the rest drop whole with atail-trimskip reason - never partially re-cut. The freed budget is then offered back toover-budgetcandidates in priority order, the only path by which a page can cross tiers, spending the tokens the trim freed and no more, and never reordering or dropping the synthesized attention item. The response carries a conditionaltail_trimblock (dropped,kept_by_query_terms,rescued,tokens_saved) only when the trim ran. With the key off - the default - packs are byte-identical, and thepacking_policybranch never trims.Semantic policy. Implicit semantic (config default) warns and falls back to keyword-only when sqlite-vec is unavailable, the key is missing, the provider is down, or no embeddings exist yet. Explicit
--semantic/semantic: trueon infrastructure failure throws a typedSearchErrorso a misconfigured run cannot hide. The data-state case (zero embeddings) always warns and skips — runningo2b search index --embeddingsis the right answer there.Semantic belief order (v1.72.0).
brain_context_packwithquery_mode: "semantic"orders the curated belief notes by meaning rather than by shared words. The query is embedded once (with the query prefix, through the configured provider), and each belief note scores the maximum cosine over its stored chunk vectors; nothing new is embedded for the notes themselves. Only vectors written by the configured model at the query's dimension count, and the scores are read only for candidates that survive the reach filter. A note without a usable vector sorts after the scored notes of its tier and is named in the response'ssemantic.unembedded. The mode refuses by name when the semantic tier is blocked, when sqlite-vec is not loadable, and when no kept candidate has a usable vector (BELIEF_VECTORS_MISSING, namingo2b search vector-backfill --path Brain/preferences/ --path Brain/retired/ --apply). The query embed is disclosed (model, price source, tokens, estimate); a remote caller's embed is refused withEMBEDDING_COST_UNPRICEDwhen the model has no known price andembedding_cost_gate_usdis positive, while a local caller's embed is not gated, as forsearch.One query-embed gateway (v1.73.0). The search lane and the semantic belief order ask the same gateway whether, and with what text, a query is embedded. It applies the caller's reach and the price gate before any provider exists (a caller that is not local, under a positive
embedding_cost_gate_usd, on an unpriced model, is refused), then fits the query, instruction prefix included, to the effective input window:embedding_input_window_tokens, then the curated model table, then unknown, which cuts nothing. The cut is at a code-point boundary under the ceiling token estimate, so it never sends more than the window and treats every language alike. A refused or cut query is disclosed by trail code (semantic-cost-unpriced,semantic-query-truncated, andsemantic-query-empty-fitwhen the instruction prefix alone fills the window). A refused answer is never cached; a cut one is cached under a key that carries the effective window and the query prefix, so a changed window re-keys it.Atomic reindex.
o2b search reindexwrites tobrain.sqlite.new, renames tobrain.sqlite, and keeps the previous file asbrain.sqlite.bak. If the main file is missing on open and a.bakexists, it is auto-restored with a stderr notice.Embedding model fingerprint. Model + dimension are recorded in the index. Changing either drops the
embeddingsandchunk_vectables on next open, logs one line, and preserveschunksandchunk_fts; the nexto2b search index --embeddingsrepopulates vectors.
The MCP brain_search tool returns at most 50 results with each
chunk's content capped at 600 characters - a window centred on the
first significant query term the chunk contains, marked with an ellipsis
on whichever side was cut, and the head of the chunk when it contains
none; diagnostic score
components (keywordScore, semanticScore, linkBoost,
recencyBoost) are intentionally absent from the MCP shape — they
live in the CLI's --verbose output only, to keep the agent context
small. Index-management verbs (index, reindex, check) are
CLI-only.
Since v1.46.0 both surfaces also report why an answer narrowed. A search
that returned fewer rows than it might have, or none at all, carries a
retrieval_trail naming the lanes that degraded and - when nothing
degraded and nothing matched - what the corpus can honestly be said to
hold. The codes are a closed vocabulary and the human transcript names the
cause on its no-results line instead of printing a bare (no results).
The vocabulary is in
cli-reference.md.
Full design and migration notes:
docs/plans/2026-05-16-brain-search-design.md
and the matching implementation plan
docs/plans/2026-05-16-brain-search-impl.md.
Keeping the index current (since v1.76.0)
The index is kept current by the reads that need it. When a search or a
session start finds last_indexed_at older than
search_freshen_interval_s (60 s by default), it answers from the
index as it is and starts one detached, low-priority incremental run
(o2b search index --freshen); an exclusive claim next to the index
keeps concurrent readers from starting one each, and a failed run backs
off from one minute to an hour. Nothing stays resident: with no agent
working, nothing indexes. Two indexer changes make frequent runs cheap:
a run that changed no document skips link and alias resolution, and a
changed document keeps every chunk whose position and content did not
change in place (row, full-text entry, entities, vector), so appending
to a 1 MB daily log costs about 0.2 MB of writes instead of about 9 MB.
Details: "Freshen on read" in docs/cli-reference.md.
Workspace reach and proactive insight (since v0.38.0)
Two suites extend where the Brain can be reached and what it volunteers.
Workspace reach: a .o2b-vault.json pointer file written by
o2b brain project link binds any project directory (repo, monorepo
package, worktree) to its owning vault; resolveVault walks up from
the working directory and honours the nearest pointer after the
VAULT_DIR env override and before the profile chain, so commands run
from a linked tree resolve the right Brain with zero per-command
flags. External vaults attach as read-only recall sources
(o2b brain source add), and o2b search <query> --global unions the
active vault, profile vaults, and sources with per-result origin
labels - reads only, never a write or an index build inside an
external vault. For shell-first use, o2b brain profile materializes
a compact Brain/profile.md digest plus a .o2bfs root marker, and
o2b brain sgrep answers with grep-shaped path:line: lines.
Proactive insight: o2b brain trigger scan converts semantic-health
and retention findings into Markdown trigger records under
Brain/triggers/ with a strict lifecycle (pending, delivered,
acknowledged, acted, dismissed, expired, suppressed). Stable cooldown
keys make scans idempotent, and the morning brief delivers pending
triggers at most once per cooldown window - the Brain remembers what
the operator already saw, dismissed, or acted on instead of
rediscovering it every run.
dismissed and acted carry a clock: after trigger_cooldown_days
the same finding may return. suppressed carries none, and it is the
answer for a finding the operator has judged structurally benign -
o2b brain trigger suppress <id> silences that cooldown key
indefinitely, from any state including an expired or already-acted
one. It is reversible: suppressing stamps the status it interrupted
and leaves the delivery and resolution instants untouched, so
unsuppress restores the original state together with its original
cooldown arithmetic rather than reconstructing them. A suppressed
trigger is terminal, so it is hidden from list, shown in history,
and never reaches the morning brief; list prints how many triggers
are currently suppressed so silence is always accounted for. Silence
is also audited: a scan whose candidate an existing record silenced -
suppressed, cooling down, still open, or dropped by the per-kind cap -
increments occurrences and stamps last_seen_at on that record, so a
suppressed finding that keeps firing is distinguishable from one that
never fired again. The count is per scan, not per candidate: one scan
seeing the same finding twice counts once, and a candidate silenced
before any record for its cooldown key existed has no ledger to write to
and is reported in the scan's skipped list instead.
A record nobody can parse is confined to itself. A hand-edited field is
still refused rather than degraded, but the refusal now names the file
and the field and stops there, so list, history, the brief, delivery,
a new scan and the transitions all keep working on the records that read
cleanly - the operator can still dismiss or suppress a healthy trigger
while a sibling is broken. Every read reports the records it could not
read next to the ones it could, and the morning brief says the queue is
unreadable rather than rendering the pending-trigger section as absent,
which used to be indistinguishable from an empty queue.
o2b brain deep-synthesis <topic> builds a deterministic topic
dossier (agreements, contradictions, stale claims, knowledge gaps)
and o2b brain ideas ranks next directions from open loops; both can
enqueue their findings as triggers. Opt-in recall-gate telemetry
records every automatic-recall decision with a hashed prompt for
tuning, never the raw text.
Memory observability (since v0.39.0)
The continuity store is the Brain's flight recorder, and the Memory
Observability Suite turns it into a contract. Every new record is
stamped with a contract-wide schema version (o2b.continuity.v1);
legacy records read as v1, nothing is migrated, and the dedup id
deliberately excludes the stamp so identical records keep identical
ids. Gated telemetry surfaces (context-pack receipts and telemetry,
pre-compress, search, the recall gate) route through one lazy emit
kernel: with the gate off the payload thunk never runs, and a broken
continuity store can never fail the operation it was observing. A
read-model normalizes stamped and legacy records identically, drops
private records by default, and honours a caller-declared record
scope - for the four consumers that read through it. Most readers
call the store directly and see raw records; the split is asserted in
tests/core/brain/continuity/reader-census.test.ts and detailed in
docs/observability.md.
Two consumers ship with the suite. o2b brain continuity export
renders the store as standard trajectory formats - ATOF JSONL events
or ATIF v1.7 documents (one per session, memory-layer events marked
as deterministic dispatch) - so recall traces feed existing replay
and eval tooling. o2b brain bench memory measures recall quality
against disposable fixture vaults: checkpointed phases with resume by
run id, deterministic no-network evaluation, and a report that keeps
quality, latency, and context cost as separate families so a ranking
change cannot hide a token-cost regression behind a quality win. The
full contract - every event kind, its gate, correlation ids, payload
safety, fail-open rules - lives in docs/observability.md.
Project history as memory (since v0.40.0)
A linked project's git history is durable project memory, and the
Project History Suite makes it queryable without the repository
present. o2b brain git ingest <repo-path> walks a worktree with a
sanitized read-only reader (fixed argv, full-40-hex sha validation on
everything a caller could supply) and lands commits, tags, and
release ranges as structured records in
Brain/projects/git/<repo-key>/commits[.<deviceId>].jsonl (one shard per
device, merged by record time on read, one copy of each commit and of
each tag even when two devices ingested the same history). Typed edges are record
fields - touched files, author, the carrying release attributed by
chronological tag ranges - and a watermark bounds every later run to
<last>..HEAD, so incremental ingest is duplicate-free by
construction. A force-pushed or tampered watermark degrades to a
reported full re-scan, never to silent loss. Each repo gets one
deterministic digest note (releases, recent commits, hot files), so
full-text search can discover the history, while o2b brain git find
answers "why and when did this file change" from the store alone.
Decision-shaped commits become first-class memory through o2b brain git mine: deterministic heuristics (conventional breaking markers,
BREAKING CHANGE footers, word-boundary decision keywords, revert
shape) surface draft ADR candidates under
Brain/decisions/candidates/ with matched-signal provenance. A
candidate's identity is its commit sha: re-runs never duplicate, and
an existing file is never touched, so operator curation owns the
draft from the moment it exists.
Architecture knowledge gets the same treatment via o2b brain architect <project-path>: a scanner that uses the built-in runtime
only, no dependency, derives structural facts (module layout, language
mix, entry points, manifests, test layout - no LLM, no network) and
renders an overview plus per-module notes under
Brain/projects/arch/<repo-key>/. Generated content
lives between paired <!-- o2b:begin <id> --> / <!-- o2b:end <id> --> sentinels: regeneration replaces only generated bodies, operator
prose outside regions survives byte-for-byte, and corrupted markers
fail closed before any write - every note of a run is decided before
the first one is written, so an abort leaves no half-refreshed
prefix. The tree is walked exactly once, and the walk enters no
dot-directory and no build or dependency output (node_modules,
dist, build, out, coverage, vendor, target, venv,
__pycache__); architecture notes describe source, not tooling
state. A run reports its two stages - walk, a counter with no
denominator, and render, which knows its note count - through the
progress spine, and honours a safeguard deadline at each directory
read. The same release also closes the observability gap v0.39.0 left
open: brain_query now emits opt-in recall telemetry with a kind-only
payload, so the supplied preference id, topic, or timestamp never lands
in a continuity record.
Since v1.68.0 the scan reads dependency manifests at the project root
and at each detected module: package.json, pyproject.toml (PEP 621
and Poetry), Cargo.toml and go.mod. pom.xml, build.gradle,
Gemfile and composer.json are detected and reported unsupported
by name. The walk skips symlinks, so a manifest that is a symlink is
not read. Every manifest gets one status - read, malformed (with a
fixed reason such as invalid JSON or invalid TOML, never the
manifest's own text), unreadable (with the error code, or because it
is not a regular file or is larger than 1 MiB, which is not read) or
unsupported - and one bad manifest never aborts the run. The overview's
dependencies region lists every manifest of the root and the modules
with its status, and the runtime dependencies they declare per
ecosystem, leaving out a name that is the manifest name of exactly one
other module (those edges are drawn in module-dependencies; a name two
modules share binds no edge and stays listed), one canonical name
each (PyPI names
normalised per PEP 503, a renamed Cargo dependency by its real crate
name), and counts the dev, build, optional, peer and indirect groups on
one line per ecosystem instead of listing them. A declared name that is
not a plausible package name (one holding a line break, a space or a
bracket) is not written into a note: it is counted on the same line as
unrepresentable. The project's name, version and description are
written on one line, with [[ escaped so they cannot open a link. The
project name comes from the first root manifest read that names a
project, in the order
package.json, pyproject.toml, Cargo.toml, go.mod.
A module whose manifest declares a runtime dependency on the manifest
name of exactly one other module gets a declared edge; a name two
modules carry binds nothing. The edges render in their own
module-dependencies region (a Mermaid diagram under its own claim:
declared by manifests, not measured from imports), in a dependencies
region on each module note, and as a depends_on frontmatter key on
the module note, a YAML list of wikilinks to the target module notes,
which the indexer turns into typed depends_on links. A module whose
directory name holds a character a link cannot carry (a control
character, [, ], |, # or ^) is named once on the overview's
module list and left out of every link and edge. The generator
owns that one key: it rewrites it on every run, removes it when a
module has no edge, overwrites any value typed under it, and never
touches another frontmatter key. The module-map region stays
containment only. On an existing overview or module note the new
regions are appended at the end of the note, once, so the first run
after an upgrade reports those notes updated.
Since v1.69.0, when modules declare edges but every one of them touches
such a module, the module-dependencies region says so in a fixed
sentence and lists the dropped edges as code spans instead of claiming
that no module depends on another; with some edges drawn, one line under
the diagram names the dropped ones. A module note names the modules it
depends on but cannot link on a Not linked: line, and its
depends_on key still holds only links.
The agent write contract (since v0.41.0)
The Brain core stays deterministic - no LLM ever runs inside it. The write-session protocol is how that rule survives contact with agents that need to PROPOSE structured artifacts: schema-typed notes, handoffs, curated summaries, panel deliberations.
A calling agent opens a session and receives a JSON envelope: what to generate (prompt), what shape it must satisfy (schema_hints), and what already occupies the target (existing with byte length, content hash, and first heading). The agent generates and submits; the Brain validates fail-closed. A failing submit returns machine-readable {code, path, message} errors plus a compact correction prompt - and the session keeps its state, so the agent fixes and resubmits without losing the target, the schema, or its attempt budget (default 3, then terminal failed). A clean submit commits atomically, or parks at needs-review when the session was opened with the operator gate. Sessions are plain JSON files under Brain/.sessions/write/ with lazy TTL: they survive process restarts, sync with the vault, and expire on read instead of by daemon.
Three rules are enforced at the commit boundary, not by convention: create intent never overwrites an existing file, merge appends a session-stamped delimited section without touching existing bytes, and a target outside the agent page lanes (sources/, reports/, distillations/, notes/, decisions/panels/ under Brain/) is refused before a session even opens, then again at commit against where the bytes actually land. Every terminal transition - committed, failed, abandoned, approved - lands exactly one write-session audit event in the Brain log.
The decision panel is the first consumer of the kernel: persona steps (operator-curated notes under Brain/personas/, or the built-in technical / strategic / risk / user-experience set) walk in declared order, the synthesis step sees every accepted answer, and the committed decision note under Brain/decisions/panels/ records who argued what through which lens. The Brain contributes sequencing, validation, and the commit; every word of analysis comes from the calling agent.
Two adjacent seams complete the contract. The memory backend boundary (memory_backend config key) routes memory import through a format-adapter registry - the Claude Code adapter is the default and byte-identical to the pre-seam behavior, and a second runtime becomes one self-contained module. The shared namespace (shared_namespace config key) mirrors explicit remember-writes into a second vault after the primary write succeeds, attributed by agent and origin vault; mirroring is fail-soft by contract, so a broken shared vault degrades to a reported mirror: "failed" and the primary write never notices.
Time-aware recall and activation (since v0.42.0)
Recency used to be one Weibull curve over file mtime: a constantly-useful preference and an untouched note of equal age ranked identically, and since/until filters confused when a file was written with when its content happened. The Time-Aware Recall & Activation Suite splits those concerns into deterministic, bounded layers.
Activation. Every CLI/MCP search that surfaces results records one small JSON event under Brain/search/activation/ - the query as an FNV-1a hash (never raw text) plus the surfaced paths. The derived activation state is a pure fold over the retained events (delete it, refold, identical), and at query time each candidate's stored strength decays by a content-type half-life: preferences, decisions, and antipatterns never decay, projects fade over ~120 days, handoffs and sessions over ~30, plain notes over 60. The result is a capped activation boost that keeps habitually-recalled memories hot without ever floating an irrelevant one. Recording happens AFTER ranking at the orchestrator edge - a query is never affected by its own recording, cache hits never record, and a vault with no events ranks bit-identically to before. Documents repeatedly surfaced together also accumulate co-access pairs in the same fold; when companions land in one candidate pool they reinforce each other with a capped co_access boost (single co-occurrences are noise and ignored).
Freshness trends. The dream refresh classifies every preference's evidence time distribution - recent 30 days against the prior 30 - into new | strengthening | stable | weakening | stale and stamps the label into frontmatter. Recall reads the stamp off preference pages and multiplies the relevance portion (strengthening 1.05, weakening 0.93, stale 0.85), so a rule that keeps earning applied events outranks one drifting toward retirement. The belief-evolution envelope carries the live classification with the windowed counts.
Event time over storage time. A document declaring valid_from / valid_until passes a since/until filter when its validity interval OVERLAPS the query window - storage mtime is only the fallback for documents without event time. An old file describing yesterday's migration window is found by --since 7d; a freshly-synced file describing a long-closed event is not. With a time range active, link-graph traversal applies the same discipline: an expansion document bridges in only when its event time falls within a padded neighbourhood of the window (default 7 days), scored by temporal proximity with a temporal_bridge reason - causes and consequences, not arbitrary link leakage.
Two-pass recovery. Keyword matching is implicit-AND, so a multi-term evidence query whose terms live in different documents can return nothing. In evidence-pack mode that dead end now triggers exactly one broadened retry - the first significant term stays the base group, the rest OR in as alternatives - and the recovered results flow through the normal ranking and a recomputed evidence pack, labelled second_pass with a secondPass envelope report. Plain searches never broaden; search_two_pass_enabled: false turns the retry off entirely.
Entity truth and the self-improving dream (since v0.43.0)
The Brain recorded facts but had no notion of which one is true NOW: two contradictory claims about the same entity coexisted silently, dedup could merge notes about different people, and the dream pass trusted self-reported evidence with no outcome signal. The Entity Truth & Self-Improving Dream Suite closes both gaps with the same fold-over-append-only discipline the activation store proved.
The claim ledger. Claims are append-only JSONL events under Brain/truth/ (one shard per device, Syncthing-safe), each a versioned (entity, aspect, value, source, agent, ts) line parsed fail-closed. A deterministic, order-insensitive fold projects them into addressable slots: the current value of employer of alice mason, every superseded prior value with provenance, and value_conflict records when two distinct values land within the conflict window from independent sources - resolution is always ask_user, never automatic, while a later value outside the window supersedes silently because that is fact evolution, not contradiction. Atomic-fact decomposition feeds the ledger deterministically (markdown structure plus a sentence splitter with an abbreviation guard - no model calls), the actor-framed quantity family captures I spent 120 USD shapes, and aggregation sums only exact (entity, action, unit) tuples so nearby numbers never pollute a total. The same ledger powers two guards: merges refuse to collapse claims about disjoint people/orgs, and synthesized notes asserting entities their cited sources never mention are flagged as contaminated. Two agents independently writing claims about one entity within 14 days surface push-mode through the trigger queue.
The self-improving dream. Apply-evidence events now optionally carry a downstream outcome (success | failure | unknown); a rule whose applied events keep co-occurring with failures looks confirmed while actively hurting, so the dream refresh stages an outcome_regressions finding with a deterministic confidence penalty - staged for the operator, never silently retired. Failed approaches persist as markdown dead-ends (Brain/dead-ends/) that FTS indexes like any note, so recall surfaces "avoid X" beside "prefer Y". Inbox signals rank by surprisal - mean embedding distance to their nearest indexed neighbours, computed over the existing sqlite-vec index with zero provider calls - so review attention lands on genuinely new signals first. The weekly synthesis nominates the single most-developable note of the window, and the foresight fold projects forward for the first time: recurrence cadences yield next-due dates, recent open commitments and questions surface with sources, and an empty vault folds to an empty envelope.
Write-time integrity and governance (since v0.44.0)
Until now almost nothing about a write into the Brain was declared: any frontmatter field could be hand-edited including the join keys the index depends on, labels were free-form strings that fragment, typed relations connected any two page types, credentials could only be redacted out of memory but never safely used, and the heavy dream/reindex pass could land on top of a live interactive recall. The Write-Time Integrity & Governance Suite makes the schema pack the single declarative ontology and enforces each contract at its existing seam - no new framework layer.
The ontology lives in the schema pack. Four additive fields - labels (controlled-vocabulary dimensions with fixed enum values), link_constraints (allowed source->target page-type pairs per link type), attributes (per-type fields with natural-language descriptions), and frontmatter_tiers (per-kind field tier overrides) - parse, render, and mutate through the same audited machinery the schema pack always had. A vault that declares none of them behaves bit-identically.
Enforcement at the seam, fail-closed with guidance. Label assignment rejects an unknown dimension or value WITH the declared vocabulary in the error; attribute validation lists the declared fields with their descriptions, so the error message teaches the domain language instead of just refusing. Link constraints enforce at index materialization: each page's declared type is persisted, a post-pass recomputes every typed edge's blocked flag from the current pack on every run - a violating edge falls back to an untyped link instead of feeding typed-relation recall, schema lint lists each violation, and removing the constraint restores the edges without touching any file.
The tier guard never fights the editor. Four tiers (identity / system / business / user) map onto the fields framework writers actually emit; the preference files' _-prefix convention becomes an explicit system-tier rule. Framework rewrites merge through the tier model - a field a human added by hand survives, and a changed identity join key throws instead of being silently re-accepted - while hand-edits in Obsidian or Vim are never write-denied: the index pass snapshots identity fields, stages a drift finding the snapshot never absorbs, and the operator resolves it through tiers check | restore | accept. The doctor warns with the open count.
Custody, not just redaction. Secrets live as per-value AES-256-GCM ciphertext beside a 0600 keyfile in the vault-local state dir (.open-second-brain/secrets/), never returned by any surface. That directory carries a .gitignore so a vault that is a git repository never commits it; Syncthing does not read .gitignore, so a vault inside a Syncthing folder needs /<path-to-vault>/.open-second-brain/secrets in the folder's root .stignore - o2b brain doctor (secrets-sync-exposed) and secret set warn with the exact line when it is missing, and never edit .stignore themselves. An agent uses a credential by running an allowlisted command with the value injected into the subprocess env; captured output comes back with the value scrubbed as a known literal, the material enters only via the operator's stdin, and every operation lands a no-values audit record under Brain/log/secret-custody/. The threat model is stated honestly: this protects against context leakage, git exposure, and casual reads - sync exposure only once the syncer's own ignore list covers the directory - and not against root.
The keyfile can be wrapped under a passphrase. o2b brain secret unlock verifies a passphrase (read from stdin or --passphrase-from-env, never argv) and - on a store whose keyfile is still raw - wraps the 32-byte key into a versioned scrypt envelope at the same path, then holds the unwrapped key in memory for that one process only; lock overwrites and drops it. There is no daemon: every other CLI invocation and the MCP server stay locked, and a key-bearing operation over a locked store refuses with a named error whose remedy is the unlock command (the refusal's prose carries no paths - the structured field does, for the surfaces allowed to name them). Every unlock and lock lands a no-values custody record. A credential reference, $secret:NAME, resolves store-first with environment fallback at the use sites that already name it (the exec allowlist, the embedding and rerank keys, the decision-model key, the Telegram capture token, the research keys); a locked store surfaces the named refusal rather than silently answering from the environment. secret export writes every entry as one passphrase-encrypted bundle to an operator-named file - the values re-encrypted under a key the passphrase derives, the names, env-var mappings and allowlists traveling in the clear past the shared egress redactor - and secret import decrypts and validates every entry (names, env-var mappings, allow patterns, all under the store's own rules) before writing anything. The honesty clause is the same for the keyfile envelope and for bundles: the passphrase is never persisted, logged or audited, and a lost one is unrecoverable - every stored value and every exported bundle stays unreadable forever, and nothing recovers them.
Heavy work waits its turn. The maintenance lane runs dream and reindex behind five gates: a tz-aware local-time window, a busy gate over recent recall telemetry, an optional host-pressure gate, and - since v1.85.0, when a storage cap is configured - a capacity gate that skips a vault past its storage.pause_percent line (since v1.86.0; the line defaults to the warn band, and interactive ingest keeps working until the critical tier refuses it outright) or measuring at its critical tier, before the failure-streak limit refuses a task that keeps failing; under them all sits an expiring SQLite lease in its own database so holding it never contends with the search index writer lock. Force bypasses the soft gates but never the lease, tasks run stale-first, and every attempt - including each gate refusal - lands in a bounded journal, because a lane you cannot audit is a lane you cannot trust.
Link and recall intelligence (since v0.45.0)
A second brain's value compounds through connections, but until now the graph only knew edges someone wrote by hand, and recall quality was an unmeasured feeling. The Link & Recall Intelligence Suite makes the graph self-organizing and the recall measurable - deterministically, with every output reviewable before it touches a note.
Aliases resolve where edges materialize. Obsidian vaults lean on frontmatter aliases: for shorthand, but the search index resolved wikilink targets by exact path only - [[PA]] to a note titled "Project Alpha" stayed an unresolved string. The indexer now extracts every note's aliases into the index (schema v7) and a shadowing-safe post-pass materializes the target id: exact paths always win, a real document basename is never shadowed by someone else's alias, and collisions resolve deterministically. Traversal, backlinks, and link constraints all see the edge from then on.
Bridges surface the connections nobody wrote. Traversal follows existing edges; unlinked-mentions catches textual name-drops. The third kind of latent structure - two notes that are embedding-near but never name each other - is found by an orphan-first pass over the vec index: weakly connected notes get scanned before well-wired hubs, chunk KNN similarity aggregates to document level, and pairs that already share an edge stay out. The output is a PROPOSAL artifact (Brain/proposals/bridges.md), regenerated per run; nothing touches a user note until someone runs accept, which writes exactly one related: wikilink, idempotently, under the schema pack's link constraints. Dismissals persist so re-runs stay quiet.
Communities become visible structure. Deterministic label propagation (sorted sweeps, lowest-label tie-break, a hard iteration cap so bipartite stars cannot oscillate forever) discovers graph-wide communities; each one of four or more notes materializes a derived digest under Brain/clusters/ - members by internal degree, shared entities, link density. No LLM prose: synthesis belongs to the reading agent, the digest is a projection. Stale generated notes are removed on the next run; a hand-written file in the same directory is never touched.
Recall quality gets a number. A fixed query/expected-result dataset scores the live hybrid pipeline - hit@k and MRR per query and aggregate. The repository pins thresholds over a committed fixture vault with the deterministic local embedding provider, so a ranking regression fails CI instead of shipping silently; the same runner scores any operator vault on demand. And that number becomes an objective function: the self-tuner grid-evaluates a BOUNDED parameter space (keyword pool multiplier, traversal depth, learned weights, expansion) against it and persists the winner with every evaluated score and the dataset hash. Search applies the tuned point only under an explicit opt-in flag, re-validates it against the grid bounds on every read, and deleting the file restores defaults - the same replayable-fold philosophy as learned weights.
Expansion without a model. A bare query can become a structured lex/vec/hyde document locally: stopword-stripped lex terms (the FTS lane is implicit AND, so one stopword absent from the target note kills the match), an entity-context line anchored to the vault's own registry, one template hyde passage for embedding retrieval. Opt-in per call, never silently active, so cached queries and benchmark runs stay comparable.
Every pass reports to one place. Each feature appends one run-level record to Brain/metrics/<surface>.jsonl (o2b.metrics.v1 envelope) - the stable contract a dashboard reads without importing internals, documented in docs/metrics.md.
Stability and trust (since v1.0.0)
The first major release froze the public contracts - the advertised
MCP tool surface, the CLI verb tree, configuration keys, the search
schema ladder, and every on-disk schema string - under
docs/stability.md, and spent its one breaking change on deleting the
18 hidden alias tools from the token-diet era. A stale client that
calls a removed name gets a tombstone naming the exact replacement,
and brain_doctor flags vault-side text that still references one.
Three hardening layers ship alongside the freeze. A cooperative
safeguard gives every long pass (dream, reindex, bridge discovery,
communities, the maintenance lane) a deadline checked at natural
iteration boundaries: Bun runs SQLite synchronously, so the honest
contract is to abort BETWEEN atomic writes rather than pretend an
async cancellation. The staged dream pipeline turns the
self-improvement loop inspectable before anything lands: stage
persists the dry-run plan as a discardable bundle, validate
recomputes it and reports drift, apply re-validates with the clock
it runs with and then executes the one and only promotion engine -
determinism guarantees the live run performs exactly the reviewed
plan. And the reporting surfaces become self-comparing: with
snapshots enabled, each digest / daily / weekly run persists a
machine-diffable JSON snapshot and reports a deterministic "since
last run" delta keyed on stable identities, never on array order.
Presentation grows timezone awareness on the same principle the rest
of the system follows - storage stays canonical UTC, conversion
happens at the boundary, and an unconfigured vault renders
byte-identically.
Checked quotes and capture scope (since v1.67.0)
A distillation page presents itself as a faithful record of its source, so two things it asserts are now checked or recorded rather than assumed.
Quotes are verified at write time. When an agent submits claims to
brain_distill_source or o2b brain distill, every span between
quotation marks is compared with the block the claim cites, or with the
whole source when it cites none. The comparison is deterministic and
language-neutral: quotation marks are recognised by their Unicode property,
paired by position, and both sides are normalised the same way for the
comparison only. A span that does not verify loses its quotation marks and
is named in the result, so the page never shows an unverified quote; a
caller that prefers a refusal passes strict_quotes and gets the
registered error code quote_unverified with nothing written. The bytes
checked are the bytes hashed into source_hash, read once.
Every page says how much of its source the vault holds. The capture scope has three values:
full-local: the source is a file this vault holds;bounded-local: the source has no local bytes, but the page stores a verbatim excerpt the writer read, with its digest (excerpt_hash);url-only: the page rests on a locator only - a URL, or a path with no file behind it.
The scope is stamped at write time on distillation, ingest summary and
research report pages (capture_scope, or capture_scopes per source on a
report), and only when it is not full-local, so pages over local sources
stay byte-identical. An absent stamp means "full-local, or written before
this release", and readers re-derive rather than assume: the default-on
capture-scope hygiene detector re-classifies each cited source from its
identity at scan time, without reading its bytes. A retrievable page of
active knowledge - a distillation, an ingest summary, a research report or
a canonical entity - whose every cited source is currently url-only gets
a warning finding with proposed_action: review. A local source that
was deleted therefore surfaces as url-only, a bounded-local page counts
as backed only while its excerpt still matches its digest, and quarantined
pages are skipped: they already carry the untrusted_source marker, which
names the same condition (and which the retrieval trust gate excludes
under search_trust_gate_mode: enforce or the legacy
search_trust_gate_enabled: true; shadow only measures).
Non-Markdown sources (since v1.69.0)
Ingest used to be text only: a file that was not Markdown or plain text
was counted per extension and set aside. One format registry now maps a
file's extension to a format and each format to an extractor. Markdown
and the lightweight markups are read as they are; CSV, TSV and HTML are
extracted; PDF, the Office formats, EPUB, RTF and images are named but
not extracted, so the ingest plan lists each such file with reason
format-not-extractable and its format, the hook a caller that runs
its own converter or OCR routes on. Open Second Brain adds no parser
dependency and runs no external program for this.
The calling agent still writes the summary of a source. What the kernel
adds is structure it can derive without judgement, written onto the
summary page, because the summary page is what search indexes: the
source file itself is not a Markdown page. brain_ingest_source does
this by format, for a file in the vault, in the trusted lane, after
the same reach check every reader applies, and reads the bytes once to
derive the section and its source_content_hash.
For HTML, a linear scanner (no regular expression over unbounded input)
reduces the page to text: comments, scripts, styles and embedded SVG or
MathML are dropped, entities are decoded, a <private> region becomes
its placeholder and no attribute value is ever kept. The headings become
parts, each with its level, its trail of ancestor headings and its line
span in the extracted text. Only the parts reach the page, under
## Parts inside one fenced block, so a heading that holds [[...]] or
#word adds no link and no tag while full-text search still finds it.
Each heading and the title are redacted (key=value credentials and URL
userinfo) before they are capped, over their first 4,096 code units (a
longer one ends in …), and in the parts list a backslash is written
as \\ and a | as \|, so a heading cannot forge a line span.
The text itself stays out of the page; o2b brain extract --json
returns it.
For CSV and TSV, the first record is the header. Quoted fields follow
RFC 4180 leniently, a semicolon export is recognised from its first
record (at least two fields on semicolons and more than on commas, so a
header such as name;"price, eur";qty reads as three columns), and an
unterminated quote is refused by name with the record number. A leading
frontmatter block in the source is skipped for HTML and tables alike, so
it is never read as data. The rows are rendered as fenced plain text,
not a Markdown table, in groups that each repeat the header and stay
under one search chunk, so every chunk of the page carries the column
names and a Table > Rows a-b heading path. Rows, columns, cell length
and section size are capped, and every cut is named; only the first
4,096 code units of a cell are read for redaction, and a longer cell
counts as cut. A <private> region is hidden before the records are
split, so a region spanning records hides every row between its tags. A column whose header names a
credential has its values replaced, and every other cell goes through
the output redactor. Inside a cell a backslash, |, a backtick, a line
feed, a carriage return and a tab are escaped (\\, \|, \` ,
\n, \r, \t), and every other control character (C0, DEL and C1)
is written as \u{XXXX} with its code point in four uppercase
hexadecimal digits, so no row
line can open or close a fence and no escape sequence reaches a
terminal. One limit of the chunk alignment remains, and it loses no
content: a single row longer than one group's token budget forms a
group of its own and can still be split across search chunks.
The redactor does not catch everything in a cell. It replaces whole
columns under a credential-named header, key=value and key: value
pairs, JSON entries, Bearer tokens, JWTs, private regions and URL
userinfo. It does not redact a bare vendor token (an access key with a
vendor prefix and nothing naming it), a base64 or other high-entropy
blob, a PEM block inside a quoted multi-line cell, or a credential-like
value under a header that does not name a credential. Bare tokens are
left on purpose, because a token pass would also erase order ids, SKUs
and hashes. Such a value reaches the summary page, full-text search and
the embeddings, as it already sits in the source file in the vault.
A summary page that carries a derived section is at most as visible as
its source: it takes on the source's own visibility tokens, so a reader
that cannot read the source cannot read what was derived from it. When
the page already had a visibility, it gets the audience both the
operator and the source allow; when they share none, the page is
withheld below local reach.
A Markdown or text source gets nothing new: its page is byte for byte what it was.
Brain store dual writes (phase 1 trial)
For setting up the store on one or more machines, syncing its partitions, the human view, moving an existing install out of the vault and the threat model, see the brain store guide. This section describes the phase 1 dual-write trial.
Open Second Brain is moving its machine data out of the vault into the brain
store: an append-only folder outside the vault whose format is documented in
FORMAT.md inside the store (source: src/core/store/FORMAT.md). In this
phase the store is a trial. Nothing reads from it yet, and the vault tree
under Brain/ stays the source of truth.
The store is created once, by an explicit command, on the first machine:
o2b store init # both partitions
o2b store init --partitions shared # a machine that must not hold private recordsIt writes one store id into every partition it creates, and the format
document into the root and each partition. Every other machine receives the
partitions through sync; sync each partition folder as its own sync folder,
never the store root. A second run refuses an existing store. A run that
finds some requested partitions already present (a first run that stopped
half way, or a machine adding private later) adds the missing ones with
the same store id. A partition folder that holds store files but no
store.json is refused by name rather than given a new id.
o2b store path prints where the store is on this machine: the store root,
each partition and whether this machine holds it, the device-local state
directory, and the folder the human view is rendered into (none when
view_target is not set), each with the setting that put it there (env,
config or default) and whether it exists. It creates nothing, so it also answers
before o2b store init has run. second_brain_status carries the same
answer in its store block, so an agent never has to know a path by heart.
The store_dual_write setting turns the trial on. It is off by default.
Set store_dual_write: true in the machine config, or set the environment
variable OPEN_SECOND_BRAIN_STORE_DUAL_WRITE=1, which wins over the config.
While it is on, the existing writers also append the same data to the store:
| Legacy write | Store record |
|---|---|
| A signal (feedback, inline and extracted signals) | signal in events |
| A Brain log event (notes, apply-evidence, every other event) | log-event in log |
| A preference write, retire or pin | preference-set in events |
| Apply-evidence | preference-evidence in events, besides its log event |
| Pinned context write, append, batch or clear | pinned-set or pinned-clear in events |
| A row of a per-device ledger | ledger-row in ledgers/<name> |
Preference and pinned records carry who decided them. An operator running
o2b decides as human; an agent calling a tool, a bulk import and the
dream pass decide as agent (dream is automatic even when an operator
starts it); a decision-model verdict is model. No legacy writer acts on a
decision-model verdict yet, so model appears only when a caller passes it.
Records of a page whose frontmatter reserves it as private go to the store's
private partition. The rule is the one search and remote reads use: a
visibility that includes private, or an informal marker such as
access: private, confidentiality: secret or sensitivity: confidential.
A page that exists but cannot be read counts as private. The apply-evidence
log event, the audit rows and the edit-history rows about a private
preference follow it into private.
Truth-claim ledger rows go to private when this machine holds that
partition and to shared when it does not, since a claim can be about a note
of any visibility. Audit rows follow the same rule: the preference audit
trail and every reliability audit trail (hygiene, secret custody, hook audit,
watchdog and the others under Brain/log/) go to private when this machine
holds it and to shared when it does not. An audit row about a private
preference goes to private only; a machine without that partition refuses
it and counts a failed dual write, the legacy row is kept.
Rules of the trial:
With the setting off, nothing is written to the store and the legacy files are byte-identical to what they were before the store existed.
A store failure never fails the legacy write. The first failure in a process prints one warning; every failure is counted in
dual-write.jsonin the device-local state directory (${XDG_STATE_HOME:-~/.local/state}/open-second-brain/stores/on Linux and macOS,%LOCALAPPDATA%\open-second-brain\stores\on Windows).Dual writes never create the store. With no store at the root they are skipped, counted and warned about once; doctor says to run
o2b store init.The store needs a real device id. With
O2B_DEVICE_IDset to the empty string, or when the device id cannot be resolved (for example an unreadable or unwritable machine config), the dual writes are skipped, counted and warned about once. A missingdevice_idis generated and saved to the machine config on first use, as for the sharded logs.The store root comes from
OPEN_SECOND_BRAIN_STORE_ROOT, then thestore_rootconfig key, then a per-user default (${XDG_DATA_HOME:-~/.local/share}/open-second-brain/store). A root inside the vault is refused.A machine that holds only the
sharedpartition refuses private records; that counts as a failure, and the legacy write is kept.
o2b brain doctor has a store section. It stays silent while the store is
not in use (the setting is off and no store root is configured). When the
store is in use it reports failed or skipped dual writes, a store that has
not been created yet, a store root it cannot use, state digests that
disagree between machines over the same records, devices that stopped
publishing digests, record ids that arrived with different content, and
records whose clock is more than one day ahead of now (per device, with the
newest stamp). Such records are still folded, and they win ties on the
later clock, so the fix is the clock of the named device.
Search and brain_changes catch the index up with the store before they
read. To keep a burst of reads cheap, one process looks at the store at most
once per second; a record another machine adds inside that window is found
by the next read after it. The environment variable
OPEN_SECOND_BRAIN_STORE_CATCHUP_INTERVAL_MS sets the window in whole
milliseconds (0 looks on every read); any other value keeps the default.
Writers that edit preference files directly rather than through the preference writer (for example protection marks, expiration dates, the Claude memory import and batch writes) are not covered in this phase.
Safety properties
These are invariants of the system, not configuration to enable.
Filesystem-first. Every Brain artifact is a Markdown file with YAML frontmatter.
cp -r Brain/is a complete backup;tar -czfis a portable bundle.Deterministic. The dream pass is a pure function of (signals, preferences, retired, log, configuration, current time). Given identical inputs and a fixed
--now, two runs produce byte-identical output.Idempotent. Re-running dream without new input or expired timestamps is a no-op. Safe to schedule at any frequency.
Atomic per-file writes. Every mutation goes through write-temp + rename; an interrupted run never leaves partial files.
Audit-traceable. Every state change emits a typed event in
Brain/log/<day>.mdplus a structured<day>.jsonlsidecar (machine readers prefer the JSONL and fall back to parsing the markdown for historical days). The log is append-only; the dream log entry for a run records exactly which preferences moved and why.Reversible. Pre-run snapshots plus
o2b brain rollback <run-id>let you undo any single dream pass.Path-safe. Every writer routes through a vault-boundary check;
Brain/operations cannot escape the configured vault root.Local MCP transport.
o2b mcpis a stdio subprocess of the client by default. The optional--transport httpbinds127.0.0.1, checksHostandOriginon every request against DNS rebinding, and refuses a non-loopback--hostwithout--api-key(orOPEN_SECOND_BRAIN_MCP_API_KEY). Shutdown drains in-flight calls, see mcp.md.Guarded injected context. Automatically surfaced Brain context passes a prompt-injection guard. With
guardrails.untrusted_source_delimitingenabled in_brain.yaml, the context pack (brain_context_pack) wraps untrusted spans in a provenance-carrying delimiter instead of filtering them by a word list. The pre-compress pack (brain_pre_compress_pack) does not apply the delimiter yet and uses the guard only.No LLM inside the algorithm. Semantic merging of similar but differently-slugged topics is left to external agents who can call the CLI / MCP surface directly — the dream pass itself only does counting and atomic file moves.
Sample lifecycle of one preference
A worked example spanning a hundred days of vault activity:
sequenceDiagram
autonumber
actor User
participant Agent
participant Brain
participant Dream as dream pass
User->>Agent: day 0 — "don't use internal abbreviations"
Agent->>Brain: brain_feedback (neg, topic)
Brain-->>Brain: sig #1 in inbox/
User->>Agent: day 1 — same complaint
Agent->>Brain: brain_feedback
Brain-->>Brain: sig #2
User->>Agent: day 3 — third complaint
Agent->>Brain: brain_feedback
Brain-->>Brain: sig #3 (threshold met)
Note over Dream: day 3 dream pass
Dream->>Brain: create pref-X status: unconfirmed<br/>(trial: 14 days)
Dream->>Brain: move sig #1..3 → processed/
Agent->>Agent: day 5 — produces public blog post
Agent->>Brain: brain_apply_evidence applied
Note over Dream: day 5 dream pass
Dream->>Brain: pref-X status: confirmed,<br/>applied_count=1, confidence=low
Agent->>Brain: days 6..30 — nine more applied evidences
Note over Dream: day 30 dream pass
Dream->>Brain: pref-X applied_count=10,<br/>confidence=high
Note over Dream: days 31..120 — silence on this topic
Note over Dream: day 120 dream pass (90 days past last evidence)
Dream->>Brain: move pref-X → retired/<br/>reason: stale-no-evidenceAt day 120 the rule has retired itself. The retired note keeps the full origin (the three signals, the confirmation timestamp, the ten evidence applications) so its history is auditable forever; only the active-rule attention budget is freed.
How a new vault gets bootstrapped
flowchart LR
User[operator] --> Init([o2b brain init])
Init --> Skel["Create Brain/inbox, preferences, retired, log, .snapshots"]
Init --> Yaml[Render Brain/_brain.yaml with defaults]
Init --> Manual[Render Brain/_BRAIN.md operating manual]Brain/_brain.yaml defaults are sensible for most uses; tune them in
place when real usage reveals different timing. Brain/_BRAIN.md is
the per-vault contract for agents — agents read it at the top of any
session that interacts with this vault, and o2b brain init --force
re-renders it from the current template.
Where to go next
docs/observability.md— the observability contract: every log and continuity event kind, the always-on vs opt-in matrix, correlation IDs, payload safety, fail-open rules, and theo2b.continuity.v1schema version with its evolution rule.docs/architecture.md— layered system architecture beyond Brain (vault model, runtime adapters, configuration model).docs/plans/2026-05-15-brain-observing-memory.md— the Brain design document, including the full file-format specs and test strategy.docs/plans/2026-05-15-brain-roadmap.md— trigger-based roadmap of future capabilities (BRAIN-FUT-NNNentries).docs/plans/2026-05-16-brain-search-design.mdanddocs/plans/2026-05-16-brain-search-impl.md— design and implementation plan for the v0.10.0 full-text search layer.docs/mcp.md— protocol, schemas, lifecycle, and resource surface of the MCP server.Brain/_BRAIN.md(in any initialised vault) — the operating manual for agents working with that specific vault.
512 files · 486 not served here
- .agents/plugins/marketplace.json (not served here)
- .ai-notes/.gitignore (not served here)
- .apb/.gitignore (not served here)
- .apb/config.yaml (not served here)
- .apb/playbooks/osb-feature-release/1.1.0/playbook.yaml (not served here)
- .apb/playbooks/osb-feature-release/1.2.0/playbook.yaml (not served here)
- .apb/playbooks/osb-feature-release/1.3.0/playbook.yaml (not served here)
- .apb/playbooks/osb-feature-release/1.4.0/playbook.yaml (not served here)
- .apb/playbooks/osb-feature-release/current (not served here)
- .apb/playbooks/osb-feature-release/lifecycle (not served here)
- .apb/playbooks/osb-feature-release/meta/1.1.0.yaml (not served here)
- .apb/playbooks/osb-feature-release/meta/1.2.0.yaml (not served here)
- .apb/playbooks/osb-feature-release/meta/1.3.0.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/playbook.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/arch-gate.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/check-context.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/check-pr.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/check-release.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/ci-wait.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/lib.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/qa-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.0.0/scripts/zg-index.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/playbook.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/arch-gate.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/check-context.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/check-pr.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/check-release.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/ci-wait.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/final-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/lib.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/qa-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.1.0/scripts/zg-index.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/playbook.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/arch-gate.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/check-context.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/check-pr.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/check-release.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/ci-wait.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/final-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/lib.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/qa-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.2.0/scripts/zg-index.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/playbook.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/arch-gate.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/check-context.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/check-pr.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/check-release.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/ci-wait.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/cycle-run.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/final-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/lib.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/qa-gates.sh (not served here)
- .apb/playbooks/osb-pr-prepare/1.4.0/scripts/zg-index.sh (not served here)
- .apb/playbooks/osb-pr-prepare/current (not served here)
- .apb/playbooks/osb-pr-prepare/lifecycle (not served here)
- .apb/playbooks/osb-pr-prepare/meta/1.0.0.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/meta/1.1.0.yaml (not served here)
- .apb/playbooks/osb-pr-prepare/meta/1.4.0.yaml (not served here)
- .apb/playbooks/osb-release/1.0.0/playbook.yaml (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/discard.sh (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/lib.sh (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/publish.sh (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/release-image-style.md (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/render.sh (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/render.ts (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/scope.sh (not served here)
- .apb/playbooks/osb-release/1.0.0/scripts/stage.sh (not served here)
- .apb/playbooks/osb-release/current (not served here)
- .apb/playbooks/osb-release/lifecycle (not served here)
- .apb/playbooks/osb-release/meta/1.0.0.yaml (not served here)
- .apb/playbooks/osb-triage-validator/1.2.0/playbook.yaml (not served here)
- .apb/playbooks/osb-triage-validator/1.3.0/playbook.yaml (not served here)
- .apb/playbooks/osb-triage-validator/current (not served here)
- .apb/playbooks/osb-triage-validator/lifecycle (not served here)
- .apb/playbooks/osb-triage-validator/meta/1.2.0.yaml (not served here)
- .apb/playbooks/osb-triage-validator/meta/1.3.0.yaml (not served here)
- .apb/playbooks/osb-upstream-watch/1.0.0/playbook.yaml (not served here)
- .apb/playbooks/osb-upstream-watch/1.0.0/scripts/materialize-collector.sh (not served here)
- .apb/playbooks/osb-upstream-watch/current (not served here)
- .apb/playbooks/osb-upstream-watch/lifecycle (not served here)
- .apb/playbooks/osb-upstream-watch/meta/1.0.0.yaml (not served here)
- .apb/profiles/osb-collector/SOUL.md (not served here)
- .apb/profiles/osb-collector/profile.yaml (not served here)
- .apb/profiles/osb-developer/SOUL.md (not served here)
- .apb/profiles/osb-developer/profile.yaml (not served here)
- .apb/profiles/osb-reviewer/SOUL.md (not served here)
- .apb/profiles/osb-reviewer/profile.yaml (not served here)
- .apb/profiles/osb-validator/SOUL.md (not served here)
- .apb/profiles/osb-validator/profile.yaml (not served here)
- .claude-plugin/marketplace.json (not served here)
- .claude-plugin/plugin.json
- .codex-plugin/plugin.json (not served here)
- .codexignore (not served here)
- .gitattributes (not served here)
- .githooks/pre-commit (not served here)
- .githooks/pre-push (not served here)
- .github/dependabot.yml (not served here)
- .github/scripts/ci-scope.sh (not served here)
- .github/scripts/windows-timings.sh (not served here)
- .github/windows-test-timings.json (not served here)
- .github/workflows/ci.yml (not served here)
- .github/workflows/plugin-scanner.yml (not served here)
- .github/workflows/release.yml (not served here)
- .gitignore (not served here)
- .oxfmtrc.json (not served here)
- ATTRIBUTION.md
- AUTOGINEER-RUNS.md
- CHANGELOG.md
- CLAUDE.md
- LICENSE
- README.md
- SECURITY.md
- __init__.py (not served here)
- after-install.md
- bin/o2b-discipline-report (not served here)
- bun.lock (not served here)
- bunfig.toml (not served here)
- cli.py (not served here)
- docs/architecture.md
- docs/brain-store.md
- docs/brainstorm/a-label-is-not-a-boundary/cli-output/claude.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/cli-output/prompt.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/design.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/plan.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/census-lexer.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/conventions-and-release-machinery.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/egress-boundary.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/fleet-and-shared-scope.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/identity-and-attribution.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/mcp-instruction-surface.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/owner-scope-isolation.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/recon/retrieval-confidence-floor.md (not served here)
- docs/brainstorm/a-label-is-not-a-boundary/variants.md (not served here)
- docs/brainstorm/actionable-now-batch/cli-output/consultant.md (not served here)
- docs/brainstorm/actionable-now-batch/cli-output/prompt.md (not served here)
- docs/brainstorm/actionable-now-batch/design.md (not served here)
- docs/brainstorm/actionable-now-batch/plan.md (not served here)
- docs/brainstorm/actionable-now-batch/variants.md (not served here)
- docs/brainstorm/agent-boundary-control-surfaces/README.md (not served here)
- docs/brainstorm/agent-boundary-control-surfaces/cli-output/claude.md (not served here)
- docs/brainstorm/agent-boundary-control-surfaces/cli-output/prompt.md (not served here)
- docs/brainstorm/agent-boundary-control-surfaces/design.md (not served here)
- docs/brainstorm/agent-boundary-control-surfaces/plan.md (not served here)
- docs/brainstorm/agent-boundary-control-surfaces/variants.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/README.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/adr.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/cli-output/claude.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/cli-output/prompt.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/context.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/design.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/implementation-notes.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/notes.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/phase-log.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/plan.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/pr-notes.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/scope.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/task-body.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/test-matrix.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/variants.md (not served here)
- docs/brainstorm/agent-capability-cli-integration/verification.md (not served here)
- docs/brainstorm/agent-presence-self-improvement/cli-output/consultant.md (not served here)
- docs/brainstorm/agent-presence-self-improvement/cli-output/prompt.md (not served here)
- docs/brainstorm/agent-presence-self-improvement/design.md (not served here)
- docs/brainstorm/agent-presence-self-improvement/plan.md (not served here)
- docs/brainstorm/agent-presence-self-improvement/variants.md (not served here)
- docs/brainstorm/agent-surface-suite/cli-output/claude.md (not served here)
- docs/brainstorm/agent-surface-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/agent-surface-suite/design.md (not served here)
- docs/brainstorm/agent-surface-suite/plan.md (not served here)
- docs/brainstorm/agent-surface-suite/variants.md (not served here)
- docs/brainstorm/agent-write-contract-suite/cli-output/claude.md (not served here)
- docs/brainstorm/agent-write-contract-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/agent-write-contract-suite/design.md (not served here)
- docs/brainstorm/agent-write-contract-suite/plan.md (not served here)
- docs/brainstorm/agent-write-contract-suite/variants.md (not served here)
- docs/brainstorm/answer-trust/cli-output/consultant.md (not served here)
- docs/brainstorm/answer-trust/cli-output/prompt.md (not served here)
- docs/brainstorm/answer-trust/design.md (not served here)
- docs/brainstorm/answer-trust/plan.md (not served here)
- docs/brainstorm/answer-trust/variants.md (not served here)
- docs/brainstorm/auto-migrate-on-upgrade/design.md (not served here)
- docs/brainstorm/auto-migrate-on-upgrade/plan.md (not served here)
- docs/brainstorm/auto-migrate-on-upgrade/variants.md (not served here)
- docs/brainstorm/belief-lifecycle-decision-memory/cli-output/claude.md (not served here)
- docs/brainstorm/belief-lifecycle-decision-memory/cli-output/prompt.md (not served here)
- docs/brainstorm/belief-lifecycle-decision-memory/design.md (not served here)
- docs/brainstorm/belief-lifecycle-decision-memory/plan.md (not served here)
- docs/brainstorm/belief-lifecycle-decision-memory/variants.md (not served here)
- docs/brainstorm/brain-centric-layout/design.md (not served here)
- docs/brainstorm/brain-centric-layout/plan.md (not served here)
- docs/brainstorm/brain-integrity-safety-hardening/cli-output/claude.md (not served here)
- docs/brainstorm/brain-integrity-safety-hardening/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-integrity-safety-hardening/design.md (not served here)
- docs/brainstorm/brain-integrity-safety-hardening/plan.md (not served here)
- docs/brainstorm/brain-integrity-safety-hardening/variants.md (not served here)
- docs/brainstorm/brain-integrity-suite/cli-output/claude.md (not served here)
- docs/brainstorm/brain-integrity-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-integrity-suite/design.md (not served here)
- docs/brainstorm/brain-integrity-suite/plan.md (not served here)
- docs/brainstorm/brain-integrity-suite/pr-body.md (not served here)
- docs/brainstorm/brain-integrity-suite/variants.md (not served here)
- docs/brainstorm/brain-lifecycle-review-suite/cli-output/claude.md (not served here)
- docs/brainstorm/brain-lifecycle-review-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-lifecycle-review-suite/design.md (not served here)
- docs/brainstorm/brain-lifecycle-review-suite/plan.md (not served here)
- docs/brainstorm/brain-lifecycle-review-suite/variants.md (not served here)
- docs/brainstorm/brain-lifecycle-suite/cli-output/claude.md (not served here)
- docs/brainstorm/brain-lifecycle-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-lifecycle-suite/design.md (not served here)
- docs/brainstorm/brain-lifecycle-suite/plan.md (not served here)
- docs/brainstorm/brain-lifecycle-suite/variants.md (not served here)
- docs/brainstorm/brain-model-semantics-foundation/adr.md (not served here)
- docs/brainstorm/brain-model-semantics-foundation/cli-output/claude.md (not served here)
- docs/brainstorm/brain-model-semantics-foundation/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-model-semantics-foundation/design.md (not served here)
- docs/brainstorm/brain-model-semantics-foundation/plan.md (not served here)
- docs/brainstorm/brain-model-semantics-foundation/variants.md (not served here)
- docs/brainstorm/brain-portability-interop/cli-output/claude.md (not served here)
- docs/brainstorm/brain-portability-interop/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-portability-interop/design.md (not served here)
- docs/brainstorm/brain-portability-interop/plan.md (not served here)
- docs/brainstorm/brain-portability-interop/variants.md (not served here)
- docs/brainstorm/brain-safety-governance-suite/cli-output/claude.md (not served here)
- docs/brainstorm/brain-safety-governance-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/brain-safety-governance-suite/design.md (not served here)
- docs/brainstorm/brain-safety-governance-suite/plan.md (not served here)
- docs/brainstorm/brain-safety-governance-suite/variants.md (not served here)
- docs/brainstorm/cjk-schema-lifecycle-recovery/cli-output/claude.md (not served here)
- docs/brainstorm/cjk-schema-lifecycle-recovery/cli-output/prompt.md (not served here)
- docs/brainstorm/cjk-schema-lifecycle-recovery/design.md (not served here)
- docs/brainstorm/cjk-schema-lifecycle-recovery/plan.md (not served here)
- docs/brainstorm/cjk-schema-lifecycle-recovery/variants.md (not served here)
- docs/brainstorm/codegraph-link-depth-mcp/cli-output/claude.md (not served here)
- docs/brainstorm/codegraph-link-depth-mcp/cli-output/prompt.md (not served here)
- docs/brainstorm/codegraph-link-depth-mcp/design.md (not served here)
- docs/brainstorm/codegraph-link-depth-mcp/plan.md (not served here)
- docs/brainstorm/codegraph-link-depth-mcp/variants.md (not served here)
- docs/brainstorm/codegraph-mcp-operational-readability/cli-output/claude.md (not served here)
- docs/brainstorm/codegraph-mcp-operational-readability/cli-output/prompt.md (not served here)
- docs/brainstorm/codegraph-mcp-operational-readability/design.md (not served here)
- docs/brainstorm/codegraph-mcp-operational-readability/plan.md (not served here)
- docs/brainstorm/codegraph-mcp-operational-readability/variants.md (not served here)
- docs/brainstorm/context-continuity-receipts-suite/cli-output/claude.md (not served here)
- docs/brainstorm/context-continuity-receipts-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/context-continuity-receipts-suite/design.md (not served here)
- docs/brainstorm/context-continuity-receipts-suite/plan.md (not served here)
- docs/brainstorm/context-continuity-receipts-suite/variants.md (not served here)
- docs/brainstorm/context-injection-pipeline/cli-output/consultant.md (not served here)
- docs/brainstorm/context-injection-pipeline/cli-output/prompt.md (not served here)
- docs/brainstorm/context-injection-pipeline/design.md (not served here)
- docs/brainstorm/context-injection-pipeline/plan.md (not served here)
- docs/brainstorm/context-injection-pipeline/variants.md (not served here)
- docs/brainstorm/context-integrity-gates/cli-output/claude.md (not served here)
- docs/brainstorm/context-integrity-gates/cli-output/prompt.md (not served here)
- docs/brainstorm/context-integrity-gates/design.md (not served here)
- docs/brainstorm/context-integrity-gates/plan.md (not served here)
- docs/brainstorm/context-integrity-gates/variants.md (not served here)
- docs/brainstorm/context-pack-economics-observability/cli-output/claude.md (not served here)
- docs/brainstorm/context-pack-economics-observability/cli-output/prompt.md (not served here)
- docs/brainstorm/context-pack-economics-observability/design.md (not served here)
- docs/brainstorm/context-pack-economics-observability/plan.md (not served here)
- docs/brainstorm/context-pack-economics-observability/variants.md (not served here)
- docs/brainstorm/continuity-hygiene-freshness/cli-output/claude.md (not served here)
- docs/brainstorm/continuity-hygiene-freshness/cli-output/prompt.md (not served here)
- docs/brainstorm/continuity-hygiene-freshness/design.md (not served here)
- docs/brainstorm/continuity-hygiene-freshness/plan.md (not served here)
- docs/brainstorm/continuity-hygiene-freshness/variants.md (not served here)
- docs/brainstorm/cross-agent-query-foundation/cli-output/claude.md (not served here)
- docs/brainstorm/cross-agent-query-foundation/cli-output/prompt.md (not served here)
- docs/brainstorm/cross-agent-query-foundation/design.md (not served here)
- docs/brainstorm/cross-agent-query-foundation/plan.md (not served here)
- docs/brainstorm/cross-agent-query-foundation/variants.md (not served here)
- docs/brainstorm/crossvault-cards-eventtrace-fixes/design.md (not served here)
- docs/brainstorm/crossvault-cards-eventtrace-fixes/plan.md (not served here)
- docs/brainstorm/crossvault-cards-eventtrace-fixes/variants.md (not served here)
- docs/brainstorm/cycle-20261020/cli-output/claude.md (not served here)
- docs/brainstorm/cycle-20261020/cli-output/prompt.md (not served here)
- docs/brainstorm/cycle-20261020/design.md (not served here)
- docs/brainstorm/cycle-20261020/plan.md (not served here)
- docs/brainstorm/cycle-20261020/variants.md (not served here)
- docs/brainstorm/dated-commitments/cli-output/claude.md (not served here)
- docs/brainstorm/dated-commitments/cli-output/prompt.md (not served here)
- docs/brainstorm/dated-commitments/design.md (not served here)
- docs/brainstorm/dated-commitments/plan.md (not served here)
- docs/brainstorm/dated-commitments/variants.md (not served here)
- docs/brainstorm/default-scope-feedback/cli-output/claude.md (not served here)
- docs/brainstorm/default-scope-feedback/cli-output/prompt.md (not served here)
- docs/brainstorm/default-scope-feedback/design.md (not served here)
- docs/brainstorm/default-scope-feedback/plan.md (not served here)
- docs/brainstorm/default-scope-feedback/variants.md (not served here)
- docs/brainstorm/delivery-trust/cli-output/consultant.md (not served here)
- docs/brainstorm/delivery-trust/cli-output/prompt.md (not served here)
- docs/brainstorm/delivery-trust/design.md (not served here)
- docs/brainstorm/delivery-trust/plan.md (not served here)
- docs/brainstorm/delivery-trust/variants.md (not served here)
- docs/brainstorm/distilled-provenance/cli-output/claude.md (not served here)
- docs/brainstorm/distilled-provenance/cli-output/expected-output.md (not served here)
- docs/brainstorm/distilled-provenance/cli-output/prompt.md (not served here)
- docs/brainstorm/distilled-provenance/design.md (not served here)
- docs/brainstorm/distilled-provenance/plan.md (not served here)
- docs/brainstorm/distilled-provenance/variants.md (not served here)
- docs/brainstorm/embedding-provider-suite/cli-output/claude.md (not served here)
- docs/brainstorm/embedding-provider-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/embedding-provider-suite/design.md (not served here)
- docs/brainstorm/embedding-provider-suite/plan.md (not served here)
- docs/brainstorm/embedding-provider-suite/variants.md (not served here)
- docs/brainstorm/entity-truth-dream-suite/cli-output/claude.md (not served here)
- docs/brainstorm/entity-truth-dream-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/entity-truth-dream-suite/design.md (not served here)
- docs/brainstorm/entity-truth-dream-suite/plan.md (not served here)
- docs/brainstorm/entity-truth-dream-suite/variants.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/cli-output/claude.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/cli-output/prompt.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/design.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/plan.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/0e79f0-lint-on-write.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/130-hermes-readiness.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/155-include-paths.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/160-source-trust.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/161-slug-race.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/3309-retrieval-trail.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/a3254-readiness-liveness.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/conventions.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/e24c6d-schema-completeness.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/recon/e5f447-recall-channels.md (not served here)
- docs/brainstorm/evidence-at-the-boundary/variants.md (not served here)
- docs/brainstorm/grok-native-integration/cli-output/claude.md (not served here)
- docs/brainstorm/grok-native-integration/cli-output/prompt.md (not served here)
- docs/brainstorm/grok-native-integration/design.md (not served here)
- docs/brainstorm/grok-native-integration/plan.md (not served here)
- docs/brainstorm/grok-native-integration/variants.md (not served here)
- docs/brainstorm/hermes-memory-provider/cli-output/claude.md (not served here)
- docs/brainstorm/hermes-memory-provider/cli-output/prompt.md (not served here)
- docs/brainstorm/hermes-memory-provider/design.md (not served here)
- docs/brainstorm/hermes-memory-provider/plan.md (not served here)
- docs/brainstorm/hermes-memory-provider/variants.md (not served here)
- docs/brainstorm/hermes-static-tool-schemas/cli-output/claude.md (not served here)
- docs/brainstorm/hermes-static-tool-schemas/cli-output/prompt.md (not served here)
- docs/brainstorm/hermes-static-tool-schemas/design.md (not served here)
- docs/brainstorm/hermes-static-tool-schemas/plan.md (not served here)
- docs/brainstorm/hermes-static-tool-schemas/variants.md (not served here)
- docs/brainstorm/hindsight-brain-loop-ops/cli-output/claude.md (not served here)
- docs/brainstorm/hindsight-brain-loop-ops/cli-output/prompt.md (not served here)
- docs/brainstorm/hindsight-brain-loop-ops/design.md (not served here)
- docs/brainstorm/hindsight-brain-loop-ops/plan.md (not served here)
- docs/brainstorm/hindsight-brain-loop-ops/variants.md (not served here)
- docs/brainstorm/honest-embedding-spend/cli-output/claude.md (not served here)
- docs/brainstorm/honest-embedding-spend/cli-output/prompt.md (not served here)
- docs/brainstorm/honest-embedding-spend/design.md (not served here)
- docs/brainstorm/honest-embedding-spend/plan.md (not served here)
- docs/brainstorm/honest-embedding-spend/variants.md (not served here)
- docs/brainstorm/honest-query-embed-and-safe-upgrades/cli-output/claude.md (not served here)
- docs/brainstorm/honest-query-embed-and-safe-upgrades/cli-output/prompt.md (not served here)
- docs/brainstorm/honest-query-embed-and-safe-upgrades/design.md (not served here)
- docs/brainstorm/honest-query-embed-and-safe-upgrades/plan.md (not served here)
- docs/brainstorm/honest-query-embed-and-safe-upgrades/variants.md (not served here)
- docs/brainstorm/hook-update-resilience/design.md (not served here)
- docs/brainstorm/hook-update-resilience/plan.md (not served here)
- docs/brainstorm/hook-update-resilience/variants.md (not served here)
- docs/brainstorm/index-freshness/design.md (not served here)
- docs/brainstorm/index-freshness/plan.md (not served here)
- docs/brainstorm/indexer-durability/cli-output/claude.md (not served here)
- docs/brainstorm/indexer-durability/cli-output/prompt.md (not served here)
- docs/brainstorm/indexer-durability/design.md (not served here)
- docs/brainstorm/indexer-durability/plan.md (not served here)
- docs/brainstorm/indexer-durability/variants.md (not served here)
- docs/brainstorm/ingestion-import-robustness/design.md (not served here)
- docs/brainstorm/ingestion-import-robustness/plan.md (not served here)
- docs/brainstorm/ingestion-import-robustness/variants.md (not served here)
- docs/brainstorm/integrity-control-agent-memory-surface/cli-output/claude.md (not served here)
- docs/brainstorm/integrity-control-agent-memory-surface/cli-output/prompt.md (not served here)
- docs/brainstorm/integrity-control-agent-memory-surface/design.md (not served here)
- docs/brainstorm/integrity-control-agent-memory-surface/plan.md (not served here)
- docs/brainstorm/integrity-control-agent-memory-surface/variants.md (not served here)
- docs/brainstorm/knowledge-intake-and-consolidation/cli-output/claude.md (not served here)
- docs/brainstorm/knowledge-intake-and-consolidation/cli-output/prompt.md (not served here)
- docs/brainstorm/knowledge-intake-and-consolidation/design.md (not served here)
- docs/brainstorm/knowledge-intake-and-consolidation/plan.md (not served here)
- docs/brainstorm/knowledge-intake-and-consolidation/variants.md (not served here)
- docs/brainstorm/knowledge-provenance/cli-output/claude.md (not served here)
- docs/brainstorm/knowledge-provenance/cli-output/prompt.md (not served here)
- docs/brainstorm/knowledge-provenance/design.md (not served here)
- docs/brainstorm/knowledge-provenance/plan.md (not served here)
- docs/brainstorm/knowledge-provenance/variants.md (not served here)
- docs/brainstorm/language-agnostic-fact-extraction/cli-output/claude.md (not served here)
- docs/brainstorm/language-agnostic-fact-extraction/cli-output/prompt.md (not served here)
- docs/brainstorm/language-agnostic-fact-extraction/design.md (not served here)
- docs/brainstorm/language-agnostic-fact-extraction/plan.md (not served here)
- docs/brainstorm/language-agnostic-fact-extraction/variants.md (not served here)
- docs/brainstorm/link-graph-surfaces/cli-output/claude.md (not served here)
- docs/brainstorm/link-graph-surfaces/cli-output/prompt.md (not served here)
- docs/brainstorm/link-graph-surfaces/design.md (not served here)
- docs/brainstorm/link-graph-surfaces/plan.md (not served here)
- docs/brainstorm/link-graph-surfaces/variants.md (not served here)
- docs/brainstorm/link-recall-intelligence/cli-output/claude.md (not served here)
- docs/brainstorm/link-recall-intelligence/cli-output/prompt.md (not served here)
- docs/brainstorm/link-recall-intelligence/design.md (not served here)
- docs/brainstorm/link-recall-intelligence/plan.md (not served here)
- docs/brainstorm/link-recall-intelligence/variants.md (not served here)
- docs/brainstorm/manifest-dependency-facts/cli-output/claude.md (not served here)
- docs/brainstorm/manifest-dependency-facts/cli-output/expected-output.md (not served here)
- docs/brainstorm/manifest-dependency-facts/cli-output/prompt.md (not served here)
- docs/brainstorm/manifest-dependency-facts/design.md (not served here)
- docs/brainstorm/manifest-dependency-facts/plan.md (not served here)
- docs/brainstorm/manifest-dependency-facts/variants.md (not served here)
- docs/brainstorm/mcp-context-economy/cli-output/claude.md (not served here)
- docs/brainstorm/mcp-context-economy/cli-output/prompt.md (not served here)
- docs/brainstorm/mcp-context-economy/design.md (not served here)
- docs/brainstorm/mcp-context-economy/plan.md (not served here)
- docs/brainstorm/mcp-context-economy/variants.md (not served here)
- docs/brainstorm/mcp-error-codes/cli-output/claude.md (not served here)
- docs/brainstorm/mcp-error-codes/cli-output/prompt.md (not served here)
- docs/brainstorm/mcp-error-codes/design.md (not served here)
- docs/brainstorm/mcp-error-codes/plan.md (not served here)
- docs/brainstorm/mcp-error-codes/variants.md (not served here)
- docs/brainstorm/memory-integrity/cli-output/claude.md (not served here)
- docs/brainstorm/memory-integrity/cli-output/prompt.md (not served here)
- docs/brainstorm/memory-integrity/design.md (not served here)
- docs/brainstorm/memory-integrity/plan.md (not served here)
- docs/brainstorm/memory-integrity/variants.md (not served here)
- docs/brainstorm/memory-observability-suite/atof-atif-mapping.md (not served here)
- docs/brainstorm/memory-observability-suite/cli-output/claude.md (not served here)
- docs/brainstorm/memory-observability-suite/cli-output/prompt.md (not served here)
- docs/brainstorm/memory-observability-suite/design.md (not served here)
- docs/brainstorm/memory-observability-suite/plan.md (not served here)
- docs/brainstorm/memory-observability-suite/variants.md (not served here)
- docs/brainstorm/memory-signal-provenance-lifecycle/cli-output/claude.md (not served here)
- docs/brainstorm/memory-signal-provenance-lifecycle/cli-output/prompt.md (not served here)
- docs/brainstorm/memory-signal-provenance-lifecycle/design.md (not served here)
- docs/brainstorm/memory-signal-provenance-lifecycle/plan.md (not served here)
- docs/brainstorm/memory-signal-provenance-lifecycle/variants.md (not served here)
- docs/brainstorm/memory-subsystem-alignment/cli-output/claude.md (not served here)
- docs/brainstorm/memory-subsystem-alignment/cli-output/prompt.md (not served here)
- docs/brainstorm/memory-subsystem-alignment/design.md (not served here)
- docs/brainstorm/memory-subsystem-alignment/plan.md (not served here)
- docs/brainstorm/memory-subsystem-alignment/variants.md (not served here)
- docs/brainstorm/memory-write-path-integrity/cli-output/claude.md (not served here)
- docs/brainstorm/memory-write-path-integrity/cli-output/prompt.md (not served here)
- docs/brainstorm/memory-write-path-integrity/design.md (not served here)
- docs/brainstorm/memory-write-path-integrity/plan.md (not served here)
- docs/brainstorm/memory-write-path-integrity/variants.md (not served here)
- docs/brainstorm/near-duplicate-defense/cli-output/claude.md (not served here)
- docs/brainstorm/near-duplicate-defense/cli-output/codex.md (not served here)
- docs/brainstorm/near-duplicate-defense/cli-output/opencode.md (not served here)
- docs/brainstorm/near-duplicate-defense/cli-output/prompt.md (not served here)
- docs/brainstorm/near-duplicate-defense/cli-output/second-claude.md (not served here)
- docs/brainstorm/near-duplicate-defense/design.md (not served here)
- docs/brainstorm/near-duplicate-defense/plan.md (not served here)
- docs/brainstorm/near-duplicate-defense/variants.md (not served here)
- docs/brainstorm/no-dead-ends/cli-output/claude.md (not served here)
- docs/brainstorm/no-dead-ends/cli-output/prompt.md (not served here)
- docs/brainstorm/no-dead-ends/design.md (not served here)
- docs/brainstorm/no-dead-ends/plan.md (not served here)
- docs/brainstorm/no-dead-ends/variants.md (not served here)
- docs/brainstorm/non-markdown-ingest/cli-output/claude.md (not served here)
- docs/brainstorm/non-markdown-ingest/cli-output/expected-output.md (not served here)
- docs/brainstorm/non-markdown-ingest/cli-output/prompt.md (not served here)
- docs/brainstorm/non-markdown-ingest/design.md (not served here)
- docs/brainstorm/non-markdown-ingest/plan.md (not served here)
- docs/brainstorm/non-markdown-ingest/variants.md (not served here)
- docs/brainstorm/note-change-truth-maintenance/cli-output/claude.md (not served here)
- docs/brainstorm/note-change-truth-maintenance/cli-output/prompt.md (not served here)
- docs/brainstorm/note-change-truth-maintenance/cli-output/task-bodies.md (not served here)
- docs/brainstorm/note-change-truth-maintenance/design.md (not served here)
- docs/brainstorm/note-change-truth-maintenance/plan.md (not served here)
- docs/brainstorm/note-change-truth-maintenance/variants.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/cli-output/claude.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/cli-output/prompt.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/design.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/plan.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/architect-module.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/concurrency-and-cancellation.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/durability-and-ownership.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/failure-mode-bench.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/ingest-adapters-and-lease.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/progress-reporting.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/recon/provider-sunset.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/review/claims-and-refusals.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/review/cross-cutting.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/review/locks-and-races.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/review/progress-and-cancellation.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/review/tests-and-docs.md (not served here)
- docs/brainstorm/nothing-runs-unwatched/variants.md (not served here)
- docs/brainstorm/nothing-writes-silently/cli-output/claude.md (not served here)
- docs/brainstorm/nothing-writes-silently/cli-output/prompt.md (not served here)
- docs/brainstorm/nothing-writes-silently/design.md (not served here)
- docs/brainstorm/nothing-writes-silently/plan.md (not served here)
- docs/brainstorm/nothing-writes-silently/variants.md (not served here)
- docs/brainstorm/opencode-native-integration/cli-output/claude.md (not served here)
- docs/brainstorm/opencode-native-integration/cli-output/prompt.md (not served here)
- docs/brainstorm/opencode-native-integration/design.md (not served here)
- docs/brainstorm/opencode-native-integration/plan.md (not served here)
- docs/brainstorm/opencode-native-integration/variants.md (not served here)
- docs/brainstorm/operability-first-run/design.md (not served here)
- docs/brainstorm/operability-first-run/plan.md (not served here)
- docs/brainstorm/operability-first-run/variants.md (not served here)
- docs/brainstorm/operational-friction-reduction/design.md (not served here)
- docs/cli-reference.md
- docs/cross-project-pointer.md
- docs/decision-models.md
- docs/hermes-cron.md
- docs/how-it-works.md
- docs/idea.md
- docs/ingest-manifest.md
- docs/mcp.md
- docs/metrics.md
- docs/observability.md
- docs/stability.md
- docs/updating.md
- hooks/hooks.json
- install.md
- plugins/codex/LICENSE
Advantages
- Human-readable and editable memory stored as standard Markdown files in an Obsidian vault. evidence: README.md
- Deterministic dream pass ensures rules are derived from repeated signals without requiring additional model runs. evidence: README.md
- Supports reversible changes through snapshots and rollback capabilities. evidence: README.md
Limitations
- Requires Bun runtime for the CLI and core operations. evidence: README.md
- Advanced features like semantic search require additional setup with sqlite-vec. evidence: README.md
- Some integrations depend on specific client-side plugin support or MCP host capabilities. evidence: README.md
Security
Risk level: Low risk
This brain runs
MCP servers (2)
.claude-plugin/plugin.json(open-second-brain):${CLAUDE_PLUGIN_ROOT}/scripts/o2b mcp --harness claude-code.claude-plugin/plugin.json(open-second-brain-writer):${CLAUDE_PLUGIN_ROOT}/scripts/o2b mcp --scope writer --harness claude-code
Git hooks (2)
.githooks/pre-commit(pre-commit):runs on git pre-commit.githooks/pre-push(pre-push):runs on git pre-push
Agent instructions that run or fetch something (46)
CLAUDE.md:bun run scripts/sync-version.tsCLAUDE.md:CI gates on `bun run scripts/sync-version.ts --check` (the `validate` job, step "Verify manifest version sync").CLAUDE.md:Run the `--check` form locally before pushing a version change.CLAUDE.md:Bump `package.json` (and run `sync-version.ts`) on the feature branch and include it in the SAME pull request as the feature, before you push.CLAUDE.md:Bump `package.json` to `X.Y.Z` and run `bun run scripts/sync-version.ts`.CLAUDE.md:After changing a skill or `hooks/hooks.json`, run:CLAUDE.md:bun run sync-plugin-mirrorsCLAUDE.md:CI gates on `bun run sync-plugin-mirrors:check` (the `validate` and `windows` jobs), and the pre-commit hook runs the same check.docs/legacy-skills/agent-event-log/SKILL.md:Read-only queries (running `git log`, `o2b status`, `vault_health`, `second_brain_query`).docs/legacy-skills/agent-event-log/SKILL.md:**Technical identifiers stay verbatim regardless of surrounding language**: command names (`bun test`, `o2b doctor`), file paths (`src/core/event-log.ts`), package and library names (`proper-lockfile`, `@anthropic-ai/sdk`), API symbols, error messages quoted from logs.plugins/codex/skills/brain-memory/SKILL.md:Read-only inspection (running `git log`, `o2b status`, `vault_health`).plugins/codex/skills/brain-memory/SKILL.md:After scan, the source line becomes `@osb✓ [[sig-...]]` so a second run is idempotent.plugins/codex/skills/codegraph-partner/SKILL.md:Run these checks in order.plugins/codex/skills/codegraph-partner/SKILL.md:No install talk.plugins/codex/skills/codegraph-partner/SKILL.md:| | not_indexed (CLI present, no `.codegraph/` or `initialized: false`) | Offer one line: "run `codegraph init <path>` to index this repo".plugins/codex/skills/codegraph-partner/SKILL.md:Whenever you run `codegraph init` in any repository on the user's behalf, immediately read that repo's `.gitignore` and append a `.codegraph/` line if one is not already present.plugins/codex/skills/decision-model-setup/SKILL.md:That is the operator's command, and only the operator can run it: the key is operator-only, so the `brain_settings` tool refuses an agent's write to it with `operator_only_setting` on every machine, switched or not.plugins/codex/skills/decision-model-setup/SKILL.md:Ask the operator to run the command;plugins/codex/skills/decision-model-setup/SKILL.md:Does not install servers or download model weights.plugins/codex/skills/embeddings-setup/SKILL.md:Every run that uses it prints one stderr warning per endpoint.plugins/codex/skills/embeddings-setup/SKILL.md:If the install path missed it, force a rebuild:plugins/codex/skills/embeddings-setup/SKILL.md:bun pm ls | grep sqlite-vecplugins/codex/skills/embeddings-setup/SKILL.md:bun install --forceplugins/codex/skills/embeddings-setup/SKILL.md:When the index already exists and only some chunks lack vectors, price the work first with the dry run, then apply it:plugins/codex/skills/embeddings-setup/SKILL.md:Does not invoke `brew install` on the user's behalf.plugins/codex/skills/schema-author/SKILL.md:Preview the batch first with `schema_apply_mutations` (`dry_run=true`) or `o2b brain schema apply --dry-run --mutation ...`: it returns the pack that would result and its diff and writes nothing at all - no config write, no audit record, no lock file.plugins/codex/skills/schema-author/SKILL.md:It does not run the two checks that exist only because the apply writes: the vault-identity write guard, and the atomic writer's re-parse of the rendered YAML.plugins/codex/skills/schema-author/SKILL.md:Re-run lint/stats after mutation and report changed tokens plus any remaining findings.skills/brain-memory/SKILL.md:Read-only inspection (running `git log`, `o2b status`, `vault_health`).skills/brain-memory/SKILL.md:After scan, the source line becomes `@osb✓ [[sig-...]]` so a second run is idempotent.skills/codegraph-partner/SKILL.md:Run these checks in order.skills/codegraph-partner/SKILL.md:No install talk.skills/codegraph-partner/SKILL.md:| | not_indexed (CLI present, no `.codegraph/` or `initialized: false`) | Offer one line: "run `codegraph init <path>` to index this repo".skills/codegraph-partner/SKILL.md:Whenever you run `codegraph init` in any repository on the user's behalf, immediately read that repo's `.gitignore` and append a `.codegraph/` line if one is not already present.skills/decision-model-setup/SKILL.md:That is the operator's command, and only the operator can run it: the key is operator-only, so the `brain_settings` tool refuses an agent's write to it with `operator_only_setting` on every machine, switched or not.skills/decision-model-setup/SKILL.md:Ask the operator to run the command;skills/decision-model-setup/SKILL.md:Does not install servers or download model weights.skills/embeddings-setup/SKILL.md:Every run that uses it prints one stderr warning per endpoint.skills/embeddings-setup/SKILL.md:If the install path missed it, force a rebuild:skills/embeddings-setup/SKILL.md:bun pm ls | grep sqlite-vecskills/embeddings-setup/SKILL.md:bun install --forceskills/embeddings-setup/SKILL.md:When the index already exists and only some chunks lack vectors, price the work first with the dry run, then apply it:skills/embeddings-setup/SKILL.md:Does not invoke `brew install` on the user's behalf.skills/schema-author/SKILL.md:Preview the batch first with `schema_apply_mutations` (`dry_run=true`) or `o2b brain schema apply --dry-run --mutation ...`: it returns the pack that would result and its diff and writes nothing at all - no config write, no audit record, no lock file.skills/schema-author/SKILL.md:It does not run the two checks that exist only because the apply writes: the vault-identity write guard, and the atomic writer's re-parse of the rendered YAML.skills/schema-author/SKILL.md:Re-run lint/stats after mutation and report changed tokens plus any remaining findings.
Hooks (54)
hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" post-write-reminder; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook post-write-reminder; exit 0hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-agenda; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-agenda; exit 0hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" backlog-nudge; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook backlog-nudge; exit 0hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" log-catchup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook log-catchup; exit 0hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" view-render; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook view-render; exit 0hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" recall-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook recall-inject; exit 0hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" nav-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook nav-inject; exit 0hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" counterparty-brief; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook counterparty-brief; exit 0hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0hooks/hooks.json(PreToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-orient; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-orient; exit 0hooks/hooks.json(PreToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-host-backup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-host-backup; exit 0hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" stop-log-guardrail; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook stop-log-guardrail; exit 0hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" answer-quote-check; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook answer-quote-check; exit 0hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" hygiene-digest; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook hygiene-digest; exit 0hooks/hooks.json(SubagentStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0hooks/hooks.json(SubagentStop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json(SessionEnd):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0hooks/hooks.json(SessionEnd):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-promote; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-promote; exit 0hooks/hooks.json(PostCompact):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0hooks/hooks.json(PostCompact):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" post-write-reminder; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook post-write-reminder; exit 0plugins/codex/hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0plugins/codex/hooks/hooks.json(PostToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0plugins/codex/hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0plugins/codex/hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-agenda; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-agenda; exit 0plugins/codex/hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" backlog-nudge; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook backlog-nudge; exit 0plugins/codex/hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" log-catchup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook log-catchup; exit 0plugins/codex/hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" view-render; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook view-render; exit 0plugins/codex/hooks/hooks.json(SessionStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" recall-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook recall-inject; exit 0plugins/codex/hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" nav-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook nav-inject; exit 0plugins/codex/hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" counterparty-brief; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook counterparty-brief; exit 0plugins/codex/hooks/hooks.json(UserPromptSubmit):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" reground-deliver; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook reground-deliver; exit 0plugins/codex/hooks/hooks.json(PreToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-orient; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-orient; exit 0plugins/codex/hooks/hooks.json(PreToolUse):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" pretool-host-backup; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook pretool-host-backup; exit 0plugins/codex/hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" stop-log-guardrail; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook stop-log-guardrail; exit 0plugins/codex/hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" answer-quote-check; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook answer-quote-check; exit 0plugins/codex/hooks/hooks.json(Stop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" hygiene-digest; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook hygiene-digest; exit 0plugins/codex/hooks/hooks.json(SubagentStart):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" subagent-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook subagent-inject; exit 0plugins/codex/hooks/hooks.json(SubagentStop):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(SessionEnd):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0plugins/codex/hooks/hooks.json(SessionEnd):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" gap-promote; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook gap-promote; exit 0plugins/codex/hooks/hooks.json(PostCompact):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" active-inject; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook active-inject; exit 0plugins/codex/hooks/hooks.json(PostCompact):r="$CLAUDE_PLUGIN_ROOT"; if [ -n "$r" ] && [ -x "$r/scripts/o2b-hook" ]; then exec "$r/scripts/o2b-hook" session-capture; fi; command -v o2b-hook >/dev/null 2>&1 && exec o2b-hook session-capture; exit 0
Package scripts (1)
package.json(prepare):bun scripts/prepare.ts || exit 0
- Low risk: scripts-present (.githooks/pre-commit)
- Low risk: scripts-present (.githooks/pre-push)
- Low risk: network-instructions (plugins/codex/skills/codegraph-partner/SKILL.md:115)
- Low risk: scripts-present (scripts/prepare.ts)
- Low risk: network-instructions (skills/codegraph-partner/SKILL.md:115)
Attribution and licence
- First committed by
hermes-vps-agent
- Declared authors
- Open Second Brain contributors (declared in .claude-plugin/plugin.json)
- Open Second Brain contributors (declared in pyproject.toml)
- Publisher
- Serg Eroshe (Community)
- Source
- itechmeat/open-second-brain · commit 233b91d
- Licence
- MIT License (files served here)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- solaitken, itechmeat, Yori940619, hermes-vps-agent, DanBennettUK
AutoGineer is not affiliated with or endorsed by hermes-vps-agent or Serg Eroshe.
Install from source
486 of its files are not served here, so there is no ZIP. itechmeat/open-second-brain at 233b91d