brainllm 10.1.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/.env.example +53 -0
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/config.example.json +41 -0
- package/dist/index.js +25858 -0
- package/manifest.json +72 -0
- package/package.json +66 -0
- package/public/BrainLLM.png +0 -0
- package/public/BrainLLM.svg +41 -0
- package/public/icon-128.png +0 -0
- package/public/icon-180.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon-64.png +0 -0
- package/server.json +69 -0
- package/skills/brainllm/SKILL.md +336 -0
- package/skills/brainllm/references/connections.md +31 -0
- package/skills/brainllm/references/domains.md +60 -0
- package/skills/brainllm/references/fullmode.md +108 -0
- package/skills/brainllm/references/taxonomy.md +51 -0
- package/skills/brainllm/references/troubleshooting.md +48 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Taxonomy Reference
|
|
2
|
+
|
|
3
|
+
## Relation Vocabulary
|
|
4
|
+
|
|
5
|
+
Closed — `connect()` rejects anything not on this list. Pick the most specific verb that's true; `relatesTo` is the last resort.
|
|
6
|
+
|
|
7
|
+
| Relation | Direction | Use when |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `relatesTo` | A → B | Generic connection — last resort when nothing more specific fits |
|
|
10
|
+
| `extends` | A → B | A builds on, elaborates, or deepens B |
|
|
11
|
+
| `contradicts` | A → B | A conflicts with or argues against B |
|
|
12
|
+
| `supports` | A → B | A provides evidence or rationale for B |
|
|
13
|
+
| `causes` | A → B | A produces or leads to B |
|
|
14
|
+
| `references` | A → B | A cites B as a source or pointer |
|
|
15
|
+
| `partOf` | A → B | A semantically belongs to B |
|
|
16
|
+
| `worksWith` | A ↔ B | Collaboration — symmetric, wired both ways automatically |
|
|
17
|
+
| `mentors` | A → B | A teaches, guides, or coaches B |
|
|
18
|
+
| `instanceOf` | A → B | A is a concrete example of concept B |
|
|
19
|
+
| `supersedes` | A → B | A replaces B (auto-wired via `supersedes=`; B is archived) |
|
|
20
|
+
| `implements` | A → B | A is the realisation or execution of concept B |
|
|
21
|
+
| `inspiredBy` | A → B | A was conceptually influenced by B |
|
|
22
|
+
| `sourceOf` | A → B | A is the origin or provenance of B |
|
|
23
|
+
| `derivedFrom` | A → B | A was synthesised from B |
|
|
24
|
+
| `corrects` | A → B | A overturns a specific **claim** B held — not the whole note |
|
|
25
|
+
|
|
26
|
+
**`corrects` vs `supersedes`.** `supersedes` replaces a note wholesale and archives the old one. `corrects` is for the far commoner case: a note stays live and correct, but one assertion inside it turned out to be false. Wire it when you rewrite a claim you now know was wrong — a spec that recorded a reference site as dark when it is light, a credential map wrong in both directions, a token declared "untinted grey" that was inferred rather than read. Revising in place leaves no trace that the old claim was ever believed, so nothing tells a later reader to be suspicious of *sibling* notes written in the same pass, by the same reasoning, on the same day. In a memory whose whole value is being trustworthy about the past, silent overwriting is the operation to trust least.
|
|
27
|
+
|
|
28
|
+
**Auto-wired — don't duplicate manually:**
|
|
29
|
+
- `new → old` via `supersedes=` on `remember()` → `supersedes`, old note archived.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Label Conventions
|
|
34
|
+
|
|
35
|
+
Written by the server — you never set `#noteType`, `#status`, `#created`, `#updated`, `#closed`, `#archived` manually through the normal write path (`remember`/`revise`/`resolve`/`withdraw`/`recover`). The one sanctioned exception is `label(noteId, name, value?, remove?)` — a guarded direct edit for fixing a stray or drifted value; `#noteType` is refused there too (it's never editable post-creation). Documented here so you can read and filter on them in `recall()` and `search_notes()`.
|
|
36
|
+
|
|
37
|
+
| Label | Values | Purpose |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `#noteType` | `biography` `goals` `preferences` `responsibilities` `protocols` `diary` `session` `thread` `threadEntry` `user` `domain` `information` `sources` `log` | Kind — exactly one per note, set at creation, never edited after. `threadEntry` is never created directly — only as a thread's day-child |
|
|
40
|
+
| `#status` | `active` `dormant` `resolved` `superseded` `eternal` | Lifecycle state — threads age; `resolve()` sets terminal. `eternal` = lifecycle-exempt, never aged by the sweep — for user-curated permanent threads |
|
|
41
|
+
| `#created` | ISO date | Set at creation (the user's local day) |
|
|
42
|
+
| `#updated` | ISO date | Updated on every write |
|
|
43
|
+
| `#closed` | ISO date | Set when `resolve()` or `forget()` archives a note |
|
|
44
|
+
| `#topic` | slugged, repeatable | Subject tags (`ai-tooling`, `infra`) — capitalization normalized server-side |
|
|
45
|
+
| `#domain` | slugged | Knowledge domain — book auto-created on first use |
|
|
46
|
+
| `#archived` | (flag) | Excludes the note from default `recall()`; content preserved in place |
|
|
47
|
+
| `#reviewed` | content blob id | Set by `maintain(ack=[…])` — this note's current content was reviewed and its maintenance findings accepted. Suppresses them until the body actually changes, then all of them return |
|
|
48
|
+
| `#brainLlmRoot` | (flag) | Marks the brain root — used by auto-discovery |
|
|
49
|
+
| `#iconClass` | `bx …` | Display icon — set via `icon=` on `remember`/`revise`/`diary`/`close` (class or bare name, normalized) |
|
|
50
|
+
|
|
51
|
+
**Searching by label:** `recall()` accepts `kinds=[]`, `domain=`, `includeArchived=`. Raw queries via `search_notes()`: `#status=active`, `#noteType=thread`, `#topic=infra`, `#archived` (presence), `note.dateModified < 'YYYY-MM-DD'`. Combine with `AND`/`OR` (space = AND).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Edge Cases & Failure Modes
|
|
2
|
+
|
|
3
|
+
| Situation | What happens / what to do |
|
|
4
|
+
|---|---|
|
|
5
|
+
| BrainLLM not initialized | `start` returns `status: "uninitialized"` → run `bootstrap` (idempotent, safe anytime) |
|
|
6
|
+
| Second `close` same day | Appends an addendum to today's session note — by design, not an error. Pass `continuing=true` so you don't re-run the whole pre-close protocol for a session whose brain writes are already done; it's verified against today's note, so it can't stand in for a first close |
|
|
7
|
+
| `remember()` says `action: "updated"` unexpectedly | A same-kind note with that title existed; content was appended there. If it was genuinely a different subject, `remember()` again with a distinguishing title |
|
|
8
|
+
| User contradicts a stored fact about themselves | `master(which)` to read it, then `revise(id, section=…)` with the correction — the Master singletons hold current-state truth, not history |
|
|
9
|
+
| A stored fact was wrong from the start | `revise(mode="replace")` — a revision snapshot is taken automatically, nothing is lost |
|
|
10
|
+
| User asks you to forget something | `forget(noteId, reason)` archives it. If they want it *gone* (privacy), `forget(noteId, hard=true)` |
|
|
11
|
+
| `forget(hard=true)` returns `blocked` | Other notes still link there. Remove the listed backlinks (`connect(..., remove=true)`) or archive instead |
|
|
12
|
+
| A relationship between notes changes | `connect(from, rel, oldTarget, remove=true)`, then `connect` the new target; note the change in the body via `revise()` |
|
|
13
|
+
| A thread's line of work concludes | `resolve(threadId, outcome)` — writes the outcome, sets the terminal status, archives in place |
|
|
14
|
+
| A dormant item becomes relevant again | Any `revise()` touch reactivates it to `active` automatically |
|
|
15
|
+
| Two notes turn out to be the same subject | `revise()` the better one with the other's content (append), then `forget(worseId, reason="merged into <id>")` |
|
|
16
|
+
| `resolve()` on a note with no Resolution section | Works — the section is appended |
|
|
17
|
+
| Structural note passed to revise/resolve/forget | Returns `{error, detail, hint}` — read `hint` and call again with a content noteId, not a container |
|
|
18
|
+
| Long conversation, no natural end | Call `close` when the work *topic* wraps, even if chat continues; a later wrap-up appends |
|
|
19
|
+
| User edited notes directly in Trilium | Fine — that's a feature. Run `maintain(deep=true)` next session to re-check the tree |
|
|
20
|
+
| Sweep flags a stray you can't classify | Tell the user what it is and where; flags are conversation starters, not auto-fixes |
|
|
21
|
+
| A task needs direct note surgery | Use the full-mode tools (`create_note`, `patch_note`, `delete_note`, `add_label`, …) — see `references/fullmode.md`. Prefer the high-level surface for routine memory. |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Troubleshooting
|
|
26
|
+
|
|
27
|
+
| Symptom | Fix |
|
|
28
|
+
|---|---|
|
|
29
|
+
| BrainLLM tools time out or return connection errors | The backend is unreachable, not the tool — every ETAPI call is bounded at 30 s with one retry on idempotent reads, so a hang surfaces as a failed tool call rather than a stuck session. Check the instance `TRILIUM_BASE_URL` names: a hosted deploy should answer at its own URL, a local desktop install needs the Trilium app running. Then retry. |
|
|
30
|
+
| `start` → `uninitialized` | `bootstrap` |
|
|
31
|
+
| Deep maintenance flags the same items every session | Act on them — `connect()` orphans, `revise()`/`resolve()` stale notes — or `maintain(ack=[…])` the ones that are correct as they stand. Don't just keep ignoring them: a list you skim is a list where the item that *did* change gets missed |
|
|
32
|
+
| `revise(find=)` returns `replaced: 0` | Read the hint — it names the cause. Escaped search string (`<h3>` against stored real tags): pass the tag literally. Spans a block boundary (`</h3>` then `<ol>`): anchor inside one element, or use `section=` with `mode="before"/"after"`. Otherwise it was already replaced, or the text genuinely differs — check with `inspect(noteId, find="<shorter substring>")` |
|
|
33
|
+
| `revise(section=)` returns `matched: false` | No heading matched, so a NEW section was written — at the note's own level, with the note's real headings in `available[]`. Re-target from that list; `outline(noteId)` gives the same tree plus levels and occurrence indices before you write |
|
|
34
|
+
| Two sections legitimately share a heading | `revise(section=…, occurrence=2)`. Repeating a closing heading under every category is the consistency rule working, not drift — `occurrence=` is how you reach the second one without rewriting the whole section |
|
|
35
|
+
| Note body looks like literal `<p>` text in Trilium | It was written entity-encoded before the decode existed. New writes are fixed; repairing an existing note is a deliberate `revise(mode="replace")` with decoded content — nothing decodes it for you as a side effect |
|
|
36
|
+
| Unsure whether a note is still structurally sound after several edits | `outline(noteId)` — duplicate headings, unbalanced tags and size in one call, no body read. `maintain(deep=true)` runs the same check across the brain |
|
|
37
|
+
| `recall` returns odd results | It already filters untyped notes; if it persists, `maintain(deep=true)` then retry |
|
|
38
|
+
| Deep maintenance flags something you have never seen before | V10 added hygiene passes: `entity-corrupted`, `dated title`, `long title`, `stub`, `revision bloat`, `consolidate`, `unverified sources`. Each names its own fix in the flag text — read it rather than guessing. |
|
|
39
|
+
| A session was interrupted and you are resuming it | The pre-close gate is durable (a `#gate` label on today's session note), so steps you already ran still count. Re-run only what `close()` says is missing. |
|
|
40
|
+
| `start()` did not give you the singleton content you expected | By design — singletons arrive as section headings. Pull one with `master(which)`/`llm(which)`, or re-run `start(depth="full")` if the session genuinely needs the whole self-model. |
|
|
41
|
+
| A keyword search should have matched and did not | Try `recall(query, fuzzy=true)` for typo tolerance, or `recall(regex="…")` when the question is structural (a pattern in the stored HTML) rather than lexical. |
|
|
42
|
+
| Items going dormant too fast / too slow | User edits `policy` in `brainllm.json` (`dormantAfterDays` / `archiveDormantAfterDays` / `staleAfterDays`) |
|
|
43
|
+
| claude.ai says "Couldn't reach the MCP server" | OAuth discovery failed — it is almost never a network problem. Check BRAINLLM_OWNER_PASSWORD is set (no password, no OAuth endpoints), and that the `resource` in /.well-known/oauth-protected-resource matches the URL you typed EXACTLY, trailing slash included. |
|
|
44
|
+
| Connected once, then every call 401s after an hour | Access tokens live 1 hour and Claude refreshes on 401. If refresh fails, the signing secret changed — BRAINLLM_CONFIG must point at a persistent volume, or a redeploy invalidates every issued token. |
|
|
45
|
+
| Need raw Trilium access (attachments, calendar, custom queries) | Full-mode tools — see `references/fullmode.md` |
|
|
46
|
+
| Config IDs stale after restructuring in Trilium | `bootstrap` re-discovers and rewrites `brainllm.json` |
|
|
47
|
+
| `bootstrap` created a duplicate brain tree | Caused by a transient network/auth error during the existence check — the old catch-all fell through to fresh create. Fixed: only a confirmed 404 (root deleted) now triggers a new tree. To recover: identify the newer duplicate by `dateCreated`, then `forget(rootId, hard=true)` its entire subtree or delete it directly in Trilium. |
|
|
48
|
+
| Hosted deploy: `ENOENT` on startup / auto-discovery fails with a file-path error | `BRAINLLM_CONFIG` is set to a directory path instead of a file path (e.g. `/home/node/trilium-data` instead of `/vol/brainllm.json`). Mount a persistent volume on the BrainLLM MCP service (not the Trilium service) and set `BRAINLLM_CONFIG` to a file path inside it, or remove `BRAINLLM_CONFIG` entirely to rely on auto-discovery each startup. |
|