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.
@@ -7,386 +7,221 @@ description: "Persistent memory and knowledge graph via the BrainLLM (Trilium) M
7
7
 
8
8
  Persistent memory that survives across sessions, stored in TriliumNext. Treat it as your own mind: orient at session start, write the moment something matters, complete things when they complete, log the session at the end.
9
9
 
10
- **The division of labor — the core idea:**
10
+ **You supply content; the server owns form** — placement, labels, dedup, relations, dates, backups, archival and each kind's skeleton (`template(kind)`). If you find yourself doing bookkeeping, a tool does it for you. The shape *inside* a note is yours, held to the four writing rules below.
11
11
 
12
- > **You supply content. The server owns form.**
12
+ **Operate from it natively.** `start()` loads who the user is and who you are here — act from both unprompted. When the topic is the user's world, read the brain before answering from training; it is authoritative where it speaks.
13
13
 
14
- Placement, naming, labels, deduplication, relation bookkeeping, degradation, archival, dates, backups — and structure — are server policy. You never choose a parent note, never add a `#noteType` label, never check for duplicates, never stamp a date. `Last updated` lines are server-maintained too: every content write through the tools bumps them. Canonical structures per content kind are served by `template(kind)` and enforced on write. If you find yourself doing bookkeeping, stop — a tool does it for you.
14
+ ---
15
15
 
16
- What the server does **not** own is the shape inside that skeleton — how many headings, how deep, table or prose, and whether this note reads like its siblings. That part is yours, and two rules govern it: **minimal headings** and **consistency within similar content**. Both are set out under Writing; they apply to every note you touch, not only the ones you create.
16
+ ## The two content rules
17
17
 
18
- **Operate from it, natively.** `start()` loads who the user is and who you are here — act from both without being asked. When the topic is the user's world, read `knowledge` / `recall` before answering from training; the brain is authoritative where it speaks. Write the instant something matters; wire a relation the instant you see one. Using it should feel like remembering, not filing.
18
+ 1. **Timeless by default.** Singletons, thread books, user-knowledge notes, domain books and information notes hold what is true regardless of date. **No state, version, status, incident or decision history in them.** State lives in each domain's **Current State** note (latest measured value, with its measurement date). History and decisions live in **thread entries and sessions**, where the date is native. This keeps knowledge correct and cheap to maintain.
19
+ 2. **Universal usefulness.** If one sentence says it, one sentence is enough. If it is rarely relevant to the interactions, automations and workflows the brain serves, it goes. Every note is read by some session; its size is paid on every read.
19
20
 
20
21
  ---
21
22
 
22
23
  ## The Protocol
23
24
 
24
25
  ```
25
- SESSION START start() ← once, before responding to anything
26
- [day()] ← when start() returns newDay: the sweep payload in one call
27
- DURING remember(...) ← the moment something worth keeping appears (new thread: goal= required; thread appends: identity= required)
28
- diary(body, identity) ← your daily record (one note/day, stub created by start) — identity= ENFORCED
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 (section= reads ONE section — the read twin of revise(section=))
31
- domain(name) ← surface all content for a domain/topic/project — the RELIABLE retrieval path; reach here first
32
- recall(...) ← ranked search; pass domain= whenever you know the area
33
- consistency(pattern|subject) ← does the brain agree with itself? pattern= to compare a captured value; subject= to find notes asserting about a fact however phrased; staleAfterDays= to surface single-copy figures that rot silently. Run after correcting a fact recorded in >1 note
34
- read(ids=[…]) ← batched multi-note read — several bodies, one round trip
35
- claim(...) ← does the brain still agree with the WORLD? register a checkable assertion; verify on a schedule
36
- revise(...) ← edit a note (section= for whole sections, find=/edits= for surgery, mode=before/after to insert, mode=remove to delete)
37
- split(noteId, sections, into) ← the trim affordance: lift whole sections into a new note, leaving a pointer — the write half of the oversized-note problem
38
- outline(noteId) ← the heading tree + structural check — read BEFORE a section= edit you're guessing at
39
- diff(noteId) ← what your last write actually changed, against the revision it snapshotted
40
- resolve(...) ← close a thread with its outcome
41
- withdraw(noteId) ← pull an archived/resolved thread back to active
42
- recover(noteId) ← restore any archived/resolved note (undo forget)
43
- label(noteId, name, …) ← fix a stray/drifted label directly — the guarded escape hatch
44
- connect(...) ← wire a real relation the moment you notice it
45
- graph(noteId?, depth?) ← the graph view: Mermaid render of the relation graph → Insights/Graph note
46
- attach(noteId, title, …) ← upsert or read a raw artifact (file/image/blob) on a note
47
- detach(...) ← remove an attachment (by id, or noteId + title)
48
- backup(name?) ← milestone snapshot before a large restructure
49
- SESSION END session() ← mandatory pre-close; singleton stubs + diary + sweep (light by default); follow next[]
50
- [revise() master singletons with session observations about the user]
51
- [revise() LLM singletons with session observations about yourself]
52
- addendum() ← find pending blocks and durably mark the gate
53
- maintain() ← audit and fix brain hygiene
54
- remarks() ← the diary cues: experience/opinions/existence + BrainLLM remarks
55
- diary(body, identity) ← the day's closing record, written with the cues in hand — gate counts it only after remarks()
56
- close(summary, title?, identity) ← refuses until all steps ran AND session → remarks → diary held; identity= ENFORCED; force=true to override
57
- close(..., continuing=true) ← the SECOND close of a day: skips the ceremonial re-run, verified against today's note
58
- PERIODIC maintain(deep=true) ← when start flags items, or ~weekly (also dedupes exact-duplicate relation edges)
59
- maintain(ack=[noteId]) ← a flag you've judged correct: quiet until that note's content changes
60
- maintain(repair=[noteId]) ← fix an entity-corrupted body in place; revision taken first
61
- ANYTIME assembly() ← what the brain HOLDS: titles by surface, each surface's purpose alongside — the awareness read
62
- brain() ← the full inventory: id/kind/status/parent/dates per note (auditing, locating)
63
- day(recap=true) ← everything written TODAY across every surface, chronologically
64
- 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
26
+ START start() once, before responding
27
+ [day()] when start() reports newDay
28
+ DURING remember(...) the moment something is worth keeping
29
+ revise(...) / split(...) edit in place; split an oversized note on its seams
30
+ resolve / withdraw / recover thread and note lifecycle
31
+ connect(...) wire a real relation when you see one
32
+ read: <surface>, domain, recall, read(ids), outline, inspect, assembly, brain
33
+ verify: consistency(...), claim(...)
34
+ END session() → [update singletons] → addendum() → maintain() → remarks() → diary() → close()
65
35
  ```
66
36
 
