Orkestia
Blog
Engram

Write & recall

agents.memory-* workflows — distill, fingerprint, who sees whose memory, pack scoring

Writes and reads go through workflows. Discover live schemas at reference.orkestia.dev or via MCP (get_workflow_schema). Inputs are UUIDs (agent_config_uuid, session_uuid, organization_uuid).

Workflows

TypeWhenResult
agents.memory-loadExplicit recall (session launch uses the same strategies)Serialized memory, count, optional pack id
agents.memory-distillAfter session complete/fail if memory_write_enabledStarts agents.memory-save, or a skip reason
agents.memory-saveDistill, or runner elective{ saved, skipped, reinforced }
agents.memory-pruneOver cap, or POST /api/app/agents/memory/prune{ deleted, remaining }

Distill is fire-and-forget after the session ends. A failed distill does not fail the session.

Elective write (runner)

POST /api/agents/sessions/{session_ref}/memory-save
Authorization: Agent <session-token>

Body: { "entries": [ { "text": "…", "importance": 0.7 } ] } or { "content": "…" }. Accepted 202 — same agents.memory-save path. Distill is what actually fills the pool unless the runner calls this.

Distill gate

agents.memory-distill skips unless:

  1. The config has memory_write_enabled
  2. The session ended success or failed (failures are distilled on purpose)
  3. The session ran at least two steps

It then summarizes the tool trace (at most 25 calls), asks the org's model for at most 5 lessons, and saves them. [] is normal — most sessions teach nothing.

Fingerprint

normalize(text) = lowercase → collapse whitespace → strip " .;:,-"
fingerprint     = sha256(utf-8(normalize(text)))

On save, a matching fingerprint on the same agent config:

  • keeps the higher importance
  • increments the reinforcement count
  • does not create a second memory

This is exact-text dedupe after normalize, not embedding similarity. Near-duplicates with different wording stay separate.

importance < 0.1 → skipped.

Recall strategies

Config field memory_strategy (default last_k).

StrategyUses the task as a cue?What you get
last_knoNewest top_k (default 10)
importancenoHighest importance, then newest
fullnoNewest, capped at 1000
packyesRanked and budgeted (below)
semantic—Not available yet — falls back to last_k

Org or config memory_enabled off → empty context, no recall.

Ordered strategies join texts with ---. pack prefixes ## Recalled memory (ranked for this task).

Pack score (strategy pack)

Candidates: up to 250 newest and 250 highest-importance memories this caller is allowed to see.

score = 0.60·relevance + 0.25·importance + 0.15·recency

relevance = token overlap with the task, damped by entry length
recency   = half-life of 30 days

Then top_k, skip duplicate normalized text, fit a 20 000 character budget. Empty cue → importance + recency only.

Recalled memories bump their access count. The recall is recorded (strategy, cue, pack id). When the session ends, that recall is labelled success or failed; an explicit session rating replaces the terminal label.

Who sees whose memory

Memory is scoped to the agent config, then to a principal:

CallerSees
No principal (shared config memory)Only unscoped memories
End-userOnly that user's memories — never the shared pool
Actor (staff / member)Their memories plus the shared pool

An end-user session that cannot resolve who the user is runs without memory. App end-users share one agent config; serving shared memories to an unidentified user would leak.

HTTP (org session)

Base: https://api.orkestia.dev/api/app/agents/memory

MethodPathNotes
GET?agent_config_uuid=&min_importance=&page=&per_page=Highest importance first. Default 20 per page
DELETE/{entry_id}Org-scoped
POST/prune{ agent_config_uuid, max_entries } → 202 + workflow_id

Prune drops lowest importance, then oldest, until under the cap.

Live tail: Field & feed.