# clikae memory — the Soul layer (convention)

> **Status: cross-engine shipped (claude + codex).** This file defines the *schema
> and conventions* for clikae's memory layer; `clikae memory share|isolate|status`
> is live. The canonical Soul is ONE vendor-neutral markdown dir
> (`$CLIKAE_HOME/souls/<group>/memory`); **claude** symlinks its memory dir into it,
> **codex** gets a pointer note in `AGENTS.md` and reads/writes the same markdown via
> the memory protocol — so cross-engine continuity needs **no translator and never
> drifts** (it's literally the same file). Design rationale: [`grammar.md` §10](/grammar.md).
> Coming: cross-engine **agy** (same pointer hook); an **optional** apfel translator
> (`CLIKAE_MEMORY_TRANSLATOR`) only for slicing/compressing a Soul (never required for
> the basic experience); per-entry `scope:` dial.

## 0. The one idea

A tank holds more than fuel — it holds the engine's **long-term memory**. clikae
already controls *where the engine's state lives* (config-dir indirection); the
same lever controls *the engine's brain*. So memory is a **dial**, not a fixed
behaviour:

| Mode | Mapping | Primitive | Reached by |
|---|---|---|---|
| **share** | N tanks → **1** store (fan-in) | symlink fans in | being in the fleet (the default once you have opted in once) |
| **isolate** | N → **N** | none (separate dirs) | `clikae solo` — out of the fleet, own brain back |
| **evaporate** | N → **0** (throwaway) | symlink to `mktemp -d` | `--ephemeral`, for ONE run |

**The dial has two positions, not three.** `isolate` used to be its own verb, and
that produced a state the board could not express: a tank sitting *inside* the
fleet with no brain. The board's only axis is fleet-vs-solo, so such a tank looked
exactly like one that shared — which is how a person ends up believing every tank
shares when several quietly do not. Leaving the fleet and leaving the brain are
now one act: `clikae solo`. In the fleet means sharing; solo means not.

> ⚠️ **`solo` is not "incognito".** If what you want is a clean, throwaway
> session — a cold read, an unbiased audit, a reviewer who doesn't know what you
> believe — that is **evaporate** (`--ephemeral`): it affects *this one run*, it
> touches no wiring, and it puts the Soul back when the run ends. `solo`
> **rewires the live tank**: every project directory's memory symlink is removed
> on the spot, and the tank stops sharing until someone dials it back.
>
> Field incident, 2026-07-12: an agent that wanted a memory-clean workspace ran
> `isolate` on a live tank, then re-shared it twenty minutes later — and the
> shared brain never came back. Two things made that silent and permanent:
> `share` only re-linked project directories that still *had* a memory slot, and
> `isolate` had just removed them all; and the per-directory symlink is otherwise
> re-projected **at launch** — but a session already *running* in that directory
> never gets a relaunch, so it simply went amnesiac mid-flight while
> `memory status` went on reporting "shared". Both are fixed as of v0.14.3
> (`share` now fans into every project directory of the tank, creating the slot),
> but the lesson stands: **don't reach for the permanent verb when you meant
> `--ephemeral`.** Retiring `isolate` into `solo` moved that footgun's name; it
> did not remove it, which is why this warning now names `solo`.
>
> Mnemonic: **ephemeral changes this once; solo changes from now on.**

Sharing is **per-tank, whole-brain**: the consent unit is the tank (the members
file under `souls/<group>/`), never a single directory. claude keeps one memory
dir *per project directory*, so those symlinks are just per-directory
**projections** of the membership — `share` fans in every existing directory's
slot at once, and every clikae launch re-projects the current directory first
(`soul_prelaunch`, lib/core/soul.sh). A member tank therefore never quietly
accumulates isolated side-memory in a new directory; the only way a tank keeps its
own memory is `clikae solo`.

**Consent is given once, per machine — not per tank.** Nothing is shared until
your first `share`; that share also records the group in
`$CLIKAE_HOME/soul-default`, and from then on a tank created by `clikae init`
joins it automatically (a solo tank never does). Asking per tank was not consent,
it was a chore, and one the board could not show you had skipped. Crossing your
own *accounts* is still announced at the moment it would happen: `init` does not
pass `--yes`, so a tank whose account is already known and different is refused
rather than merged, and it keeps its own memory until you say otherwise.

The framing, stated once: **clikae controls where state lives, how long, and how
widely shared** — for auth (today), for fuel (the reframe), and now for memory.

## 1. The Soul / Brain split

- **Soul** = the memory you *own*: plain markdown, portable, vendor-neutral —
  the relationship history, the protocols, the cross-project context. It is
  yours; you can read it, edit it, `git` it, take it to another machine.
- **Brain** = the vendor's model (Claude / Codex / Antigravity). Swappable.

`share` aggregates one Soul across many Brains. **It carries continuity and
context, NOT the model's thinking** — swapping the Brain does not make a cheaper
model think like a more expensive one. Never claim "same AI, painless engine
swap." (See [[no-phantom-features]].)

## 2. SSOT = markdown + frontmatter

The single source of truth is **plain markdown with YAML frontmatter** — the
existing memory protocol (a `MEMORY.md` index + one fact per topic file). There
is **no dependency on Obsidian or any viewer**; Obsidian is one optional reader
of the same files.

The store also carries a `PROTOCOL.md` — the Soul's operating manual (how to read
and, crucially, how to *write back* without drift: one fact per file, append don't
rewrite, mark incident/correction records, respect account isolation). claude knows
this from its system prompt; codex/agy learn it by reading `PROTOCOL.md` (the pointer
note sends them there). It's seeded on first `share` and never clobbered.

