# Vault Layout & Frontmatter Conventions

**This document is canonical.** The ECHO vault holds data only — there are no `CLAUDE.md` / `STRUCTURE.md` / `BOOTSTRAP.md` / `index.md` control docs in it. Layout, taxonomy, and frontmatter conventions live here in the plugin; the bootstrap procedure (`references/bootstrap.md`) builds the tree below into any empty vault.

## Folder Map (root-addressed)

```
/vault/
├── README.md          ← thin human signpost (NOT read for routing)
├── inbox/
│   ├── captures/      ← quick captures (inbox.md), date-prefixed lines
│   ├── imports/       ← raw imported material
│   └── processing-log/
├── journal/          ← one append-only time-series stream; rollups are coarser journal entries, NOT a separate reviews/ tree
│   ├── daily/         ← YYYY-MM-DD.md (has an "Agent Log" section)
│   ├── weekly/        ← YYYY-Www.md (ISO week, opt-in rollup)
│   ├── monthly/       ← YYYY-MM.md (monthly rollup)
│   ├── quarterly/     ← YYYY-Qn.md (manual / on request)
│   ├── annual/        ← YYYY.md (manual / on request)
│   └── templates/
├── projects/         ← lifecycle: incubating → active → on-hold/archived
│   ├── active/        ← current work (status: active)
│   ├── incubating/    ← idea captured, not yet started (status: incubating)
│   ├── on-hold/       ← paused but kept (status: on-hold)
│   ├── archived/      ← done / abandoned (status: archived)
│   └── project-template.md
├── areas/             ← business / personal / learning / systems
├── resources/
│   ├── companies/     ← <slug>.md — organizations (clients, vendors, partners, employers)
│   ├── concepts/  references/  meetings/
│   └── people/        ← <name>.md
├── decisions/
│   ├── by-date/       ← YYYY-MM-DD-<slug>.md (ADR-style) — the canonical home
│   └── decision-template.md
│   (decisions/by-project/ is retired and not created —
│    mirror an ADR into a project's `## Key Decisions` heading instead)
│  (reviews/ is retired — journal rollups live under journal/; vault-health audits under _agent/health/)
└── _agent/
    ├── echo-vault.md  ← bootstrap marker: schema_version + bootstrap date (plugin-owned; the "is this vault set up?" probe)
    ├── context/       ← current-context.md and task bundles
    ├── memory/
    │   ├── working/   ← transient, time-boxed
    │   ├── episodic/  ← what happened, when
    │   └── semantic/  ← durable facts/patterns (operator-preferences.md)
    ├── sessions/      ← YYYY-MM-DD-HHMM-<slug>.md
    ├── health/        ← YYYY-MM-vault-health.md (monthly self-maintenance audit; NOT a journal entry)
    ├── templates/     ← canonical note templates
    ├── skills/        ← active / archived
    ├── heartbeat/     ← single-line pointers (e.g. last-session.md → most-recent session log path)
    ├── index/         ← entities.json — machine-maintained slug→{path,kind,title,aliases} registry
    └── locks/         ← vault.lock — cooperative advisory multi-writer lock
```

**Entity index:** `_agent/index/entities.json` is a machine-maintained registry mapping each entity slug to its `{path, kind, title, aliases, last_seen}`. It makes routing/resolve an O(1), alias-aware lookup and supplies the name→path map for cross-linking and recall. `resolve` matches slug/title/alias exactly; when nothing matches exactly it falls back to **fuzzy candidates** — entities sharing a distinctive token with the mention — so a shortened name ("echo memory" → the project `echo`) surfaces the existing note instead of spawning a duplicate. Aliases are auto-derived from titles, learned from mentions on update, and re-folded from note frontmatter on `sweep`. It is rebuilt automatically by `echo.py capture` and by `sweep.py`; do not hand-edit. The only JSON file in the vault.

**Heartbeat:** `_agent/heartbeat/last-session.md` is a one-line pointer (`<session-log-path> @ <ISO-timestamp>`) the **session-logging procedure writes (PUT, overwrite) at session end** and the **loading procedure reads first (Step 4)** as an O(1) shortcut to the latest session log. It is a hint, not a source of truth — fall back to the `sessions/` directory listing if it's missing or stale. Because it's PUT-overwritten, it never grows or duplicates.

**Slug rules:** kebab-case, ASCII only, truncate to ~40 chars.

---

## Canonical Frontmatter

Every note starts with this block. Fill what applies; leave the rest empty rather than guessing — **except `status:` and `tags:` on entity notes** (person/company/concept/reference/project/area/skill, plus `tags` on meeting/decision), which must be populated: `capture` stamps a kind-appropriate default `status` and seeds `tags` with the note's kind, `/echo-health` flags missing/empty ones (`incomplete-frontmatter`), and `sweep.py --apply` backfills older notes. The per-kind requirement map is `KIND_REQUIRED_FM` / `KIND_STATUS` in `scripts/echo_index.py`.

```yaml
---
type:            # see Note Types below
status:          # active | draft | done | archived | complete — REQUIRED on entity notes (kind default stamped by capture)
created:         # YYYY-MM-DD (or YYYY-MM-DDTHH:mm for sessions)
updated:         # YYYY-MM-DD
tags: []         # REQUIRED (non-empty) on entity notes — capture seeds the kind (e.g. [company]); enrich via --tags
aliases: []        # optional — other names this entity is called (Obsidian-native). Folded
                   #   into the entity index so a shortened/expanded name resolves to this note.
