Files
nexu-io--open-design/specs/current/creative-memory-integration-shape.md
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

532 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Creative Memory Integration Shape
## Purpose
Capture the integration-boundary decisions the product/pipeline team needs to
make before any creative-memory implementation can land in the live generation
loop. What this doc covers is **not** the memory engine itself. It is the
contract between memory and the rest of Open Design: where signals come from,
where the prompt block goes (and how it relates to the existing `## Personal
memory` slot the daemon composer already populates), how users control it,
and how the raw-events / content-addressed-derivations contract (background
below) reshapes the boundary.
This is doc-only. No code lands with this PR. Each section enumerates the
option space, names a working lean, and flags the decision that needs an
explicit product/pipeline call. The intent is to react against this doc rather
than against committed code, so reversing a decision costs a doc edit instead
of a refactor. The doc is written to apply to either implementation that
might land — PR [#1746](https://github.com/nexu-io/open-design/pull/1746)'s
RFC/prototype package or a team-internal implementation — per the resolved
foundation-vs-reference question on the parent issue.
Anchor threads:
- Issue [#1637](https://github.com/nexu-io/open-design/issues/1637) — the
product-direction thread; carries the Section 11 raw-events contract
discussion in line.
- PR [#1746](https://github.com/nexu-io/open-design/pull/1746) — the
RFC/prototype engine package, parked while this doc is reviewed.
## Background
This section inlines just enough of the engine's external shape that the
later sections do not depend on any file outside `main`. Anyone who wants
the full simulation suite, lifecycle rationale, or open-questions ledger
can read PR [#1746](https://github.com/nexu-io/open-design/pull/1746); the
material below is what this doc itself relies on.
### Engine external API (current shape, subject to integration)
The prototype exposes a deliberately small surface:
- `retrieveForInjection(userId, context)` — pure function over stored state
plus request context. Returns a structured result describing the
preferences that should be injected, plus diagnostic events explaining
what was kept, dropped, capped, suppressed, or backfilled at each stage.
- `buildPromptBlock(retrieved, projectId)` — formats the retrieval result
as a fixed `[MEMORY CONTEXT]` block (Prefer / Avoid sections per
confidence tier, plus an optional `Project override:` line).
- `ingestSignal(userId, signal)` — accepts one classified signal at a time
(one of `generation_accepted`, `manual_refinement`, `explicit_tag`,
`thumbs`, `abandon`, `revert`).
- `runDecay(userId)` — invoked by the host on a fixed schedule (typically
session start); decays untouched preferences and archives stale ones.
- Typed adapter handlers — `onGenerationAccepted`, `onArtifactEditedAndSaved`,
`onExplicitTagApplied`, `onThumbsRated`, `onGenerationAbandoned`,
`onRevertAfterEdit`. Each wraps `ingestSignal` with the right signal type;
these are the integration touch points §1 of this doc decides on.
The integration contract this doc defines does **not** require keeping
those exact names or signatures. They are listed so the §1 / §2 decisions
have a concrete starting point.
### Engine storage shape (current shape, also subject to integration)
The prototype stores plain JSON per user at
`<storage_root>/<userId>/preferences.json`, schema-versioned, with:
- `global_preferences[]` — preferences with no project scope.
- `project_overrides{<project_id>: prefs[]}` — per-project preferences.
- `refinement_log[]` — diff history; dormant today (nothing reads it),
preserved as the natural seed for the future raw-events log.
- `memory_enabled` — soft kill switch; when `false`, ingestion no-ops and
retrieval returns empty.
`<storage_root>` defaults to a package-local directory and is overridable
via `MEMORY_STORAGE_ROOT`. §3 of this doc proposes aligning that with
`OD_DATA_DIR` precedence per [`AGENTS.md`](../../AGENTS.md) FAQ "Where is
data written?".
### Raw-events / content-addressed-derivations contract
The current engine is **write-time-derivation**: `ingestSignal` mutates
preference records directly. Two failure modes were identified during PR
[#1746](https://github.com/nexu-io/open-design/pull/1746) review:
1. **Attribution.** A unary accept/reject is one bit; without contrastive
context, an extractor will attribute rejections to whatever it finds
most salient in each candidate, not what the user actually disliked.
After enough events, rejection memory hardens around extractor salience
rather than user intent.
2. **Re-derivation impossible.** Derived features are written directly,
so an improved extractor or new contextual signals cannot retroactively
re-interpret older events.
The agreed forward-looking contract:
- **Raw events are canonical.** Every interaction event is persisted as an
append-only record with enough context to re-derive features later.
- **Derived features are a cache.** What is currently the preference
records becomes a cached interpretation over the raw event log. Each
derived feature carries the derivation function version that produced it.
- **Pairwise > unary for attribution.** Where the UX can support it, prefer
pairwise comparison over unary accept/reject. Unary signals stay
weaker/provisional unless reinforced by explicit tags, edits, or
contrastive evidence.
- **Cache invalidation is content-addressed.** Cache key for a derived
feature is `hash(raw_event_set, derivation_fn_version)`. When either
side changes — new raw events arrive, the derivation function changes,
the version tag bumps — the key changes and the old derived value is
unreachable. Re-derivation happens on demand against whatever set of
raw events the new key resolves over. A derived feature can never be
stale, because if it were stale its key would have changed and the
lookup would miss.
This is the "Section 11 contract" §4 of this doc maps onto integration
boundaries.
## Non-goals
- Choosing between PR #1746's package and an internal team implementation as
"the" foundation. The product call has been made: PR #1746 is RFC/prototype
input. This doc is written to apply to either implementation.
- Designing UI screens. Where user controls are needed, the doc names the
surfaces and the dual-track UI/CLI obligations from
[`AGENTS.md`](../../AGENTS.md), but stops short of pixel-level proposals.
- Tuning normalizer values, reversal multipliers, or injection format.
Those are engine-internal numerics, not integration boundaries.
## 1. Signal capture points
The engine exposes six typed handlers. Each needs a concrete pipeline event to
fire from. The decisions here are the integration form of the same questions
called out in the Background section above; the table captures the decision
space.
| Handler | Engine assumption | Candidate trigger sites | Lean | Open product call |
|---|---|---|---|---|
| `onGenerationAccepted` | One signal per acceptance event. | (a) Explicit "Use this" button. (b) Tab/run promotion to the active artifact. (c) Idle timeout after preview opens with no edit. | (a) only. (b) and (c) inflate the confidence ladder by treating non-rejection as approval. | Is there an explicit-accept affordance today, or should we add one before wiring this handler? |
| `onArtifactEditedAndSaved` | One signal per saved diff. | Manual-edit panel save, srcdoc bridge save, finalize-design save. | All three, host coalesces by `(artifactId, session)` window before calling. | Coalesce window. 30s feels conservative; longer windows risk dropping intent shifts within a single edit session. |
| `onExplicitTagApplied` | One signal per tag. | A "tag this" / "I like the spacing here" affordance that does not exist today. | Defer until the affordance exists; do not synthesize tags from chat NLP. | Is the surface area for this UI in scope, or should the handler stay dormant? |
| `onThumbsRated` | One signal per click. | Critique theater rating, comment-mode reaction. | Critique theater first, comment-mode if/when it ships ratings. Host debounces double-click. | Whether thumbs are unary or pairwise (see §4 below). |
| `onGenerationAbandoned` | One signal per abandon. | (a) Explicit discard. (b) Inactivity timeout. (c) New generation started with previous unaccepted. | (a) + (c). (b) is too noisy across multitasking. | Whether (c) requires the previous run to be the active tab, or just any pending one. |
| `onRevertAfterEdit` | One signal per revert. | History panel revert, `git revert` on artifact dir, `Cmd-Z` rapid-undo. | History-panel revert only. The other two are noisy and context-free. | Whether to surface a "revert as feedback" affordance distinct from undo. |
### Cross-cutting
- **Debounce ownership.** Lean: host coalesces and calls the handler once per
coalesced window. Adapter stays simple.
- **Classifier placement.** Lean: pipeline populates
`artifact_meta.signals[]` upstream of the adapter call. The adapter remains
a pass-through. ML classification, if any, belongs in the host, not in the
memory subsystem.
- **Headless / CLI parity.** Per the dual-track rule, every signal capture
surface in the UI must have a CLI equivalent that emits the same event
shape. `od memory ingest` (or similar) is the contract; without it,
external agents driving Open Design through `od` cannot contribute to the
user's preference memory and the memory becomes UI-only.
## 2. Retrieval insertion into generation / critique
The engine returns a structured retrieval result and `buildPromptBlock` formats
it as a fixed `[MEMORY CONTEXT]` block. The decisions are: where in prompt
composition that block lands, how it relates to the existing `## Personal
memory` slot the daemon composer already populates, and whether critique sees
the same block.
### Memory-relevant prefix of the live composer order
`apps/daemon/src/prompts/system.ts` composes a longer system prompt than the
prefix below; this section enumerates only the blocks that bear on memory
placement. Blocks 113 are the ones the new memory layer has to insert into
or alongside; the tail blocks (summarized at the end of this subsection)
are mode-specific overrides that creative memory neither displaces nor sits
between. Each block below is gated on its input being non-empty.
0. `API_MODE_OVERRIDE` (plain-stream daemon agents only) — pinned at the
absolute top, before block 1. Out of scope for memory placement.
1. Locale prompt (optional).
2. `DISCOVERY_AND_PHILOSOPHY` + `BASE_SYSTEM_PROMPT` — identity / workflow
charter.
3. `## Personal memory (auto-extracted from past chats)` — facts sedimented
from previous conversations and edited in Settings. Treated as
preferences not hard rules; brand wins on token conflicts; skill wins on
workflow conflicts.
4. `## Custom instructions (user-level)` — persistent user instructions,
apply to every project.
5. `## Custom instructions (project-level)` — per-project instructions;
overrides user-level on conflict.
6. `## How to use this design system` — usage guidance for the active brand.
7. `## Active design system` — the DESIGN.md prose contract.
8. `## Design system import mode` (optional) — how the brand was imported.
9. `## Active design system tokens` — the tokens.css contract; verbatim
`:root` block to paste into the artifact.
10. `## Reference component manifest` (or `## Reference fixture` fallback) —
component inventory grounded in the brand.
11. `## Pull-layer files available on demand` — paths the agent can read on
request.
12. `## Active craft references` — universal brand-agnostic rules opted into
via `od.craft.requires`.
13. `## Active skill` — the skill body the agent must follow.
#### Tail blocks (mode-specific, after `## Active skill`)
The composer keeps appending after block 13. These blocks are noted here so
implementers do not treat block 13 as the tail of the stack and miss
precedence layers a creative-memory block has to coexist with. None of them
are candidate placements for the memory block itself; each is gated by mode,
agent identity, or feature flag.
- Plugin block and per-stage atom blocks (active plugin + stage-bundled
atom guidance).
- Metadata block (project metadata, skill template, audio voice options).
- Deck framework directive — pinned for deck projects without a skill seed,
conditional variant for kind=other projects.
- Media generation contract — for image/video/audio surfaces.
- Codex imagegen override — gated on agent + metadata.
- Critique theater addendum — gated on `cfg.enabled`, suppressed on media.
- Active-design visual-direction override — gated on the brand being bound.
- Connected external MCP directive — gated on connected MCP servers.
- Claude-only `AskUserQuestion` clarifying-questions block — gated on
agent identity.
Source: `apps/daemon/src/prompts/system.ts:430-650`. The exact line numbers
are unstable; the gating conditions and order are the durable shape.
The existing precedence rule, declared in the `## Personal memory` block's
own narrative, is: **personal memory is preferences, not hard rules; brand
tokens win on style conflicts; skill workflow wins on workflow conflicts.**
Creative memory has to either inherit that rule or replace it explicitly.
### Decision A: relationship to `## Personal memory`
The existing `## Personal memory` slot is auto-extracted from chat history
and editable in Settings. Creative memory is a different signal source
(typed pipeline events: accept / refine / tag / thumbs / abandon / revert)
with a different invalidation contract (deterministic ranking, decay,
reversal, and the §4 raw-events / content-addressed-derivations contract).
| Option | What it does | Cost | Benefit |
|---|---|---|---|
| A1. Replace | Creative memory subsumes `## Personal memory`. Single block, single pipeline. | Loses the existing block's identity / tone / "things-already-told-you" content, which chat NLP captures and event-based extraction does not. | One layer to reason about. |
| A2. Append | Creative memory body is concatenated into the same `## Personal memory` block. Same precedence rules. | Two extractors writing into one block: no audit trail, no per-source diagnostics, harder to disable one source without the other. | Smallest composer change. |
| A3. Sibling block, shared precedence | New `## Creative memory` block placed adjacent to `## Personal memory`. Each has explicit scope; both follow the same "preferences not rules; brand wins; skill wins" precedence. | One more block in the system prompt; small token cost. | Audit trail preserved (which extractor produced what); §4 contract advantages preserved; either source can be disabled independently. |
Lean: **A3.** The two memories serve different purposes (identity facts vs
stylistic preferences) and have different invalidation models. Mashing them
together loses the §4 raw-events contract advantages (re-derivability,
content-addressed cache, derivation-version provenance). Sibling placement
with shared precedence wording lets the model treat them similarly without
collapsing them.
This is the one explicit decision §2 surfaces that the live composer makes
unavoidable; treating it as implicit is the failure mode this section is
meant to remove.
### Decision B: insertion point
Given A3, the question is where the new `## Creative memory` block sits in
the live order above.
| Option | Where | Effect | Lean |
|---|---|---|---|
| B1. Inside the soft-preferences cluster, after `## Personal memory` | Position 3.5, before custom instructions | Both preference layers are adjacent; custom instructions and the brand contract follow. Coarse-to-fine progression preserved. | **Lean.** Matches the live composer's existing grouping (soft → instructions → brand → craft → skill). |
| B2. After the brand contract, before craft | Position 9.5 / 10 | Brand wins by virtue of preceding the block; memory becomes a brand-aware nudge. | Reasonable if real-world traces show creative memory drifting against the brand. Treat as a fallback. |
| B3. After craft, before the skill | Position 12.5 | Memory becomes a skill-prefix nudge. Weakest influence; easy for opinionated skills to ignore. | Not preferred — buries the signal too late in the stack. |
| B4. As a separate user-role turn | Outside system | Treats memory as a runtime hint, not a system constraint. | Worth A/B testing once integration ships, not the default. |
Lean: **B1**. Placement keeps the two preference layers together; the
existing precedence narrative (brand wins on tokens, skill wins on workflow)
already covers both blocks once they share the precedence wording.
### Decision C: precedence wording
If A3 + B1, the new block needs a header narrative that mirrors `## Personal
memory` so the model treats them with the same hierarchy. Proposed wording:
> ## Creative memory (preferences from generation feedback)
>
> The following stylistic preferences have been derived from this user's
> past acceptances, edits, tags, and rejections in generation runs. Treat
> them as preferences and context, NOT hard rules: when they collide with
> the active design system tokens, the brand wins; when they collide with
> the active skill's workflow, the skill wins. Project-level preferences
> take precedence over global ones whenever both address the same pattern.
This duplicates the `brand wins / skill wins` clause from `## Personal
memory` deliberately — the model treats each block on its own; a single
shared rule split across two blocks would be lost.
### Critique path
Critique runs (existing critique theater + future critique-conformance work)
should also see the memory block, but the contract is different:
- **Generation:** memory is *aspirational* — what the user prefers.
- **Critique:** memory is *evaluative* — what the user rejected.
Lean: same retrieval call (`retrieveForInjection`) but a different
`buildPromptBlock` variant that flips emphasis (Avoid items first, Prefer
items as supporting context). The structured retrieval result is sufficient
for either format; only the formatter changes. Scope: the same A3 sibling
block, with the formatter chosen by the caller (composer for generation,
critique runner for critique).
### Project-override semantics
The engine already returns project-scoped overrides separately. Two surfaces
to confirm:
- **Generation block.** Project line appended after global Prefer/Avoid, as
the engine already produces. Mirrors how `## Custom instructions
(project-level)` overrides user-level in the live composer.
- **Critique block.** Project overrides should be presented as harder
constraints than global preferences ("project says X", not "user prefers
X"), because critique is project-scoped by definition.
### Failure modes
- **Memory disabled.** Engine short-circuits and returns empty. The composer
must tolerate an empty block without a stray separator. The existing
composer already gates `## Personal memory` on non-empty body; the new
block follows the same pattern.
- **Empty retrieval.** No invariants violated; same handling as disabled.
- **Token budget exceeded.** Engine drops at cut and emits a diagnostic. The
composer should not see a half-formed block — engine already prevents this.
- **Latency.** `retrieveForInjection` is local-file IO, but worst case (50+
patterns, multi-stage balancing) should still complete well under 50ms.
Open question: budget alarm threshold.
## 3. User controls and escape hatches
The engine exposes a `memory_enabled` boolean kill switch in stored state and
no other user surface. Everything else needs to be designed.
### Required surfaces (UI/CLI dual-track)
| Surface | UI | CLI | Why required |
|---|---|---|---|
| Master enable/disable | Settings → Memory toggle | `od memory disable` / `od memory enable` | Trust posture; users must be able to turn it off. |
| Per-project override | Project settings → "this project ignores global memory" | `od memory project-disable <project>` | Sensitive projects (client work, NDAs) should not leak general preference memory. |
| "What's in my memory right now" inspector | Settings → Memory → Inspect | `od memory inspect --json` | Trust requires legibility. The engine emits diagnostics; this surface reads them. |
| Forget a specific pattern | Inspector row → Forget | `od memory forget <type> <pattern>` | GDPR-shaped escape hatch and recovery from ingestion errors. |
| Wipe all memory | Settings → Memory → Reset | `od memory reset --confirm` | Recovery from corruption, account handover, fresh start. |
| Pause without forgetting | Settings → Memory → Pause | `od memory pause [--until <date>]` | Useful when the user knows they're working in an atypical mode (client work, exploration) without wanting to lose the existing model. |
### Surfaces to consider
- **Diagnostic feed in chat.** The engine emits typed diagnostics on every
retrieval (`hard_cap_applied`, `polarity_ceiling_applied`, etc.). Surfacing
these as a collapsed "memory adjusted X" line in chat is high-trust but
also potentially noisy. Lean: off by default, opt-in via Settings.
- **Per-conversation override.** "Don't use memory in this conversation"
toggle. Defer until usage data shows it's needed.
- **Edit a pattern's strength.** The engine's deterministic ranking would
break if users edited strengths directly. Lean: do not expose. Forget +
re-ingest is the supported workflow.
### Storage location and portability
The engine defaults to `<package install dir>/memory/<userId>/preferences.json`
overridable via `MEMORY_STORAGE_ROOT`. For Open Design integration, two
decisions:
- **Default location.** Lean: `<OD_DATA_DIR>/memory/<userId>/preferences.json`
so memory follows the same `OD_DATA_DIR` precedence as other daemon state
(`AGENTS.md` FAQ "Where is data written?"). Packaged installs and Home
Manager / NixOS modules already point `OD_DATA_DIR` at a writable directory;
memory should ride that contract.
- **Portability.** `od memory export --to <path>` and `od memory import <path>`
for moving memory across machines without a cloud sync layer. The engine's
storage is already plain JSON; this is just CLI plumbing.
### Trust boundary
The engine deliberately has no UI of its own and no API surface beyond
function exports. That is the right boundary for the engine. The integration
layer is where trust controls live, and the dual-track rule means every
control needs both UI and CLI from day one — not staged across PRs.
## 4. Section 11 contract shape
The Background section above states the raw-events / content-addressed-
derivations contract: raw events canonical, derived features as a re-derivable
cache keyed on `hash(raw_event_set, derivation_fn_version)`, pairwise
preferred over unary for attribution. This section maps that contract onto
integration boundaries.
### Ownership: who stores raw events?
| Option | Where raw events live | Implication |
|---|---|---|
| A. Engine package owns raw events | Engine-owned storage alongside `preferences.json` | Engine boundary expands to include event log. Re-derivation runs inside the package. |
| B. Daemon owns raw events | Daemon-managed storage | Engine becomes a pure derivation function over an event slice handed in by the daemon. |
| C. Hybrid — daemon writes, engine reads | Daemon writes through its storage contract; engine reads through a typed accessor | Decouples write path (event capture is a host concern) from derivation (engine concern). |
This spec MUST NOT define daemon data paths. Before documenting or changing
daemon storage, read root [`AGENTS.md`](../../AGENTS.md) → **Daemon data
directory contract**.
Media config storage is outside this spec. Read the same root contract before
documenting any storage exception. Raw events are general daemon runtime data
and must follow the daemon storage contract; an implementation that routes
preference event data into credentials storage would use the wrong contract.
Lean: **C**. Event capture is intrinsically a host concern — the daemon is
where the pipeline events fire from, where debounce/coalesce happens, and where
sensitivity/redaction policy applies (a chat transcript can carry PII; raw
events should respect redaction before they hit disk). Derivation is
intrinsically a memory-package concern. Splitting the responsibilities along
that axis matches the existing daemon ↔ package boundary in the repo.
### Where does the derivation function version live?
The content-addressed key is `hash(raw_event_set, derivation_fn_version)`. The
version tag has to live somewhere both the writer and reader agree on.
Lean: as an exported constant in `@open-design/creative-memory-system`,
imported by the daemon when computing the cache key. Bumping the version is a
package release. This matches how the package already exports
`schema_version: "1.0"` in `preferences.json`.
### Read-time vs background derivation
Two modes for when re-derivation runs:
| Mode | Trigger | Latency | Cost |
|---|---|---|---|
| Lazy on read | First call to `retrieveForInjection` after raw events change | Adds derivation cost to first call | Small if events are batched, can spike if a session's events are large |
| Background worker | Daemon job after each accepted batch of events | Read is always fast | Constant background cost, even when the user is idle |
Lean: **lazy on read** for MVP, with the structured option to add a background
worker behind a feature flag later. Lazy-on-read with a write-back cache is
the documented path; background re-derivation is a follow-up once derivations
grow heavy enough to dominate p99 latency.
### Pairwise vs unary signal capture
The engine accepts unary signals today (accept / reject / edit / tag). The
contract favors pairwise where the UX can support it.
Pairwise candidate surfaces:
- **Critique theater.** Naturally pairwise — the user is comparing variants.
Lowest-friction place to introduce a `comparison` event type.
- **Tab promotion.** When a user switches the active artifact between two
open generations, the implicit comparison signal is "I prefer this one
right now." High noise, but also high volume.
- **Live-artifact refresh.** When a refresh produces a new variant of a
saved artifact, accepting the new one over the old is pairwise.
Lean: ship pairwise for critique theater first, leave the other two as
follow-ups. Adding pairwise means a new event shape (`{ chose: artifactA, over: artifactB, dimensions?: [...] }`)
and a new handler (`onPairwiseComparison`); the engine's existing handlers
stay valid for unary cases. The weighting policy between pairwise and unary
aggregates lives in `derivation_fn_version` and is auditable per derived
record, per the §11 contract.
### Cache eviction
Content-addressed cache → eviction is by capacity, not by age. Decision:
- **Cap.** Lean: 1000 derived feature entries per user, evicted LRU. Worst
case is a single `preferences.json` storing 1000 records, which is well
under any practical token or memory limit.
- **Persistence.** Cache survives daemon restarts (it is a file on disk).
Engine handles cold start by computing on first read.
### Migration from the current write-time-derivation engine
The current engine writes derived features directly. Migration path:
1. Land raw-events writer in the daemon (this PR's contract gates it).
2. Backfill: each existing `preferences.json` record becomes a synthetic
"v0" raw event. Ugly but bounded.
3. Switch derivation to read from raw events, content-addressed cache fills
on demand.
4. Old write-time derivation paths in `ingestSignal` / `_applyReversal` /
`runDecay` deprecate behind a `derivation_mode: "raw" | "write-time"`
flag for one release, then remove.
Lean: this migration belongs to whichever implementation lands as the
foundation, not to PR #1746. The package and daemon contracts in this doc
should be agnostic to which side does the work.
## 5. Decision summary
For maintainer review, the explicit calls this doc surfaces:
1. **Acceptance trigger** for `onGenerationAccepted`. (§1)
2. **Edit-coalesce window.** (§1)
3. **Whether explicit-tag UI is in scope.** (§1)
4. **Relationship to `## Personal memory`** — replace, append, or sibling block. (§2; lean A3 sibling block)
5. **Memory block insertion point** in the live composer order. (§2; lean B1, after `## Personal memory`)
6. **Precedence wording** for the new block. (§2; lean: mirror the existing `brand wins / skill wins` clause)
7. **Critique-side memory format.** (§2)
8. **Default storage path** under `OD_DATA_DIR`. (§3; lean yes)
9. **Diagnostic feed surface** in chat. (§3; lean off by default)
10. **Raw-events ownership** between daemon and engine package. (§4; lean C)
11. **Derivation-version exporter.** (§4)
12. **Pairwise rollout sequencing.** (§4)
13. **Cache eviction cap.** (§4; lean 1000/user)
None of these require a decision today. They are surfaced so that when the
memory roadmap moves, the conversation has a concrete option space rather
than starting from a blank page.
## What this doc does not commit to
- A specific pairwise event shape. The lean is `{ chose, over, dimensions? }`,
but UX details (does the user pick one, or pick + dimension annotation?)
belong to the surface that actually ships pairwise first (critique theater).
- A specific CLI subcommand layout for `od memory`. The dual-track rule says
every UI control needs a CLI peer; the names in §3 are illustrative, the
actual subcommand grammar follows whatever pattern the existing
`od automation`, `od plugin`, `od ui` family establishes.
- A rollout schedule. The decisions above are independent and can land in
any order behind a `memory_enabled: false` default.
## Open follow-ups
- Confirm that `OD_DATA_DIR` precedence applies to the memory storage root
(it should, per AGENTS.md FAQ, but the integration code has not been
written yet).
- Decide whether memory state is part of `od project export` /
per-project portability flows, or strictly user-scoped.
- Sketch the `od memory inspect --json` output shape — likely just a passthrough
of the engine's diagnostic events plus the current preference list.
These are tractable doc edits once the §5 decisions land; flagged here so
they are not forgotten when implementation work resumes.