Normandy.Components.AgentMemory (normandy v1.0.0)

View Source

Conversation memory as a graph of parent-linked entries.

Each message is an AgentMemory.Entry carrying its own id and a parent_id link to the prior entry on its branch. A linear conversation is a degenerate single-parent chain: head points at the most-recent entry, and walking parent_id back to a root reproduces the conversation. Branching is opt-in via fork/2.

history/1 reconstructs the active branch in chronological order — output identical to the previous linear implementation.

Summary

Functions

Total number of stored entries across all branches (map_size(entries)).

The active branch head -> root, returned chronological (oldest -> newest).

Move head to from_entry_id; subsequent appends branch from there.

Rebuild a memory from a chronological list of Entry.t() (e.g. the output of SessionStore.history/2). The head becomes the last entry's id and the current_turn_id the last entry's turn_id, so the active branch reconstructs.

The newest message (the head entry), or nil when empty.

The active branch as %Message{} structs, chronological.

Types

t()

@type t() :: %Normandy.Components.AgentMemory{
  current_turn_id: String.t() | nil,
  entries: %{required(String.t()) => Normandy.Components.AgentMemory.Entry.t()},
  head: String.t() | nil,
  max_messages: pos_integer() | nil
}

Functions

add_message(memory, role, content)

@spec add_message(t(), String.t(), term()) :: t()

count_messages(agent_memory)

@spec count_messages(t()) :: non_neg_integer()

Total number of stored entries across all branches (map_size(entries)).

For a linear conversation this equals the active-chain length; after a fork/2 with divergent appends it counts entries on every branch, not just the active one.

delete_turn(memory, turn_id)

@spec delete_turn(t(), String.t()) :: t()

dump(memory)

@spec dump(t()) :: String.t()

entries(agent_memory)

@spec entries(t()) :: %{
  required(String.t()) => Normandy.Components.AgentMemory.Entry.t()
}

entry_chain(memory)

@spec entry_chain(t()) :: [Normandy.Components.AgentMemory.Entry.t()]

The active branch head -> root, returned chronological (oldest -> newest).

fork(memory, from_entry_id)

@spec fork(t(), String.t()) :: {:ok, t()} | {:error, :no_such_entry}

Move head to from_entry_id; subsequent appends branch from there.

from_entries(entries)

@spec from_entries([Normandy.Components.AgentMemory.Entry.t()]) :: t()

Rebuild a memory from a chronological list of Entry.t() (e.g. the output of SessionStore.history/2). The head becomes the last entry's id and the current_turn_id the last entry's turn_id, so the active branch reconstructs.

get_current_turn_id(agent_memory)

@spec get_current_turn_id(t()) :: String.t() | nil

get_entry(agent_memory, id)

@spec get_entry(t(), String.t()) :: Normandy.Components.AgentMemory.Entry.t() | nil

history(memory)

@spec history(t()) :: [%{role: String.t(), content: String.t() | list()}]

initialize_turn(memory)

@spec initialize_turn(t()) :: t()

latest_message(agent_memory)

@spec latest_message(t()) :: Normandy.Components.Message.t() | nil

The newest message (the head entry), or nil when empty.

load(dump)

@spec load(String.t()) :: t()

messages(memory)

@spec messages(t()) :: [Normandy.Components.Message.t()]

The active branch as %Message{} structs, chronological.

new_memory(max_messages \\ nil)

@spec new_memory(pos_integer() | nil) :: t()