ElGraph.Memory (ElGraph v0.3.0)

Copy Markdown View Source

스코프 기반 장기 기억 (트렌드 보고서 Tier 2.6).

2026 트렌드: 프로덕션 #1 장애가 memory hallucination(자기 히스토리에서 모순·낡은 사실 회수). 이를 완화하려고 기억을 3-스코프로 나누고 시점 진실(latest-wins)을 적용한다.

  • episodic — 시간순 이벤트 로그 (무엇이 일어났나)
  • semantic — subject별 사실, 최신이 과거를 대체 (지금 참인 것)
  • procedural — 학습된 규칙/선호

ElGraph.Store 어댑터(KV) 위의 순수 계층이다 — 오직 ElGraph.Store behaviour만 사용한다(put/get/delete/list). 따라서 ETS/Postgres 등 어느 Store 어댑터로든 동일하게 동작하며 어댑터별 가정이 새지 않는다. namespace로 주체(사용자 등)를 분리한다.

mem = ElGraph.Memory.new({ElGraph.Store.ETS, config})
ElGraph.Memory.set_fact(mem, ["users", "u1"], "plan", "pro")
ElGraph.Memory.get_fact(mem, ["users", "u1"], "plan")  #=> {:ok, "pro"}

추가 기능:

Summary

Types

같은 subject에 새 값이 들어올 때의 충돌 해소 정책.

t()

Functions

주어진 시점 at에 참이었던 값을 회수한다(temporal 쿼리).

subject의 대체된 과거 값들을 최신순으로 회수한다(시점 진실 + 출처 감사).

키로 사실(:semantic)/규칙(:procedural)을 삭제한다.

subject의 현재 참 값을 회수한다.

규칙/선호를 학습한다.

Store 어댑터({module, config})를 감싼 Memory를 만든다.

에피소드를 최신순으로 회수한다. :limit.

현재 참인 모든 사실을 %{subject => value}로 회수한다.

쿼리와 의미적으로 가까운 기억을 코사인 유사도로 랭킹해 회수한다.

학습된 규칙을 %{name => rule}로 회수한다.

에피소드(이벤트)를 기록한다. :at(정렬 키, 기본 단조 증가).

사실을 기록한다 — 같은 subject의 과거 값을 대체한다(시점 진실).

Types

namespace()

@type namespace() :: [String.t()]

on_conflict()

@type on_conflict() :: :latest | :reject | (term(), term() -> term())

같은 subject에 새 값이 들어올 때의 충돌 해소 정책.

  • :latest — 새 값이 과거를 대체(기본). 직전 값은 히스토리로.
  • :reject — 기존 값을 유지하고 새 값을 버린다(write-once 사실). 히스토리 변화 없음.
  • fun/2(기존값, 새값) -> 병합값. 병합 결과를 현재 값으로 두고 직전 값은 히스토리로.

t()

@type t() :: %ElGraph.Memory{store: {module(), ElGraph.Store.config()}}

Functions

fact_at(mem, ns, subject, at)

@spec fact_at(t(), namespace(), String.t(), term()) :: {:ok, term()} | :unknown

주어진 시점 at에 참이었던 값을 회수한다(temporal 쿼리).

현재 값과 fact_history/3의 과거 값을 합친 타임라인에서, entry.at <= at인 것 중 가장 최근 값을 돌려준다. at이 가장 이른 값보다 앞서면 :unknown. 비교는 set_fact:at과 동일 시계를 쓴다는 전제다.

fact_history(memory, ns, subject)

@spec fact_history(t(), namespace(), String.t()) :: [%{value: term(), at: term()}]

subject의 대체된 과거 값들을 최신순으로 회수한다(시점 진실 + 출처 감사).

현재 참인 값은 포함하지 않는다 — 그건 get_fact/3로 얻는다.

forget(memory, ns, atom, key)

@spec forget(t(), namespace(), :semantic | :episodic | :procedural, String.t()) :: :ok

키로 사실(:semantic)/규칙(:procedural)을 삭제한다.

episodic은 키가 시간 정렬용 내부 키라 키 기반 삭제 대상이 아니다(미지원).

get_fact(memory, ns, subject)

@spec get_fact(t(), namespace(), String.t()) :: {:ok, term()} | :unknown

subject의 현재 참 값을 회수한다.

learn(mem, ns, name, rule)

@spec learn(t(), namespace(), String.t(), term()) :: :ok

규칙/선호를 학습한다.

new(store)

@spec new({module(), ElGraph.Store.config()}) :: t()

Store 어댑터({module, config})를 감싼 Memory를 만든다.

recall_episodes(mem, ns, opts \\ [])

@spec recall_episodes(t(), namespace(), keyword()) :: [term()]

에피소드를 최신순으로 회수한다. :limit.

recall_facts(memory, ns)

@spec recall_facts(t(), namespace()) :: %{required(String.t()) => term()}

현재 참인 모든 사실을 %{subject => value}로 회수한다.

recall_relevant(mem, ns, query, opts)

@spec recall_relevant(t(), namespace(), String.t(), keyword()) :: [term()]

쿼리와 의미적으로 가까운 기억을 코사인 유사도로 랭킹해 회수한다.

옵션:

  • :embedder (필수) — ElGraph.Memory.Embedder를 구현한 모듈(atom) 또는 {module, _}.
  • :scope — 검색할 스코프 (기본 "episodic", "semantic" 등 허용).
  • :limit — 상위 개수 (기본 5).

쿼리와 각 엔트리 값(binary 문자열인 것만)을 임베딩해 유사도 내림차순으로 정렬하고 상위 limit개의 을 돌려준다. binary가 아닌 값은 건너뛴다.

recall_rules(memory, ns)

@spec recall_rules(t(), namespace()) :: %{required(String.t()) => term()}

학습된 규칙을 %{name => rule}로 회수한다.

record_episode(mem, ns, event, opts \\ [])

@spec record_episode(t(), namespace(), term(), keyword()) :: :ok

에피소드(이벤트)를 기록한다. :at(정렬 키, 기본 단조 증가).

set_fact(mem, ns, subject, value, opts \\ [])

@spec set_fact(t(), namespace(), String.t(), term(), keyword()) :: :ok

사실을 기록한다 — 같은 subject의 과거 값을 대체한다(시점 진실).

대체되는 직전 값은 fact_history/3로 감사할 수 있도록 subject별 히스토리에 보관한다.

옵션:

  • :at — 정렬/시점 키(기본 단조 증가). fact_at/4의 비교 기준.
  • :on_conflict — 기존 값이 있을 때의 정책(on_conflict/0, 기본 :latest).