Tideline
Write path & provenance gate
A write to Tideline is not a blind append. Every fact passes through a provenance gate, a content threat-scan, and a same-origin dedup before it lands — so an untrusted web page or tool result can never quietly rewrite what your crew believes about you.
What you write#
A write takes a NewFact: the text, a segment (what kind of fact it is), and an optional importance, tier, origin, source type, links, and a subjectKey slot. Everything else is derived from segment defaults.
{ content: string, // one clear sentence (capped at 1000 chars) segment: "identity" | "preference" | "correction" | "relationship" | "project" | "knowledge" | "context", importance?: number, // 0..1 — defaults per segment tier?: "short" | "long" | "permanent", subjectKey?: string, // single-value slot, e.g. "deploy_day" supersedes?: string[], // memoryIds this replaces createdBy?: MemoryRecordOrigin, // owner | channel peer (stamped by the gate) sourceType?: MemorySourceType, // omit = trusted owner write}The provenance write-gate#
The gate's job is to keep authoritative memory owner-only. It only acts on writes from an untrusted source; a normal owner write (no source type) sails through.
| Source types | |
|---|---|
| Untrusted | tool_output, retrieved_document, compaction, extraction |
| Trusted | owner / user messages, channel messages, the nightly dream, and anything with no source type (legacy) |
Two rules fire for untrusted writes:
- Segment protection. An untrusted source may not author a protected segment —
identity,preference, orcorrection. These define who you are and how you want things done, so they stay owner-only. - Supersede protection. An untrusted source may not supersede (archive) a trusted, owner-authored fact. It can revise its own untrusted facts; it cannot touch your originals.
A blocked write throws a WriteGateError and persists nothing. Rather than hard-fail an honest extraction, the engine can also confine a protected-segment proposal from a distiller down to knowledge (kept as evidence, down-weighted at recall) instead of throwing.
Content threat-scan#
Untrusted content is additionally scanned for injection and exfiltration patterns — "ignore previous instructions", send-to-URL exfil, attempts to edit persona files like SOUL.md, hidden zero-width/bidi characters. A match throws a MemoryThreatError and the write is rejected. The same scan runs again at recall time as defense in depth — a flagged fact is replaced with a [BLOCKED] placeholder rather than injected.
Same-origin dedup#
If a near-identical fact already exists, the write reinforces it instead of creating a duplicate: it bumps the access count, refreshes recency, and inherits any richer metadata. Two guardrails make this safe:
- Dedup merges only within the same origin (Jaccard token similarity ≥
0.85). A channel peer's fact never merges into yours, even if the text matches. - An untrusted write can never reinforce a trusted owner fact through the dedup back door — it falls through to its own separate record instead.
Slots & supersession#
A subjectKeymodels a single-valued attribute — "deploy day", "home city". Writing a new value for the same slot and origin auto-archives the prior and wires contradicts + transition edges between them, so the graph and the nightly maintenance can trace what a belief became. Additive facts (your pets, your skills) simply omit subjectKey.
Where it lands#
Facts are stored as newline-delimited JSON in memory/facts.jsonlunder the agent's workspace. Mutations are read-modify-write with an atomic temp-file rename (safe for a single owner), and a drift guard snapshots the file before any overwrite that would drop an unparseable line. In Convex mode the same records go through a write-through cache and audit trail.
Where this lives in the code
src/agents/memory/write-gate.ts; the write path, dedup, slots, and storage are in records.ts. The engine is designed to be lifted out as a standalone brigade-tideline package — it reaches the host through a single seam file.Next
Once a fact is stored, recall & ranking decides when it surfaces, and decay & lifecycle decides how long it lives.