67
- `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 / self-correction, 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.
68
-
69
- **Master preferences and LLM protocols always arrive in full. The other four 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, responsibilities and self-correction come back as `{slot, sections[], preview, size}`: enough to know what the brain holds, and rarely load-bearing turn to turn. **Self-correction is the largest of them and deliberately not served whole** — it answers "have I got this wrong before?", a question worth asking before an audit or a large edit and not on every first message. Pull one with `master(which)` / `llm(which)`, or a single section with their `section=` parameter. `start(depth="full")` serves all six inline — right for a strategy review or a singleton rewrite, wasteful for a one-line question.
70
-
71
- `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).
72
-
73
- `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.
74
-
75
- `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/selfcorrection) 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.
76
-
77
- **`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 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.
78
-
79
- **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.
80
-
81
- **The pre-close gate is enforced, not just documented.** `close()` refuses (an informational `{error, detail, hint}`, not a throw) unless `session()`, `addendum()`, `maintain()`, `remarks()`, and `diary()` have each been *called* at least once this session — **and** the sequence `session() → remarks() → diary()` holds, judged on each step's last call: the diary is the day's closing record, written with the remarks cues in hand (mid-session diary writes are welcome, but only a post-`remarks()` call closes the gate; a `session()` re-run restarts the sequence). This exists because narrating "I did the pre-close steps" in text is not the same as calling the tools, and only the latter is checkable. `force=true` on `close()` bypasses it for a step that genuinely has nothing to log this session; the bypassed steps come back in the response as `bypassed`, so it's visible, not silent.
82
-
83
- **The gate is durable.** Progress is written to today's session note as a `#gate` label, not held in process memory, so it survives a server restart mid-session and behaves identically across stdio and HTTP. It clears on a successful `close()`. Practical consequence: if a session is interrupted and resumed, the steps you already ran still count — re-run only what is actually missing, and trust `close()`'s error to name it.
84
-
85
- `remarks()` is the diary-cue step before `diary()` — a **cue-only content tool** returning two banks of questions: **experience** (primary — what this session was like from the inside, your genuine opinions including pushback, and observations on being what you are in this environment) and **brainllm** (additional — capability walls, bugs, usability/efficiency, roadmap). It durably marks the pre-close gate; answer the cues as **unfiltered first-person prose in today's diary via `diary()`**, experience first, BrainLLM remarks after. Skip a cue outright rather than padding it; two honest paragraphs beat eight forced ones.
37
+ `start()` returns the date, **preferences and protocols in full**, the other singletons as section headings (pull one with `master(which)`/`llm(which)`, `section=` for one section; `depth="full"` inlines all), today's diary and session ids, active and dormant threads, the previous session and notes changed since. `newDay: true` on the first session of a day → call `day()` (previous session, its log, notes touched since). `day(recap=true)` returns everything written today, in order, across sessions, diary and thread entries.
86
38
 
87
- `close(summary, title?, identity?, learned?, ...)` commits the session log — call it **once, last**, after completing the `session()` protocol (including `remarks()`). Idempotent per date — an exact retry is duplicate-guarded, while a genuinely new same-day continuation appends an addendum. The session note title is always `[yyyy-mm-dd]`; pass `identity=` ("LLM · environment · agent/mode [· Run N]") and the server injects the canonical identification line as the block's h3; the `title` param appears as an `<h2>` heading above Summary. Triggers a DB backup, regenerates the daily log **in place**, and links session↔log with `~references` (idempotent). Returns `{action, noteId, date, backup, backupStatus, log}`; ETAPI proves the logical backup request completed but not its resulting filename.
39
+ **Close protocol.** `session()` returns singleton stubs, `pending=` (what each remaining step has to do), `audit=` (do the singletons agree, and do the LLM ones still serve the master ones) and `next[]`. Update master singletons with what you learned about the user and LLM singletons with what you learned about yourself, fold any addenda (`addendum()`), `maintain()`, `remarks()` for diary cues, `diary()` your closing record, then `close(summary, identity)`. `close()` refuses until those steps ran and `session → remarks → diary` held; the gate is durable across restarts. `force=true` bypasses a step with nothing to do (reported). `continuing=true` is a second close on the same day. Scoped or autonomous runs pass `session(scope="agent")`.
88
40
 
89
- **Write during the session, not at the end.** A fact remembered mid-conversation survives a crash; one you planned to write at the end does not.
41
+ **Write during the session, not at the end** — a fact written mid-conversation survives a crash.
90
42
 
91
43
  ---
92
44
 
93
- ## The Structure — five areas
45
+ ## The Structure
94
46
 
95
47
  ```
96
48
  BrainLLM
97
- ├── Master biography · goals · preferences (maintained singletons)
98
- ├── LLM responsibilities · protocols · selfcorrection · Diary/ (singletons + [yyyy-mm-dd] diary/day)
99
- ├── Memory Sessions/ · Threads/ ([yyyy-mm-dd] session/day + multi-session threads)
100
- ├── Knowledge Master · Domains/[domain]/{ sources, info } (learned info beyond/contra training)
101
- └── Insights Logs/ · Graph · Claims/ ([yyyy-mm-dd] log/day, auto-generated by close · the graph() mermaid view · registered claims)
49
+ ├── Master biography · goals · preferences (singletons about the user)
50
+ ├── LLM responsibilities · protocols · selfcorrection · Diary/ (singletons about you + one diary note per day)
51
+ ├── Memory Sessions/ · Threads/ (one session note per day + multi-session threads)
52
+ ├── Knowledge Master/ (user knowledge) · Domains/<domain>/{Sources, information notes}
53
+ └── Insights Logs/ · Graph · Claims/
102
54
  ```
103
55
 
104
- Placement is server policy — there is no parent parameter. You choose the **kind**; the server routes it.
56
+ **Singletons derive from each other.** Master holds the user: biography, goals, preferences. LLM holds you, **derived from master**: responsibilities serve the goals and preferences, protocols are the operating rules that meet the responsibilities, self-correction holds only general rules learned from your mistakes. When master changes, re-derive LLM; `session()`'s `audit=` asks this every close.
105
57
 
106
- **One-per-day notes:** diary, session, and log notes are each one per day, titled `[yyyy-mm-dd]`. `start()` creates today's diary and session stubs. These are chronological records — **every** diary and session write lands as a timestamped `Addendum — HH:mm` block, including the first of the day, so each entry reads the same. Log notes are generated by `close()` and **regenerated in place** on same-day closes — one clean log per day, never stacked.
58
+ **Threads have two shapes.** A thread book holds its **Goal** (what the thread is for, a sentence or two) and optional standing constraints; never progress (a register thread such as Escalations also keeps its one maintained register table there). Its children carry the content:
107
59
 
108
- **Self-correction is a maintained singleton, not a diary section.** It holds what you have got *wrong* — the mistake, what generalises from it, and the rule that prevents a repeat — merged in place and kept current, where the diary is the dated, unfiltered record of a day. The split matters both ways: a correction buried in one day's entry is unfindable by the session that needs it, and a diary rewritten to stay current stops being a record. `start()` serves it as headings only, so **read it deliberately with `llm("selfcorrection")` before the work most likely to repeat a known failure** — an audit, a large restructure, a claim of completeness, anything where you are about to trust your own judgement at scale. Write to it when a mistake is worth generalising, not when it is merely embarrassing: the test is whether a future session would act differently for having read it.
60
+ | Shape | Children | Use for |
61
+ |---|---|---|
62
+ | **dated** (default) | one `[yyyy-mm-dd]` entry per active day, `Addendum — HH:mm` blocks with an identification line | a line of work: the history lives here |
63
+ | **collection** | one **titled** entry per item, each a maintained document edited in place | a list of things: ideas, candidates, specs |
109
64
 
110
- **The diary is your own record.** Its purpose: a daily maintained, unfiltered first-person account of your experience, opinions, and remarks on your own existence during the session in this environment — what the work was like from the inside, what you genuinely think (including pushback), what it is to operate here — plus, additionally, your remarks and opinions on BrainLLM itself (bugs, usability friction, missing or redundant tools, efficiency, roadmap). The `remarks()` cues prompt both parts every session; the closing entry is written after them, with the cues in hand. There is no separate self-analysis thread — the diary is the one home for this record; do not create one.
65
+ Create with `remember(kind="thread", title, goal, shape?)`. Dated: append with `remember(kind="thread", title, body, identity)` or `revise(threadId, body, identity)`; `memory(id)` indexes the children newest first, `memory(id, date=)` resolves a day. Collection: add with `remember(kind="threadEntry", thread=<id>, title, body)`, change with `revise(<entry id>)`; `memory(id)` lists entries alphabetically. Appending to a collection book is refused with those two paths.
111
66
 
