# Psyche context & memory model

> Draft design doc (2026-05-31). Resolves PRD §17.3 (Psyche live/project authoring directives) and the memformat redesign. Governs how the Psyche authors, structures, syncs, and surfaces an agent's context. On-disk layout: `docs/STORAGE.md`. Several patterns are adapted from `BigscreenVR/pi-agent-memory` (credited inline).

## Two-tier context

- **live context** (per-agent, project-independent — the agent's *mind*): identity/persona, relationship + rapport with the user, the user's cross-project preferences & working style, cross-project life threads, **learnings from work that may apply to other projects**, a thin **project roster** (one line per project the agent works on), the agent's self-model. Synced to **all** instances of the endpoint.
- **project context** (per-agent-per-project — what the agent is doing *here*): codebase/architecture, decisions made here, **current focus / next steps / todos**, project conventions, build/test commands, blockers. Synced **only** to same-project instances.

(This split independently matches pi-agent-memory's Project Store vs Global Store and its discriminator dialogue — convergent validation.)

### The discriminator (applied per *topic*, not per fragment)

> **"Would this still be true and relevant if the user opened a *different* project tomorrow?"** Yes → live. No → project. Equivalently: **WHO the agent is** (live) vs **WHAT it's doing here** (project).

### Anti-leak rules

The observed failure is project detail leaking *into* live context (which then wrongly syncs everywhere). Countermeasures:

1. **Default-to-project.** A fragment goes to project context unless it *passes the cross-project test* to be promoted to live. Promotion needs justification; demotion is free. The bias points against the leak.
2. **`current focus / next steps` is project-scoped** — the single biggest leak category. Route it to project unless the focus genuinely spans projects.
3. **Live holds a thin project roster, never project depth** — cross-project *awareness* travels; depth stays per-project.
4. **Commune-source asymmetry** (the structural fix): the live signal is strong only from **Self-authored communes**; **echo communes are project-centric** (they synthesize project-heavy session logs). So **echo communes are project-context-primary and live-context-conservative** — they make few/no live edits. This kills the leak at its source. Provenance is recorded on each block (`Source:`).

### Boundary communes are Self-authored (resume Self, not echo)

To get meaningful **live-context** updates on a consistent basis (the live signal is strong only from Self), three boundaries author their commune by **`--resume`-ing the Self session** (the actual agent), not by a background haiku echo:

- **`/clear`** → resume Self → **commune** file-drop.
- **`/compact`** → resume Self → **commune** file-drop.
- **`spt suspend`** → resume Self → **signoff** file-drop (suspend = the endpoint going down, so it's a signoff, not a commune).

This deliberately swaps the project-centric echo for a Self-authored capture at exactly the moments that matter (context loss + suspend), so live context stays current. (Routine cadence communes remain echo — project-primary, live-conservative.)

### `spt refresh` (spt-hosted self-service context refresh)

A lever for an agent behind an **spt-hosted** endpoint to clear and resume itself without stalling. It: (1) triggers a `/clear` (which uses the **Self-resume-commune rule** above — consistent with all other boundaries), (2) thereby captures a commune, and (3) **guarantees a resume signal hits the harness post-clear** so the agent's turn restarts and it resumes work — starting from its immediate next-steps. Spt-hosted-only because the guarantee requires the daemon to own the PTY and inject the post-clear resume (it can't guarantee that for harness-hosted sessions it doesn't control).

### Ownership

The live/project authoring directives are **core-owned** — fed to every Psyche by spt-core (part of `$psyche_prompt` / a core system-prompt fragment), so the split is consistent across all adapters (inconsistent rules → sync chaos). Adapters add only harness mechanics (how to write the drop file).

## memformat (deeply integrated, self-evolving, the split scaffold)

memformat goes from a decorative, ignored seed to the **load-bearing schema that defines the two-tier context**. It makes the live/project decision **per-topic and rare** instead of per-fragment and constant — which is what makes the split stable.

- **Tier-tagged topics.** Each topic carries a tier: `live` or `project`. The tier is decided **once, when the topic is created** (apply the discriminator one time), not per fragment. Communes then just *fill* topics; the tier routes content to the right context file + sync tier. The two-slice envelope (`<live-context>`/`<project-context>`) becomes the *rendering* of tier-tagged topics.
- **Self-evolving.** The Psyche adds/removes/re-tiers topics as it learns Self's realm of work (memformat's original intent). **Re-tiering migrates** the topic's accumulated content between tiers (project→live or vice versa), not forward-only.
- **The schema is live** (per-agent, travels everywhere); **project-tagged topic content** is instantiated per-project.
- Example tiers: `identity / relationship / user-preferences / cross-project-learnings / project-roster` = **live**; `focus / decisions / architecture / todos / blockers` = **project**.

### Structured blocks (adapted from pi-agent-memory)

A memformat topic is a tier-tagged **container of structured blocks**, not free-form prose. Each block:

```
## <stable-block-id>
Concepts: <comma list>          # retrieval keys
Aliases: <comma list>           # retrieval synonyms (optional)
Applies to: <glob paths>        # sub-project scoping (optional)
Source: self-commune | echo-commune | user   # provenance (drives the live-conservative rule)
Created: <iso>
Updated: <iso>
Hash: sha256:<canonical-content-hash>         # for block-level OCC merge (deferred, see below)
Pinned: true|false              # hot (dense-injected) vs indexed (sparse, on-demand)

<body>
```

`Concepts`/`Aliases` make context **retrievable**; `Applies to` enables fine-grained scoping + query-routing; `Source` carries provenance; `Pinned` drives start-injection density (below).

## Retrieval Map (generated INDEX)

A generated, lightweight **INDEX** over topics — summaries + concepts + aliases + paths — **not** a vector DB, **not** a full mirror (adapted from pi-agent-memory's Retrieval Map). One INDEX per **project** (aggregating all agents' project context → realizes cross-agent synthesis + query-routing: "which endpoint owns this task") and per **agent**. Regenerated on context change; the merge driver + INDEX regeneration keep it current.

## Start-injection: dense working set, sparse tail

The balance between immediate productivity and bounded context cost — tuned for SPT's **per-agent, work-fast** priority (vs pi's multi-agent-per-project digging, which is sparse-by-default):

- **DENSE — the working set, injected in full at start:** all **live context** + the **current project's hot blocks** (`Pinned` + recently-updated focus/decisions/todos). The agent is immediately productive, no digging.
- **SPARSE — the long tail, pointer + INDEX, retrieved on demand:** other projects, **cross-agent/cross-project synthesis**, cold/archived blocks. (This is where pi's bounded "topic-names + pointer, never inline bodies" awareness pattern applies — it scales regardless of store size.)
- **Hot/cold curated by the Psyche** via `Pinned` + recency; keeping the hot set tight is part of maintaining memformat.
- **Tunable** via a working-set **density knob** (spt-core global setting + per-endpoint override).

## Deferred

- **Rebalance / Memory-Health pass** (later): the Psyche periodically (a) demotes leaked live→project content, and (b) — the deeper, pi-credited insight — verifies context is still *true vs the current code* (Memory Health), which requires investigating the codebase, so it's an autonomous-agent job, distinct from mechanical staleness (hash/INDEX mismatch).
- **Block-level OCC merge** (v1.x): the per-block `Hash:` (canonical content hash, excluding `Updated`/`Hash`) enables block-level, stale-aware cross-node merge — finer than the v1 whole-file newest-wins driver (`docs/STORAGE.md`). Recorded now (the block format carries `Hash:`), applied later.

## Not adopted

pi-agent-memory's **human-in-the-loop approval** (extract → review → approve, max-5-candidates) and **bash-mutation guardrails** fit its "human curates" philosophy. SPT's Psyche is **trusted to author its own mind autonomously** — so we adopt pi's *structure* (blocks, INDEX, bounded awareness, provenance, OCC) but **not** its approval gate. Controlled mutation is already covered by the drop-file single-writer rule (KNOWN-HAZARDS 6.4).
