From b8ba6835950537bc6fd6814270866606f147dddb Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Tue, 8 Sep 2026 19:20:43 +0000 Subject: [PATCH 1/2] Add A17, the knowledge store contract (Outline) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part D said what Outline is for but never how to build against it — the only external integration with no A-series component, while Transport, LLM, Browser, Secrets, SSO and Logging all have one. A17 supplies the contract. Settled here: - Sharing is read access, never write access. Every document has exactly one writer, so concurrent writers never exist and Part D's last-write-wins objection disposes of itself. Default shared membership is operator + nisse-ops; nisse-dev gets private space only, since B3 seals it from the LAN and a shared network map is a map of what it is sealed from - Agent-owned knowledge is always a cache, never authority: rely on it to know what exists and where to look, verify a specific fact against the live system before acting on it - Enforcement is structural, because a prompt is not enforcement. A model cannot be stopped from believing what it reads, so the enforceable goal is narrower — an unverified belief must not become an irreversible action — and four mechanisms carry it, leaning hardest on surfacing provenance at the human gate that C2 already requires - Never an instruction channel and never an approval channel, stated both here and in A4 where approvals are specified - Corrections flow through transport rather than direct edits, which is better provenance: a hand edit is invisible to the agent's record, an instruction is authenticated, logged, and reconcilable. Break-glass reuses Part F's idle flag - Retrieved content is snapshotted into the canonical log, since A10 forbids a mutable provider handle being the only reference — with the A3 consequence that boot must re-fetch snapshots for resumed tasks - Outage posture is asymmetric: write failures never block, but a failed read of a procedure bound to a confirm-first action gates, because proceeding is acting unconstrained on exactly the operation someone wrote a procedure for Co-Authored-By: Claude Opus 5 --- docs/Architecture-Build-Spec.md | 120 +++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/docs/Architecture-Build-Spec.md b/docs/Architecture-Build-Spec.md index 597cf62..a0d18e3 100644 --- a/docs/Architecture-Build-Spec.md +++ b/docs/Architecture-Build-Spec.md @@ -175,7 +175,9 @@ Requirements: task. The agent has no path to approve itself. - **Approval sources.** A gate waits on an approval event from any registered **`ApprovalSource`** — the Matrix transport (A1), the Gitea comment poller (B2), or - the Vikunja comment stream (C4). First valid approval wins and is consumed; the + the Vikunja comment stream (C4). **The knowledge store (A17) is never an approval + source**, and never an instruction channel: a wiki page has neither the identity + guarantees nor the revision binding an approval requires. First valid approval wins and is consumed; the gate records which channel satisfied it, and the orchestrator echoes the outcome to the other channels so the records converge. Each source authenticates the approver against a per-channel operator allowlist (Matrix MXID, Gitea username, Vikunja @@ -507,6 +509,117 @@ additional cost is marking outcomes as good or bad. Without this, "best model this week" is decided by benchmark chatter, and swapping the frontier tier becomes something to avoid because the effect is unmeasurable. With it, a new model is a config change and a re-run. +### A17. Knowledge Store (Outline) + +**Purpose:** durable knowledge that outlives a single task — an agent-maintained +**world model** (what infrastructure exists, how it is arranged) and, later, +operator-authored **procedures**. Distinct from A2, which holds *task* state: A2 +is the agent's memory of what it is doing, A17 is its memory of what the world +looks like. Neither is a fallback for the other. + +Part D describes the service; this defines the contract. + +**Layout — sharing is read access, never write access.** +- Every document has **exactly one writer**: one agent, or the operator. Sharing + governs who may *read*. Concurrent writers therefore never exist, which + disposes of the last-write-wins objection Part D raises, without needing + distributed-write machinery. +- **Agent-private** space per agent: its own world model. +- **Shared** space: readable by the operator and designated agents. Default + membership is **operator + `nisse-ops`**. `nisse-dev` gets private space only — + B3 seals it from RFC1918 precisely so it cannot reach the NAS, Proxmox, or the + UDM, and a shared network-architecture document is a map of exactly those + things. Cross-agent read access is justified per collection, never assumed. +- The dev agent's project conventions live in the **repo it is working on**, not + here: versioned with the code, reviewed in the PR, and impossible to + desynchronise from the project. + +**Agent-owned knowledge is always a cache, never authority.** An agent may rely on +it to know *what exists and where to look*; it must confirm a specific fact +against the live system *before acting on it*. Hard requirement for anything in +C2's confirm-first tier; advisory for read-only and reversible actions. + +**Enforcement is structural, because a prompt is not enforcement.** "Treat this as +a cache" is exactly the advisory guarantee A4 and A12 reject, and the model cannot +be prevented from *believing* a fact it has read. The enforceable goal is +narrower: **an unverified belief must not become an irreversible action.** +1. **The store decides what it serves.** Facts carry provenance and an observed-at + timestamp; retrieval marks or withholds anything past a staleness bound for + high-consequence classes. +2. **Verification as a precondition, checked against the log.** For confirm-first + actions, refuse unless a verifying observation exists *in this task's canonical + log*, recorded after the task began, naming the action's target. Inspectable + without asking the model anything — the same move as A12's observable triggers. + Approximate by nature, so build it for the confirm-first tier only. +3. **Provenance at the human gate** — the primary reliance, because C2 already + stops these actions for confirmation. The prompt states evidence quality: + "target recalled from the world model, observed 2026-08-02, not verified this + session" versus "confirmed by `zfs list` two minutes ago". The operator is + already the authority there; this makes the risk legible rather than invisible. +4. **Audit, not prevention, for the cheap tiers.** Log which facts were retrieved + and which actions followed, so a wrong fact is traceable afterwards. + +Residual risk, stated plainly: a model can retype a fact from context with no +retrieval event, and no mechanism here catches that. That is why the world model +stays out of the authority path entirely. + +**Never an instruction channel, and never an approval channel.** +- **Task instructions never come from the knowledge store.** Work is driven by + Vikunja tickets (C4) and operator commands (B2). An agent must never read the + store looking for work to do. +- **Approvals never come from the knowledge store.** The registered + `ApprovalSource`s are Matrix (A1), Gitea (B2), and Vikunja (C4). A wiki page is + not one and must not become one. +- An agent must never write here intending to cause another agent to act. This + extends Part D's rejection of the store as a message bus: the erosion path is + one well-meaning "note for the other agent" at a time. + +**Corrections flow through transport, not through direct edits.** The operator +corrects a fact by telling the agent, which is strictly better than editing the +page by hand: a hand edit is invisible to the agent's record, whereas an +instruction arrives on an authenticated channel from an allowlisted identity, is +recorded in the canonical log, and can be reconciled against what the agent +believed. Corrections are stored **verbatim with attribution**, never paraphrased — +a model rewriting a precise fact loses precision invisibly. +- **Break-glass:** the operator keeps write permission. The procedure reuses Part + F's idle flag — wait for idle, stop the agent, edit freely, restart. Single + writer holds trivially while the agent is down, and A2/A3 make stopping safe. +- Because that permission exists, an agent re-reads and compares the revision + immediately before writing, and re-merges rather than clobbering if it moved. + A safety net for the rare edit, not a designed workflow. + +**Canonical log representation (A10).** Retrieved content is **snapshotted into the +log** — the text, plus document id and revision for provenance — never referenced +by handle alone: A10 forbids a provider handle being the only reference to content, +and these documents are mutable. Snapshot once per task at first use, not per turn. +**A3 consequence:** a task resumed across a restart carries the old snapshot, so +boot reconciliation re-fetches snapshotted knowledge and flags divergence rather +than silently continuing on a stale copy. + +**Retrieval is structural first, discretionary second** — the A12/A14 pattern. +- Procedures are **bound to C2 action classes**: the policy taxonomy and the + procedure index are the same list. Binding is deterministic and auditable + ("which procedure governed this action?" has an exact answer), and costs nothing + extra because the taxonomy must exist anyway. +- A model-invoked **search** exists *in addition* for discovery, never as a + replacement for the bound retrieval — a procedure the model may skip does not + constrain it. + +**Outage posture is asymmetric.** Unlike B2's poller, "never fails a task" does not +generalise here. +- **Write failure never blocks:** retry, or record locally and reconcile. +- **Read failure for a procedure bound to a confirm-first action gates.** + Proceeding is not a delayed reaction; it is acting unconstrained on precisely the + operation someone wrote a procedure for. +- Read failure for read-only or reversible actions proceeds, logging the gap. + +**Accepted single point of failure.** Once the agent is the sole author and Outline +the only home, losing Outline loses the world model. No local mirror: infrastructure +facts are re-derivable by observation, which is the same premise the cache rule +rests on. + +**Deferred:** the operator-writable procedures collection. The interface is defined +here; the content and the C2 binding list arrive with the policy taxonomy (Part H). --- @@ -689,6 +802,10 @@ Requirements: back to Outline: searchable, off-box, and an extra externalized record that survives volume loss. - Access via Outline **REST API**, dedicated service account, token from Bitwarden. +- **The library contract is A17**, which settles what this section left implicit: + per-agent private space plus a shared space, one writer per document, agent-owned + knowledge treated as a cache rather than authority, and the structural means of + keeping it that way. **Deliberately rejected:** using a shared notes store (Outline *or* an Obsidian vault) as an **agent-to-agent message bus**. It's unstructured, race-prone (last-write-wins, @@ -835,6 +952,7 @@ nisse-core/ # shared library (extracted after nisse-dev works) secrets/ # Bitwarden machine-account client (A7) sso/ # LLDAP identity + auth path (A8) logging/ # structured operational logs (A9) + knowledge/ # Outline client, world model, procedures (A17) nisse-dev/ # app: sealed VM (Part B) workflow (arch/milestone gating), Gitea PR layer, allow-all sandbox -- 2.54.0 From 9362a134c9a1d60772e239bc72d68ad123e415be Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Tue, 8 Sep 2026 19:23:17 +0000 Subject: [PATCH 2/2] Give A17 its canonical event, and point Part G at the contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two loose ends in the A17 amendment, both found by asking whether the build order needed changing: - A17 requires retrieved content to be snapshotted into the canonical log, but A10's event table — the authoritative list — had no row for it, so an implementer reading A10 would never know to add one. knowledge_retrieved now carries the text as read, the document ID, its revision, and when it was read - Part G step 10 already scheduled Outline, so the ordering does not change; it now names A17 rather than only Part D, and says which half arrives first — the world model, with operator procedures waiting on the C2 taxonomy they bind to Co-Authored-By: Claude Opus 5 --- docs/Architecture-Build-Spec.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Architecture-Build-Spec.md b/docs/Architecture-Build-Spec.md index a0d18e3..54e8b71 100644 --- a/docs/Architecture-Build-Spec.md +++ b/docs/Architecture-Build-Spec.md @@ -335,6 +335,7 @@ The durable record is a **canonical event log** in SQLite, not a provider-shaped | `delegation_started` | brief, target role, resolved model, caps | | `delegation_completed` | handback note, artifacts, tokens, duration, exit status | | `compaction` | summary, producing model, range of events replaced | +| `knowledge_retrieved` | document text as read, document ID, revision, retrieved-at (A17) | Rules: - **Canonical tool-call IDs are minted locally.** Provider IDs are mapped at the @@ -884,7 +885,8 @@ each agent is built — not standing up the service itself. here). Crash-atomic writes and reconcile-on-boot must be in place first. 10. **Extract `nisse-core`**; build the **Sysadmin Agent** (`nisse-ops`): policy engine (C2), SSO + scoped browser (A8/A6), secrets (A7), Vikunja control (C4), - Outline runbooks (D), scheduled checks, alert intake. + knowledge store (A17/D) — world model first, operator procedures when the + C2 taxonomy they bind to exists — scheduled checks, alert intake. - With the monorepo layout (Part H, decided), extraction is a **package split** within one tree: move code behind a package boundary, no release machinery required. -- 2.54.0