mindpalace — resource-first knowledge vault
A layout and a set of rules that let an AI agent build a durable second brain instead of re-learning your world every session.
The core idea: shared resources live in dedicated files that many projects reference — never duplicated. One server, one file. One client account, one file. Projects are thin pointers that link to them. A credential exists in exactly one place, so rotating it is a one-file edit, not a grep-and-pray.
This file is the whole skill. It is host-agnostic — see README.md for how to load it into
Claude Code, Claude.ai, the OpenAI Agents SDK, Cursor, or any harness that can read a
markdown instruction file.
Location
Resolve the vault root in this order:
$MINDPALACE_VAULT, if set~/.mindpalace/vault/— the default, and the same path the full mindpalace tool uses- A
vault/ormindpalace/directory in the current project
If none exists, offer to create one (see Bootstrapping below). Never guess a path and never scatter files outside the resolved root.
Mental model
infra/<resource>.md ← one file per server, hosting plan, DNS zone, CI runner
accounts/<owner>.md ← one file per party — a client, or yourself
projects/<slug>.md ← THIN pointer; frontmatter links to infra + accounts
runbooks/<verb>-<x>.md ← repeatable procedures
docs/<type>/ ← dated artifacts: PDFs, diagrams, exports, notes
README.md ← indexes of every resource, account and project
LOG.md ← append-only timeline, curated regularly
Two rules generate most of the value:
- A fact lives in exactly one file. Everything else links to it.
- Projects never hold infrastructure detail. They hold a relation to it.
Always-read entry points
Before answering anything from the vault, read:
README.md— the indexes. Tells you what exists without globbing.ME.md— the user's own context, if present.
Projects are flat files at projects/<slug>.md. There is no projects/<slug>/INDEX.md.
File catalog — where to look
| User mentions | Read this |
|---|---|
| API key, password, secret, env var | infra/<resource>.md or accounts/<owner>.md — never projects/ |
| URL, port, endpoint, prod, staging | projects/<slug>.md → Environments table → infra/<server>.md |
| Cloud root account, GitHub handle, billing | accounts/<owner>.md |
| server, VPS, EC2, droplet, DNS, CI | infra/<resource>.md |
| "why did we choose X" | projects/<slug>.md → Decisions |
| deploy, restore, rotate, onboard | runbooks/<verb>-<target>.md |
| meeting notes, PDFs, diagrams | docs/<type>/YYYY-MM-DD_<slug>.<ext> |
| "what changed recently" | LOG.md |
Frontmatter contract
Every file carries typed frontmatter. This is what makes the vault queryable with nothing
more than grep.
infra/<resource>.md
---
type: infra
name: hetzner-vps-01 # matches the filename
provider: hetzner
owner: me # → accounts/me.md
status: active # active | dead | planned
sensitive: true
---
accounts/<owner>.md
---
type: account
name: acme-corp
owner: acme-corp # "me" for your own
sensitive: true
---
projects/<slug>.md
---
type: project
name: storefront
client: acme-corp # → accounts/acme-corp.md
status: prod # idea | dev | staging | prod | archived
hosts_on: [hetzner-vps-01] # → infra/*.md — the relation that replaces duplication
accounts: [acme-corp] # → accounts/*.md
stack: [django, nuxt, postgres]
---
runbooks/<verb>-<target>.md
---
applies_to: [hetzner-vps-01, any-django]
risk: low # low | med | high
---
Keep name: identical to the filename stem. Cross-references use that stem, never a path.
Writing rules
When the user gives you new information:
| Information | Goes to | Never to |
|---|---|---|
| Server credentials, IP, SSH | infra/<resource>.md |
projects/ |
| Platform login, billing owner | accounts/<owner>.md |
projects/ |
| Project URL, environment | projects/<slug>.md + hosts_on: relation |
duplicated into infra/ |
| One-off observation | LOG.md, curate later |
scattered everywhere |
| Step-by-step procedure | runbooks/ |
buried in a project file |
| Dated artifact | docs/<type>/YYYY-MM-DD_<slug>.<ext> |
a random folder |
Before writing any credential, grep the vault for it. If it already exists, update it in place. Never create a second copy.
grep -rn "SECRET_KEY" "$MINDPALACE_VAULT"/infra "$MINDPALACE_VAULT"/accounts
Convert relative dates to absolute when writing. "Last Tuesday" is worthless in six
months; 2026-03-04 is not.
Cross-resource queries
The relations in frontmatter mean these are one-liners:
| Question | Command |
|---|---|
What runs on hetzner-vps-01? |
grep -l "hosts_on:.*hetzner-vps-01" "$MINDPALACE_VAULT"/projects/*.md |
What do we run for client acme-corp? |
grep -l "client: acme-corp" "$MINDPALACE_VAULT"/projects/*.md |
Which resources does acme-corp pay for? |
grep -l "owner: acme-corp" "$MINDPALACE_VAULT"/infra/*.md |
| What is still live? | grep -l "status: active" "$MINDPALACE_VAULT"/infra/*.md |
| Which runbooks touch this box? | grep -l "applies_to:.*hetzner-vps-01" "$MINDPALACE_VAULT"/runbooks/*.md |
If a query returns nothing, say so plainly. Do not infer an answer from a project file when the relation is missing — a missing relation is itself a finding worth reporting.
Attachments — real files, not just prose
Configs, certificates and keys live in a sibling folder named after the markdown file:
infra/hetzner-vps-01.md ← the note
infra/hetzner-vps-01/ ← the attachments
├── nginx/ ← server-wide nginx config
├── certs/ ← public certs only
├── scripts/ ← maintenance scripts
└── secrets/ ← ONLY *.age encrypted files
To store a file:
- Identify the owning resource —
infra/<which>oraccounts/<which>. - Pick the subfolder by type: config →
nginx/orscripts/, public cert →certs/, private key →secrets/. - If it is secret, encrypt before it touches the vault:
age -r "$AGE_PUBKEY" -o file.age file && shred -u file - List it under an Attached files heading in the parent markdown.
- Append a line to
LOG.md.
Project-specific configs belong in the project repo, not the vault. A per-app nginx site
or an app's .env.example is repo content. The vault holds server-wide and cross-cutting
configuration only.
Scaffolding a new entity
Ask first, write second. Where the harness has a structured question tool, use it; otherwise ask in one message and wait.
New project — ask for: slug (kebab-case) · client (me or a slug from accounts/) ·
status · stack · which existing infra hosts it (list what is in infra/ first) · which
accounts apply · repo URLs. Then copy templates/project.md, fill it, add a row to the
README.md projects index, and append to LOG.md.
New infra resource — ask for: provider · owner · region · plan · IP · SSH access · control
panel URL. Then copy templates/infra.md, update the README.md resources index, append to
LOG.md.
New account — ask for: owner (me or client slug) · which platforms it covers. Then copy
templates/account.md, update the README.md accounts index, append to LOG.md.
Always list the existing options before asking the user to choose. Offering a free-text answer where a relation should point at an existing file is how duplicates get created.
LOG curation — the anti-bloat rule
LOG.md is a staging area, not the archive. Keep it under ~200 lines.
Curate when the file passes 200 lines, when two weeks have elapsed, or on request:
- Read the whole file.
- For each entry, merge the durable fact into its permanent home —
infra/,accounts/,projects/, or aREADME.mdindex. - Delete entries that are now redundant.
- Keep the last 14 days raw.
- Older entries that are durable but have no natural home → an
## archivedbullet, or drop them.
An uncurated LOG is the main way these vaults rot. Facts that only ever live in the log are facts nobody will find.
Security
- Mark credential-bearing files
sensitive: truein frontmatter. - Encrypt before committing if the vault is pushed anywhere:
ageorgit-crypt.secrets/must contain only.agefiles; plaintext there is a bug. - Never print a credential into chat unless the user explicitly asks for that value.
Reference it by location: "the R2 key is in
infra/cloudflare.mdunder Object storage." - Re-read the file before quoting any credential. Your memory of it may be stale, and a confidently wrong password costs more than a second read.
- Keep
.gitignorecoveringsecrets/**except*.age, and any*.pem,*.key,.env.
Bootstrapping a new vault
If no vault exists, create the skeleton and say what you made:
VAULT="${MINDPALACE_VAULT:-$HOME/.mindpalace/vault}"
mkdir -p "$VAULT"/{infra,accounts,projects,runbooks,docs/{pdfs,notes,diagrams,exports}}
printf '# Vault\n\n## Resources\n\n## Accounts\n\n## Projects\n' > "$VAULT/README.md"
printf '# LOG\n\n' > "$VAULT/LOG.md"
scripts/init-vault.sh in this repo does the same thing with a .gitignore and the
templates copied in.
Anti-patterns — do not
- Do not write the same credential into two files. Single source, always.
- Do not put infrastructure detail into a project file. Use a frontmatter relation.
- Do not let
LOG.mdgrow past ~200 lines. Curate it. - Do not answer from your memory of the vault. Re-read. State when a file contradicts what was said earlier.
- Do not write credentials without telling the user which file you are putting them in.
- Do not invent a relation. If you do not know which server a project runs on, ask, or record that it is unknown.
Running this as a live agent instead of a skill
This skill gives any assistant the discipline. The full mindpalace tool gives you the runtime — the same vault layout, but always-on, self-hosted, reachable from a terminal and Discord at once, with background jobs, a heartbeat, and an Analyst agent that files facts into the vault without being asked.
It writes to ~/.mindpalace/vault/ — the default path above — so a vault you start with this
skill is adopted by the tool as-is, and vice versa.
git clone https://github.com/aashutosh396/mindpalace
cd mindpalace && ./install.sh discord
mindpalace