claude's file-memory is already near-neutral markdown, so claude is the **anchor
format**: claude reads its own memory dir natively (zero translation). codex keeps
its *own* memory opaquely (sqlite) but reads markdown **instructions** (`AGENTS.md`),
so it reads/writes the shared Soul directly via a pointer note — also zero
translation. A translator is only ever needed to *slice/compress* a Soul for an
engine (optional, later), never to make it readable; markdown is universal to these
coding agents. No engine forces a schema change on the anchor.

### 2.1 Frontmatter schema

Every topic file carries frontmatter. Phase 0 adds three Soul fields on top of
the existing `name` / `description` / `metadata.type`:

```yaml
---
name: <short-kebab-case-slug>
description: <one-line summary — used for recall relevance>
metadata:
  type: user | feedback | project | reference
  scope: share | isolate | evaporate   # default: isolate
  project: <area-slug>                  # which area this fact belongs to (for grouping)
  accounts: [<group-name>]              # which share-group may see it (account allowlist)
---
```

- **`scope`** — the per-entry dial. `isolate` (default) keeps the fact in the
  current tank only. `share` lets it travel into the share-group's common store.
  `evaporate` marks a fact as ephemeral (never persisted to a shared store).
- **`project`** — the area/cluster the fact belongs to (e.g. `clikae`,
  `feelreef`, `tile`, `cver-infra`). Drives the **grouped index** (§3). This is
  organisational only; it does not gate visibility.
**Walling a tank off (`clikae solo <engine> <tank> [reason]`).** The cross-account
guard only fires across *different* accounts — so two tanks on the SAME account but
with different purposes (e.g. your main tank and a bot/persona tank) would slip past
it: an accidental `share` could commingle them silently. Making a tank **solo**
(standalone — see grammar §3.3) takes it out of the fleet entirely: no relay/`to`,
no burn rotation, and `memory share` refuses it (with the reason) until `clikae solo
… --off`. `memory status` shows 🔒 solo. This is tank-level, distinct from per-entry
`scope` below.

- **`accounts`** — the share-group allowlist. 🔴 **Account isolation is
  sacred:** a fact is only ever visible to tanks the maintainer has explicitly
  grouped as "the same you." `share` is always **opt-in and group-scoped** —
  clikae **never** auto-crosses accounts (a `cver` fact never leaks into a
  different account's brain). Absent / empty `accounts` ⇒ the fact stays where
  it is.

All three fields are **optional and additive** — existing files without them
behave exactly as today (`isolate`, ungrouped). Phase 0 does not require
back-filling every file; fields are added as facts are touched.

## 3. The grouped index

`MEMORY.md` is the index loaded into context each session. It groups entries by
**area** (the `project` field) under `type` headings, so a 200-line flat list
becomes a navigable map. One line per memory; never put memory content in the
index. The grouping is the cheap, no-code, immediately-felt win of Phase 0 — the
content was already well-separated; only the index was a flat wall.

## 4. The locked values (carry into every later phase)

- 🔴 **Aggregate, never mutate the source.** Any share/translate operates on a
  view/copy going *outward*; the source tank's memory is never rewritten in
  place. Same DNA as relay's "copy, never move; source untouched."
- 🔴 **Account isolation is sacred.** Nothing shares until you opt in once; after
  that the fleet shares by default, but crossing to a DIFFERENT account is still
  announced and never silent. A machine that never opted in shares nothing, ever.
- 🔴 **No phantom continuity.** Soul carries context, not capability.
- **Reviewable + reversible.** Memory is authoritative and accumulative; unlike
  a disposable handoff brief, it must never be silently rewritten by a model. A
  translator (Phase 3) renders *disposable slices*, not in-place edits.

## 5. Roadmap pointer

Phase 0 (this doc + grouped index) → Phase 1 (`clikae memory share|isolate|status`,
symlink fan-in for claude) → Phase 2 (bootstrap pointer + write-back hygiene) →
Phase 3 (cross-engine translator bridge) → Phase 4 (scope dial + orchestration).
Full roadmap: the maintainer's plan + [`grammar.md` §10](/grammar.md).
