Skip to content

How It Fits Together

A single end-to-end pass through the system, in the order it actually happens.

Some external action triggers a capture. The most common sources are:

  • A session lifecycle hook (Stop or PreCompact).
  • A meeting ingest pipeline pulling from a transcripts source.
  • A manual capture from a CLI or AI client.

The capture writes a single markdown file into ~/.cortex/inbox/pending/ with a YAML frontmatter (event ID, type, timestamp, project hint, tags) and a body containing the raw content.

The capture path does no parsing, no validation beyond the frontmatter structure, and no DB writes. It is intentionally small.

A separate normalize process scans the pending inbox under a single-writer lockfile. For each event, it:

  • Parses the frontmatter.
  • Inspects the body for structural patterns (one record vs many, summary lines, dates, related references).
  • Generates one or more records — atomic markdown files with their own stable IDs (rec-<scope>-<slug>-NNN.md).
  • Routes each record into the appropriate PARA directory (projects/, areas/, resources/, archive/, or specialised buckets like meetings/).
  • Updates the SQLite sidecar: full-text index, dedup keys, related-to links, provenance back to the source event.
  • Moves the event from pending/ to processed/ (or failed/ on error).

Optionally, the normalizer can git commit the new records to the Cortex directory, so history is preserved.

The SessionStart hook fires. It calls Cortex’s recall path with the active project context and gets back a small bundle of relevant records. That bundle is injected into the model’s context as the session begins.

The size and shape of the bundle is bounded: only the most relevant L0 (active project) and L1 (broader area) records, ranked by activation and recency.

During the session, the AI client may call MCP operations directly:

  • recall to look up records by scope, query, or relation.
  • capture to save something the user explicitly wants remembered.
  • supersede to replace a stale record with a newer one.
  • reflect to roll up the current session into a summary.
  • status to surface counts, recent activity, and overdue items.

Each of these is a structured call, returning structured records. The model does not see the filesystem; it sees the MCP surface.

If the session compacts, the PreCompact hook fires first — it captures an emergency gist of the current conversation as a stop-style event so nothing is lost.

If the session ends cleanly, the Stop hook fires, capturing the session in full for later normalization.

In both cases, the next normalize cycle picks up the resulting events and turns them into records.

On a slower cadence (typically scheduled), the distillation loop scans for high-volume scopes and produces rolled-up summary records. Originals stay in place; the summaries become first-class records in their own right and can be recalled directly.

┌────────────┐ ┌────────────┐
│ capture │────────▶│ inbox │
│ (any src) │ │ /pending │
└────────────┘ └─────┬──────┘
│ normalize
┌────────────┐ ┌────────────┐
│ recall │◀────────│ records │
│ (MCP) │ │ + SQLite │
└─────┬──────┘ └─────┬──────┘
│ │
▼ │
┌────────────┐ │
│ session │ │
│ (AI app) │───────────────┘
└────────────┘ capture / supersede

Each arrow is a discrete process. They do not need to run on the same machine, the same schedule, or the same lifetime. The filesystem and the SQLite sidecar are the only state shared between them.