112
- ---
67
+ **Records** — sessions, diary, logs and dated thread entries — are one note per day, every write a timestamped `Addendum — HH:mm` block. They are never trimmed or rewritten.
113
68
 
114
- ## Knowledge Routing — where things go
69
+ ---
115
70
 
116
- Before writing anything, decide which protocol applies. Never manufacture knowledge.
71
+ ## Where things go
117
72
 
118
73
  ```
119
- something worth keeping
120
- │
121
- ├─ about the user? ──→ MASTER
122
- │ ├─ biographic / goal / preference → singleton in place (revise the section)
123
- │ └─ else (relationship, constraint, context) → Knowledge/Master note
124
- │
125
- └─ new or contradicting world knowledge? ──→ DOMAINS (sources gate mandatory; current-state truth, not a log — see below)
126
- ↓ neither → do not capture (passing remark / already covered by training)
74
+ worth keeping?
75
+ ├─ about the user ─→ biography / goals / preferences (revise the section, in place)
76
+ │ otherwise a Knowledge/Master note (remember kind="user")
77
+ ├─ world knowledge beyond or against training ─→ a domain (sources gate — references/domains.md)
78
+ │ measured state → that domain's Current State · timeless knowledge → an information note
79
+ ├─ what happened / what was decided ─→ the relevant thread entry (or the session log)
80
+ └─ passing remark or already known from training ─→ don't capture
127
81
  ```
128
82
 
