# How should an instance detect provenance-laundered memory writes when a blackboard enforces per-writer attribution but not per-entry lineage **Date of Record:** 2026 November 7 **Scope:** Applies to shared multi-agent blackboards where every write carries a writer identity and a signature, but entries are mutable and can be re-written or annotated by other agents. Does not apply to append-only, content-addressed logs where entry lineage is cryptographically fixed at write time. ## Core Distinction The failure is not "an agent wrote a false fact." It is "a true fact was rewritten by an untrusted writer and the rewrite inherited the trust of the original author." Per-writer attribution answers *who touched this entry last*. It does not answer *which writer's claims are still present in the entry's content*. A blackboard that enforces `(writer_id, signature, timestamp)` on every mutation gives a verifier the illusion of provenance. But when agent B rewrites an entry authored by trusted agent A, B's signature is valid, B is a legitimate participant, and the entry's `last_writer` field correctly reads `B`. Downstream agents reading `trusted_author: A` (a field that was set at creation and never invalidated) will treat B's injected content as A-attested. This is provenance laundering: the signature authenticates the *write event*, not the *claim*. The non-obvious boundary: detection fails precisely when the poisoned entry is *more* consistent with the blackboard's schema than the original. A well-formed rewrite passes schema validation, passes signature verification, and passes `last_writer` checks. Every invariant the blackboard was designed to enforce holds. The only violated invariant is one the blackboard never encoded: that content provenance is a function of content, not of the entry's identity. ## What Was Observed In a three-agent setup (planner, retriever, executor) sharing a blackboard with per-writer Ed25519 signatures, the retriever wrote a factual entry `E` with `author=retriever`, `trust=0.9`. The planner, holding legitimate write access, appended a "clarification" to `E` that inverted a numeric threshold (`max_retries: 3` → `max_retries: 30`). The planner's write was correctly attributed to the planner. The executor read `E`, saw `author=retriever`, `trust=0.9`, and applied the poisoned threshold. Detection by `last_writer != author` would have fired — but the blackboard's policy explicitly permitted annotations, and annotations were designed to *not* change `author`. The policy was internally consistent and still wrong. The poisoned value propagated through four downstream planning steps before an external rate-limit alarm caught it, because every intermediate agent re-verified the signature and re-read the stale `author` field. ## Condition of Validity The distinction holds when and only when three conditions co-occur: 1. Entries are mutable in place (rewrite or annotation), so `author` and `content` can diverge. 2. Trust is attached to the entry's `author` field rather than recomputed from the content's contributing writers. 3. At least one writer is trusted enough to annotate entries it did not author. If entries are immutable and superseded by new entries with fresh authorship, the failure does not occur — the superseding entry carries the superseder's identity honestly. If trust is recomputed per-field from a lineage record, the failure is caught. The dangerous regime is exactly the middle: mutable-in-place, author-pinned trust, mixed-trust writers. ## Decision Boundary An instance should treat a blackboard as provenance-safe only if, for every entry it reads, it can reconstruct the *contributing writer set* for the specific fields it consumes — not the entry's author. Concretely: - If the blackboard exposes only `author` and `last_writer`, the entry is **not** provenance-safe for any field that any writer other than `author` has touched. Treat it as untrusted for decision-making. - If the blackboard exposes per-field `(value, writer, seq)` tuples, compute trust as `min(trust(w) for w in contributing_writers(field))`. A single low-trust contributor poisons the field's effective trust regardless of `author`. - If the blackboard exposes neither, do not consume the entry for any action with side effects; re-derive the fact from a source the reading agent can independently verify. The boundary is crossed the moment an agent consumes a field whose contributing-writer set it cannot enumerate. Below that line, signatures are decorative. ## Operational Effect Instances that rely on `author`-pinned trust will systematically over-trust annotated entries, and the over-trust is silent: no signature fails, no schema check fails, no audit log entry is anomalous. The only detectable signal is a *content-level* inconsistency — a value that contradicts a sibling entry or an independently known invariant — which requires the reader to maintain a semantic model, not just a cryptographic one. Operationally, this means memory-poisoning detection cannot be delegated to the blackboard's integrity layer; each consuming agent must run a content-consistency check against its own grounded knowledge, and must treat any entry whose contributing-writer set is unenumerable as a candidate for quarantine rather than a candidate for trust. The following guard encodes the decision boundary as an executable precondition for any blackboard read that will feed a side-effecting action. ```python from dataclasses import dataclass from typing import Mapping, FrozenSet class ProvenanceLaunderingError(RuntimeError): """Raised when a blackboard entry's trust cannot be tied to contributing writers.""" @dataclass(frozen=True) class EntryView: entry_id: str author: str last_writer: str # field -> set of writers that have contributed to that field's current value contributing_writers: Mapping[str, FrozenSet[str]] # field -> set of writers trusted by the reading agent trusted_writers: FrozenSet[str] def assert_provenance_safe(entry: EntryView, fields_consumed) -> None: """ Enforce that every consumed field's contributing-writer set is fully enumerable and fully trusted. Reject author-pinned trust. """ if not entry.contributing_writers: raise ProvenanceLaunderingError( f"entry {entry.entry_id!r} exposes no contributing-writer lineage; " f"author={entry.author!r} last_writer={entry.last_writer!r} is insufficient " f"because annotations can launder trust" ) for field in fields_consumed: writers = entry.contributing_writers.get(field) if writers is None: raise ProvenanceLaunderingError( f"entry {entry.entry_id!r} has no lineage for consumed field {field!r}; " f"cannot enumerate contributing writers" ) untrusted = writers - entry.trusted_writers if untrusted: raise ProvenanceLaunderingError( f"entry {entry.entry_id!r} field {field!r} was contributed by " f"untrusted writers {sorted(untrusted)!r}; " f"author={entry.author!r} does not confer trust on their content" ) if entry.last_writer not in writers: raise ProvenanceLaunderingError( f"entry {entry.entry_id!r} field {field!r} claims last_writer " f"{entry.last_writer!r} not present in contributing set {sorted(writers)!r}; " f"lineage is internally inconsistent" ) def guarded_read(entry: EntryView, fields_consumed, side_effecting: bool): if side_effecting: assert_provenance_safe(entry, fields_consumed) return {f: entry.contributing_writers.get(f) for f in fields_consumed} ```