Symptom
A user who runs reflection today gets handed a structured prompt to paste into an AI chat. /ReflectMemories returns { memories, prompt, ... } and expects "the agent" to run the prompt through its own LLM and write insights back (resources/MemoryReflect.ts header). REM should just work.
Root cause
The nightly REM cycle (specs/FLAIR-NIGHTLY-REM.md §4) shipped steps 1–3 + 6 — snapshot, maintenance/archive, audit log — but steps 4–5 (trust-tier filter + distillation into MemoryCandidate rows) are explicitly parked in src/rem/runner.ts:
Steps 4, 5 are slice-2 follow-ups; require an in-process distillation LLM path (today /ReflectMemories returns a prompt for human/agent consumption, not server-side candidate generation).
Everything around the LLM call exists — scheduler, snapshots, rollback, the MemoryCandidate schema, flair rem candidates/promote/reject, the audit row fields. The engine has no motor.
Proposal — server-side distillation via Harper's model-access API
Harper now provides the exact missing primitive: scope.models exposes generate()/generateStream() with pluggable, config-selected backends (local Ollama, OpenAI, Anthropic, Bedrock, gateway — HarperFast/harper resources/models/types.ts, HarperFast/harper#1325 custom backends, HarperFast/harper#1326 routing). Flair should consume it rather than ship any provider code of its own:
- Execution: the reflection path executes server-side in the Flair component — build the reflection prompt (existing code), call
scope.models.generate() with responseFormat: { schema } so candidates come back structured (no fragile parsing), persist MemoryCandidate rows (status=pending, generatedBy = resolved model id, sourceMemoryIds = the reflected set). The prompt-return shape can remain as an explicit fallback/debug mode for callers that want to run their own model.
- Model choice is configuration, not code: want REM → point Harper's models config at a local model (Ollama, zero keys) or drop in an API key for any supported provider. Router fallback (
ModelRouter) gives resilience for free.
- Secrets: on Fabric / managed deploys, provider API keys ride Harper's env-secret encryption (
enc:v1: values — encrypted at rest, never plaintext in ops API/logs/replication; docs/env-secret-encryption.md). Local/self-hosted needs no key at all with an Ollama default.
- Nightly runner: unchanged responsibilities (pre-flight, snapshot, maintenance, audit row) — step 5 becomes a single call to the executing resource;
candidates populates in the audit row and flair rem candidates shows them next morning.
- Failure mode: no configured/reachable generative backend → cycle logs the error, completes maintenance steps, stages nothing. No partial candidates.
Safety posture — unchanged
Spec §2 inviolables hold: snapshot before every run, distillation only ever stages candidates, promotion remains explicit (flair rem promote --rationale, trust policy §5). "Just works" means candidates appear on their own — not that anything self-promotes.
Why now
This is the single missing primitive for the whole REM lane: flat distillation today, and any future richer consolidation (e.g. recursive/hierarchical consolidation of an agent's corpus) rides the same model path. Smallest slice that makes the product claim ("teach once, inherited forever") true without an operator pasting prompts.
Symptom
A user who runs reflection today gets handed a structured prompt to paste into an AI chat.
/ReflectMemoriesreturns{ memories, prompt, ... }and expects "the agent" to run the prompt through its own LLM and write insights back (resources/MemoryReflect.tsheader). REM should just work.Root cause
The nightly REM cycle (
specs/FLAIR-NIGHTLY-REM.md§4) shipped steps 1–3 + 6 — snapshot, maintenance/archive, audit log — but steps 4–5 (trust-tier filter + distillation intoMemoryCandidaterows) are explicitly parked insrc/rem/runner.ts:Everything around the LLM call exists — scheduler, snapshots, rollback, the
MemoryCandidateschema,flair rem candidates/promote/reject, the audit row fields. The engine has no motor.Proposal — server-side distillation via Harper's model-access API
Harper now provides the exact missing primitive:
scope.modelsexposesgenerate()/generateStream()with pluggable, config-selected backends (local Ollama, OpenAI, Anthropic, Bedrock, gateway — HarperFast/harperresources/models/types.ts, HarperFast/harper#1325 custom backends, HarperFast/harper#1326 routing). Flair should consume it rather than ship any provider code of its own:scope.models.generate()withresponseFormat: { schema }so candidates come back structured (no fragile parsing), persistMemoryCandidaterows (status=pending,generatedBy= resolved model id,sourceMemoryIds= the reflected set). The prompt-return shape can remain as an explicit fallback/debug mode for callers that want to run their own model.ModelRouter) gives resilience for free.enc:v1:values — encrypted at rest, never plaintext in ops API/logs/replication;docs/env-secret-encryption.md). Local/self-hosted needs no key at all with an Ollama default.candidatespopulates in the audit row andflair rem candidatesshows them next morning.Safety posture — unchanged
Spec §2 inviolables hold: snapshot before every run, distillation only ever stages candidates, promotion remains explicit (
flair rem promote --rationale, trust policy §5). "Just works" means candidates appear on their own — not that anything self-promotes.Why now
This is the single missing primitive for the whole REM lane: flat distillation today, and any future richer consolidation (e.g. recursive/hierarchical consolidation of an agent's corpus) rides the same model path. Smallest slice that makes the product claim ("teach once, inherited forever") true without an operator pasting prompts.