# Task Brief — Second Brain (compounding per-user knowledge engine)

> **Status:** revised after adversarial brief-review (light-Voyage step 2 → findings folded in). Architecture approved by operator 2026-06-23 (`architecture.md`).
> **Inputs:** `architecture.md` (approved design) · `research/{connector-egress,secondbrain-sota,silo-inventory}.md`.
> **Build target of this brief:** the arc objective + scope fence, and the **first slice SB-S0** in detail. SB-S1..S4 are scoped one-line; their detailed plans come later, one per slice.
> **Review delta (2026-06-23):** B1 fold-source rewritten (template-as-field-source + source-absent no-op); B2 pathguard claim corrected (discrepancy flagged); M1 floors as `≥`; M2 scaffold trigger decided (idempotent CLI/TS subcommand, session-start wiring deferred to SB-S2); M3 profile grammar contract pinned; M4 `brain/` is runtime-only (no committed seed); minors m1–m4 folded.

---

## 1. Objective

Turn the plugin's existing siloed per-user stores into **one compounding "second brain"**: a per-user knowledge engine that accumulates everything about a creator (posts, articles, newsletters, plans, ideas), keeps a two-layer profile that improves over time, and stays aligned to who the user *is now* — drift-resistant and collapse-resistant. It is **memory AND an operations centre**.

The engine is a **plugin capability** (domain-general, shareable). The user's data lives in the per-user data dir. The user's personal cockpit lives in Maskinrommet (out of scope here).

## 2. Background (why this shape)

- **Nothing to migrate** — M0 already routed the **data-root silos** (trends, specifics, analytics, drafts/queue, plans, voice, profile, scaffolds) through one tested seam (`getDataRoot`), all surviving reinstall. The **state file** (silo A) stays on its own resolver (`getStateFile()`, `~/.claude/linkedin-studio.local.md`) and is NOT under the seam (see §7). The task is **unify**, not move. (`silo-inventory.md`)
- **Plain-text + JSON + git, not a vector/graph DB** — Claude *is* the retrieval engine (reads files in-context); a heavyweight store adds infra + lock-in for no gain. (`secondbrain-sota.md` §B.1)
- **Ingest is manual-first as the contract** — only EU/EEA DMA portability gives automated *content* pull (no analytics); scraping is a ToS breach. Connector is an additive tributary, never a dependency. (`connector-egress.md`)

## 3. Confirmed decisions (not open for re-litigation in review)

1. **Boundary:** engine → plugin · user data → data dir · cockpit → Maskinrommet.
2. **Shape:** thin Markdown `brain/` hub (two-layer `profile.md` + episodic `journal/` + `operations.md` + `index.md` MOC) over the **existing typed tributaries** (voice-samples, specifics-bank, trends, analytics), fed by a provenance-tagged `ingest/` seam.
3. **Tributaries keep their own schema/dedupe/lifecycle** — do NOT force one schema across silos. Only the user-profile field-set folds into `brain/profile.md`.
4. **Invariants:** provenance-weighted learning (`published` only, never `ai-draft`); one canonical entity id + one provenance shape (`provenance ∈ {human, published, ai-draft}`); episodic/semantic split; evidence-threshold promotion; temporal-validity + freshness nudge; anti-sycophancy default; frozen-past-self anchor; zero required curation.

Review SHOULD challenge: feasibility, sequencing, hidden coupling, testability, scope leakage, missing risks. Review SHOULD NOT re-open the four decisions above (operator-approved) unless it finds they are internally contradictory or technically impossible.

## 4. Scope — the arc (slices)

| Slice | One-line scope |
|-------|----------------|
| **SB-S0 — Foundation** *(this brief)* | `brain/` scaffold + two-layer `profile.md` (fold in the user-profile field-set) + `index.md` + `operations.md` + `journal/`; entity-id + provenance shape as a small typed, tested module; `ingest/{inbox,published}` dirs + the manual-import **contract** (shape only). No loop, no ingest-processing logic, no connector. |
| SB-S1 — Ingest + gold signal | Manual import populates `ingest/published/` with `provenance=published`; wire profile/voice to learn from published-only. |
| SB-S2 — Evolution loop | Sleep-time consolidation: journal+published+tributary deltas → profile diff w/ evidence_count/provenance/timestamps; threshold promotion; contradiction reconciliation; brain-wide freshness nudge. **Owns the session-start wiring of the scaffold init.** |
| SB-S3 — Cross-silo graph + ops centre | Thread the id through tributaries (post↔specific↔trend↔analytics); flesh out `operations.md`; retire dead `content-history.md` + triple-post reconciliation. |
| SB-S4 *(optional, later)* | EU/EEA DMA portability API as auto-tributary into `ingest/inbox/`. |

