You are a coding agent running inside a fresh clone of this repo. The user wants their own AI second brain. The scaffold is in
./template/. Interview them, copy it to its real home, personalize it, and build the launcher.Most of this runbook is agent-independent. PHASE 4 is the exception: hooks are a Claude Code feature. If you are another agent, skip it and say so in the final report.
- Ask for their language first, then use it. PHASE 1 question 1 settles which language the user wants. Ask that first question in the language the machine's locale suggests, and speak whatever they answer for the rest of the run. Every question and message below is written in English so the instructions stay precise; translate them as you go. What you write into the vault stays English except where a placeholder says otherwise.
- Interview first, build second. Never leave a literal
{{...}}in any file. - Never destroy. If the target vault path exists, show it and ask before overwriting.
- Don't block on optional steps (mem0, icons). Log it, tell the user, continue.
- Detect the OS first - every phase below branches on it. Don't run macOS commands on Windows.
- Verify each phase by running it, not by reading it. End with the first-run report.
Placeholders: {{LANGUAGE}} · {{OS_NAME}} · {{USER_NAME}} · {{USER_BIO}} · {{COMPANION}} ·
{{VAULT_PATH}} · {{TODAY}} · {{USER_ID}} · {{VENV_PYTHON}} · {{PYTHON_PATH}} ·
{{GUARD_COMMAND}} · {{GUARD_ARG1}} · {{GUARD_SCRIPT}}
| Windows | Linux | macOS | |
|---|---|---|---|
| detect | $env:OS -eq 'Windows_NT' |
uname -s = Linux |
uname -s = Darwin |
| machine name | $env:COMPUTERNAME |
hostname |
scutil --get ComputerName |
| vault default | $env:USERPROFILE\Documents\{{OS_NAME}} |
~/Documents/{{OS_NAME}} |
see below |
macOS vault default: if ~/Library/Mobile Documents/iCloud~md~obsidian/Documents/ exists use
.../Documents/{{OS_NAME}} (syncs across devices), else ~/Documents/{{OS_NAME}}.
Derive {{OS_NAME}}: PascalCase the machine name and append OS, stripping
MacBook/Pro/Air/iMac/'s and dashes. Johns-MacBook-Pro → JohnOS, AETHROX → AethroxOS.
Propose it, let the user override. Set {{TODAY}} = date +%F.
The hooks are Python now, invoked directly (exec form, no shell, no shebang), so {{PYTHON_PATH}}
must be an absolute path to an interpreter that actually runs. Presence on PATH is not enough: on
this user's Windows machine python3 resolves to a Microsoft Store stub that exists on PATH but
fails the moment it is executed, while python works. So run each candidate, don't just check
for it:
python3 -c "import sys; print(sys.version_info[0])" # try first
python -c "import sys; print(sys.version_info[0])" # fall back to thisTake the first candidate that actually prints 3, then resolve it to an absolute path
(command -v python3 / (Get-Command python).Source) and use that as {{PYTHON_PATH}}.
The guard is the second hook entry on SessionStart only, a dependency-free fallback that warns
the user when {{PYTHON_PATH}} stops working. It sits on SessionStart alone because that is the
only event whose output reaches the user as injected context, and one warning per session is
enough. Resolve its three placeholders per platform:
{{GUARD_COMMAND}} |
{{GUARD_ARG1}} |
{{GUARD_SCRIPT}} |
|
|---|---|---|---|
| Linux, macOS | sh |
-- |
${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh |
| Windows | absolute cmd.exe path, e.g. C:\\Windows\\System32\\cmd.exe |
/c |
${CLAUDE_PROJECT_DIR}\\.claude\\hooks\\guard.cmd |
The -- on POSIX is what keeps both platforms on the same three-slot argument shape, so
settings.json stays valid JSON with placeholders only ever appearing inside strings.
Resolve cmd.exe to an absolute path on Windows ((Get-Command cmd).Source) instead of leaving
it bare. Hooks are launched without a shell, and some hosts run them with a PATH that has no
System32 in it, which fails the hook with Executable not found in $PATH: cmd.
backup.sh and scripts/schedule-backup.ps1 still require Git Bash on Windows: the Git Bash
dependency was removed from the hooks, not from the repository. scripts/schedule-backup.ps1
throws if it cannot find one. See PHASE 6b for finding it.
- Which language should your companion speak to you in? →
{{LANGUAGE}}. Ask this first, in the language the locale suggests, and switch to their answer for everything after it. Free text, not a menu. - What is your name? →
{{USER_NAME}}(also becomes{{USER_ID}}, lowercased, for mem0) - What do you do, and what will you use this brain for most? (1-2 sentences) →
{{USER_BIO}} - What do you want to call your AI companion? →
{{COMPANION}} - Scope: core (everyone) plus the optional
⚔️ 200-Goals,🔐 400-Vault,💪 700-Body,🧘 800-Mind - Semantic memory (mem0)? The file-based memory works with no API and is enough for most people. mem0 adds semantic search on top; the base tier is free (mem0.ai, no credit card). Recommended, but optional.
- Local web panel? A read-only page showing the engine's health, open threads and where the last session stopped, plus a note browser with search. Needs Python and one package. Optional; the vault works exactly the same without it.
Confirm the vault path with the user before creating anything.
Obsidian is required; everything else is optional. Install only what is missing, and never install anything without telling the user first.
- Windows:
winget install Obsidian.Obsidian - Linux:
flatpak install flathub md.obsidian.Obsidian(or the distro package / AppImage) - macOS:
brew install --cask obsidian
Claude Code is already installed - the user is running you. Don't reinstall it.
mkdir -p "{{VAULT_PATH}}"
cp -R ./template/. "{{VAULT_PATH}}/"# Windows
New-Item -ItemType Directory -Force "{{VAULT_PATH}}" | Out-Null
Copy-Item ".\template\*" "{{VAULT_PATH}}" -Recurse -ForceThe trailing /. is not a typo: cp -R ./template/ dest/ puts a template/ folder inside an
existing destination instead of copying the contents out. Check afterwards that AGENTS.md sits
at the vault root and there is no template directory under it.
Nothing here needs chmod +x. The hooks are invoked in exec form ({{PYTHON_PATH}} with
-S and hooks.py as arguments, sh/cmd with guard.sh/guard.cmd as an argument), so no
shebang or execute bit is ever relied on, on any platform. The -S is a startup saving, not a
preference: the engine imports nothing outside the standard library, so skipping site
initialization cuts roughly 15 ms off every hook invocation and changes the output not at all.
Keep it in front of the script path in all four entries.
Create only the optional scope folders the user picked:
⚔️ 200-Goals · 🔐 400-Vault · 💪 700-Body · 🧘 800-Mind
Then make the vault a git repo. backup.sh refuses to run outside one, and PHASE 6b has nothing
to schedule without it. The .gitignore came with the template, so the key file is already
excluded:
git -C "{{VAULT_PATH}}" init -b main
git -C "{{VAULT_PATH}}" add -A
git -C "{{VAULT_PATH}}" commit -m "{{OS_NAME}}: initial vault"If the commit fails because git has no identity on this machine, ask the user for the name and
email to use and set them with git config. Do not guess them.
Hooks are what make the memory protocol automatic: they inject the last session at startup and
nudge for a memory write before the session ends. No other agent has them. If you are not Claude
Code, skip to PHASE 5 and tell the user at the end that the protocol in AGENTS.md holds either
way, it is just read rather than enforced.
template/.claude/settings.json is the same file on every platform: one Python dispatcher
(hooks.py) on each of the four events, plus one guard fallback on SessionStart, wired in
exec form. Rename it to
settings.local.json in the vault, then substitute the placeholders found in PHASE 0:
{{PYTHON_PATH}}, {{GUARD_COMMAND}}, {{GUARD_ARG1}}, {{GUARD_SCRIPT}}.
Then run each subcommand by hand and confirm the output before moving on:
echo '{"session_id":"test"}' | "{{PYTHON_PATH}}" -S "{{VAULT_PATH}}/.claude/hooks/hooks.py" session-startThat must print exactly one line of JSON. If it prints nothing, the hook is broken and continuity is silently dead, debug it now.
session-start assembles that line from Last-Session.md, Threads.md, the first 60 lines of
🔮 850-{{COMPANION}}/Rules.md (standing corrections, injected every session so nothing written
there is ever forgotten), the journal bridge (the most recent ## entry of Journal.md), the first
150 lines of knowledge/index.md, and the last 25 lines of today's (or, failing that, yesterday's)
daily/*.md file. All of it is held to a 16,000 character budget: per-section caps run first, and
if the total still does not fit, whole sections drop in this order: the knowledge index, then the
daily tail, then the journal bridge, then any memory-write warning. Last-Session, Threads and Rules
never drop, only truncate. A vault with none of the newer files (Rules.md, Journal.md,
knowledge/index.md, daily/) still produces valid context, this is what every vault installed
before this feature looks like.
.claude/hooks/graph_check.py scans the vault for broken [[wikilinks]] and orphan notes; the
doctor skill wraps it together with every other mechanical check (hooks wired, scripts import,
daily log freshness, compile status) into one report. Point the user at "run the doctor skill" if
memory ever seems to be silently failing.
SessionEnd and PreCompact both hand their hook payload to .claude/hooks/flush.py, spawned
detached so the hook returns immediately: the payload is written to a short-lived file in
.claude/hooks/.state/ (the detached child has no inherited stdin), then flush.py runs against
it without the hook waiting. It reads the transcript, trims it to a bounded window, and calls
claude -p --model haiku to turn that window into a five-field summary written in {{LANGUAGE}},
which it appends to {{VAULT_PATH}}/daily/YYYY-MM-DD.md, creating that file with a small skeleton
on first write. This summarization call runs on haiku and costs a small amount per session. If the
call fails, flush.py falls back to the raw transcript slice under a note naming the error, so a
session is never silently lost. PreCompact runs the same path just before a context compaction,
so a long session is not lost to the compaction boundary. daily/ is machine-written: read it,
never hand-edit it.
flush.py also calls maybe_trigger_compile right after a successful daily-log append: at or
after 18:00 local time it spawns .claude/hooks/compile.py detached, which turns changed
daily/*.md files into knowledge/ articles (index.md, log.md, concepts/, connections/)
using claude -p --model sonnet --permission-mode acceptEdits against an isolated staging copy,
outside the vault. hooks.py also fires a catch-up pass, detached, at the tail of every
session-start (flush.py --maybe-compile), so a day whose last session ends before 18:00 still
gets compiled the next time a session starts. This is the one part of the engine that runs
unattended with write tools, so it is fenced by a strict before/after manifest diff that rejects
anything outside knowledge/ before it ever reaches the live vault; see
docs/COMPILE-SECURITY.md for the full threat model. knowledge/ is machine-written like
daily/: read it, never hand-edit it.
First rename the folder from 🔮 850-Companion to 🔮 850-{{COMPANION}} (same value used
everywhere else). hooks.py never hardcodes this name (it globs for 🔮 850-*), but
semantic-memory.py does reference the resolved name and expects it post-personalization. Keep
the emoji and the 850- prefix exactly; only the name after the dash changes.
Then replace every placeholder in every file under the vault. Files that contain them:
AGENTS.md, CLAUDE.md, 🎯 100-Command-Center/Dashboard.md, all of
🔮 850-{{COMPANION}}/*.md, .claude/settings.local.json ({{PYTHON_PATH}}, {{GUARD_COMMAND}},
{{GUARD_ARG1}}, {{GUARD_SCRIPT}}), .claude/backup.sh, .claude/semantic-memory.py, and
.claude/hooks/flush.py and .claude/hooks/compile.py (both carry {{LANGUAGE}}, inside the
prompt each sends to claude -p). hooks.py, _common.py, portalock.py and graph_check.py
themselves carry no placeholders. Then verify:
grep -rl "{{" "{{VAULT_PATH}}" || echo "all placeholders resolved"Also add a line to the vault structure section of AGENTS.md for each optional folder created.
CLAUDE.md is a pointer to it and carries no structure of its own, so it needs nothing here.
# Linux
./scripts/launcher-linux.sh "{{OS_NAME}}"
# macOS
./scripts/launcher-macos.sh "{{OS_NAME}}"# Windows
powershell -ExecutionPolicy Bypass -File scripts\launcher-windows.ps1 -VaultName "{{OS_NAME}}" -VaultPath "{{VAULT_PATH}}"The obsidian:// handler only resolves after the vault has been opened in Obsidian once, so the
launcher does nothing until PHASE 8. Say so rather than letting the user think it is broken.
The backup pushes to a remote, so the vault needs one. PHASE 3 made it a repo; check whether it
already tracks anything with git -C "{{VAULT_PATH}}" rev-parse '@{u}'. If that fails, offer to
set one up before scheduling.
The repo must be private. This vault holds notes, plans and possibly finances. Never create it public, and say this out loud rather than assuming the user knows.
With the gh CLI available and the user agreeing:
gh repo create "{{OS_NAME}}" --private --source "{{VAULT_PATH}}" --remote origin --pushWithout gh, ask the user to create an empty private repo and paste its URL, then:
git -C "{{VAULT_PATH}}" remote add origin <url>
git -C "{{VAULT_PATH}}" push -u origin mainIf the user declines a remote, skip the rest of this phase and say the backup is not scheduled, so it does not look like it silently failed.
# Linux (systemd user timer) and macOS (launchd agent)
./scripts/schedule-backup.sh "{{VAULT_PATH}}" "{{OS_NAME}}"# Windows (per-user scheduled task)
powershell -ExecutionPolicy Bypass -File scripts\schedule-backup.ps1 -VaultPath "{{VAULT_PATH}}" -VaultName "{{OS_NAME}}"Ask before running it: this is a persistent change to the user's machine. Then confirm it registered, and say plainly that nothing announces a failed backup except its exit code and the warning the session-start hook prints.
On Windows the script routes the task through .claude/run-hidden.vbs, because Git Bash is a
console program and the task would otherwise flash a black window on the desktop every hour. If
the vault does not carry that file, the task still works but the window comes back.
Note that backup.sh and scripts/schedule-backup.ps1 still require Git Bash on Windows even
though the hooks no longer do. scripts/schedule-backup.ps1 locates it itself and throws if it
cannot find one; the Git Bash dependency was removed from the hooks, not from the repository.
On Linux a user timer only runs while the user is logged in. Mention
loginctl enable-lingeras an option, do not run it: it is a persistent system change of its own.
Skip entirely if the user said no; the system is fully functional without it.
uv venv "{{VAULT_PATH}}/.claude/mem0-venv"
uv pip install --python "{{VENV_PYTHON}}" mem0ai{{VENV_PYTHON}} is .claude/mem0-venv/bin/python, or .claude/mem0-venv/Scripts/python.exe
on Windows. uv tool install mem0ai does not work - it is a library and ships no executables.
Then have the user paste a free key from https://mem0.ai into .claude/settings.local.json
under "env": { "MEM0_API_KEY": "..." }. Never ask them to send you the key; that file is
gitignored and must stay uncommitted. Verify with:
"{{VENV_PYTHON}}" "{{VAULT_PATH}}/.claude/semantic-memory.py" add "test"
"{{VENV_PYTHON}}" "{{VAULT_PATH}}/.claude/semantic-memory.py" search "test"Skip entirely if the user said no; nothing else depends on it.
The panel lives in panel/ in this clone and reads the vault from outside. It never writes
into the vault. Ask the user where they want it to live:
- Left in this clone (simplest):
git pullupdates the panel along with everything else. - Copied elsewhere: copy the
panel/directory wherever they prefer. It then stops tracking this repo and has to be updated by hand.
Either way it needs one package, and it needs to be told where the vault is:
pip install markdown # or: uv pip install markdown
python panel/panel.py --vault "{{VAULT_PATH}}"Open http://127.0.0.1:8420/ and confirm the engine strip, the threads and the note browser render. Stop with Ctrl+C.
To avoid passing --vault every time:
export AETHROM_VAULT="{{VAULT_PATH}}" # Linux and macOS, add to the shell profile
setx AETHROM_VAULT "{{VAULT_PATH}}" # Windows, applies to new shellsIf it cannot find a vault it prints the path it tried and exits 1 rather than serving an empty
page. panel/README.md carries the rest, including what it deliberately never reads.
ls -la "{{VAULT_PATH}}"
test -f "{{VAULT_PATH}}/AGENTS.md" && echo "AGENTS.md ok"
test -f "{{VAULT_PATH}}/🔮 850-{{COMPANION}}/Last-Session.md" && echo "memory ok"
ls -la "{{VAULT_PATH}}/.claude/hooks/" # only if you did PHASE 4Report to the user in {{LANGUAGE}}:
- ✅ What was built: folders, memory files, the companion's name, the 🧠 shortcut, and the hooks if you wired them
▶️ First run: open Obsidian and pick{{VAULT_PATH}}as a vault. That introduces it to Obsidian once; the 🧠 icon opens it in one click from then on. Then open your agent in that folder.- ✨ Show them the magic: say something, end the session, start a new one. {{COMPANION}} will remember the last one. Continuity is the whole difference.
⚠️ Name every optional step that was skipped or failed (icon, mem0, Obsidian install, the scheduled backup). Do not pass over them silently. If you skipped the hooks, say that the memory protocol now depends on the agent followingAGENTS.mdrather than being reminded by the harness.
The vault is a git repo, and that is the whole rollback story here, there is no snapshot tool and no upgrade script because a commit already does that job.
- Commit the vault first. That commit is the rollback if anything below goes wrong.
- Run
python scripts/upgrade-check.py "{{VAULT_PATH}}"from a clone of this repo. It is report-only: it writes nothing, moves nothing, deletes nothing, and just prints what differs. Exit code 0 means already current, 1 means there is something to do. - Replace only the files it names as code:
.claude/hooks/and.claude/skills/entries reported missing, differing or new, plus the bash-era leftovers it names for removal. - Leave
daily/,knowledge/and the🔮 850-*memory folder alone. The report never suggests replacing them, they are the user's memory, not code. - Seed only what it reports missing (
Rules.md,knowledge/index.md,knowledge/log.md, theknowledge/concepts/andknowledge/connections/folders), never overwriting one that already exists. - Re-resolve
{{PYTHON_PATH}}and the guard placeholders. A vault from the bash era has none of them in itssettings.local.json; redo PHASE 0's Python discovery and PHASE 4's settings write rather than patching the old file. - Run the
doctorskill and confirm every check is green.
Two traps to call out by name:
settings.local.jsonstill wired to the deleted bash hooks (session-start.sh,prompt-counter.sh,session-end.sh) means the engine never runs, and nothing says so.upgrade-check.pyflags this; treat it as the top-priority line in the report.python3on Windows can be a Microsoft Store stub that is on PATH but fails when run. Re-resolve{{PYTHON_PATH}}by running each candidate, not by checking presence on PATH.