--- name: astral-tenant-kb description: Read/write per-user astrology memory — people, charts, readings, transit profiles. Use whenever a reading or chart computation should be remembered, or when recalling a known person before asking for birth data. version: 0.1.0 author: Hermes Agent + Alex license: MIT metadata: hermes: tags: [astrology, memory, knowledge-base, tenant, kb] related_skills: [astral-chart-api, astrology-reading-compose, llm-wiki] --- # Astral Tenant KB Per-user astrology memory store. **Separate from source wikis** (`astro-sources-wiki`, `wiki-human-design`) which are interpretive content shared across users. ## Root Default: `~/.hermes/astral-kb-prototype/knowledge/` Override via env: `ASTRAL_KB_ROOT` (used when running inside an Astral Hermes tenant). ## Layout ``` knowledge/ ├── entities/ │ ├── people/.md ← person profile w/ birth frontmatter │ ├── places/ trips/ events/ ├── charts/-.json ← computed chart cache (hashed by birth data) ├── readings/--.md ├── transits/ │ ├── global/.json │ ├── profiles/-.json │ └── calendars/-.ics ├── raw/ ← long-form reports, source PDFs (S3-mirrored) ├── _meta/manifest.json ├── index.md log.md SCHEMA.md ``` ## Person profile frontmatter ```yaml --- 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 --- ``` ### Disambiguation policy (CRITICAL — prevents identity collisions) The compose layer **must** use this resolution order when a user mentions a person: 1. **Exact slug match**: `slug: ` — unambiguous, use directly. 2. **Exact name match**: case-insensitive `name == input` — single hit → use; multiple hits → DISAMBIGUATE. 3. **Alias match**: `` appears in any person's `aliases:` list — single hit → use; multiple hits → DISAMBIGUATE. 4. **Fuzzy match** (first-name only or partial): collect all matches → if more than one, DISAMBIGUATE. 5. **No match**: confirm with the user this is a new person, propose a slug (e.g. `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. ## Reading frontmatter (required for A/B testing) ```yaml --- person: alex date: 2026-05-22T19:45-07:00 chart_hash: 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" --- ``` ## Helpers (call via terminal/python) Conceptually: - `recall_person(slug_or_name)` → person frontmatter or null - `save_person(slug, birth_dict)` → upsert entities/people/.md - `chart_hash(birth_dict)` → sha1 of normalized birth UTC + coordinates - `save_chart(slug, chart_json)` → charts/-.json (idempotent) - `load_chart(slug, hash)` → JSON or null - `save_reading(slug, lenses[], markdown, citations[])` → readings/--.md - `list_readings(slug)` → sorted list with frontmatter - `save_transit_profile(slug, range, json)` → transits/profiles/-.json - `save_transit_calendar(slug, range, ics)` → transits/calendars/-.ics Implementation: plain Python with `pathlib` + `pyyaml` frontmatter. No daemon, no DB. ## Required workflow when used by a reading skill **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. ## What goes in Hermes memory vs. this KB 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 ## Hard rules - Never write to KB without a person slug. - Never overwrite an existing chart file if hashes match — load it instead. - Always include `lenses: [...]` frontmatter in reading files (even if just one lens) so combos are searchable. - Birth data lives in `entities/people/.md` only — do not duplicate into charts/readings frontmatter beyond `chart_hash`. - Per-tenant isolation: when `ASTRAL_KB_ROOT` is set, use it; never fall back to global. - **Hermes built-in memory holds the user's identity and birth data canonically.** The self profile in this KB mirrors that — never invents it. If they ever disagree, the user is the tiebreaker. - Exactly one profile per tenant may have `relationship: self`. ## Verification After any save: - `entities/people/.md` exists - `charts/-.json` exists - latest `readings/*.md` has correct `lenses:` list + ≥1 citation when sources were used ## References - `references/wiki-kb-locations.md` — full source-wiki and tenant KB paths, repos, S3 buckets, knowledge-config ids - `references/tenant-isolation.md` — per-tenant `ASTRAL_KB_ROOT` provisioning checklist - `references/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