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.

NewFact (the essentials)
{  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
Untrustedtool_output, retrieved_document, compaction, extraction
Trustedowner / 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, or correction. 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

The gate is 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.