## 5. SB-S0 in detail (the build target)

**Deliverables:**

1. **`brain/` scaffold initialiser — an idempotent CLI/TS subcommand** (the trends-store idiom: runtime-created under the data-dir seam, NOT a tracked repo seed). It creates `brain/{index.md,profile.md,operations.md}` + `brain/journal/` + `ingest/{inbox,published}/` under `getDataRoot(subdir)` (which already accepts arbitrary subdirs — no new seam function needed). **Idempotent + migration-safe** (running twice is a no-op; never clobbers existing content). **No per-session cost** — session-start *wiring* of this init is explicitly deferred to SB-S2; SB-S0 ships only the invokable subcommand.

2. **`brain/profile.md` — two-layer schema** with `§ Static` and `§ Dynamic` sections. **Pinned contract (fixed):** one fact = one parseable record carrying all six fields `value · first_seen · last_seen · evidence_count · provenance(human|published|ai-draft) · status(active|superseded)`, and `serialize ∘ parse = identity`. **Open to the plan:** only the surface serialization syntax (frontmatter vs a defined line grammar).

3. **Fold the user-profile field-set into `brain/profile.md` — lossless + idempotent, with a defined source-absent path.** Source of the field *structure* is `config/user-profile.template.md` (the only file that ships; `profile/user-profile.md` is `expected-absent`, migrate-data.mjs:36). Behaviour:
   - **Source-absent (the common case):** scaffold an empty two-layer `profile.md` whose static layer contains every template field present-but-unfilled.
   - **Runtime instance present** (`${data}/profile/user-profile.md` exists): fold its filled values into the static layer.
   - Re-running never duplicates fields and never overwrites already-folded values.

