Knowledge, notes, memory and skills, and a harness to make them used
Four places a model can reach for, differing in who writes a record and how it gets in front of the model. **Knowledge** is uploaded by a person and searched by the model. It goes through `services/files.py:prepare` — the same pipeline as a chat attachment — so the same PDF produces the same text whichever way it arrived, and `Document` carries the same content columns as `Attachment` for the same reason. **Notes** are written by the model and edited by you. Too long to inject, so they are searched. **Memory** is short facts, and every one of them goes into every request. That single decision is where the rest of its design comes from: records are capped short, the block has a budget, there is no search tool because the model is already looking at them, and they are not shareable — a record about a person is not content to hand round. **Skills** are saved procedures. Only the name and description are injected; the body is fetched when the model decides one applies, which is what makes a hundred skills affordable. A model may write and revise its own — the safety story is not a gate but a record: every revision is kept, attributed and revertible. A model that has just read a hostile page can save a skill that outlives the conversation, and the honest mitigation is that it is visible and undoable rather than that it was prevented. **The harness** is why any of it gets used. A model handed a tools array ignores it and answers from recall, because nothing in the request suggests otherwise. `services/harness.py` assembles a preamble from what this chat actually has: when to reach for each tool, the memories, the skill index. This is an exception to "system prompts are precedence, not concatenation", and a deliberate one. That rule governs the three *authored* layers and is untouched — exactly one still wins. The harness is a different axis: it describes the machinery rather than the behaviour, nobody authored it, and there is nothing for it to disagree with. It is prepended to whichever authored prompt won, in one system message, since several endpoints reject a second. Supporting changes: - **Sharing**, in one helper. `visible_to()` is the only definition of who can see a library item and every listing and tool goes through it. Sharing grants *reading*; two people editing one note with no history and no merge is worse than copying it. **Administrators do not bypass this** — they bypass permissions elsewhere because an admin can grant themselves those anyway, but reading somebody's private notes is a different act. - **FTS5**, created by `db/migrations.py:ensure_fts` with the triggers an external-content index needs. Idempotent, like the column sync beside it. Terms are ANDed and then ORed: the caller is usually a model writing a whole question, and requiring every word loses the match on one absent term. - **The attach button is a menu** — file, image, a web page, or a document from the library. Attaching a document copies it, because history must not change when a document is edited later. - **A URL fetcher with an SSRF guard.** This server can reach the router, the other services on the box and LLeMbas itself, and the address can come from a model. Private ranges are refused *after resolution* and redirects are followed by hand so every hop is checked. An admin can open it deliberately. - **Model capabilities split** into protocol support and a toggle per built-in tool. Rows predating the split have no `tool_*` keys, and absent counts as on when `tools` is on — otherwise an upgrade silently takes web search away from every model already configured for it. Also fixes the test fixture, which built the schema with `create_all` and so ran against a database without the FTS tables production has; it now runs `sync_schema`, the same path startup takes. 430 tests, ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,9 @@ that would be expensive to revisit. Kept current as work lands; the detail of
|
||||
*how* things work lives in [`CLAUDE.md`](CLAUDE.md).
|
||||
|
||||
**Status:** usable daily. Streaming chat, attachments, reasoning, tool calling
|
||||
with web search, speech in and out, users and groups, model administration,
|
||||
installable as an app. 338 tests, `ruff` clean.
|
||||
with web search, a knowledge library, notes, memory and skills, speech in and
|
||||
out, users and groups, model administration, installable as an app. 428 tests,
|
||||
`ruff` clean.
|
||||
|
||||
---
|
||||
|
||||
@@ -57,6 +58,25 @@ be a different project, not a refactor.
|
||||
- [x] Sources stay in the transcript; results are **not** replayed as context on
|
||||
the next turn, for the same reasons reasoning is not
|
||||
|
||||
### The library
|
||||
- [x] **Knowledge** — documents, images and saved web pages, ingested through
|
||||
the same pipeline as chat attachments, searched with SQLite FTS5
|
||||
- [x] **Notes** — longer things the model writes down and searches later;
|
||||
editable by hand, because they are yours
|
||||
- [x] **Memory** — short facts, injected on every turn to a budget rather than
|
||||
searched, and managed in your settings
|
||||
- [x] **Skills** — saved procedures. Only the name and description are injected;
|
||||
the body is fetched when the model decides it applies
|
||||
- [x] A model may write and revise its own notes, memories and skills. Every
|
||||
skill revision is kept, attributed and revertible — the safety story is a
|
||||
record and a way back, not a gate
|
||||
- [x] **Sharing** — any of the three can be shared with a group or with named
|
||||
people, read-only. One visibility rule, and administrators do not bypass it
|
||||
- [x] **The harness** — an operational prompt assembled from what a model
|
||||
actually has, so the tools get used rather than ignored
|
||||
- [x] Attach menu: file, image, a web page fetched on the spot, or a document
|
||||
from the library
|
||||
|
||||
### Audio
|
||||
- [x] **Dictation** — record in the composer, transcribed by any OpenAI-shaped
|
||||
`/v1/audio/transcriptions` endpoint. The recording never touches disk
|
||||
@@ -114,10 +134,11 @@ be a different project, not a refactor.
|
||||
In the order they are likely to be worth doing.
|
||||
|
||||
### Custom tools and MCP servers
|
||||
An MCP client managing configured servers, their tools surfaced alongside
|
||||
`web_search`. The loop they plug into exists now — `services/tools.py` is a
|
||||
registry and `services/generation.py` already runs bounded rounds — so this is
|
||||
a client and an admin screen rather than a change to how chat works.
|
||||
An MCP client managing configured servers, their tools surfaced alongside the
|
||||
built-in ones. The loop they plug into exists now — `services/tools.py` is a
|
||||
registry of thirteen tools and `services/generation.py` already runs bounded
|
||||
rounds — so this is a client and an admin screen rather than a change to how
|
||||
chat works.
|
||||
|
||||
### Agentic execution
|
||||
Two modes, as originally specified:
|
||||
@@ -137,6 +158,8 @@ already running on this machine and is the obvious first target.
|
||||
- **Conversation branching** — `Message.parent_id` exists unused; needs a UI for
|
||||
choosing between versions, which is why rewind truncates for now
|
||||
- **Chat export** (Markdown, JSON)
|
||||
- **Semantic search** in the library — the retrieval service is one call, so an
|
||||
embedding backend can go behind it without touching the tools or the UI
|
||||
- **Archived chats** — the column exists, nothing surfaces it
|
||||
- **Per-user quotas**
|
||||
|
||||
@@ -172,6 +195,15 @@ microphone is unavailable for the same reason.
|
||||
administrator's assertion, not something endpoints reliably advertise. Set it on
|
||||
a model that cannot, and its replies fail rather than degrade.
|
||||
|
||||
**Library search is keyword, not semantic.** FTS5 ranks well and needs no
|
||||
dependency or embedding endpoint, but "how do I get paid" will not find a
|
||||
document that says "invoicing".
|
||||
|
||||
**A model can write its own skills, and they take effect at once.** Marked as
|
||||
model-authored and fully revertible, but a model that has just read a hostile
|
||||
page could save a skill that outlives the conversation. The mitigation is that
|
||||
it is visible and undoable, not that it was prevented.
|
||||
|
||||
---
|
||||
|
||||
## Deliberate decisions
|
||||
@@ -191,6 +223,15 @@ Recorded because each looks like an oversight until you know the reason.
|
||||
- **Images only reach models marked `vision`.** Not graceful degradation: most
|
||||
endpoints reject the entire request rather than ignoring an image part. Tools
|
||||
are gated the same way, for the same reason.
|
||||
- **Sharing grants reading, never writing.** Two people editing one note with no
|
||||
history and no merge is worse than the inconvenience of copying it.
|
||||
- **Memory is never shareable.** A record about a person is not content to hand
|
||||
round.
|
||||
- **Knowledge attached to a message is copied, not referenced.** History must not
|
||||
change under a conversation because a document was edited later.
|
||||
- **The harness is prepended to the authored prompt, not a fourth layer.** It
|
||||
describes the machinery; the authored layers describe the behaviour. Only one
|
||||
authored layer still wins.
|
||||
- **Tool results are not replayed.** Like reasoning: the answer already contains
|
||||
what the model made of them, and replaying stale results into every later
|
||||
request wastes the window and sends small models into search loops.
|
||||
|
||||
Reference in New Issue
Block a user