brainllm 12.4.1 → 12.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,66 +1,50 @@
1
1
  # Domain Knowledge Lifecycle
2
2
 
3
- Domain knowledge is **gated and current-state**. Read this before creating a domain, adding a sub-category note, or refreshing existing domain content — then pick the one protocol that matches what's actually happening. Never skip the sources gate where one applies, and never manufacture knowledge.
3
+ Read this before creating a domain, adding a sub-category note, or refreshing domain content, then follow the one protocol that matches. Never skip the sources gate and never manufacture knowledge.
4
4
 
5
- **The markers:** ❇️ = discovered/credible, not yet used. ✅ = used. Markers appear on each source entry as **just the emoji** — marker dates live in the Sources note's **Revision** table, never inline in the list.
5
+ ## The shape of a domain
6
6
 
7
- **Dates are server policy.** Every Sources and information note carries a `Last updated` line that the server bumps on every content write — you never hand-maintain it. (V8's "one manual date" exception is gone.)
7
+ A domain is **one Sources note plus one information note per sub-category**, each a maintained document revised in place.
8
8
 
9
- **Siblings set the pattern.** This is where the consistency rule bites hardest, because domain notes are the most numerous and the most repeated kind in the brain: every Sources note reads like the other Sources notes, every information note like its peers in the same domain and in every other domain. Read one before writing another — the canonical structure below gives you the skeleton, the sibling gives you the conventions it can't encode (heading depth, table shape, how much prose a sub-category warrants). And keep headings minimal: a domain note earns its structure from the source list and the Revision table, not from splitting three paragraphs across four h3s. If a pattern genuinely needs improving, improve it across every domain — one better-shaped note among twenty is drift, and the next writer has to guess which shape is canonical.
9
+ | Note | Holds |
10
+ |---|---|
11
+ | **Sources** | every source, marked ❇️ discovered / ✅ used, grouped under h3s; a **Revision** table (Source · Marker · Date) |
12
+ | **Current State** | the domain's **measured state** — what is live, built, deployed, counted — latest value only, with the date it was measured. The only information note that holds state |
13
+ | any other information note | **timeless knowledge**: how the thing works, its rules, its architecture, its constraints. No state, versions, dates, incidents or decision history |
14
+ | a `#mandate` note (optional) | a standing brief a session must follow (e.g. a founder brief) — `remember(..., mandate=true)` |
10
15
 
11
- ## The canonical Sources note
16
+ **History and decisions never live in a domain.** "What run N found" and "what Miiso decided on a date" go to the venture's thread entries; the domain holds the resulting truth. If you are about to write a date into a non-Current-State note, it belongs in Current State (a measurement) or in a thread entry (an event).
12
17
 
13
- **Domains are born complete** — creating a domain (any `remember(domain=…)` call that resolves a new name) creates the book AND its canonical Sources note. Its structure, top to bottom (serve it anytime with `template(kind="sources")`):
18
+ Markers are just the emoji on each source; their dates live only in the Revision table. `Last updated` lines are server-maintained. Titles are ≤ 4 words with no dates or run numbers; a sub-category that needs more words is two sub-categories. Read a sibling before writing and match it; improve a pattern across every domain or not at all.
14
19
 
15
- 1. Server header (`sources · domain: <name>`)
16
- 2. `Last updated - <date>` (h4) — server-maintained
17
- 3. **Sources** (h2) — the ❇️/✅ legend line, then the full, complete source list: **every** source (URL, doc, file, dataset, …) listed and marked individually with just its emoji; related sources grouped under h3 subheadings
18
- 4. **Revision** (h2) — a `Source | Marker | Date` table recording each source's current marker and the date it earned it
20
+ ## The Sources note
19
21
 
20
- `remember(kind="sources", domain=…)` **merges into the Sources section** — the note is a maintained clean document, never a stack of dated addendum blocks. Pass `revision=[{source, marker, date?}]` on the same call to upsert Revision-table rows by source name — re-verifying a source replaces its existing row in place, it never grows a new one. Fall back to `revise(find=…)` surgery only for something the upsert can't express (e.g. renaming a source's row key).
22
+ Creating a domain creates the book and its Sources note together. `remember(kind="sources", domain=…)` merges into the Sources section; pass `revision=[{source, marker, date?}]` to upsert Revision rows by source name (the name must match the list exactly).
21
23
 
22
- ## 1. Creating a new domain (the domain doesn't exist yet)
24
+ ## 1. Creating a domain
23
25
 