4. **Entity-id + provenance module** — a small typed module (TS, matching the trends/specifics idiom) that mints one canonical entity id at creation and defines the single provenance shape (`{human|published|ai-draft}`). Pure functions, deterministic, unit-tested. **Not yet threaded through tributaries** (that's SB-S3) — SB-S0 only establishes the module + shape.

5. **`index.md` MOC + `operations.md` seeds (runtime-generated)** — minimal but real: `index.md` points to every tributary with a freshness placeholder; `operations.md` has the "who I am now" anchor section. Generated by the initialiser (D1), not committed to the repo tree.

6. **Tests** — unit tests for the module + the scaffold + the fold (incl. the source-absent path). No structure-lint count change expected (SB-S0 adds no `agents/`, `commands/`, `references/`, or `skills/` file); a version bump, if any, updates README/CHANGELOG badges only.

## 6. Success criteria (SB-S0 — testable)

- **SC1 (scaffold, runtime):** with `LINKEDIN_STUDIO_DATA` set to a temp dir, invoking the init subcommand creates `brain/{index,profile,operations}.md` + `brain/journal/` + `ingest/{inbox,published}/` **at the runtime data-path** (not the repo tree), through `getDataRoot`; invoking it twice is a no-op (idempotent, no clobber). Verified by a test that sets the env var, runs init twice, asserts existence + unchanged content.
- **SC2 (profile parse):** `brain/profile.md` parses into `§Static`/`§Dynamic`; a fact round-trips through the pinned grammar with all six fields intact (`serialize ∘ parse = identity`). Verified by a parse/serialize unit test.
- **SC3 (fold lossless + source-absent):** (a) **source-absent:** running the fold with no runtime user-profile yields a `profile.md` whose static layer lists every `config/user-profile.template.md` field; (b) **populated:** given a synthetic populated fixture, the fold carries every filled field into the static layer (diff shows none dropped); (c) re-running either case does not duplicate or overwrite. Verified by fixture-based diff tests.
- **SC4 (id/provenance module):** the module mints a deterministic id (same input → same id) and **rejects** a malformed provenance value (throws; only `{human,published,ai-draft}` accepted); ≥1 unit test per public function.
- **SC5 (no regression):** `scripts/test-runner.sh` is green (currently 89 passed / 0 failed / 0 warnings); each floored suite stays **≥ its floor** (trends ≥24, specifics ≥28, contract ≥33); all hook tests pass.
- **SC6 (seam, no-op):** SB-S0 adds **no new seam function** (`brain/`/`ingest/` use the existing `getDataRoot(subdir)`), so the twin-sync surface is unchanged. Asserted as a no-op; *if* a future slice adds a seam function, the existing twin-sync test (`hooks/scripts/__tests__/data-root.test.mjs`) must be extended.

## 7. Non-goals / scope fence (SB-S0 does NOT)

- **No evolution loop / consolidation** (SB-S2).
- **No ingest processing logic** — `ingest/` dirs + the documented manual-import contract only; no parser that reads inbox files yet (SB-S1).
- **No connector** — no LinkedIn API code (SB-S4).
- **No cross-silo threading** — the id module exists but is NOT yet woven into trends/specifics/analytics (SB-S3).
- **No session-start wiring** of the scaffold init (SB-S2 owns it) — SB-S0 ships only the invokable subcommand.
- **Do NOT touch tributary schemas** (voice/specifics/trends/analytics stores keep their current shape).
- **Do NOT reconcile the two-roots question** (state file at `~/.claude/linkedin-studio.local.md` stays a sibling on `getStateFile()`; not folded in SB-S0).
- **Do NOT retire `content-history.md` or reconcile the triple-recorded post** (SB-S3).
- **No GUI / no cockpit** (Maskinrommet, out of repo scope).

## 8. Research plan / knowledge status

- **Done:** the three threads (connector reality, second-brain SOTA, silo inventory) are complete and persisted. No further research is required to start SB-S0. (Brief-review confirmed both §8 open assumptions are correctly non-blocking.)
- **Open assumptions to test (flagged, not blocking SB-S0):**
  - "Claude-as-retrieval-engine over plain files is sufficient (no vector DB)" — testable once the brain holds content (post-SB-S1). Marked assumption.
  - Newsletter-edition coverage by the portability `ARTICLES` domain — only relevant to SB-S4; verify against a real export then.

## 9. Constraints / risks

- **TDD iron law:** no production code without a failing test first.
- **New code placement:** SB-S0's new code is a **TS module under `scripts/`** (the trends/specifics idiom) + a runtime scaffold + edits to existing files. It creates **no new `.mjs` under `hooks/scripts/`**, so the pathguard does not affect SB-S0. **Verified 2026-06-23 (code-checked; STATE.md line 39 CONFIRMED correct, brief-review B2 was WRONG):** the llm-security plugin guard `pre-write-pathguard.mjs` IS enabled (`~/.claude/settings.json:148`) and registered as a PreToolUse hook with `matcher:"Write"` (Write-only); its Category 6 regex `/[\\/]hooks[\\/]scripts[\\/].*\.mjs$/` matches any `…/hooks/scripts/*.mjs`, **including this repo**. ⇒ a NEW `.mjs` under `hooks/scripts/` **cannot be written with the Write tool**; **editing an EXISTING `.mjs` is fine** (matcher is Write-only). Relevant to SB-S2 (which touches hook code → create new hook `.mjs` via Bash-heredoc, or edit an existing file). SB-S0 stays entirely in `scripts/`, so it is unaffected.
- **Twin data-root implementations** must stay byte-compatible (`hooks/scripts/data-root.mjs` ⇄ `scripts/analytics/src/utils/storage.ts`); SB-S0 adds no new seam function so the twin contract is untouched (SC6).
- **Public repo (`open/` Forgejo):** confirm before push; push only in the allowed window; STATE.md stays gitignored.
- **Structure lint** counts `agents/`, `commands/`, `references/`, `skills/` + the version badge. SB-S0 adds none of these, so no count guard fires absent a version bump.
- **Node v25 test quirk:** `node --test <dir>` doesn't work — use glob `__tests__/*.test.mjs`; run TS tools from their own dir.
- **Risk — fold ambiguity:** the user-profile→profile.md fold is the one data-shaping step in SB-S0; if the grammar is under-specified it risks lossy or non-idempotent folding. Mitigate with fixture-based diff tests (SC3) covering both the source-absent and populated paths before wiring.

## 10. Definition of done (SB-S0)

SC1–SC6 all pass; SB-S0 deliverables 1–6 exist; non-goals untouched; committed (push per window); `architecture.md` slice status updated to "SB-S0 landed"; `CLAUDE.md`/README touched only if a version bump is taken.