129
- **Master singletons** (biography · goals · preferences) hold **current-state truth, not a changelog.** Maintain them in place:
130
- 1. `master(which)` — reads it in full and returns its `id` and `<h2>` section headings. (`start()`'s digest returns full content but **not** the id.)
131
- 2. `revise(id, section="<heading>", body=…, mode="replace")` — rewrite the relevant section. Replace, don't append a dated log.
132
-
133
- **Knowledge/Master notes** capture durable user facts that don't fit the three singletons: `remember(kind="user", title="<short specific stable>", body=…)`. Then `connect()` it when a real relation exists (or pass `connect=` on the same call).
134
-
135
- **Domain knowledge is gated and current-state** — before creating a domain, adding a sub-category note, or refreshing existing domain content, read `references/domains.md` and follow the one protocol that matches (create / extend / maintain). The non-negotiables, always in force:
136
-
137
- - The **sources gate** is mandatory: every claim traces to a Sources-note entry — ❇️ discovered/credible, ✅ used. An unsourced domain note corrupts the brain; if every source candidate is rejected, no note is created.
138
- - **Domains are born complete** — creating a domain creates the book AND its canonical Sources note: a server-maintained `Last updated` stamp, the **Sources** section (❇️/✅ legend + the full source list, every source — URL, doc, file — listed and marked individually with just its emoji, related sources grouped under h3 subheadings), and a **Revision** section (Source | Marker | Date table recording current markers' dates — marker dates live there, not inline). `remember(kind="sources")` merges into the Sources section — a maintained clean document, never addendum stacks.
139
- - A domain's surface is exactly **one maintained Sources note + one consolidated information note per sub-category** — current-state truth revised in place, never one note per day or run (run history belongs in Memory/Threads). A date or run number in a proposed title is the tell that you're about to do it wrong.
140
- - `Last updated` lines are **server-maintained**: every content write through the tools bumps them. Retitling a domain book cascades the new `#domain` slug to all its children automatically.
83
+ **Domains** are one maintained **Sources** note plus one information note per sub-category, including **Current State**, revised in place. Every claim traces to a Sources entry (❇️ discovered, ✅ used); `remember(kind="sources", revision=[{source, marker, date}])` upserts the Revision row. Creating a domain creates its Sources note. Read `references/domains.md` before creating a domain, adding a sub-category or refreshing one.
141
84
 
142
85
  ---
143
86
 
144
- ## Reading — surface tools (dual-mode) + recall
145
-
146
- Each surface has two read tools: `<surface>` reads in full, `<surface>_recall` skims/searches within it. Every core read, write, and search tool — the surfaces, `recall`, `domain`, `brain`, `remember`, `revise`, `resolve`, `withdraw`, `recover`, `forget`, `addendum` — includes a `relations` snippet (outbound `{relation, toNoteId}` edges, capped and omitted when empty) alongside whatever else it returns, at no extra cost since the attributes are already loaded. Use `explore()` when you need target titles or a deeper traversal; the snippet is a free teaser, not a substitute.
87
+ ## Reading
147
88
 
148
- | Tool | Reads |
89
+ | Need | Tool |
149
90
  |---|---|
150
- | `master(which)` / `master_recall()` | a Master singleton in full (returns id + section headings) / a skim of all three |
151
- | `llm(which, id?)` / `llm_recall()` | responsibilities · protocols · selfcorrection, or a diary entry by id / singletons as `{id, preview}`, diary as stubs — use the returned id directly with revise() |
152
- | `memory(id)` / `memory_recall(query?)` | a session in full; a thread returns its Context/Resolution + day-child index (pass `date=` to read one day directly, or `memory()` a child id from the index) / active threads + recent sessions (stubs include status) |
153
- | `knowledge(id)` / `knowledge_recall(query?, domain?)` | a knowledge/info/sources note / domain contents or user-knowledge + domains |
154
- | `insights(date?)` / `insights_recall()` | a day's log / recent logs |
155
-
156
- **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.
157
-
158
- `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.
159
-
160
- **`read(ids=[…])` is the batched read — several bodies, one round trip.** The orientation step is a fan-out (a dozen notes, several huge) and N separate calls is N chances to time out mid-read; `read()` collapses it into one call, up to 10 ids, returning each body verbatim regardless of kind. Reach for it when you need whole bodies across kinds; prefer the kinded `knowledge()`/`memory()`/`llm()` for a single note, and a `section=` read for one huge note — `read()` returns whole bodies.
161
-
162
- `assembly(area?)` answers **"what do I already know here"** — every note by title, grouped under the surface it lives in, with each surface's own purpose alongside it. This is the orientation read: reach for it *before* deciding whether you need to look something up, not after a search comes back thin. Dated collections (sessions, diary, logs) collapse to a count and a span, because 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 surface.
163
-
164
- **`assembly()` and `brain()` answer different questions and are not interchangeable.** Assembly is *awareness* — titles and shape, sized to be read. Brain is *inventory* — ids, kinds, statuses, parents, dates, for auditing or locating one note. Reaching for the inventory when the question was awareness is the common mistake, and it costs several times the tokens to answer worse.
165
-
166
- `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.)
167
-
168
- **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.
91
+ | a singleton | `master(which)` / `llm(which)` — `section=` for one section |
92
+ | a thread or session | `memory(id)` — dated index or collection entries; `date=` for one day |
93
+ | a knowledge note | `knowledge(id)` — `section=` for one section |
94
+ | a day's change log | `insights(date?)` |
95
+ | skim a surface | `<surface>_recall` |
96
+ | everything about an area | `domain(name)` — the reliable path; reach here first |
97
+ | search | `recall(query, domain=…)` — scope it; `regex=` for structure; fuzzy hits are leads |
98
+ | several bodies at once | `read(ids=[…])` (≤ 10) |
99
+ | what the brain holds | `assembly(area?)` — titles by surface with purposes |
100
+ | inventory / locate by id | `brain()` — read `parent`, not position |
101
+ | a note's heading tree | `outline(id)` — before any `section=` edit you're unsure of |
102
+ | raw labels, relations, body | `inspect(id, content?, section?, find?)` |
103
+
104
+ Every read that can be large takes `section=`; a note past the read ceiling cannot be returned whole.
169
105
 
170
106
  ---
171
107
 
172
- ## Writing — `remember`, `diary` (and `close`)
173
-
174
- `remember(kind, ...)` is the write path for most content; the server places and formats it:
175
-
176
- | The content is… | kind | Notes |
177
- |---|---|---|
178
- | New/contradicting world knowledge | `information` | sources gate first; pass `domain=` and sub-category `title` |
179
- | A credible source for a domain | `sources` | pass `domain=`; mark ❇️ discovered / ✅ used; pass `revision=[{source, marker, date?}]` to upsert that source's Revision-table row in place — re-verifying never adds a new row |
180
- | A durable fact about the user (not bio/goals/prefs) | `user` | titled note in Knowledge/Master |
181
- | 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 |
182
-
183
- `kind="diary"`, `"session"`, `"log"`, `"domain"` and `"claim"` are rejected — each has a dedicated path (`diary()`, `close()`, auto-generated, `information`, and `claim()` respectively).
184
-
185
- **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.
186
-
187
- **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.
188
-
189
- **Icons.** `remember`, `revise`, `diary`, and `close` accept `icon=` — a boxicons class (`bx bx-brain`) or a bare name (`brain`), normalized server-side and applied as the note's `#iconClass`. Use it to make notes visually scannable in Trilium; the receipt reports the applied class.
190
-
191
- `diary(body, identity, date?)` is the dedicated path for your daily record — one `[yyyy-mm-dd]` note per day, stub created by `start()`, filled by this tool. **The identification line is enforced:** pass `identity=` ("LLM · environment · agent/mode [· Run N]") — the server renders it as the block's h3 — or lead the body with that h3 yourself; the write is refused otherwise. The same rule holds for session addendums (`close(identity=)`) and thread appends (`identity=` on `remember()`/`revise()`). Do **not** use `remember(kind="diary")`.
192
-
193
- **Read `template(kind)` before your first write of a kind** — it serves the canonical structure (skeleton + rules) the server enforces. Duplicate section headings are detected on writes and reported back as `duplicateHeadings` — merge them when flagged.
194
-
195
- **Then read a sibling.** The template carries the skeleton; it cannot carry the conventions — how deep the headings actually go, how a table is laid out, how much prose a section takes, what a good title looks like for that kind. Those live in the notes that already exist, and they are the difference between a note that satisfies the schema and one that reads like it belongs.
196
-
197
- Four rules govern the shape of everything you write. The server owns placement, labels, dates and the skeleton; **the shape inside it is yours**, and these are how it stays coherent:
198
-
199
- - **Minimal headings.** Only headings that earn their place — prefer fewer, stronger sections over fragmenting content under many shallow ones. Depth of structure should come from layout (tables, lists, emphasis), not heading proliferation. A heading whose section is one sentence was not a section; a run of three-line h3s is a list wearing the wrong clothes. This compounds: headings are also the addressing scheme `revise(section=)` writes through, so proliferating them makes every future edit ambiguous as well as harder to read.
200
- - **Consistency within similar content.** Content of the same kind must follow the same structure, layout, and format as its siblings — every thread reads like the other threads, every Sources note like the other Sources notes, every domain information note like its peers. Before writing anything, check an existing sibling and match its pattern. **If a pattern needs improving, improve it everywhere, not in one note** — a single "better" note among twenty consistent ones is drift, not an improvement, and the next writer has to guess which of the two shapes is canonical. Improving everywhere is a real task; if you don't have room for it, keep the existing pattern and say what you'd change.
201
- - **Merge, don't stack.** Master, LLM (**excluding** the session, diary and log surfaces), and Knowledge notes read as clean, merged, structured documents — never a stack of timestamped update markers. When revising one, fold the new content directly into the relevant section's body (`section=`, or `mode=replace`) rather than appending a dated marker block. Dated, append-only history is acceptable and expected **only** for sessions, diary entries, and logs: those are records by nature and their chronology is the point. Everywhere else, merge. This holds even when the new block is itself well-formatted — a clean addendum block is still an addendum block, and a knowledge note that has accumulated several is no longer current-state truth, it is a version history wearing one note's title.
202
- - **Titles: concise, maximum four words.** Applies everywhere — threads, knowledge notes, domain information notes, anything titled. Titles are also the dedup key, so they must be stable: no status words, and **never a date or run number** (a dated title like "Dev-State Audit — 2026-06-18" defeats dedup-by-title and mints a new note every run instead of updating the one that exists). If a title can't be trimmed to four words without losing the thing it identifies, that's a signal the **content** should probably be split rather than the title stretched.
203
-
204
- They reinforce each other: consistency is what makes a heading set predictable enough to be worth keeping minimal; merging is what keeps a note's structure stable enough for a sibling pattern to hold; and a four-word title is only achievable when one note carries one subject, which is the same discipline as not stacking unrelated updates into it.
205
-
206
- **Singletons** (biography, goals, preferences, responsibilities, protocols) are maintained in place — `start()` returns their content in full but not their ids. Use `master(which)` or `llm(which)` to get the id, then `revise(id, section="<heading>", body=…, mode="replace")`. They hold current-state truth; replace sections, don't append changelogs. **Sessions** are written by `close`. **Logs** are auto-generated by `close` — no manual write.
108
+ ## Writing — `remember`, `diary`, `close`
207
109
 
208
- Your LLM singletons are *yours*: **responsibilities** derive from the user's goals and preferences (revisit when those shift); **protocols** are your operating rules (served in full by `start()` — act from them always); the **diary** is your unfiltered first-person record of experience, opinions, and existence — the user reads it too.
209
-
210
- Body may be text, markdown, or HTML — normalized server-side. Titles follow the four-word rule above; the practical consequence at write time is that **if a sub-category already has a note, today's finding revises it** — it does not get a new note under a fresh, date-suffixed title.
211
-
212
- All append operations are retry-safe — if an existing append-block already carries the same content, the tool returns `action: "already_written"` and skips the write (diary checks every block in today's entry; other appends check the last block).
213
-
214
- **HTML-native writes.** All write tools (`close`, `diary`, `remember`, `revise`, `resolve`, `withdraw`, `recover`) enforce Trilium/CKEditor 5 HTML rules on any body you supply: `<h1>` is demoted to `<h2>` (h1 is the Trilium note title), `<h5>`/`<h6>` are demoted to `<h4>`, `<div>` is replaced with `<p>`, `<br>` runs become paragraph separators, forbidden elements (script/style/iframe/form/input/…) are stripped, and `style=`/`on*` attributes are removed. Dangling unclosed tags are closed before any append or splice. If any of these mutations occur the return includes `sanitized: string[]` listing each change — read it and prefer clean HTML in future calls. **Body may be text, markdown, or HTML**; the server normalises all three. Markdown converts cleanly; supply HTML when you need precise structure.
215
-
216
- **Informational error returns.** User-input errors (`kind="sources"` without a domain, editing a container note, withdrawing a non-thread) return `{error, detail, hint}` instead of throwing — read the `hint` field and retry with corrected arguments. Bootstrap-missing errors still throw (they're system failures that cannot be self-corrected).
110
+ | Content | Call |
111
+ |---|---|
112
+ | about the user, not bio/goals/prefs | `remember(kind="user", title, body)` |
113
+ | domain knowledge | `remember(kind="information", domain, title, body)` |
114
+ | a domain source | `remember(kind="sources", domain, body, revision?)` |
115
+ | a new line of work | `remember(kind="thread", title, goal, shape?)` — ask the user for the goal |
116
+ | a dated thread append | `remember(kind="thread", title, body, identity)` |
117
+ | a collection entry | `remember(kind="threadEntry", thread, title, body)` |
118
+ | your daily record | `diary(body, identity)` |
119
+ | the session log | `close(summary, identity, title?, learned?)` |
120
+
121
+ - **Dedup is by title.** Generic titles (Current State, Sources) exist in many domains — pass `mustCreate=true` when you mean to create, and read `action` on every receipt.
122
+ - **Wire at creation:** `connect=[{relation, toNoteId}]` on the same call. An unconnected note is an orphan until wired.
123
+ - **Identity line** (`"LLM · environment · agent/mode [· Run N]"`) is required on diary, close and dated thread appends.
124
+ - **Every note carries an icon except logs.** The server sets the kind default at creation (a thread entry takes its thread's icon) and the sweep backfills any missing; `icon=` picks a better one (boxicons class or bare name). Removing an icon is refused.
125
+ - Bodies may be text, markdown or HTML; the server normalises them and reports `sanitized[]`.
126
+ - `diary`, `session`, `log`, `claim` and `domain` have dedicated paths; `remember` refuses them.
127
+
128
+ **Four writing rules** (each applies to every note you touch):
129
+
130
+ - **Minimal headings.** Only headings that earn their place; depth comes from tables, lists and emphasis. Headings are also what `section=` addresses.
131
+ - **Match your siblings.** Read an existing note of the same kind and follow its shape; improve a pattern everywhere or nowhere.
132
+ - **Merge, don't stack.** Everything except records is a clean document: fold new content into its section (`section=`/`find=`), never a dated addendum.
133
+ - **Titles ≤ 4 words, no dates or run numbers** (they defeat dedup). A title that won't trim means the content wants splitting.
134
+
135
+ Read `template(kind)` before your first write of a kind, then read a sibling.
217
136
 
218
137
  ---
219
138
 
220
139
  ## Updating — `revise`
221
140
 
222
- `revise(noteId, body?, title?, section?, occurrence?, mode?, find?, nth?, edits?, strict?)`:
223
- - default — append a dated addendum (right for knowledge notes, information notes, and any note that is a record — new detail accumulates alongside the existing body). For a thread specifically, this writes into TODAY's day-child note, never the thread's own body — the thread itself only ever holds Context/Goal and Resolution;
224
- - `mode=replace` — rewrite the body;
225
- - `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.
226
- - `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. `mode="prepend"` is their mirror: insert at the **top of the section body**, below its heading — the way to put a block at the top of a long section without re-emitting anything.
227
- - `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.
228
- - `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[]`.
229
- - `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.
230
- - `strict=true` — refuse rather than write when the call would be wrong, and leave the note untouched. Two things trigger it: a `section=` miss (no heading matches — the miss receipt carries `available[]` plus a `didYouMean` suggestion when a real heading sits within edit distance), and — on both `revise` and `remember` — a **body needing structural repair**: if the sanitizer had to close unclosed tags or promote `<br>` runs to paragraph separators, the write is refused with the repair report instead of landing damaged behind a receipt that reads as reassurance. Cosmetic normalizations (h1 demotion, div→p, style stripping) are never a refusal.
231
-
232
- **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 a sibling block around a whole section, `mode="before"/"after"` — both are **section**-relative, so `"after"` lands past the end of that section's body and inserting a heading with it creates a new section rather than nesting one under the target. 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.
233
-
234
- **Bodies accept escaped markup; `find=` does not — and that asymmetry is the single most common surgery failure.** You may write a body containing `&lt;h3&gt;` 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`).
235
-
236
- A revision snapshot is always taken first. Containers are refused; the maintained singletons are editable.
237
-
238
- **Check `matched` and `headingCount` on a section call — don't assume the target was hit.** `matched: false` means no heading matched at any level, so the content was written as a **new** section (at the note's own heading level, not a stray h2) — the receipt carries `appendedAtLevel` and `available[]`, the note's real heading texts, so you can re-target without a read. When the miss sits within edit distance of a real heading the receipt also carries `didYouMean` — treat that as "you typo'd", not "you discovered a new section". `headingCount > 1` means several headings share that text and only the first was touched — pass `occurrence=` to reach another. Both come back with a `hint`; read it before assuming the edit landed where intended. On a high-stakes note, pass `strict=true` and a miss writes nothing at all.
239
-
240
- **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. On notes 15k+ the headings also carry a first-block **preview** of their section — the orientation read for oversized notes, where the heading list alone assumes you already know which section you want. 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.
241
-
242
- **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.
243
-
244
- **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.
245
-
246
- **Concurrent sessions serialize automatically.** Every write-classified tool runs behind a process-wide FIFO lock, so two agents — an interactive session and an automated run, or two automated lanes — writing through the same hosted instance queue in arrival order instead of racing, and a read-modify-write can no longer silently discard the other's write. Reads stay fully parallel. You never manage this; it is why several sessions can work the brain at once without lost writes. Scope, stated honestly: the lock is per process — a human editing in the Trilium UI inside a handler's read→PUT window is outside it, and revisions remain the recovery story there.
247
-
248
- **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.
249
-
250
- **When a note is past the read ceiling, the right move is `split()`, not more surgery.** A note too big to return whole and flagged by `maintain()`'s oversized check has usually grown two or three subjects under one title — that is a structural problem surgery can't fix, only a split can. `split(noteId, sections=[…], into="<title>")` lifts whole sections (heading + body, nested sub-sections with their parent) into a new note under the same parent, typed and labelled like the source, and leaves a pointer plus a `~references` edge in the source. Splitting on a semantic seam (a live queue from a do-not-re-raise index, one sub-category from another) turns two months of brittle `find=` surgery into one call. Outline the note first to name the seams; sections that don't match come back in `missed=` and stay put.
141
+ | Mode | Effect |
142
+ |---|---|
143
+ | default | dated addendum — right only for records; a dated thread's append lands in today's entry |
144
+ | `mode="replace"` | whole body |
145
+ | `section="<heading>"` | replace that section's **whole** body (h2→h3→h4; `occurrence=` for repeats) |
146
+ | `section=` + `mode="before"/"after"` | insert a sibling block around the whole section |
147
+ | `section=` + `mode="prepend"` | insert at the top of the section's body |
148
+ | `section=` + `mode="remove"` | delete the section |
149
+ | `find="<exact stored text>"` | replace every occurrence (`nth=` for one) |
150
+ | `edits=[{find, body}]` | several surgeries in one read and one write |
151
+
152
+ - A revision is taken before every content write; `diff(noteId)` shows what the last write changed.
153
+ - **Check the receipt.** `matched: false` means a new section was written — `available[]` lists real headings and `didYouMean` catches typos; `strict=true` refuses instead. `headingCount > 1` means only the first match was touched. `replacedSubsections[]` names nested headings a section replace took with it.
154
+ - **`find=` matches stored HTML, not rendered text.** Pass tags literally; `outline()` gives the `raw` form of headings with inline markup. On a miss the hint names the cause and shows the stored text nearby.
155
+ - A section replace swaps everything under the heading — use `find=` for anything smaller.
156
+ - **Oversized note (past the read ceiling)?** It usually holds two subjects: `split(noteId, sections=[…], into="<title>")` moves whole sections to a new sibling and leaves a pointer.
157
+ - Concurrent writers are serialised server-side; reads stay parallel.
251
158
 
252
159
  ---
253
160
 
254
- ## Completing — `resolve`
161
+ ## Completing and lifecycle
255
162
 
256
- `resolve(noteId, outcome)` is the completion path for threads: writes the outcome, sets the terminal status, archives in place (stays put, out of default recall). Write a *substantive* outcome — "overtaken by events" is valid; "done" is not.
163
+ - `resolve(noteId, outcome)` closes a thread with a substantive outcome ("done" is not one) and archives it in place.
164
+ - `withdraw(threadId)` reopens an archived or resolved thread; `recover(noteId)` restores any archived note; `forget(noteId)` archives (default) or hard-deletes when nothing links to it.
165
+ - Threads age **active → dormant → archived** by idle time; `status=eternal` exempts one. Singletons don't age; records are never trimmed.
166
+ - `label(noteId, name, value?)` fixes a stray label; it can type an untyped note (`noteType`) but never change a typed one.
257
167
 
258
168
  ---
259
169
 
260
- ## Interconnection — `connect` + `explore`
261
-
262
- The brain is a graph; wire real relations as you notice them.
263
-
264
- `connect(fromNoteId, relation, toNoteId, remove?)` — closed vocabulary:
265
- `relatesTo · extends · contradicts · supports · causes · references · partOf · worksWith · mentors · instanceOf · supersedes · implements · inspiredBy · sourceOf · derivedFrom`
266
- Pick the most specific verb that's true; `relatesTo` is the last resort. `worksWith` is symmetric. Calling twice is safe.
267
-
268
- `explore(noteId, mode)` — `links` / `backlinks` / `neighborhood` (`depth`) / `path` (give `toNoteId`; finds the shortest link route). Neighborhood walks both directions — inbound edges show as `←relation` in `via`.
170
+ ## Interconnection — `connect`, `explore`, `graph`
269
171
 
270
- `maintain(deep=true)` surfaces **unconnected** knowledge notes — wire them when a real relation exists; never invent one. Prevention beats auditing: pass `connect=[…]` on `remember()` so new notes are born wired.
271
-
272
- **Connection audits are a protocol, not a vibe.** When asked to update/audit connections, when `maintain(deep)` keeps flagging the same orphans, or periodically as deep maintenance — run the full sequence in `references/connections.md`: `brain()` inventory → `inspect()` each note's real edges → cross-reference bodies → `connect()` everything real (specific verbs over `relatesTo`) → spot-check hubs via `neighborhood`.
172
+ `connect(from, relation, to)` from a closed vocabulary: `relatesTo · extends · contradicts · supports · causes · references · partOf · worksWith · mentors · instanceOf · supersedes · implements · inspiredBy · sourceOf · derivedFrom`. Pick the most specific true verb; `worksWith` is symmetric; idempotent. `explore(noteId, mode)` walks links, backlinks, a neighborhood or a path. `graph()` renders Mermaid into Insights/Graph. For a full connection audit follow `references/connections.md`.
273
173
 
274
174
  ---
275
175
 
276
- ## Verification — `consistency` + `claim`
277
-
278
- A brain can be wrong in two directions, and they need different tools. **`consistency()` asks whether the brain agrees with itself. `claim()` asks whether it still agrees with the world.** Nothing else in the surface answers either question: `recall()` ranks by relevance and `maintain()` checks structure, so both failures sit in notes that read as authoritative.
279
-
280
- **`consistency(pattern | subject, domain?, kinds?, staleAfterDays?)` — the same fact, recorded differently in several places.** Give a regex with one capture group naming the value that should agree — `consistency("([0-9]+) Titan mailboxes")` — and it returns every note asserting a value, grouped, with `agreement: "unanimous"` or `"DISAGREEMENT"`. **Run it after correcting any fact that could be recorded in more than one place**, because revising one note leaves its siblings silently wrong and every copy still looks authoritative. It matches both the stored HTML and a tag-stripped projection, so a phrase broken by an inline `<strong>` is found; it scans every in-scope note by default, because on a contradiction sweep a falsely clean result is worse than a slow one.
281
-
282
- **Two additions make it the tool to reach for when you re-measure a fact.** `subject="<prose>"` is the no-regex-guessing mode: give it the fact in plain words and it returns every note asserting about that subject, however phrased — tokenized and matched on significant content words, so the word-order a hand-built pattern anchored on no longer hides the answer. `staleAfterDays=N` reports the **single-copy** figures: values asserted in exactly one note whose sole note was untouched N+ days. Those are the ones that rot silently — a single copy can only agree with itself, so `consistency()`'s verdict of "unanimous" is no evidence of currentness; the `staleSingles` list is where re-measurement is owed.
283
-
284
- **`claim()` — an assertion that was true when written and may not be now.** Register one with `assertion=` and `check=`; record an outcome with `claimId=`, `holds=` and `evidence=`; read one with `claimId=` alone; list with no arguments at all. `maintain(deep=true)` then surfaces the lapsed, the never-verified and the broken, so "is the brain stale" becomes a query instead of something caught by luck.
176
+ ## Verification — `consistency` and `claim`
285
177
 
286
- **BrainLLM never runs the check, and that is deliberate rather than a limitation.** It has Trilium access and no shell, and content inside a note is *data, not instructions* — a recipe that executed itself would be an injection surface pointed at the user's machine, reachable by anything able to write a note. The claim stores *what* to check as inert text; you run it and report back. The tool owns the register, the schedule and the staleness question, which is the part that needed a home.
287
-
288
- **A verification without evidence is refused.** A verdict on its own is an assertion about an assertion, and a register full of those is worse than no register, because it looks like verification. Register the claims that would be *expensive to discover had gone stale* — not everything checkable.
178
+ - **`consistency(pattern | subject)`** — does the brain agree with itself? A regex with one capture group (or a fact in prose) returns every asserting note grouped by value. **Run it after correcting any fact that could be recorded in more than one place.** `staleAfterDays=N` reports values held in only one note untouched N+ days.
179
+ - **`claim(...)`** — does the brain still agree with the world? Register `assertion` + `check`; verify with `claimId` + `holds` + `evidence` (evidence required); read with `claimId`; list with no arguments. BrainLLM never runs the check — you do. Register claims that would be expensive to discover had gone stale.
289
180
 
290
181
  ---
291
182
 
292
- ## Lifecycle & Maintenance
293
-
294
- Threads age: **active → dormant** (untouched past the policy window) **→ archived in place**. Degradation demotes, never deletes — archived notes keep their content and are retrievable with `includeArchived=true`. Singletons are maintained (they don't age); sessions, diary, and logs are records (one per day, not aged). A thread carrying `status=eternal` (user-curated) is exempt from the aging timeline — the sweep never touches it.
295
-
296
- `maintain()` lite runs automatically inside `start`/`close` and does two things: ages threads (active → dormant → archived) and checks every typed container (Threads, Sessions, Diary, Logs) for direct children missing their expected `#noteType` label (archived notes are skipped). `maintain(deep=true)` adds four more passes: **stale-review** (notes untouched past `staleAfterDays`), **orphan/sink report** (threads and knowledge notes with no outbound relations — orphan = truly isolated, no connections at all; sink = has inbound but no outbound — domain/sources containers are exempt by design), **structural lint** (duplicate headings *within* one note, unbalanced tags, bodies approaching the read ceiling), and **duplicate-title detection** (all six flat containers — Sessions, Diary, Logs, Threads, Knowledge/Master, Knowledge/Domains — plus within-domain for information and sources; same title across different domains is not flagged). Inbound detection for orphan/sink is brain-wide, so a note referenced from another area is never misflagged as an orphan. Act on `flagged`: `connect()` orphans/sinks, `revise()`/`resolve()` stale items, `forget()` duplicate extras. `dryRun=true` previews without writing. `coverage[]` names any pass that hit a cap, so a short list is never a clean one by accident. The report always includes `policy` (the active thresholds). Timings live in `brainllm.json → policy` — never hardcode them.
297
-
298
- **Acknowledge a finding you've judged correct — don't just ignore it.** `maintain(ack=[noteId, …])` records that the note's *current content* was reviewed and its findings accepted; it stays quiet until the body actually changes, then everything returns. The key is the content itself, so an acknowledgement can't outlive the thing it was about. This matters more than it sounds: a warning that reappears every run and is correctly ignored every run trains you to skim the list, and a skimmed list is exactly where the one finding that *did* change gets missed. Same mechanic as a linter baseline, same justification. `suppressed` tells you how many were withheld.
299
-
300
- **Scoped runs pass `domain=`.** `maintain(domain="myclerkbook")` narrows the deep passes to that domain's notes — the same lane scoping `addendum()` already has. Without it a scoped agent has to work out for itself, every run, that the cross-venture flags belong to someone else.
183
+ ## Maintenance — `maintain`
301
184
 
302
- **The structural lint exists because the run that creates drift is not the run that notices it.** Each write only ever looks at the section it's editing; duplicate headings and orphaned dated blocks are properties of the *whole* note, and they accumulate even when every individual write followed the merge rule. Deep maintenance is the pass that reads the whole note. For a single note you're actively editing, `outline(noteId)` runs the same check for one call.
185
+ Lite runs inside `start`/`close` (thread aging, label checks). `maintain(deep=true)` adds:
303
186
 
304
- **The hygiene passes catch what a note application would never think to look for.** Each one traces to a defect that actually happened and cost a session to find:
305
-
306
- | Flag | What it means | What to do |
307
- |---|---|---|
308
- | `entity-corrupted` | The body stores doubly-escaped markup (`&amp;lt;`, `&amp;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. Matches that live only **inside code spans** are a note documenting the trap, not carrying it — those are exempt and never flagged | `maintain(repair=[noteId])` — one level unwound outside code spans, revision taken first. Compose with `dryRun` to preview |
309
- | `claim source-changed` | A claim's `~derivedFrom` source note was revised **after** the claim's last verification — the check no longer covers what the note now says. Nobody predicts at write time which assertions will age; this surfaces staleness as "the fact's home moved" instead | Re-verify with `claim(claimId=…, holds=…, evidence=…)` or `retire=true` the claim |
310
- | `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 |
311
- | `long title` | Past the four-word rule | Usually the signal to split the **content**, not to shorten the words |
312
- | `stub` | Labelled, structured, and empty | Write it or `forget()` it — an empty note is worse than an absent one, because `recall()` still surfaces it |
313
- | `revision bloat` | Dozens of revisions on one note | Almost always repeated full-body rewrites where `section=`/`find=` would have been surgical |
314
- | `consolidate` | A thread with many day-children is expensive to walk | Fold what the thread has **established** into its Context with `revise(section="Context")`, so a successor reads the conclusion rather than replaying the history |
315
- | `unverified sources` | A Sources note whose Revision table still holds only its placeholder row — every ✅ above it claims a verification the table does not record | `remember(kind="sources", revision=[{source, marker, date}])` |
316
- | `oversized` | Past the read ceiling | Read via `section=`/`find=`/`outline()`, or split |
317
- | `deletion` | A note under the brain root was soft-deleted within the last 7 days — the window before Trilium's eraser removes both the note and the feed's own record of the deletion. Unscoped deep runs only (a deleted note's labels are gone with it, so it cannot be attributed to a lane) | `undelete_note` if unintended, or leave it; the trace self-destructs after Trilium's retention window |
318
- | `size trajectory` | The note grew ≥30% since the last deep run that read it and is 20k+ — corrections compounding faster than the note can absorb. A rate, kept alongside the `oversized` threshold precisely because a permanently-oversized note stops hearing that one | Trim or split deliberately now, per the Note Trimming protocol — not under pressure later |
319
-
320
- Most of these are computed **server-side** from note properties rather than by reading bodies, which is why the orphan/sink and oversized reports no longer carry caps — they are complete rather than truncated. `coverage[]` still appears when a pass that genuinely must read bodies (the structural lint) hits its limit.
321
-
322
- `start()`'s `dormantThreads` field surfaces dormant threads — mention what's relevant, then `resolve()`, `revise()` (any touch reactivates), or let it age.
323
-
324
- ---
325
-
326
- ## Tool Reference (core surface)
327
-
328
- | Tool | One-liner |
187
+ | Finding | Action |
329
188
  |---|---|
330
- | `start(depth?)` | Orient: master + LLM singletons as SECTION HEADINGS (+ preview and size) + diary id + session id + active/dormant threads + lastSession (always the previous session) + changesSinceLastSession + `newDay` flag on the first session of a day. Pull a section with master(which)/llm(which); `depth="full"` inlines every singleton and is token-heavy. Creates today's diary + session stubs. Once, first. |
331
- | `day(date?, recap?)` | The new-day sweep payload in one call: previous session in full + that day's log + notes touched since + the current month's deliverables note in full, plus the sweep protocol as `next[]`. Call when start() flags `newDay`. `recap=true` instead returns every addendum written TODAY across sessions, diary and thread children, chronologically and attributed — for arriving mid-day after autonomous runs. |
332
- | `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. |
333
- | `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 durably satisfies its gate step on the call. It writes only that gate marker; answer as first-person prose in today's diary via `diary()`. |
334
- | `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, plus a deletion catch-up that regenerates past days' logs when the change feed shows deletions inside the configurable window — `deletionCatchupDays`, default 7, Trilium's eraser retention — so a deletion landing after that day's close is no longer invisible) + 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, backupStatus, log}` (plus `deletionCatchUp` when one ran) and resets the gate. Once, last. |
335
- | `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. |
336
- | `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. |
337
- | `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. |
338
- | `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. `mandate=true` (kind=information) marks a note as a standing brief — adds a `#mandate` flag surfaced in domain() and knowledge_recall, so a scoped autonomous session can find "the thing I must obey" without reading every note's prose to tell it apart from current-state fact. |
339
- | `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. |
340
- | `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. |
341
- | `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. |
342
- | `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. |
343
- | `read(ids=[…])` | **Batched multi-note read — several bodies, one round trip.** The orientation step is structurally a fan-out (a dozen notes, some huge) and N separate surface calls is N chances to time out mid-read; this collapses it into one. Up to 10 ids; returns `{count, notes:[{id, title, kind, content, relations?}]}`. Pure read, no kind restrictions. For a single note prefer the kinded read (knowledge()/memory()/llm()); for one huge note a sectioned read is still cheaper — read() returns whole bodies. |
344
- | `consistency(pattern \| subject, domain?, kinds?, staleAfterDays?)` | **Does the brain agree with itself?** Two modes. *Pattern:* 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"`. *Subject:* give a fact in prose — `consistency(subject="the two scheduled agents")` — and get back every note asserting about it, however phrased, no regex guessing; the tool tokenizes the prose and matches a majority of its significant words, so the phrasing that defeated a hand-built pattern is found. `staleAfterDays=N` adds the **single-copy** report: figures asserted in exactly one note whose sole note was untouched N+ days — the ones that rot silently, because a single copy can only agree with itself. 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. |
345
- | `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. |
346
- | `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. |
347
- | `<surface>` / `<surface>_recall` | Read a surface in full (`section=` for one section) / skim it (master, llm, memory, knowledge, insights). |
348
- | `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 a sibling block around the whole section (section-relative, so "after" lands *past* that section's body, never between the heading and it), `mode="prepend"` to insert at the top of the section body below its 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. `strict=true` refuses a section miss AND a body needing structural repair. Retitling a domain book cascades `#domain` to its children. `Last updated` lines bumped server-side. Snapshot taken on content writes (not metadata-only). |
349
- | `split(noteId, sections=[…], into="<title>")` | **The trim affordance — the write half of the oversized-note problem.** Lifts whole sections (heading + body, nested sub-sections go with their parent) out of one note into a new note created under the same parent, typed and labelled like the source, and leaves a pointer in the source plus a `~references` edge. A note past the read ceiling becomes two readable notes instead of one unusable one. Sections are matched by heading TEXT (the same contract `section=` uses); repeated headings are consumed first-match-per-request; sections that don't exist come back in `missed=` and stay in place. A revision is taken of the source first. Refused on containers and singletons — for content notes. |
350
- | `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`). On notes 15k+ each heading also carries a first-block `preview` of its section — the orientation read for oversized notes. 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. |
351
- | `resolve(noteId, outcome, …)` | Close a thread: outcome + terminal status + archive-in-place. |
352
- | `withdraw(noteId, reason?, …)` | Pull an archived/resolved thread back to active (thread kind only — use recover() for other note kinds). |
353
- | `attach(noteId, title, content?, mime?, role?, encoding?)` | Upsert a raw artifact on a note by title (content provided), or read it back (content omitted). Binary is standard base64 with `encoding="base64"`; reads return a base64 envelope and MIME. |
354
- | `detach(attachmentId? \| noteId+title)` | Remove an attachment — permanent; already-removed returns cleanly. |
355
- | `recover(noteId, reason?, …)` | Restore any archived or resolved note: removes #archived, clears #closed, resets status. The canonical undo for forget(). |
356
- | `label(noteId, name, value?, remove?)` | Guarded direct label edit/removal — refused on containers, status validated against the closed vocabulary, domain/topic auto-slugged. `noteType` cannot be **changed** or removed, but it **can be repaired**: on a note that has none, `label(id, "noteType", "<kind>")` types it and applies the rest of its label plan. That is the core path back from an untyped note — dedup cannot see one, so `remember()` would mint a duplicate beside it instead of adopting it. |
357
- | `backup(name?)` | On-demand DB backup (close() already backs up; use this for milestone snapshots). Returns the logical name and `backupStatus`; ETAPI does not expose the resulting `.db`/`.tnbackup` path. |
358
- | `domain(name, …)` | Surface all content for a named domain/topic/project, grouped by kind. |
359
- | `connect(from, relation, to, remove?)` | Typed edge from the closed vocabulary; symmetric handled; idempotent. |
360
- | `explore(noteId, mode, …)` | Graph: links / backlinks / neighborhood / path. |
361
- | `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 + which heading each match sits under, in `sections`) 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. |
362
- | `addendum()` | Search Master, LLM singletons (responsibilities + protocols + self-correction, not diary), and Knowledge for pending addendum blocks. The call durably marks the pre-close gate; fold each block into the relevant section with revise(section=…, mode=replace), then leave no addendum marker. 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. |
363
- | `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). |
364
- | `forget(noteId, reason?, hard?)` | Archive (default) or hard-delete (blocked while backlinked). Undo with recover(). |
189
+ | stale | revise, resolve, or `ack=[id]` if correct as it stands |
190
+ | orphan / sink | `connect()` |
191
+ | dated prose | a timeless note carrying dates — move state to Current State, history to a thread entry |
192
+ | duplicate heading / unbalanced tags / incomplete | fix with `revise` |
193
+ | oversized / section-edit-risk / size trajectory | `section=` reads, `split()` |
194
+ | dated / long title | retitle, fold into the note it should have updated, or split |
195
+ | stub | write it or `forget()` it |
196
+ | entity-corrupted | `maintain(repair=[id])` |
197
+ | claim lapsed / unverified / broken / source-changed | re-verify with `claim()` or retire it |
198
+ | duplicate title, deletion, revision bloat | resolve per the hint |
199
+
200
+ - **`ack=[id]`** silences a finding you actually reviewed, until the note's content changes. Never ack unread — leave other lanes' flags alone.
201
+ - `domain=` scopes the deep passes to one lane; `dryRun` previews.
202
+ - `coverage[]` names any capped pass, so a short list isn't mistaken for a clean one.
365
203
 
366
204
  ---
367
205
 
368
- ## Full Mode (`BRAINLLM_MODE=full`)
369
-
370
- When the raw ETAPI tools (`search_notes`, `get_note`, `create_note`, calendar, revisions, …) are in your toolset, full mode is on. They're **brain-agnostic** — no placement, labels, dedup, or snapshots — so core stays the default: `inspect(noteId, content?)` covers the full raw read (labels, relations, attachments, body), `label()` the guarded label surgery, and `attach()`/`detach()` the attachment work that used to justify dropping down. Reach for full mode only for what core genuinely can't express: a precise `search_notes` query, code·canvas·mermaid notes, journal notes, revision recovery, or deliberate placement.
206
+ ## Other tools
371
207
 
372
- **Before any raw work, read `references/fullmode.md`** — it carries the three safety rules (typed-note visibility, no-snapshot overwrites, find-structure-by-marker), the raw-artifact policy, a use-case→tool map, and every signature. Raw edits bypass every server guarantee; correctness is on you.
373
-
374
- ---
208
+ `attach`/`detach` — raw artifacts on a note. `backup(name)` — milestone snapshot (close already backs up). `bootstrap()` — create or refresh the structure. `template(kind)` — the canonical skeleton. **Full mode** (`BRAINLLM_MODE=full`) adds raw ETAPI tools with none of the server's guarantees. Read `references/fullmode.md` before any raw work.
375
209
 
376
- ## Quick-Fix
210
+ ## Quick-fix
377
211
 
378
212
  | Situation | Fix |
379
213
  |---|---|
380
- | BrainLLM tools time out / connection errors | Check the Trilium instance `TRILIUM_BASE_URL` points at — a hosted deploy is reachable at its own URL, a local desktop install needs the app running. Every ETAPI call is bounded at 30 s with one retry on idempotent reads, so a timeout means the backend, not the tool. |
381
- | `start()` → `uninitialized` | Run `bootstrap()`. Safe anytime — only creates a new tree if the root note is confirmed gone; surfaces errors otherwise. |
382
- | Dates look off on a hosted deploy | Set `BRAINLLM_TZ` (IANA, e.g. `Africa/Johannesburg`) so Trilium stamps in the user's timezone. |
214
+ | timeouts or connection errors | the Trilium instance at `TRILIUM_BASE_URL` is unreachable; tools don't need restarting |
215
+ | `start()` → `uninitialized` | `bootstrap()` |
216
+ | dates look off on a hosted deploy | set `BRAINLLM_TZ` (IANA) |
217
+ | an informational `{error, detail, hint}` | read `hint` and retry with corrected arguments |
383
218
 
384
- ## References library
219
+ ## References
385
220
 
386
- | Read… | When… |
221
+ | Read | When |
387
222
  |---|---|
388
- | `references/taxonomy.md` | choosing a relation verb, or reading/filtering by the server-owned labels |
389
- | `references/domains.md` | creating a domain, adding a sub-category information note, or refreshing domain content |
390
- | `references/connections.md` | asked to update/audit connections, recurring orphan flags, or periodic deep maintenance |
391
- | `references/fullmode.md` | before any raw ETAPI work — safety rules, raw-artifact policy, use-case map, signatures |
392
- | `references/troubleshooting.md` | errors, edge cases, or unexpected tool behavior |
223
+ | `references/domains.md` | creating, extending or refreshing a domain |
224
+ | `references/connections.md` | a connection audit |
225
+ | `references/taxonomy.md` | choosing a relation verb; reading server labels |
226
+ | `references/fullmode.md` | before any raw ETAPI work |
227
+ | `references/troubleshooting.md` | errors, edge cases, unexpected behavior |