24
26
  1. Propose the title.
25
- 2. Create the domain via any domain-scoped `remember()` — the book and its canonical Sources note are created together (the receipt carries `domainId`).
26
- 3. Run an info-query sources-discovery pass and record every candidate in the Sources note, each marked ❇️, grouped with its related sources.
27
- 4. Propose/obtain the learning scope from the user (which sources, how deep).
28
- 5. Read the approved sources and create the appropriate sub-category `information` note(s).
29
- 6. Flip the used entries' markers to ✅ and record the date in the Revision table.
30
- 7. `connect()` and wire relations (or pass `connect=` on the `remember()` calls).
27
+ 2. Any domain-scoped `remember()` creates the book and its Sources note.
28
+ 3. Discover sources and record each as ❇️, grouped.
29
+ 4. Agree the learning scope with the user.
30
+ 5. Read the approved sources; write the information notes, starting with **Current State**.
31
+ 6. Flip used sources to ✅ with their Revision dates.
32
+ 7. Wire relations (`connect=` on the `remember` calls).
31
33
 
32
- If all source candidates are rejected in step 4, **no information note is created** — an unsourced domain note corrupts the brain.
34
+ If every source is rejected, create no information note — an unsourced note corrupts the brain.
33
35
 
34
- ## 2. Adding a sub-category note to an existing domain
36
+ ## 2. Adding a sub-category
35
37
 
36
- Use when the Sources note already covers the relevant source — no new discovery needed. This path extends an already-sourced domain with another sub-category; it is not for introducing new claims.
38
+ For a source the Sources note already covers: `remember(kind="information", domain, title, body, mustCreate=true)`, then wire it.
37
39
 
38
- 1. Propose the title.
39
- 2. Create the `information` note directly (`remember(kind="information", domain=…, title=…)`).
40
- 3. `connect()` and wire relations.
41
-
42
- ## 3. Maintaining domain knowledge (periodic refresh)
40
+ ## 3. Refreshing a domain
43
41
 
44
- 1. Read the Sources note for any ❇️ discovered-but-unused entries → read those sources and create/update the appropriate sub-category `information` note(s).
45
- 2. Check the Revision table for ✅ entries last used more than a month ago → verify those sources are still available/credible; update Sources if not.
46
- 3. Skim the domain (`domain(name)`) for its sub-category notes → spot-check each against its source for continued correctness — if correct, stop; if not, re-read the source and update the note.
47
- 4. Flip markers to ✅ and record dates in the Revision table.
48
- 5. `connect()` and wire relations.
42
+ 1. Read ❇️ sources not yet used; fold what they teach into the right notes.
43
+ 2. Re-check ✅ sources older than a month; update Sources if they moved.
44
+ 3. Re-measure **Current State** against live sources; replace values in place, then run `consistency()` on anything recorded twice.
45
+ 4. Spot-check each other note for correctness and for state or history that has crept in (`maintain(deep)` flags dated prose).
46
+ 5. Update markers and Revision dates; wire relations.
49
47
 
50
48
  ## Renames
51
49
 
