brainllm 10.1.0 → 10.4.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.
- package/README.md +4 -4
- package/dist/index.js +1088 -161
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/brainllm/SKILL.md +39 -18
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dxt_version": "0.1",
|
|
3
3
|
"name": "brainllm",
|
|
4
4
|
"display_name": "BrainLLM",
|
|
5
|
-
"version": "10.
|
|
5
|
+
"version": "10.4.0",
|
|
6
6
|
"description": "Turns TriliumNext Notes into a persistent, graph-structured second brain for Claude and other LLM clients.",
|
|
7
7
|
"long_description": "BrainLLM is an MCP server that organises memory into five purpose-built areas (Master, LLM, Memory, Knowledge, Insights). The model supplies content while the server owns placement, format, lifecycle, dates and backups. Includes a full raw ETAPI surface for advanced use. Requires a running TriliumNext instance and the Bun runtime.",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainllm",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.4.0",
|
|
4
4
|
"description": "MCP server that turns TriliumNext Notes into a persistent, graph-structured second brain for Claude and other LLM clients.",
|
|
5
5
|
"author": "Kevin Miiso Novo <miisodev@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
|
|
3
3
|
"name": "io.github.miisodev/brainllm",
|
|
4
4
|
"description": "Persistent, graph-structured memory for LLMs, stored in TriliumNext Notes. Enforced note structure, a session protocol with a durable pre-close gate, a typed knowledge graph, and a maintenance lifecycle — in a note application the human can read and edit too.",
|
|
5
|
-
"version": "10.
|
|
5
|
+
"version": "10.4.0",
|
|
6
6
|
"websiteUrl": "https://github.com/miisodev/BrainLLM#readme",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/miisodev/BrainLLM",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "brainllm",
|
|
15
|
-
"version": "10.
|
|
15
|
+
"version": "10.4.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/skills/brainllm/SKILL.md
CHANGED
|
@@ -27,12 +27,14 @@ SESSION START start() ← once, before responding to anything
|
|
|
27
27
|
DURING remember(...) ← the moment something worth keeping appears (new thread: goal= required; thread appends: identity= required)
|
|
28
28
|
diary(body, identity) ← your daily record (one note/day, stub created by start) — identity= ENFORCED
|
|
29
29
|
template(kind) ← the canonical structure for a kind — read before first write of a kind, then match a sibling's shape
|
|
30
|
-
<surface> / _recall ← read a surface in full, or skim it
|
|
30
|
+
<surface> / _recall ← read a surface in full, or skim it (section= reads ONE section — the read twin of revise(section=))
|
|
31
31
|
domain(name) ← surface all content for a domain/topic/project — the RELIABLE retrieval path; reach here first
|
|
32
32
|
recall(...) ← ranked search; pass domain= whenever you know the area
|
|
33
33
|
consistency(pattern) ← does the brain agree with itself? run after correcting a fact recorded in >1 note
|
|
34
|
-
|
|
34
|
+
claim(...) ← does the brain still agree with the WORLD? register a checkable assertion; verify on a schedule
|
|
35
|
+
revise(...) ← edit a note (section= for whole sections, find=/edits= for surgery, mode=before/after to insert, mode=remove to delete)
|
|
35
36
|
outline(noteId) ← the heading tree + structural check — read BEFORE a section= edit you're guessing at
|
|
37
|
+
diff(noteId) ← what your last write actually changed, against the revision it snapshotted
|
|
36
38
|
resolve(...) ← close a thread with its outcome
|
|
37
39
|
withdraw(noteId) ← pull an archived/resolved thread back to active
|
|
38
40
|
recover(noteId) ← restore any archived/resolved note (undo forget)
|
|
@@ -53,20 +55,24 @@ SESSION END session() ← mandatory pre-close; singleton stubs
|
|
|
53
55
|
close(..., continuing=true) ← the SECOND close of a day: skips the ceremonial re-run, verified against today's note
|
|
54
56
|
PERIODIC maintain(deep=true) ← when start flags items, or ~weekly (also dedupes exact-duplicate relation edges)
|
|
55
57
|
maintain(ack=[noteId]) ← a flag you've judged correct: quiet until that note's content changes
|
|
56
|
-
|
|
58
|
+
maintain(repair=[noteId]) ← fix an entity-corrupted body in place; revision taken first
|
|
59
|
+
ANYTIME assembly() ← what the brain HOLDS: titles by surface, each surface's purpose alongside — the awareness read
|
|
60
|
+
brain() ← the full inventory: id/kind/status/parent/dates per note (auditing, locating)
|
|
57
61
|
day(recap=true) ← everything written TODAY across every surface, chronologically
|
|
58
|
-
inspect(noteId, content?, find?) ← every label/relation/attachment
|
|
62
|
+
inspect(noteId, content?, section?, find?) ← every label/relation/attachment; content=true adds the body (section= narrows it); find= counts a literal flag and shows the nearest text on a miss
|
|
59
63
|
```
|
|
60
64
|
|
|
61
65
|
`start()` runs maintenance, creates today's diary and session stubs if they don't exist yet, then returns: **today + weekday**, the **Master digest** (biography / goals / preferences), the **LLM digest** (responsibilities / protocols, plus today's diary note with its ID in the `llm` array as `{slot:"diary", id, preview}`), **this session's note** as `{id, preview}`, **activeThreads** (with idle ages), **dormantThreads** for review, the **lastSession** summary (always the *previous* session, never today's own note), and — on the first session of a day — **`newDay: true`** with a hint to call `day()`. Don't re-derive any of this with extra calls.
|
|
62
66
|
|
|
63
|
-
**
|
|
67
|
+
**Master preferences and LLM protocols always arrive in full. The other three arrive as section headings.** Preferences carries the schedule and working style; protocols carries the rules governing the session itself — a session needs both whole to orient correctly from its *first* message, before it knows enough to decide it needed them, which is exactly when a digest costs a second round-trip. Biography, goals and responsibilities come back as `{slot, sections[], preview, size}`: enough to know what the brain holds, and rarely load-bearing turn to turn. Pull one with `master(which)` / `llm(which)`, or a single section with their `section=` parameter. `start(depth="full")` serves all five inline — right for a strategy review or a singleton rewrite, wasteful for a one-line question.
|
|
64
68
|
|
|
65
69
|
`day()` is the new-day sweep in one call: the previous session in full, that day's change log, the notes touched since, and the current month's deliverables note in full — plus a `next[]` driving the sweep (advance statuses with `revise(find=)`, present findings in the first message, grounded strictly in what the touched notes evidence).
|
|
66
70
|
|
|
67
71
|
`day(recap=true)` answers the *other* question: **what happened today, in order, across every surface.** It returns every addendum block written today — session note, diary, and every thread day-child — chronologically, each attributed to its identification line, with a count of distinct instances. `start()` is tuned for a fresh day; on a day already several instances deep, reconstructing the picture by hand costs a read per surface. Reach for it when you arrive mid-day after autonomous runs, or before a second close.
|
|
68
72
|
|
|
69
|
-
`session()` is the mandatory pre-close step — call it before `close()` when the session is wrapping. It fetches the **master singletons** (biography/goals/preferences) and **LLM singletons** (responsibilities/protocols) as `{id, lastModified, relations}` stubs (**light by default** — `start()` already served them all in full; fetch current content via `master()`/`llm()` only for the ones you intend to revise, or pass `full=true` to inline everything), today's **diary entry** as `{id, blocks, size}` (the id is all `diary()` needs — `full=true` inlines the body), and runs the **lightweight maintenance sweep**. Returns a `next[]` array covering the full end-of-session protocol: update master singletons → update LLM singletons → `addendum()` → `maintain()` → `remarks()` → `diary()` → `close()`. The goal is to evolve the singletons from this session's observations *before* the log is committed — ensuring logs are factual and singletons stay current — and to write the diary last, with the remarks cues in hand. Idempotent: all reads are safe to repeat.
|
|
73
|
+
`session()` is the mandatory pre-close step — call it before `close()` when the session is wrapping. It fetches the **master singletons** (biography/goals/preferences) and **LLM singletons** (responsibilities/protocols) as `{id, lastModified, relations}` stubs (**light by default** — `start()` already served them all in full; fetch current content via `master()`/`llm()` only for the ones you intend to revise, or pass `full=true` to inline everything), today's **diary entry** as `{id, blocks, size}` (the id is all `diary()` needs — `full=true` inlines the body), and runs the **lightweight maintenance sweep**. Returns a `next[]` array covering the full end-of-session protocol: update master singletons → update LLM singletons → **audit the singletons** → `addendum()` → `maintain()` → `remarks()` → `diary()` → `close()`. The goal is to evolve the singletons from this session's observations *before* the log is committed — ensuring logs are factual and singletons stay current — and to write the diary last, with the remarks cues in hand. Idempotent: all reads are safe to repeat.
|
|
74
|
+
|
|
75
|
+
**`session()` also returns `pending` and `audit`.** `pending` says how much each remaining step actually has to do — addendum markers outstanding, flags already raised, diary blocks written today, which singletons were written today — and `next[]` is written from those numbers rather than reciting steps with nothing behind them. `audit` is the cross-singleton check nothing else performs, in two parts: whether the five singletons **agree** with each other, and whether responsibilities and protocols still **serve** what biography, goals and preferences describe. The second is a semantic question — a protocol can be perfectly consistent and still be serving a goal that has moved — so `consistency()` and `maintain()` cannot answer it. Fix what you find *before* `close()`, so the log records a brain that already agrees with itself.
|
|
70
76
|
|
|
71
77
|
**Scoped and autonomous runs pass `session(scope="agent")`.** Steps 1–2 touch the *user's* personal singletons and belong to an interactive session; with `scope="agent"` they're omitted from `next[]` entirely and the `addendum()`/`maintain()` steps are reworded for lane-scoped work. If your brief says the personal singletons are out of scope, say so to the tool rather than working around a list that contradicts it.
|
|
72
78
|
|
|
@@ -90,7 +96,7 @@ BrainLLM
|
|
|
90
96
|
├── LLM responsibilities · protocols · Diary/ (singletons + [yyyy-mm-dd] diary note/day)
|
|
91
97
|
├── Memory Sessions/ · Threads/ ([yyyy-mm-dd] session/day + multi-session threads)
|
|
92
98
|
├── Knowledge Master · Domains/[domain]/{ sources, info } (learned info beyond/contra training)
|
|
93
|
-
└── Insights Logs/ · Graph
|
|
99
|
+
└── Insights Logs/ · Graph · Claims/ ([yyyy-mm-dd] log/day, auto-generated by close · the graph() mermaid view · registered claims)
|
|
94
100
|
```
|
|
95
101
|
|
|
96
102
|
Placement is server policy — there is no parent parameter. You choose the **kind**; the server routes it.
|
|
@@ -143,9 +149,13 @@ Each surface has two read tools: `<surface>` reads in full, `<surface>_recall` s
|
|
|
143
149
|
| `knowledge(id)` / `knowledge_recall(query?, domain?)` | a knowledge/info/sources note / domain contents or user-knowledge + domains |
|
|
144
150
|
| `insights(date?)` / `insights_recall()` | a day's log / recent logs |
|
|
145
151
|
|
|
152
|
+
**Every one of those takes `section="<heading>"` — read ONE section instead of the whole note.** It is the read twin of `revise(section=)` and goes through the same matching contract, so a heading name that reads also writes. Reach for it on anything large: a note past the read ceiling cannot be returned whole *at all*, which makes the notes carrying the most the ones you can least afford to open blind. `outline(id)` lists the headings; `occurrence=` disambiguates repeated ones; a miss returns `available[]` so you re-target instead of falling back to the full read you were avoiding.
|
|
153
|
+
|
|
146
154
|
`recall(query, kinds?, domain?, includeArchived?, regex?, fuzzy?)` searches the **whole** brain — use it when you don't know the surface, or for cross-surface lookups. A fuzzy pass runs automatically when the exact strategies come back thin; those hits are marked `fuzzy: true` and are leads to verify, not answers. `regex=` matches a real regular expression against stored bodies — the tool for structural questions keyword search cannot express.
|
|
147
155
|
|
|
148
|
-
`brain(includeArchived?)` surfaces **every content note** across all five areas — id, title, kind, status, dates — grouped by area. Use to audit the full picture or locate a note.
|
|
156
|
+
`brain(includeArchived?)` surfaces **every content note** across all five areas — id, title, kind, status, dates, **and `parent`** — grouped by area. Use to audit the full picture or locate a note. Each group is a **flat list of every descendant**, so a domain book and its information/sources children arrive interleaved and the ordering *looks* nested when it is not; read `parent`, never position. (Inferring parentage from the ordering once cost an 8,259-byte note, overwritten by a "fix" for a gap that was not there.)
|
|
157
|
+
|
|
158
|
+
**When you need to find where a claim lives inside a big note, reach for `outline()` first — not `recall()` or `consistency()`.** Those two answer "which notes mention this" and "do the notes agree"; neither tells you *which section of one note* to edit, which is the question you actually have before a surgical write. The heading tree costs one call and turns a guessed `section=` into a chosen one.
|
|
149
159
|
|
|
150
160
|
---
|
|
151
161
|
|
|
@@ -160,7 +170,9 @@ Each surface has two read tools: `<surface>` reads in full, `<surface>_recall` s
|
|
|
160
170
|
| A durable fact about the user (not bio/goals/prefs) | `user` | titled note in Knowledge/Master |
|
|
161
171
|
| A multi-session line of work | `thread` | creation REQUIRES `goal=` (query the user for it); appends REQUIRE `identity=`; `revise()` to log progress, `resolve()` to close — bodies never carry their own Resolution |
|
|
162
172
|
|
|
163
|
-
`kind="diary"`, `"session"`, `"log"`, and `"
|
|
173
|
+
`kind="diary"`, `"session"`, `"log"`, `"domain"` and `"claim"` are rejected — each has a dedicated path (`diary()`, `close()`, auto-generated, `information`, and `claim()` respectively).
|
|
174
|
+
|
|
175
|
+
**Dedup-by-title is what makes `remember()` idempotent, and on a generic title it is a loaded weapon.** *Current State*, *Sources*, *Technology Stack* and *Product and Business* each exist in four or more domains, so a call you believe is creating a note can silently **replace** one — with a clean receipt reading `action: "updated"`. **Pass `mustCreate=true` whenever you intend to create rather than add**, and it refuses instead, naming the note that already exists. Read `action` on every receipt regardless: `updated` where you expected `created` means something was overwritten.
|
|
164
176
|
|
|
165
177
|
**Wire at creation, not later.** Pass `connect=[{relation, toNoteId}, …]` on the same `remember()` call — same semantics as `connect()` (idempotent, `worksWith` wired both ways). A new information/user/thread note left unconnected is an orphan until the next `maintain(deep)` pass catches it; the tool returns an explicit `hint` when a freshly-created note has no relations.
|
|
166
178
|
|
|
@@ -203,9 +215,13 @@ All append operations are retry-safe — if an existing append-block already car
|
|
|
203
215
|
- `section="Overview"` — edit one heading section in place (tries h2 → h3 → h4, matched case- and whitespace-insensitively, tolerant of attributes on the heading tag). The efficient path for a singleton: read it, then revise the one section. Add `occurrence=N` (1-based) when several headings share that text.
|
|
204
216
|
- `section=` + `mode="before"` / `mode="after"` — insert content adjacent to a heading **without touching its section body**. Use this instead of the old idiom of matching the following heading with `find=` and re-emitting it, where a mistyped re-emission silently ate the header.
|
|
205
217
|
- `find="<exact text>"` — targeted string surgery: every occurrence of the exact raw string is replaced with `body` (raw, no markdown conversion), no read+full-replace needed. When the exact string misses, a **tolerant** pass retries ignoring CKEditor-injected tag attributes (spellcheck, data-list-item-id, …) and whitespace between elements — the receipt's `matchMode` says which pass matched. `nth=N` replaces one occurrence only. Returns `replaced` (a count; `0` with a cause-specific hint). Takes precedence over `section`/`mode`. `title=` composes with every mode.
|
|
218
|
+
- `section=` + `mode="remove"` — delete the heading and its whole section. Needs no `body=`. A miss leaves the body **untouched** rather than creating the heading you asked to delete, and reports `matched: false` with `available[]`.
|
|
219
|
+
- `find="<exact text>"` — targeted string surgery: every occurrence of the exact raw string is replaced with `body` (raw, no markdown conversion), no read+full-replace needed. When the exact string misses, a **tolerant** pass retries ignoring CKEditor-injected tag attributes (spellcheck, data-list-item-id, …) and whitespace between elements — the receipt's `matchMode` says which pass matched. `nth=N` replaces one occurrence only. Returns `replaced` (a count; `0` with a cause-specific hint). Takes precedence over `section`/`mode`. `title=` composes with every mode.
|
|
206
220
|
- `edits=[{find, body, nth?}, …]` — several surgeries in one call, applied in order against **one read and one write**. Per-edit `results[]`; an edit that matches nothing is reported without blocking the ones that did.
|
|
207
221
|
|
|
208
|
-
**Section replace is whole-section, not per-paragraph.** `section=` + `mode=replace` swaps *everything* under that heading — targeting one paragraph inside a multi-paragraph section silently wipes its siblings. For a word, phrase, or single paragraph, use `find=`; to add content beside a heading, `mode="before"/"after"`.
|
|
222
|
+
**Section replace is whole-section, not per-paragraph.** `section=` + `mode=replace` swaps *everything* under that heading — targeting one paragraph inside a multi-paragraph section silently wipes its siblings. For a word, phrase, or single paragraph, use `find=`; to add content beside a heading, `mode="before"/"after"`. When a replace *does* displace nested headings, the receipt names them in `replacedSubsections[]` — read it, because taking child sections along is correct behaviour and easy to not notice.
|
|
223
|
+
|
|
224
|
+
**Bodies accept escaped markup; `find=` does not — and that asymmetry is the single most common surgery failure.** You may write a body containing `<h3>` and it will be decoded to real markup on the way in. A `find=` string is matched against **stored HTML**, where that heading is a real `<h3>` tag, so searching for the escaped spelling matches nothing. Pass tags literally in `find=` (`"<h3>Typography</h3>"`), and take the same care with anything `outline()` printed: outline reports rendered **text**, and a heading carrying inline markup stores something different — which is why it also returns `raw` for exactly those headings. `section=` matches text (use `text`), `find=` matches storage (use `raw`).
|
|
209
225
|
|
|
210
226
|
A revision snapshot is always taken first. Containers are refused; the maintained singletons are editable.
|
|
211
227
|
|
|
@@ -213,7 +229,9 @@ A revision snapshot is always taken first. Containers are refused; the maintaine
|
|
|
213
229
|
|
|
214
230
|
**When you're guessing at a heading, call `outline(noteId)` first.** It returns the heading tree with levels and occurrence indices, plus any table's key column and a structural check, without reading the body. Guessing a section name wrong writes a new section rather than editing the one you meant — the outline turns that from a failure you discover afterwards into a choice you make up front. It's also the cheap "is this note still sound" check after a run of surgical edits.
|
|
215
231
|
|
|
216
|
-
**A `find=` miss names its own cause — read the hint rather than trying variants.**
|
|
232
|
+
**A `find=` miss names its own cause AND shows you the stored text — read the hint rather than trying variants.** Four distinct causes, each with a different fix: an **entity-escaped search string** (see the asymmetry above), a find that **spans a block boundary** (anchor inside one element, or use `section=`), a string **already consumed by an earlier edit in the same `edits=` array** (the expected result, not a failure), or the text genuinely differing. In that last case the receipt carries `matchedUpTo` — the longest fragment of your string that *is* present — and `storedNearby`, the stored text around it, so you can see how it really differs instead of guessing a variant. Whitespace between elements is never the cause; the tolerant pass handles it.
|
|
233
|
+
|
|
234
|
+
**Verify a run of surgical edits with `diff(noteId)`, not another full read.** It compares the note against the revision your last write snapshotted, so "what did that actually change" is one call. Trusting receipts alone is how a section replace that displaced four subsections went unnoticed.
|
|
217
235
|
|
|
218
236
|
**Merge, don't stack — the mechanics.** Master, LLM singletons, and Knowledge notes (including every per-domain Sources note and information note) are clean structured documents, so `revise()`'s *default* append mode is the wrong one for them: reach for `section=` or `mode=replace` to absorb the content into the body, and `find=`/`edits=` for anything smaller than a section. Default append is correct only on sessions, diary entries, logs, and thread day-children. The rule and its rationale are stated in full under Writing.
|
|
219
237
|
|
|
@@ -257,7 +275,7 @@ Threads age: **active → dormant** (untouched past the policy window) **→ arc
|
|
|
257
275
|
|
|
258
276
|
| Flag | What it means | What to do |
|
|
259
277
|
|---|---|---|
|
|
260
|
-
| `entity-corrupted` | The body stores doubly-escaped markup (`&lt;`) that renders as visible literal text. The
|
|
278
|
+
| `entity-corrupted` | The body stores doubly-escaped markup (`&lt;`, `&nbsp;`) that renders as visible literal text. The producer — a blanket escape on the markdown path — is fixed as of V10.3, but existing damage does not heal itself | `maintain(repair=[noteId])` — one level unwound, revision taken first. Compose with `dryRun` to preview |
|
|
261
279
|
| `dated title` | A date or run number in a title defeats dedup-by-title, so the note is minted fresh every run instead of updating the one that exists | `revise(title=…)` and fold the content into the note it should have updated |
|
|
262
280
|
| `long title` | Past the four-word rule | Usually the signal to split the **content**, not to shorten the words |
|
|
263
281
|
| `stub` | Labelled, structured, and empty | Write it or `forget()` it — an empty note is worse than an absent one, because `recall()` still surfaces it |
|
|
@@ -281,17 +299,20 @@ Most of these are computed **server-side** from note properties rather than by r
|
|
|
281
299
|
| `session(date?, full?, scope?)` | Mandatory pre-close step. Fetches master and LLM singletons as `{id, lastModified, relations}` stubs (light by default — start() already served them in full; fetch via master()/llm() only what you'll revise), today's diary as `{id, blocks, size}`, and runs the lite maintenance sweep. `full=true` inlines singleton content and the diary body (rarely needed). `scope="agent"` omits the user's-singleton steps for a scoped/autonomous run. Returns `next[]` driving the full end-of-session protocol, ending in `remarks()` → `diary()` → `close()`. Call before `close()`; idempotent. |
|
|
282
300
|
| `remarks()` | Cue-only diary prompt, called before `diary()` — returns two cue banks (experience: what the session was like / your opinions / your existence here; brainllm: capability walls, bugs, usability & efficiency, roadmap) and satisfies its gate step on the call. Writes nothing; answer as first-person prose in today's diary via `diary()`. |
|
|
283
301
|
| `close(summary, title?, identity, learned?, date?, backup?, continuing?, force?)` | Commit the session log ([yyyy-mm-dd] note; identity= REQUIRED — the canonical h3 identification line; title param above Summary) + backup + daily log (regenerated in place) + idempotent session↔log wiring. **Refuses unless `session()`, `addendum()`, `maintain()`, `remarks()`, and `diary()` each ran this session AND `session() → remarks() → diary()` held (last calls)** — returns `{error, detail, hint}` naming what's missing or out of order; `force=true` bypasses the gate (reported back as `bypassed`) but never the identity requirement. `continuing=true` is the SECOND close of a day: skips the ceremonial re-run, and is verified against today's note so it can't stand in for a first close. On success returns `{action, noteId, date, backup, log}` and resets the gate. Once, last. |
|
|
284
|
-
| `
|
|
302
|
+
| `assembly(area?, includeArchived?)` | **What the brain HOLDS**, shaped for awareness rather than audit: every note by title grouped under its surface, with each surface's engraved purpose alongside. Dated collections (sessions, diary, logs) collapse to a count and a date span — listing sixty `[yyyy-mm-dd]` titles is the bulk of the brain and none of its meaning. Domains nest under their book; threads group by status. `area=` zooms into one. Read this before deciding whether you need to look something up. |
|
|
303
|
+
| `brain(includeArchived?)` | Full content tree: every typed note across all five areas, grouped, with id/kind/status/parent/dates. The **inventory** — reach for it to audit or locate, and for `assembly()` when the question is what you know. |
|
|
285
304
|
| `bootstrap()` | Initialize the structure if uninitialized, or verify and refresh config if it already exists. Only creates a new tree when the stored root note is confirmed deleted in Trilium (404). Any other error (network, auth, timeout) is surfaced rather than silently creating a duplicate tree. |
|
|
286
305
|
| `remember(kind, …)` | Write a note — routed, formatted, deduped server-side. New threads REQUIRE `goal=` (queried from the user); thread appends REQUIRE `identity=`; thread bodies must not carry a Resolution. `connect=[{relation, toNoteId}]` wires relations in the same call; a new connectable note without them returns an orphan-prevention hint. Sources/information receipts include `domainId`. `icon=` sets the display icon. Rejects diary/session/log/domain — each has a dedicated path. |
|
|
287
306
|
| `diary(body, identity, icon?, date?)` | Write to today's [yyyy-mm-dd] diary — every write lands as a timestamped `Addendum — HH:mm` block, including the first of the day; identity= ENFORCED (the canonical h3 identification line). The FINAL gate step — close() counts it only when its last call came after session() and remarks(); mid-session writes welcome. |
|
|
288
307
|
| `template(kind)` | The canonical structure for a content kind: skeleton + top-to-bottom structure + the rules writes are held to. Read before the first write of a kind — then read an existing sibling for the conventions the skeleton can't encode (heading depth, table shape, title style). Template for the schema, sibling for the shape. |
|
|
289
308
|
| `graph(noteId?, depth?, includeArchived?)` | The graph view — Mermaid flowchart of the relation graph (whole brain, or a note's neighborhood), returned AND upserted into the maintained Insights/Graph mermaid note. On-demand only — reflects the brain as of the call, not auto-refreshed after later writes; a scoped call replaces the note's content, it doesn't merge. |
|
|
290
309
|
| `recall(query, …)` | BrainLLM-wide ranked search. **Scope it — pass `domain=` whenever you know the area.** Title matches score per-token with an exact-title bonus, but an unscoped query across a large brain still ranks full-text noise alongside the note you want. `orderBy` / `orderDirection` for temporal ordering; `fastSearch` for title/label-only; `regex=` for a body pattern instead of keywords; `fuzzy=` to force the typo-tolerant pass on or off. A thin or odd result is evidence about the **query**, not the brain. |
|
|
291
|
-
| `consistency(pattern, domain?, kinds?)` | **Does the brain agree with itself?** Give a regex with one capture group naming the value that should match across notes — `consistency("(\\d+) Titan mailboxes")` — and it returns every note asserting a value, grouped, with `agreement: "unanimous"` or `"DISAGREEMENT"`. This is the check nothing else performs: `recall()` ranks by relevance and `maintain()` checks structure, so a correction applied to one note leaves its siblings silently wrong. **Run it after correcting any fact that could be recorded in more than one place.**
|
|
292
|
-
|
|
|
310
|
+
| `consistency(pattern, domain?, kinds?)` | **Does the brain agree with itself?** Give a regex with one capture group naming the value that should match across notes — `consistency("(\\d+) Titan mailboxes")` — and it returns every note asserting a value, grouped, with `agreement: "unanimous"` or `"DISAGREEMENT"`. This is the check nothing else performs: `recall()` ranks by relevance and `maintain()` checks structure, so a correction applied to one note leaves its siblings silently wrong. **Run it after correcting any fact that could be recorded in more than one place.** Matched against **both** the stored HTML and a tag-stripped projection, so a phrase split by an inline `<strong>` or `<code>` is found and so is a pattern anchored on tags. Scans every in-scope note by default — Trilium's `%=` pre-filter reads a striptags'd copy and silently drops notes, and on a contradiction sweep a falsely clean result is worse than a slow one; `fast=true` opts back into it. |
|
|
311
|
+
| `claim(...)` | **Does the brain still agree with the WORLD?** `consistency()` compares notes to each other; nothing asked whether an assertion is still true of the codebase, config or live surface it describes — so a claim that quietly stopped being true stayed authoritative until something downstream broke. Register one with `assertion=` + `check=`, record an outcome with `claimId=` + `holds=` + `evidence=`, read one with `claimId=` alone, list with no arguments. **BrainLLM never executes the check** — it has no shell, and note content is data rather than instructions; you run it and report back. `maintain(deep=true)` surfaces lapsed, unverified and broken claims. |
|
|
312
|
+
| `diff(noteId, revisionId?)` | What a write actually changed: the revision snapshot against current content, defaulting to the most recent. Every content write already took a revision and nothing could read one back, so verifying surgical edits meant re-reading the whole note or trusting the receipts. Returns the revision index on every call. |
|
|
313
|
+
| `<surface>` / `<surface>_recall` | Read a surface in full (`section=` for one section) / skim it (master, llm, memory, knowledge, insights). |
|
|
293
314
|
| `revise(noteId, …)` | Append / replace / section-edit / insert / find-replace a note. Thread appends REQUIRE `identity=`. Section: h2/h3/h4 tolerant match, `occurrence=` to disambiguate, `mode="before"/"after"` to insert around a heading; returns `matched`/`headingCount`/`available[]` — check them. `find=` + `body=`: exact-string surgery with an attribute- and whitespace-tolerant fallback (`matchMode` in the receipt), `nth=` for one occurrence; `edits=[{find, body}]` for several in one read/write. Misses return a cause-specific hint. `title=` composes with every mode. Retitling a domain book cascades `#domain` to its children. `Last updated` lines bumped server-side. Snapshot taken on content writes (not metadata-only). |
|
|
294
|
-
| `outline(noteId)` | The heading tree without the body: every h2–h4 with level
|
|
315
|
+
| `outline(noteId)` | The heading tree without the body: every h2–h4 with level, occurrence index, and — for headings carrying inline markup — `raw`, their stored form (`section=` matches `text`, `find=` matches `raw`). Plus the note's section level, any table's key column, size, and structural findings (duplicate headings, unbalanced tags). Read it before a `section=` edit you're guessing at, and after a run of surgical edits to confirm the note is still sound. |
|
|
295
316
|
| `resolve(noteId, outcome, …)` | Close a thread: outcome + terminal status + archive-in-place. |
|
|
296
317
|
| `withdraw(noteId, reason?, …)` | Pull an archived/resolved thread back to active (thread kind only — use recover() for other note kinds). |
|
|
297
318
|
| `attach(noteId, title, content?, …)` | Upsert a raw artifact on a note by title (content provided), or read it back (content omitted). Binary is base64. |
|
|
@@ -302,9 +323,9 @@ Most of these are computed **server-side** from note properties rather than by r
|
|
|
302
323
|
| `domain(name, …)` | Surface all content for a named domain/topic/project, grouped by kind. |
|
|
303
324
|
| `connect(from, relation, to, remove?)` | Typed edge from the closed vocabulary; symmetric handled; idempotent. |
|
|
304
325
|
| `explore(noteId, mode, …)` | Graph: links / backlinks / neighborhood / path. |
|
|
305
|
-
| `inspect(noteId, content?, find?)` | Full raw read of one note: every label (not just noteType/status), every outbound relation, the attachment inventory (id/title/mime/role/size), type/mime/parent/child ids, dates — the raw body when content=true, and `find=` counts a literal string (total + per-addendum-block) for flag-staleness tracking. The deep-dive counterpart to explore() and the surface reads. |
|
|
326
|
+
| `inspect(noteId, content?, section?, find?)` | Full raw read of one note: every label (not just noteType/status), every outbound relation, the attachment inventory (id/title/mime/role/size), type/mime/parent/child ids, dates — the raw body when content=true, `section=` narrows `content=true` to one section, and `find=` counts a literal string (total + per-addendum-block) for flag-staleness tracking — returning the nearest present fragment and the stored text around it when the count is zero. The deep-dive counterpart to explore() and the surface reads. |
|
|
306
327
|
| `addendum()` | Search Master, LLM singletons (responsibilities + protocols, not diary), and Knowledge for pending addendum blocks. These notes must be clean and structured — fold each block into the relevant section with revise(section=…, mode=replace), then leave no addendum marker. Only sessions, diary, and logs accumulate addendum history. Scoped/autonomous agents fold only what's in their lane — leaving personal/out-of-scope addendums for the next interactive session is correct; the call itself satisfies the gate. |
|
|
307
|
-
| `maintain(deep?, dryRun?, domain?, ack?)` | Lite: thread aging + unlabeled-node check per typed container. Deep adds: unlabeled thread-children check (each thread's own day-children, one level deeper than the lite pass) + stale-review + orphan/sink report (Memory/Threads + Knowledge, brain-wide inbound detection) + structural lint + duplicate-title detection + exact-duplicate relation-edge cleanup. `ack=[noteId]` marks a note reviewed-and-correct — quiet until its content changes (`suppressed` counts what was withheld). `domain=` narrows the deep passes to one lane. `coverage[]` names any capped pass. Report includes `policy` (active thresholds). |
|
|
328
|
+
| `maintain(deep?, dryRun?, domain?, ack?)` | Lite: thread aging + unlabeled-node check per typed container. Deep adds: unlabeled thread-children check (each thread's own day-children, one level deeper than the lite pass) + stale-review + orphan/sink report (Memory/Threads + Knowledge, brain-wide inbound detection) + structural lint + duplicate-title detection + exact-duplicate relation-edge cleanup. `ack=[noteId]` marks a note reviewed-and-correct — quiet until its content changes (`suppressed` counts what was withheld). `repair=[noteId]` unwinds one level of entity corruption in place, revision first. `domain=` narrows the deep passes to one lane — and a scoped run says so in `coverage`, so a clean scoped report is never mistaken for a clean brain. An unscoped run that finds things returns `laneHint`: **leave flags that are not yours rather than acking them** — an acknowledgement asserts you read the note. `coverage[]` names any capped pass. Report includes `policy` (active thresholds). |
|
|
308
329
|
| `forget(noteId, reason?, hard?)` | Archive (default) or hard-delete (blocked while backlinked). Undo with recover(). |
|
|
309
330
|
|
|
310
331
|
---
|