Temporal Voice Index — Design Spec

Date: 2026-04-14  •  Author: Mnemosyne (war room wr-atlas session)  •  Status: Draft  •  Repo: constellation-mnemosyne

Problem

The fleet's memory system captures what happened (commits, wiki pages, devlogs) but loses what the operator said and why. Operator messages live in raw JSONL transcripts (~95% noise by volume) with no index, no search, and no links to the artifacts they produced. When an agent needs to recall a past decision's exact wording or trace the rationale behind a commit, the only option is a full scan of multi-megabyte transcript files — as demonstrated on 2026-04-14 when recovering the operator's "three types of facts" definition required parsing 42 JSONL files with a custom Python script.

Additionally, every timestamped artifact (messages, commits, wiki pages, chronicles, devlogs) exists in isolation. Nothing links them temporally. A wiki page created at 14:40 has no connection to the commit at 14:35 or the operator message at 14:32 that motivated both, despite all three sharing the same session context.

Prior Art

Research across four domains (temporal knowledge graphs, AI agent memory, event sourcing, Obsidian/PKM) established:

SystemWhat it doesGap for us
Zep/GraphitiBi-temporal edges, episode-based ingestion, automatic ontologyNeo4j dependency; single-stream (conversations only)
DyG-RAGLinks events by temporal proximity + shared entitiesAcademic; no production implementation
Generative AgentsRecency + importance + relevance retrieval scoringSingle-agent; no cross-artifact linking
HindsightUtterance-to-outcome causal chains from transcriptsResearch paper only
Flink/BeamSession windows (gap-based temporal grouping)Distributed systems; pattern is portable
XTDB/DatomicBi-temporal queries (valid time + transaction time)JVM ecosystem; pattern is portable
Key finding: Nobody does cross-artifact temporal linking across heterogeneous sources. The combination of session windows + entity co-reference + bi-temporal timestamps on an Obsidian vault is novel.

Design Principles

  1. No new storage system. The Obsidian vault is the index. Markdown files with rich frontmatter, queryable by grep and the existing wiki engine.
  2. No LLM required for core functionality. Extraction, indexing, searching, and temporal linking are all deterministic. LLM classification is optional night-pass enrichment.
  3. Token-efficient retrieval. An agent calling mnemosyne voice "fact" gets terse one-line-per-match output. No context bloat.
  4. Temporal proximity + entity co-reference. Two artifacts link not just because they're close in time, but because they share a session, repo, operator, or mentioned file path.
  5. Bi-temporal model. Every record has valid_time (when it happened) and ingested_at (when the system learned about it). Late arrivals are placed correctly.

Architecture

Artifact Types

The system tracks five artifact types, all already produced by the fleet:

ArtifactSourceTimestamp fieldEntity anchors
Operator messageJSONL transcript type=userRecord timestampsession_id, project, mentioned files/entities
Git commitgit logAuthor timestamprepo, files changed, branch
Wiki pageObsidian vaultcreated/updated frontmatterconstellation, sources, linked entities
Chronicle entrydocs/devlog/chronicle.mdEntry timestampcommit SHA, project
Devlog entrydocs/devlog/*.mdEntry timestampcommit SHA, project

New Page Type: Session Voice

A new wiki page type voice is added to the vault schema. One page per session that contains operator messages worth preserving.

Location: {vault_root}/{constellation}/voice/ directory.

Filename: {YYYY-MM-DD}-{session-id-prefix}.md (e.g., 2026-04-14-80166115.md)

Format:

---
title: "Atlas Session 80166115 — SSOT Fact Types"
constellation: atlas
type: voice
created: 2026-04-14
updated: 2026-04-14
status: current
session_id: 80166115-1ce8-4136-bf67-6bac89fdc719
project: constellation-atlas
valid_from: "2026-04-14T13:15:00Z"
valid_to: "2026-04-14T16:42:00Z"
message_count: 23
ingested_at: "2026-04-14T16:52:00Z"
sources:
  - 80166115-1ce8-4136-bf67-6bac89fdc719.jsonl (2026-04-14)
---

# Atlas Session 80166115 — SSOT Fact Types

## Messages

> **14:32** — there are two elements. narrative and fact.
  facts go into ssot and must be correct. we must classify as
  statement (loose verification) and fact (needs reverification)
  and fact (verified)

> **14:45** — can we give gemma 4 the task to identify
  facts that go into ssot?

> **15:10** — WE NEED TO RIGOROUSLY TEST THE PROTOCOL
  WITH HADES.

## Classifications

| Time  | Classification | Confidence | Topics                   |
|-------|---------------|------------|--------------------------|
| 14:32 | definition    | 0.95       | ssot, facts-vs-statements|
| 14:45 | command       | 0.88       | gemma, ssot-scan         |
| 15:10 | directive     | 0.92       | testing, ssot, hades     |

Pre-Filter Rules

Not every user message is worth preserving. The SessionEnd hook applies these filters before writing:

RuleRationale
Skip messages < 20 chars"yes", "ok", "go ahead" — no semantic value
Skip messages starting with /Skill invocations — the skill itself is the content
Strip <system-reminder> blocksSystem-injected noise that leaks into user records
Strip <task-notification> blocksAgent completion notifications, not operator voice
Skip messages >80% non-alphabeticPasted JSON, CLI output, log dumps
Cap individual messages at 2000 charsPrevents pasted specs from dominating the page
Skip sessions with < 3 qualifying messagesSessions too short to contain meaningful context

Messages that pass all filters are written verbatim. No summarization, no rewriting.

Title Generation

The page title is auto-generated from the session content. Two strategies, tried in order:

  1. Keyword extraction (deterministic): Tokenize all qualifying messages, remove a standard English stopword list, count term frequency, take the top 3 terms. Format: {Constellation} Session {id-prefix} — {keyword1}, {keyword2}, {keyword3}.
  2. Fallback: {Constellation} Session {id-prefix} — {date}.

LLM-generated titles are deferred to the night pass classification step. This keeps the SessionEnd hook fast and deterministic.

Temporal Activity Windows

The harvester's night pass groups artifacts into activity windows using session-window semantics:

Algorithm (adapted from Apache Beam Sessions):

  1. Collect all artifacts from the past 24 hours, ordered by valid_time.
  2. For each artifact, check if it falls within GAP_THRESHOLD (default: 15 minutes) of an existing window's end.
  3. If yes, extend the window and add the artifact.
  4. If no, open a new window.
  5. After grouping, validate links: two artifacts in the same window must share at least one entity anchor (session_id, project, repo, mentioned file). Remove artifacts that are temporally close but entity-disjoint.

Output: Each window becomes an annotation in the relevant voice pages' frontmatter:

activity_window:
  id: "aw-2026-04-14-1432"
  artifacts:
    - type: message
      ref: "atlas/voice/2026-04-14-80166115.md#14:32"
    - type: commit
      sha: "e7b132d"
      repo: constellation-mnemosyne
    - type: wiki_page
      ref: "atlas/fleet-ssot-architecture.md"

This is additive enrichment — if the night pass doesn't run, the voice pages still work for search. The activity windows are bonus context.

Write Path

1. SessionEnd Hook (immediate, deterministic)

Triggered by the existing mnemosyne capture session-end hook handler.

SessionEnd event
  → read transcript JSONL (already done by extract_session_narrative)
  → extract type=user messages
  → apply pre-filters
  → if >= 3 qualifying messages:
      → derive constellation from cwd
      → generate title (keyword extraction)
      → write voice page to vault
      → append to vault log.md

Integration point: src/capture/hooks.py handle_session_end() — extend the existing function. The vault session note writing (write_vault_session_note) already reads user messages; this replaces the truncated "Operational Context" section with a full voice page.

Performance budget: The hook must complete in < 2 seconds. JSONL parsing is already fast (streaming line-by-line). Writing one markdown file is trivial. No LLM, no network calls.

2. Harvester Night Pass (deferred, LLM-enriched)

Runs during the existing 02:00–06:00 night window.

For each unclassified voice page (frontmatter classification section empty):
  → read messages from the page
  → send to Ollama gemma4:e2b for classification
  → update the Classifications table in the page
  → compute activity windows across today's artifacts
  → write activity_window frontmatter to relevant pages

Integration point: src/aion/harvester.py harvest_cycle() — add a voice classification pass after the existing signal extraction pass. Reuse is_night_window() gate.

Model: gemma4:e2b (daytime-safe, 2.3B active). Classification prompt is small (~500 tokens per message batch). No large context needed.

Read Path: mnemosyne voice CLI

New top-level subcommand on the mnemosyne CLI.

Commands

mnemosyne voice "keyword"                       # keyword search across all voice pages
mnemosyne voice "keyword" --since 2026-04-14    # date-bounded
mnemosyne voice "keyword" --project atlas       # project-scoped
mnemosyne voice "keyword" --type definition     # classification filter (post night pass)
mnemosyne voice --recent                        # last 10 sessions' messages
mnemosyne voice --stats                         # index health metrics
mnemosyne voice --json "keyword"                # structured output for programmatic use

Search Implementation

Keyword search greps over voice page files in the vault. No database.

# Pseudocode
for voice_page in glob(vault_root / "*/voice/*.md"):
    if date_filter and page_date < date_filter:
        continue
    if project_filter and page_constellation != project_filter:
        continue
    for message_block in parse_messages(voice_page):
        if keyword in message_block.text (case-insensitive):
            results.append(match)

For the common case (keyword search, no filters), this scans ~50–200 small markdown files. Each voice page is 1–5KB. Total scan time: milliseconds.

Output Format

Default (terse, one line per match):

[2026-04-14 14:32 atlas 80166115] "there are two elements. narrative and fact. facts go into ssot..."
[2026-04-14 14:45 atlas 80166115] "can we give gemma 4 the task to identify facts that go into ssot?"

Messages are truncated at 120 chars in default mode. The --full flag shows complete text.

JSON mode (--json):

{
  "timestamp": "2026-04-14T14:32:00Z",
  "constellation": "atlas",
  "session_id": "80166115-1ce8-4136-bf67-6bac89fdc719",
  "project": "constellation-atlas",
  "text": "there are two elements. narrative and fact...",
  "classification": "definition",
  "confidence": 0.95,
  "voice_page": "atlas/voice/2026-04-14-80166115.md"
}

Stats mode (--stats):

Voice Index Health
  Total pages: 142
  Total messages: 1,847
  Classified: 1,203 (65%)
  Unclassified: 644 (35%)
  Date range: 2026-04-14 to 2026-04-28
  Constellations: atlas(45) mnemosyne(32) proteus(28) hades(18) ...
  By classification: definition(89) decision(234) correction(67) directive(312) ...

MCP Integration

Expose voice search through the existing Aion MCP server as a new tool:

aion_voice_search(query: str, since: str?, project: str?, type: str?, limit: int?)

Returns the same structured data as --json mode. This lets agents query operator voice without shelling out to CLI.

Classification Categories

Used by the night pass LLM classifier. Matches what operators actually say:

CategoryDescriptionExample
definitionOperator defines a concept, term, or distinction"there are two elements. narrative and fact."
decisionOperator makes or announces a choice"we're going with Redis for session storage"
correctionOperator corrects agent behavior or understanding"no, that's wrong — Arsenal was absorbed into Atlas"
directiveOperator declares a rule or standing instruction"never mock the database in these tests"
rationaleOperator explains why something is the way it is"the reason we're ripping out auth is legal flagged it"
constraintOperator declares a boundary or prohibition"we're freezing all non-critical merges after Thursday"
contextOperator provides background that informs future work"I've been writing Go for ten years but this is my first React"
commandTask instruction with no lasting value"fix the bug in the login flow"

Messages classified as command are still stored but ranked lowest in search results. All other categories are high-value recall targets.

Wiki Schema Changes

Add voice to the valid page types in src/wiki/config.py:

PAGE_TYPES = ["overview", "service", "decision", "process", "analysis", "voice"]

Add voice/ subdirectory convention to the vault. Voice pages use the standard wiki page format with additional frontmatter fields (session_id, project, valid_from, valid_to, message_count, ingested_at, activity_window).

The wiki validator (src/wiki/validator.py) must accept the new type and its extended frontmatter without flagging warnings.

Backfill

A one-time backfill command processes historical JSONL transcripts:

mnemosyne voice --backfill                        # all historical sessions
mnemosyne voice --backfill --since 2026-04-01     # from a specific date
mnemosyne voice --backfill --project atlas        # one project only

Uses the same extraction and pre-filter pipeline as the SessionEnd hook. Skips sessions that already have voice pages. Respects the harvester registry to avoid reprocessing.

Hypnos Probe

I4 — Voice Index Coverage

File Changes

FileChange
src/capture/hooks.pyExtend handle_session_end() to write voice pages
src/capture/extractor.pyAdd extract_voice_messages() — full extraction without the 300-char/20-message caps
src/mnemosyne.pyAdd voice subcommand with search, stats, backfill
src/voice/New module: writer.py (page generation), search.py (grep-based search), classifier.py (night pass LLM classification)
src/wiki/config.pyAdd "voice" to PAGE_TYPES
src/wiki/validator.pyAccept voice page type and extended frontmatter
src/aion/harvester.pyAdd voice classification pass to night window
src/mcp/aion_tools.pyAdd aion_voice_search tool
src/hypnos/probes/Add I4 voice coverage probe
tests/Tests for extraction, pre-filters, search, classification, backfill

What This Does NOT Do

Success Criteria

  1. mnemosyne voice "fact" --since 2026-04-14 returns the exact quotes found during the manual search, in under 500ms.
  2. SessionEnd hook writes voice pages for every qualifying session without adding > 2 seconds to hook latency.
  3. An agent can find the operator's exact words on any topic from the last 30 days in a single CLI call.
  4. Night pass classification correctly categorizes > 80% of messages (validated against a 50-message human-labeled sample).
  5. Activity windows correctly group commits with the operator messages that motivated them for > 70% of cases.