52
- Retitling a domain book (`revise(bookId, title=…)`) **cascades automatically**: the book's `#domain` slug and every child's are updated server-side, so `domain()` gathering never breaks on a stale slug. Hyphenated slugs (`wall-e`, `framer-templates`) resolve correctly.
53
-
54
- ## The shape of a domain's surface
55
-
56
- A domain's Knowledge surface is exactly: **one maintained Sources note, plus a small set of consolidated, current-state information notes — one per sub-category, never one per day.** It holds what's true now, not a changelog of what was true on each date it was checked.
57
-
58
- Chronological, run-by-run history belongs in Memory/Threads — a thread is the right place for "here's what Run N found"; a domain information note is the right place for "here's what's actually true about this sub-category," kept current by revision, not accumulation. If you find yourself naming a new information note after today's date or a run number, stop — that finding either updates an existing note (revise it) or doesn't belong in Domains at all.
59
-
60
- The title rule is the same here as everywhere: **four words maximum**, no dates, no run numbers. Sub-category titles are where it's most often stretched, and a title that won't fit is telling you something — a sub-category needing eight words to name is usually two sub-categories, so split the content rather than the title. Merge behaviour follows from the same place: Sources and information notes are maintained current-state documents, so new findings fold into the relevant section (`section=`, `find=`) and never arrive as a dated addendum block.
61
-
62
- ## Standing briefs vs current-state facts — the mandate marker
63
-
64
- A domain holds two things that read alike and behave differently: a **standing brief** (instructions a future session must follow — the operating mandate of a venture) and a **current-state fact** (what's true right now). Both are `kind="information"`, and until now telling them apart meant reading each note's prose. A scoped autonomous session that needs "the thing I must obey" should not have to skim every sub-category note to find it.
65
-
66
- Mark a brief with `remember(kind="information", domain=…, title=…, body=…, mandate=true)` — it adds a `#mandate` flag, surfaced as `mandate: true` in `domain()` group rows and in `knowledge_recall` stubs. A session looking for its brief reads the domain and the mandate-flagged note is the one carrying instructions rather than facts. `label(noteId, "mandate")` / `label(noteId, "mandate", remove=true)` sets or clears the marker on an existing note. One note per mandate, titled by the brief it is ("Founder Brief", "Operating Mandate") — the four-word rule still applies.
50
+ Retitling a domain book cascades its `#domain` slug to every child automatically.
@@ -36,7 +36,8 @@ Written by the server — you never set `#noteType`, `#status`, `#created`, `#up
36
36
 
37
37
  | Label | Values | Purpose |
38
38
  |---|---|---|
39
- | `#noteType` | `biography` `goals` `preferences` `responsibilities` `protocols` `selfcorrection` `diary` `session` `thread` `threadEntry` `user` `domain` `information` `sources` `claim` `log` | Kind — exactly one per note, set at creation and immutable once present; a missing kind may be repaired explicitly. `threadEntry` is never created directly — only as a thread's day-child; `claim` is managed by `claim()` |
39
+ | `#noteType` | `biography` `goals` `preferences` `responsibilities` `protocols` `selfcorrection` `diary` `session` `thread` `threadEntry` `user` `domain` `information` `sources` `claim` `log` | Kind — exactly one per note, set at creation and immutable once present; a missing kind may be repaired explicitly. `threadEntry` is a dated thread's day-child (created by appending) or a collection thread's titled entry (`remember(kind="threadEntry", thread=)`); `claim` is managed by `claim()` |
40
+ | `#threadShape` | `collection` | On a thread book: children are titled, maintained entries rather than dated days. Absent = dated |
40
41
  | `#status` | `active` `dormant` `resolved` `superseded` `eternal` | Lifecycle state — threads age; `resolve()` sets terminal. `eternal` = lifecycle-exempt, never aged by the sweep — for user-curated permanent threads |
41
42
  | `#created` | ISO date | Set at creation (the user's local day) |
42
43
  | `#updated` | ISO date | Updated on every write |
@@ -47,6 +48,6 @@ Written by the server — you never set `#noteType`, `#status`, `#created`, `#up
47
48
  | `#mandate` | (flag) | Information notes only — marks a **standing brief** (instructions a future session must follow) as distinct from current-state fact. Set via `remember(…, mandate=true)` or `label(id, "mandate")`; surfaced in `domain()` and `knowledge_recall`. See `references/domains.md` |
48
49
  | `#reviewed` | content blob id | Set by `maintain(ack=[…])` — this note's current content was reviewed and its maintenance findings accepted. Suppresses them until the body actually changes, then all of them return |
49
50
  | `#brainLlmRoot` | (flag) | Marks the brain root — used by auto-discovery |
50
- | `#iconClass` | `bx …` | Display icon — set via `icon=` on `remember`/`revise`/`diary`/`close` (class or bare name, normalized) |
51
+ | `#iconClass` | `bx …` | Display icon — required on every note except logs. Kind default at creation (entries inherit their thread's), sweep backfills; choose one with `icon=` on `remember`/`revise`/`diary`/`close` |
51
52
 
52
53
  **Searching by label:** `recall()` accepts `kinds=[]`, `domain=`, `includeArchived=`. Raw queries via `search_notes()`: `#status=active`, `#noteType=thread`, `#topic=infra`, `#archived` (presence), `note.dateModified < 'YYYY-MM-DD'`. Combine with `AND`/`OR` (space = AND).
@@ -28,14 +28,14 @@
28
28
  |---|---|
29
29
  | BrainLLM tools time out or return connection errors | The backend is unreachable, not the tool — every ETAPI call is bounded at 30 s with one retry on idempotent reads, so a hang surfaces as a failed tool call rather than a stuck session. Check the instance `TRILIUM_BASE_URL` names: a hosted deploy should answer at its own URL, a local desktop install needs the Trilium app running. Then retry. |
30
30
  | `start` → `uninitialized` | `bootstrap` |
31
- | Deep maintenance flags the same items every session | Act on them — `connect()` orphans, `revise()`/`resolve()` stale notes — or `maintain(ack=[…])` the ones that are correct as they stand. Don't just keep ignoring them: a list you skim is a list where the item that *did* change gets missed |
31
+ | Deep maintenance flags the same items every session | Act on them, or `maintain(ack=[…])` the ones you reviewed and found correct |
32
32
  | `revise(find=)` returns `replaced: 0` | Read the hint — it names the cause. Escaped search string (`&lt;h3&gt;` against stored real tags): pass the tag literally. Spans a block boundary (`</h3>` then `<ol>`): anchor inside one element, or use `section=` with `mode="before"/"after"`. Otherwise it was already replaced, or the text genuinely differs — check with `inspect(noteId, find="<shorter substring>")` |
33
33
  | `revise(section=)` returns `matched: false` | No heading matched, so a NEW section was written — at the note's own level, with the note's real headings in `available[]`. Re-target from that list; `outline(noteId)` gives the same tree plus levels and occurrence indices before you write |
34
34
  | Two sections legitimately share a heading | `revise(section=…, occurrence=2)`. Repeating a closing heading under every category is the consistency rule working, not drift — `occurrence=` is how you reach the second one without rewriting the whole section |
35
- | Note body looks like literal `&lt;p&gt;` text in Trilium | It was written entity-encoded before the decode existed. New writes are fixed; repairing an existing note is a deliberate `revise(mode="replace")` with decoded content — nothing decodes it for you as a side effect |
35
+ | Note body shows literal `&lt;p&gt;` text in Trilium | Entity-corrupted: `maintain(repair=[noteId])` unwinds one level in place |
36
36
  | Unsure whether a note is still structurally sound after several edits | `outline(noteId)` — duplicate headings, unbalanced tags and size in one call, no body read. `maintain(deep=true)` runs the same check across the brain |
37
37
  | `recall` returns odd results | It already filters untyped notes; if it persists, `maintain(deep=true)` then retry |
38
- | Deep maintenance flags something you have never seen before | V10 added hygiene passes: `entity-corrupted`, `dated title`, `long title`, `stub`, `revision bloat`, `consolidate`, `unverified sources`. Each names its own fix in the flag text — read it rather than guessing. |
38
+ | Deep maintenance flags something unfamiliar | Every finding names its own fix in the flag text — read it rather than guessing |
39
39
  | A session was interrupted and you are resuming it | The pre-close gate is durable (a `#gate` label on today's session note), so steps you already ran still count. Re-run only what `close()` says is missing. |
40
40
  | `start()` did not give you the singleton content you expected | By design — singletons arrive as section headings. Pull one with `master(which)`/`llm(which)`, or re-run `start(depth="full")` if the session genuinely needs the whole self-model. |
41
41
  | A keyword search should have matched and did not | Try `recall(query, fuzzy=true)` for typo tolerance, or `recall(regex="…")` when the question is structural (a pattern in the stored HTML) rather than lexical. |
@@ -46,3 +46,5 @@
46
46
  | Config IDs stale after restructuring in Trilium | `bootstrap` re-discovers and rewrites `brainllm.json` |
47
47
  | `bootstrap` created a duplicate brain tree | Caused by a transient network/auth error during the existence check — the old catch-all fell through to fresh create. Fixed: only a confirmed 404 (root deleted) now triggers a new tree. To recover: identify the newer duplicate by `dateCreated`, then `forget(rootId, hard=true)` its entire subtree or delete it directly in Trilium. |
48
48
  | Hosted deploy: `ENOENT` on startup / auto-discovery fails with a file-path error | `BRAINLLM_CONFIG` is set to a directory path instead of a file path (e.g. `/home/node/trilium-data` instead of `/vol/brainllm.json`). Mount a persistent volume on the BrainLLM MCP service (not the Trilium service) and set `BRAINLLM_CONFIG` to a file path inside it, or remove `BRAINLLM_CONFIG` entirely to rely on auto-discovery each startup. |
49
+ | `revise`/`remember` on a thread returns `collection_thread` | It is a collection thread: add entries with `remember(kind="threadEntry", thread=, title=)`, edit them with `revise(<entry id>)` |
50
+ | `content is not standard RFC 4648 base64` on a text note | Fixed in V12.5: a blank MIME type is now text. On an older server, rewrite the note with `update_note_content(mime="text/html", encoding="text")` |