LemonMemory.Document (lemon_memory v0.1.0)

View Source

Normalized memory document extracted from a finalized run.

A memory document captures the key facts about a run in a compact, searchable form, separate from the full conversation payload stored in RunHistoryStore.

Fields

  • :doc_id - Unique document identifier (prefixed "mem_")
  • :run_id - Source run ID
  • :session_key - Session the run belongs to
  • :agent_id - Agent that executed the run
  • :workspace_key - Workspace the run was executed in (nil if unknown)
  • :scope - Memory scope: :session, :workspace, :agent, or :global
  • :started_at_ms - Run start timestamp in milliseconds
  • :ingested_at_ms - When this document was written to the memory store
  • :prompt_summary - Truncated prompt text (for FTS)
  • :answer_summary - Truncated answer text (for FTS)
  • :tools_used - List of tool name strings used during the run
  • :provider - LLM provider name (e.g. "anthropic")
  • :model - LLM model name (e.g. "claude-opus-4-6")
  • :outcome - Outcome label inferred by LemonCore.RunOutcome.infer/1
  • :meta - Arbitrary metadata map

Scopes

The four memory scopes follow docs/assistant_bootstrap_contract.md:

ScopeLifetimeKey field used
:sessionSingle agent runsession_key
:workspaceWorkspace lifetimeworkspace_key
:agentAgent lifetimeagent_id
:globalInstallation lifetimenone (all documents)

Summary

Functions

Build a Document from a finalized run record and its summary.

The byte cap new/1 and from_run/4 apply to each summary field.

Build a Document directly from its fields, applying the same invariants the ingest path relies on.

Types

outcome()

@type outcome() :: :unknown | :success | :partial | :failure | :aborted

scope()

@type scope() :: :session | :workspace | :agent | :global

t()

@type t() :: %LemonMemory.Document{
  agent_id: binary(),
  answer_summary: binary(),
  doc_id: binary(),
  ingested_at_ms: integer(),
  meta: map(),
  model: binary() | nil,
  outcome: outcome(),
  prompt_summary: binary(),
  provider: binary() | nil,
  run_id: binary(),
  scope: scope(),
  session_key: binary(),
  started_at_ms: integer(),
  tools_used: [binary()],
  workspace_key: binary() | nil
}

Functions

from_run(run_id, record, summary, opts \\ [])

@spec from_run(run_id :: term(), record :: map(), summary :: map(), opts :: keyword()) ::
  t()

Build a Document from a finalized run record and its summary.

Extracts normalized fields from the run summary. Unknown or missing fields are replaced with safe defaults so ingest never raises.

max_summary_bytes()

@spec max_summary_bytes() :: pos_integer()

The byte cap new/1 and from_run/4 apply to each summary field.

new(fields)

@spec new(map() | keyword()) :: t()

Build a Document directly from its fields, applying the same invariants the ingest path relies on.

Use this when you drive LemonAgent yourself and want to record a memory without the router's run-record shape that from_run/4 expects. It differs from building the struct by hand in two ways that matter:

  • Summaries are truncated. prompt_summary and answer_summary are capped at 2000 bytes (on a UTF-8 boundary), exactly as from_run/4 does. Building the struct by hand skips this, silently indexing whole transcripts into the FTS table.
  • Identity is validated. session_key and agent_id are required and must be non-empty binaries — they are NOT NULL in the store and are what scope a search, so a missing one is a programming error, not a row the store should quietly reject.

Everything else is defaulted: doc_id (a fresh "mem_"-prefixed id) and run_id are generated when absent, ingested_at_ms defaults to now, started_at_ms to ingested_at_ms, and scope to :session.

Accepts a map or keyword list. Raises ArgumentError on a missing or invalid required field, or an unknown scope.

Examples

iex> doc = LemonMemory.Document.new(session_key: "agent:demo:main", agent_id: "demo",
...>   prompt_summary: "hi", answer_summary: "hello")
iex> {doc.scope, doc.answer_summary, String.starts_with?(doc.doc_id, "mem_")}
{:session, "hello", true}