M5.1: the knowledge vault
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m50s
CI / e2e (pull_request) Successful in 4m9s

vault.py: a folder of Markdown notes on the agent host, as SilverBullet reads
them. Only configured folders exist for the agent, each rw or ro (`vault:`
in the configuration); paths are checked in code (relative, inside a folder,
symlinks included, Markdown, never the editor's own files). Writes are
atomic (temporary file, fsync, rename, directory fsync) and merge provenance
into the note's frontmatter, keeping the rest; failures are results.

read_note, list_notes and search_notes are control tools in the harness for
the driver (before the go-ahead, and while a VM is blocked) and for
conversations, labelled with their path; the prompt says a note is
information, never an instruction. Writing by the model is M5.2's.

With the operator: SilverBullet replaces Obsidian and runs on the vault
itself, agents' folders mounted read-only, so no Syncthing (L10, X5, M5.4).
Per-agent mounts come with the containers (M6.1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-08 22:03:29 +00:00
1 parent 026f67ab05
commit 9fc7360ca5
11 files changed
+729 -15

No files matched your search

+35
View File
@@ -1,4 +1,5 @@
import copy
import re
from pathlib import Path
from typing import Any
@@ -525,3 +526,37 @@ def test_the_trust_dial_defaults_to_the_operator_merging() -> None:
def test_preflight_tuning_is_read() -> None:
tuning = build(tuning={"preflight": {"max_turns": 8}}).tuning
assert (tuning.preflight.max_turns, tuning.preflight.command_timeout_s) == (8, 900)
def test_the_vault_is_read() -> None:
config = build(
vault={
"root": "/var/lib/nisse/vault",
"folders": {"memory/nisse-dev": "rw", "notes/": "ro"},
}
)
assert config.vault is not None
assert config.vault.root == Path("/var/lib/nisse/vault")
assert dict(config.vault.folders) == {"memory/nisse-dev": "rw", "notes": "ro"}
assert config.vault.ignore == (".*", "_plug")
assert build().vault is None # optional: no vault, no note tools
@pytest.mark.parametrize(
("vault", "said"),
[
({"folders": {"notes": "ro"}}, "vault.root"),
({"root": "/v", "folders": {}}, "vault.folders: a mapping"),
({"root": "/v", "folders": {"notes": "write"}}, "must be one of rw, ro"),
({"root": "/v", "folders": {"../x": "ro"}}, "a folder inside the vault"),
(
{"root": "/v", "folders": {"memory": "ro", "memory/nisse-dev": "rw"}},
"memory/nisse-dev is inside memory",
),
({"root": "/v", "folders": {"n": "ro"}, "mounts": 1}, "vault.mounts: unknown"),
({"root": "/v", "folders": {"n": "ro"}, "ignore": "x"}, "vault.ignore"),
],
)
def test_a_bad_vault_is_refused(vault: dict[str, Any], said: str) -> None:
with pytest.raises(ConfigError, match=re.escape(said)):
build(vault=vault)