agent_written: false
source_notes: []   # plain relative paths as strings — NEVER [[wikilinks]]
---
```

`aliases:` is the **durable, Obsidian-native home for alternate names** of an entity (e.g. the project `echo` is also "echo-memory" / "echo plugin"). `capture` auto-derives the obvious case/punctuation variants of the title and writes them here; `sweep` folds frontmatter aliases back into the entity index, so they survive an index rebuild. List plain strings, never `[[wikilinks]]`.

`agent_written: true` + a populated `source_notes` is the key signal separating
agent-managed content from human-authored content. When appending with POST, do
not rewrite frontmatter — the append goes after existing content. To change
`updated:` or `status:`, use PATCH with `Target-Type: frontmatter`.

**Frontmatter field semantics:**

- `created:` is the **earliest known date** the entity was tracked in the vault — *not* "today". When merging notes (e.g. promoting an `on-hold/` project into `active/`), preserve the earliest `created:` from any merged source and only update `updated:`.
- `source_notes` is a **backward link** — the note(s) that triggered or supplied content for this one (e.g. the session log a project update came from). Forward links go in the `## Related` body section, never here.
- `status:` for a project MUST match its folder under `projects/` (`active`, `incubating`, `on-hold`, `archived`). Moving the file and updating `status:` are the same operation.

> **No `[[wikilinks]]` in frontmatter.** YAML parses `[[...]]` as nested lists, so wiki
> links there break and never render as clickable links in reading view. Put all
> cross-references in a **`## Related`** section in the note **body** (bulleted `[[links]]`).
> Frontmatter holds scalar/string metadata only; `source_notes` values are plain relative
> paths, not links.

## Note Types

`daily-note`, `weekly-note`, `monthly-note`, `project`, `project-update`, `area`,
`concept`, `reference`, `person`, `company`, `meeting`, `decision`, `review`, `session-log`,
`working-memory`, `episodic-memory`, `semantic-memory`, `context-bundle`, `skill`,
`draft`, `inbox-item`.

---

## File-Specific Conventions

### operator-preferences.md (`_agent/memory/semantic/`)

The profile analog. Canonical headings:

- `## Operator` — who the vault owner is (one paragraph)
- `## Fact / Pattern` — **promoted, deduped rules.** No date prefix. Timeless.
- `## Observations` — **timestamped raw observations.** Date-prefixed lines (`- 2026-06-06: ...`). Default landing zone for new evidence.
- `## Evidence` — citations/links supporting the rules
- `## Recommendation or Implication` — how the rules should shape behavior
- `## Review Notes` — confidence / last review date

Append observed facts under `## Observations` by default. Promote to `## Fact / Pattern` (dropping the date) once a pattern stabilizes. "The operator" is the vault owner — refer to them generically and in the third person (the display name is the runtime `owner` config value).

### projects/active/\<slug\>.md

Mirrors `scaffold/templates/projects/project-template.md` — keep this block in sync with that template.

```markdown
---
type: project
status: active
created: {{date:YYYY-MM-DD}}
updated: {{date:YYYY-MM-DD}}
tags: [project]
agent_written: false
source_notes: []
owner:
review_cycle: weekly
---

# Project Name

## Purpose

## Status
One paragraph, kept fresh via PATCH replace (target `Project Name::Status`).

## Goals

## Current Context

## Open Loops
- [ ] unresolved thing

## Key Decisions
- [[YYYY-MM-DD-decision-slug]] — one-line summary (ADR mirror target)

## Related Notes

## Session History
- 2026-06-05: observation or update

## Related
- [[areas/business/business-ops]]
```

### sessions/YYYY-MM-DD-HHMM-\<slug\>.md

See `session-log-template.md`. ECHO uses an **HHMM time component** in the filename — this is **canonical, not optional**. The four-digit local-time component makes filenames lex-sort in true chronological order, which the loading procedure relies on. Older session logs without HHMM exist; leave them alone, but every new one must use the full `YYYY-MM-DD-HHMM-<slug>.md` form.

### decisions/by-date/YYYY-MM-DD-\<slug\>.md

ADR-style: Context → Decision → Consequences. If the decision belongs to an existing project, PATCH-append the wikilink into that project's `## Key Decisions` heading. Don't use `decisions/by-project/` — it's legacy scaffolding that's intentionally left empty.

### people/\<name\>.md

`type: person`. Use lowercase kebab-case for the slug (e.g. `robert-smith.md`).

### companies/\<slug\>.md

`type: company`. An organization the vault owner works with — client, vendor, partner, or employer (e.g. `acme.md`). Distinct from `people/` (individuals, who *belong to* companies) and `references/` (external sources). Lowercase kebab-case slug.

---

## Cross-References

Use Obsidian wiki links freely: `[[note-name]]` or `[[folder/note]]`. The REST API
doesn't resolve them, but Obsidian does when the operator browses the vault.
