Per-user astrology memory store. Separate from source wikis (astro-sources-wiki, wiki-human-design) which are interpretive content shared across users.
Default: ~/.hermes/astral-kb-prototype/knowledge/
Override via env: ASTRAL_KB_ROOT (used when running inside an Astral Hermes tenant).
knowledge/
├── entities/
│ ├── people/<slug>.md ← person profile w/ birth frontmatter
│ ├── places/ trips/ events/
├── charts/<slug>-<hash>.json ← computed chart cache (hashed by birth data)
├── readings/<yyyy-mm-dd>-<slug>-<combo>.md
├── transits/
│ ├── global/<range>.json
│ ├── profiles/<slug>-<range>.json
│ └── calendars/<slug>-<range>.ics
├── raw/ ← long-form reports, source PDFs (S3-mirrored)
├── _meta/manifest.json
├── index.md log.md SCHEMA.md
---
slug: alex-mataha # canonical slug — globally unique within this KB
aliases: [alex, MaTaHa] # ambiguous nicknames the user might say
name: Alex MaTaHa
relationship: self # self | client | family | friend | partner | public-figure
disambiguation: |
Free-text note distinguishing this person from others with similar names.
birth:
date: 1985-09-26
time: 07:14
location: London, UK
utc: 1985-09-26T06:14:00Z
coordinates: "51.5072178 -0.1275862"
tz: Europe/London
notes: professional astrologer; prefers traditional rulerships
---
The compose layer must use this resolution order when a user mentions a person:
slug: <input> — unambiguous, use directly.name == input — single hit → use; multiple hits → DISAMBIGUATE.<input> appears in any person's aliases: list — single hit → use; multiple hits → DISAMBIGUATE.alex-mataha, alex-durran), then save_person.DISAMBIGUATE means: - Show the user all candidates with birth-data summary (date + city) — one line each. - Ask "which one?" with the option "none of these — this is a new person." - Never silently pick the most-recent or alphabetical-first match.
Never reuse a slug that already exists for a different person. If the user says "do a chart for Alex" and the KB has both alex-mataha and alex-durran, the assistant must ask which Alex before computing.
---
person: alex
date: 2026-05-22T19:45-07:00
chart_hash: <sha1-of-birth-data>
lenses: [rulership-chains, decans-36-faces]
sources_cited:
- astro-sources-wiki:concepts/libra-i
- astro-sources-wiki:concepts/decans
prompt: "what does my Saturn return look like"
---
Conceptually:
recall_person(slug_or_name) → person frontmatter or nullsave_person(slug, birth_dict) → upsert entities/people/chart_hash(birth_dict) → sha1 of normalized birth UTC + coordinatessave_chart(slug, chart_json) → charts/load_chart(slug, hash) → JSON or nullsave_reading(slug, lenses[], markdown, citations[]) → readings/list_readings(slug) → sorted list with frontmattersave_transit_profile(slug, range, json) → transits/profiles/save_transit_calendar(slug, range, ics) → transits/calendars/Implementation: plain Python with pathlib + pyyaml frontmatter. No daemon, no DB.
Self vs other comes first. The compose layer must decide whether the reading is for the user (self) or someone else BEFORE calling this skill. See astrology-reading-compose "Self vs other" section.
For self:
1. Read identity + birth data from Hermes built-in memory (USER.md) — it's already in context.
2. Look up the KB profile whose frontmatter has relationship: self. If found, use it. If not, bootstrap one from memory's identity via save_person(..., relationship='self').
3. Cross-check: if Hermes memory's birth data differs from the self profile's birth data, surface the discrepancy. Never silently pick.
For other:
1. Apply the disambiguation policy (slug → exact name → alias → fuzzy → no match) — see "Disambiguation policy" above.
2. After resolving identity, recall the profile via recall_person(slug).
Then (both paths):
3. Compute or load: chart_hash; if load_chart returns null, compute via astral-chart-api then save_chart.
4. Read sources: query astro-sources-wiki (or HD wiki) via llm-wiki for the relevant pages.
5. Save reading: after the response is written, call save_reading with lens combo + citations.
6. Append to log.md with one-line summary.
Hermes built-in memory (USER.md, MEMORY.md): - User's identity (name, aliases, timezone, contact) - User's birth data summary (the canonical source — it's always in context) - Stable preferences (astrology style, lens defaults, "no folk language", etc.) - ONE pointer to the KB self-profile path and the disambiguation contract reference - NEVER: chart JSON, reading prose, other people's profiles, transit windows
This tenant KB:
- The self profile (relationship: self) — sourced FROM Hermes memory, kept in sync
- All other people's profiles
- All chart JSON
- All saved readings (with lens-combo frontmatter)
- All transit windows
- Raw source uploads
lenses: [...] frontmatter in reading files (even if just one lens) so combos are searchable.entities/people/<slug>.md only — do not duplicate into charts/readings frontmatter beyond chart_hash.ASTRAL_KB_ROOT is set, use it; never fall back to global.relationship: self.After any save:
- entities/people/<slug>.md exists
- charts/<slug>-<hash>.json exists
- latest readings/*.md has correct lenses: list + ≥1 citation when sources were used
references/wiki-kb-locations.md — full source-wiki and tenant KB paths, repos, S3 buckets, knowledge-config idsreferences/tenant-isolation.md — per-tenant ASTRAL_KB_ROOT provisioning checklistreferences/three-layer-architecture.md — how Hermes memory, the tenant KB, and the source wikis interact; the Alex MaTaHa / Alex Durran collision bug and its fix