linkedin-studio/docs/second-brain/brief.md
Kjell Tore Guttormsen d6acb5d36c docs(linkedin-studio): second-brain SB-S0 brief + plan (review-hardened)
Light-Voyage process artifacts for the first second-brain slice (SB-S0):

- brief.md — task brief, revised after adversarial brief-review (B1 fold-source
  premise, B2 pathguard claim [later verified: STATE was right, reviewer wrong],
  M1 floors-as->=, M2 scaffold trigger, M3 grammar contract, M4 runtime-only).
- plan-sb-s0.md — SB-S0 implementation plan, revised after adversarial
  plan-review (plan-critic + scope-guardian, both code-verified):
  B1 test convention -> real TS idiom (tests/*.test.ts via tsx, .js specifiers);
  M2 profile format -> defined line-grammar (repo has no YAML parser, no dep);
  M3 data-root -> inline per-package resolver (repo idiom, not a new seam);
  M4 fold extraction rule pinned against the actual user-profile template;
  + minors (gate-count rationale, npm-install-before-gate, provenance throws,
  stable-slug id, push-confirm).

scope-guardian: zero creep, zero MAJOR gap. plan-critic: B1+3 MAJOR, all folded.
Design phase, no code yet — awaiting operator go to build SB-S0 (TDD).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RigJBiRFNtFZKCz21qNbQ4
2026-06-23 13:24:18 +02:00

13 KiB
Raw Blame History

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 m1m4 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 logicingest/ 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.mjsscripts/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)

SC1SC6 all pass; SB-S0 deliverables 16 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.