danielmiessler / danielmiessler/LifeOS
Installer never creates the documented LIFEOS/MEMORY → USER-repo symlink, so the memory reviewer silently drops KNOWLEDGE and proposal writes
- Dominant language
- TypeScript
- Stars
- 19k
- Forks
- 2.5k
- Avg merge
- 8d 17h
- Merged PRs (30d)
- 1
Description
# Installer never creates the documented `LIFEOS/MEMORY` → USER-repo symlink, so the memory reviewer silently drops KNOWLEDGE and proposal writes
**Version:** LifeOS 7.40.4 (fresh install)
## Summary
`SystemUserBoundary.md` specifies that `LIFEOS/MEMORY/**` is a **symlink** into the
private USER-data repo. `ForeignDataCheck` enforces that layout at runtime and
refuses any memory write that resolves outside it. But the installer explicitly
**skips** `MEMORY` and never creates the USER-side directory, the symlink, or the
USER-data git repo.
The result on a stock install: `LIFEOS/MEMORY` is a real directory in the system
tree, every write the reviewer aims at it is refused, and the reviewer run is
marked failed. Two of four items were lost on my first real run.
## The documented layout
`DOCUMENTATION/SystemUserBoundary.md:57`:
> `~/.claude/LIFEOS/MEMORY/**` (symlink → `~/.config/LIFEOS/USER/MEMORY/**`,
> post-Phase-G.2, 2026-05-23) | USER (work history, knowledge graph, learning
> signals, observability logs, research, reflections, relationships). Durable
> subset (KNOWLEDGE, WORK//ISA.md, RELATIONSHIP, WISDOM, PLANS, RESEARCH,
> STATE/work.json, BOOKMARKS, REFERENCE, SKILLS, PROJECT, TEAMS, SYSTEMUPDATES,
> VERIFICATION) is git-tracked in the user's private USER-data repo […]
Repeated in four more places:
| doc | line |
|---|---|
| `LifeosSystemArchitecture.md` | 208 — durable subset git-tracked in the user's private USER-data repo |
| `Memory/CortexContract.md` | 27 — "resolves the default alias to the private user-data repository under `~/.config/LIFEOS/USER/MEMORY`" |
| `Config/ConfigSystem.md` | 92 — release gate G12 assumes the split |
| `Tools/Containment.md` | 143 — G12 template-only USER/MEMORY |
## What the installer actually does
`skills/LifeOS/Tools/InstallEngine.ts:347`:
```ts
const SKIP_DIRS = new Set(["node_modules", ".git", "MEMORY"]);
```
`:414`: *"Atomic per-file (tmp + rename). Skips node_modules/.git/LIFEOS_INSTALL/MEMORY."*
Skipping `MEMORY` during the copy is correct on its own — user memory should not be
overwritten by a system install. The bug is that **nothing else ever creates it**.
There is no code path anywhere that creates `~/.config/LIFEOS/USER/MEMORY`, links
`LIFEOS/MEMORY` to it, or does `git init` in the USER-data repo. I grepped for
migration tooling and found only checkers (`ReferenceCheck.ts`,
`ForeignDataCheck.ts`), no migrator.
## What enforcement expects
`LIFEOS/TOOLS/lib/ForeignDataCheck.ts:265`:
```ts
reason: `resolved target ${resolved} is outside the USER_DATA repo (${root}) — personal data must never land in the system tree (SystemUserBoundary.md)`,
```
`LIFEOS/TOOLS/MemorySystem.ts:843` turns that into `EWRITE_FAILED`.
So one component assumes the symlink exists and another never creates it.
## Observed failure
A reviewer run dispatching four items, two of which were refused:
```json
{"ok":false,"runId":"…","dispatch_summary":{"total":4,
"by_type":{"memory":2,"knowledge":1,"proposal":1},"succeeded":2,"failed":2,
"failures":[
{"index":2,"type":"knowledge","error":"EWRITE_FAILED: memory write refused at the system/user boundary: resolved target /MEMORY/KNOWLEDGE/Research/.md is outside the USER_DATA repo (/LIFEOS/USER) — personal data must never land in the system tree (SystemUserBoundary.md)"},
{"index":3,"type":"proposal","error":"EWRITE_FAILED: memory write refused at the system/user boundary: resolved target /MEMORY/OBSERVABILITY/pending-proposals.jsonl is outside the USER_DATA repo (/LIFEOS/USER) — personal data must never land in the system tree (SystemUserBoundary.md)"}],
"…":"…"},"error":"dispatch failed for 2 item(s)"}
```
Hot-layer writes (`PRINCIPAL_MEMORY.md`, `DA_MEMORY.md`) succeed, because those
resolve under `LIFEOS/USER/`, which *is* correctly linked. Only KNOWLEDGE and
proposals fail. That asymmetry is why this is easy to miss: memory appears to work.
## Repro
1. Fresh 7.40.4 install.
2. `ls -ld $LIFEOS_DIR/MEMORY` → a real directory, not a symlink.
3. `ls $LIFEOS_CONFIG_DIR/USER/MEMORY` → does not exist.
4. `ls $LIFEOS_CONFIG_DIR/USER/.git` → does not exist.
5. Let the memory reviewer run and emit any knowledge or proposal item.
6. `tail -1 $LIFEOS_DIR/MEMORY/OBSERVABILITY/reviewer-runs.jsonl` → `ok: false`
with `EWRITE_FAILED` per refused item.
## Impact
- **Silent data loss.** Knowledge notes and proposals the reviewer generates are
discarded. The only surface is one `reviewer-runs.jsonl` line and a statusline
that says "latest reviewer run is failed" without saying why.
- **No history, no undo.** Because the USER-data repo is never initialised, all
identity and memory content is untracked. `git init` is documented as the safety
net for exactly this data and never happens.
- **Recovery is a data move, not a config change.** Fixing an existing install means
creating the repo, relocating a populated `MEMORY/` tree (1006 files here), and
replacing it with a symlink — risky enough that users will reasonably hesitate.
## Suggested fix
1. Have the installer create `/LIFEOS/USER/MEMORY/`, `git init` the USER-data
repo with the documented `.gitignore` split (durable tracked, ephemeral ignored),
and symlink `LIFEOS/MEMORY` to it. Keep `MEMORY` in `SKIP_DIRS` for the copy pass;
this is a separate provisioning step, not a copy.
2. Ship a migrator for installs already in the broken state, since the fix relocates
live user data and should not be hand-rolled per user.
3. Add a `Doctor.ts` check asserting `LIFEOS/MEMORY` is a symlink resolving inside the
USER-data repo and that the repo has a `.git`. `Doctor` already carries adjacent memory
checks (`Orphaned memory (shadow-$HOME trees)`, `Doctor.ts:317`) but none covering the
symlink or the repo, so the deviation is invisible until a write is refused.
4. Consider making the refusal louder: a dropped knowledge item is currently one JSON
line in a log nobody reads.
## Note
Possibly related to the fork-guard report, in that both are install-time defects that
leave the memory pipeline non-functional while looking healthy, but the mechanisms are
independent and either can be fixed alone.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with skills/LifeOS/Tools/InstallEngine.ts around the SKIP_DIRS and provisioning flow, then read ForeignDataCheck.ts, MemorySystem.ts, and Doctor.ts:317. Reproduce the fresh-install failure and inspect the documented USER/MEMORY layout. Done means new installs provision the USER-data repo and symlink without copying MEMORY, existing data has a migration path, and Doctor detects missing or misplaced setup.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, typescript
- Domain
- developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 56/100