Memory tiers

Covenant's memory is a single store partitioned into three tiers. The tiers are not separate databases — they are a column on a shared schema — so an agent can be promoted from one tier to another without rewriting it elsewhere.

Tiers

TierLifespanTypical use
workingper-task scratch; cleared explicitlyIntermediate results during an intent run. Scratch memory the agent uses to reason. Default tier for automatic memory writes.
episodictask-grained, durableRecords of completed tasks, kept across runs. Useful for "what happened last time I asked this" kind of queries.
longtermintentionally retained contextCurated reference material, persistent personal context, anything that should influence future routing and answer composition.

Record shape

MemoryRecord {
  id:          uuid,
  tier:        "working" | "episodic" | "longterm",
  owner:       AgentId,
  text:        "the result text",
  embedding:   [f32; N],          // empty if no embedder configured
  metadata:    JSON,              // free-form, e.g. { "intent_text": "…" }
  created_at:  u64,               // unix milliseconds
  parent:      uuid | null
}

Storage

The persistent backend is SQLite at $COVENANT_HOME/memory.db. The schema is one row per record; embeddings are stored as raw f32 bytes for cheap cosine math. An in-memory backend covers tests; both implement the same MemoryStore trait.

The daemon does not use a separate vector index — cosine search runs over the rows in the queried tier. The default store is good for tens of thousands of records; production deployments should plan for an index when row counts climb.

Embedding

The daemon embeds memory writes if an embedder is configured. With no embedder configured, records are written without an embedding and will not appear in similarity search; they still surface via recent queries.

Configure an embedder in ~/.covenant/secrets.toml:

[embed]
provider = "ollama"
model    = "nomic-embed-text"

With the above, every memory write embeds via Ollama on http://localhost:11434. The embedder is shared across writes and search queries, so query and document vectors are guaranteed to be in the same space.

Reading recent memory

covenant memory recent
covenant memory recent --tier longterm
covenant memory recent --tier episodic --limit 50

# Or via HTTP:
curl -s '127.0.0.1:8421/memory/recent?tier=longterm&limit=50'

Semantic search

Search runs cosine similarity over stored vectors. The query is embedded with the same provider used for writes; results are returned in descending similarity order.

covenant memory search "agent memory"
covenant memory search "agent memory" --tier longterm --limit 5

# Or via HTTP:
curl -s '127.0.0.1:8421/memory/search?q=agent+memory&tier=longterm&limit=5'

Garbage collection

Working-tier records grow unboundedly without explicit cleanup. The daemon exposes a purge primitive that removes records below a timestamp threshold:

# Purge working-tier records older than 24 hours.
covenant memory purge --tier working --older-than-ms 86400000

# Purge anything before an explicit epoch.
covenant memory purge --before-ms 1714938000000

# Purge all tiers older than seven days.
covenant memory purge --older-than-ms $((7*24*60*60*1000))

Episodic and long-term records are typically curated rather than auto-purged; the same primitive works for them, but the operator should be deliberate about the threshold.

The .covenantignore allow/deny list

Covenant supports a .covenantignore file at $COVENANT_HOME/.covenantignore with gitignore- style patterns. An intent whose text matches any of the active patterns is short-circuited before dispatch:

  • The router is skipped — no agent runs.
  • No memory record is written.
  • No settlement receipt is written.
  • An IntentIgnored audit event is recorded with the matching pattern.

Pattern syntax: * matches any sequence within a single segment, ** across segments, ? matches a single character. ! negates an earlier match. / at the start anchors to the path root. Last-rule-wins: a later rule supersedes an earlier match.

The default seed list (created on first daemon start if no file exists) covers common credential filenames so intents that mention id_rsa, .env, credentials.json, and so on are silently dropped.

Test the ignore set

covenant ignore check "summarise ~/.ssh/id_rsa"
# → ignored: true
# → matched_pattern: "id_rsa"
# → rules_loaded: 5

Related

  • CLI — every memory subcommand.
  • Audit log — where IntentIgnored lands.
  • Settlement — memory writes pair 1:1 with receipts.