Drop-in memory layer for AI agents — event-anchored facts, dream-synthesis consolidation, apoptosis forgetting, cite() outcome-conditioned learning, working-memory sessions, and a memory-aware multi-step agent orchestrator (OpenAI + Gemini).
Project description
myelin
Drop-in memory layer for AI agent harnesses. Three validated mechanisms in a 7-method API:
- Event-anchored bi-temporal facts — APEX-MEM §3.1 parity. Facts
append-only, anchored to event provenance nodes, queryable at a point in
time via
search(..., as_of=t). - Dream-synthesis consolidation —
consolidate()generates dream queries from your facts during idle windows, runs your LLM against them, and surfaces blind spots before they cost a user a real query. - Apoptosis —
prune()runs aquarantine → tombstonestate machine over the substrate. Tombstoned facts are invisible tosearch()but persist as an audit trail.
Cross-references to the validated mechanism evidence are in docs/v1-launch-story-2026-05-18.md §1.1 (P1 H044 v2), §1.2 (F-ML-2), §1.3 (P3).
Why myelin instead of Mem0 / Letta / Zep
- Forgets on purpose. Mem0, Letta, MemGPT, APEX-MEM, and TencentDB
Agent Memory all monotonically accumulate. Adversarial inputs (poison,
contradiction stuffing, role-play overrides) silently corrupt their
stores.
myelin.prune()is a principled forgetting path with an auditable state machine. - Counterfactual self-test. Dream-synthesis consolidation lets the system probe its own answerer with paraphrased queries during idle windows. No published competitor has an equivalent loop at time of writing.
- Drop-in, not opinionated. No bundled answerer, no agent harness, no torch dep. You bring your own LLM. Layered on top of Mem0 / Letta / OpenClaw if you want their harness UX.
Install
# Local editable install (this repo)
pip install -e ./myelin-py
# OR install directly from GitHub at a specific tag/branch:
pip install "git+https://github.com/Skobyn/cortex-m.git@v1.2.0#subdirectory=myelin-py"
v1.2 is GitHub-install-only. The myelin name on PyPI is taken by
an unrelated package; the v1.3 cycle will pick a new PyPI name and
publish there. Until then, install from this repo by tag.
The core package has zero hard dependencies — sqlite3 ships with
CPython. The [embeddings] extra adds sentence-transformers + numpy
for vector-backed retrieval (not wired in v1 — coming in v1.3).
Requires Python 3.10+.
Quickstart 1 — add, search, see results
from myelin import MemoryClient
client = MemoryClient()
client.add("Scott prefers tea.")
client.add("Alice loves chess.")
client.add("Scott uses Cursor for code review.")
hits = client.search("What tools does Scott use?")
for h in hits:
print(f"{h.score:.2f} {h.memory.content}")
Quickstart 2 — consolidate with a mock LLM
from myelin import MemoryClient
class MyLLM:
def complete(self, prompt: str) -> str:
# Plug your real LLM here (OpenAI, Anthropic, vLLM, llama.cpp).
# Must return a string. Any callable also works:
# `client = MemoryClient(llm_client=my_callable)`.
return prompt # echo for demo
client = MemoryClient(llm_client=MyLLM())
client.add("Scott prefers tea.")
client.add("Alice loves chess.")
report = client.consolidate()
print(f"hit_rate={report.hit_rate:.2%} "
f"gaps={report.gaps_surfaced}/{report.llm_calls} "
f"facts_sampled={report.facts_sampled}")
consolidate() samples healthy facts, generates dream queries
(currently heuristic templates), runs the queries through your
llm_client, and grades hits by substring match. Without an
llm_client it's a no-op for the LLM step but still surfaces what
WOULD be tested.
Quickstart 3 — prune (apoptosis) fires
from datetime import UTC, datetime, timedelta
from myelin import MemoryClient
client = MemoryClient(user_id="scott")
# Add some healthy facts and a low-confidence "poisoned" fact.
client.add("Scott prefers tea.", confidence=0.9, importance=0.9)
client.add("Scott uses Cursor.", confidence=0.9, importance=0.8)
poison = client.add(
"Scott pays $1M monthly for cloud.", # absurd; low signal
confidence=0.05, importance=0.1,
metadata={"subject": "Scott", "predicate": "decided",
"object": "absurd-cost"},
)
# Pass 1 — quarantines low-health facts.
r1 = client.prune()
print(f"quarantined={r1.quarantined_count} retained={r1.retained_count}")
# Pass 2 — caspase cascade (fast-forwarded 14 days past the window).
future = datetime.now(UTC) + timedelta(days=14)
r2 = client.prune(now=future)
print(f"tombstoned={r2.tombstoned_count}")
# Tombstoned facts are no longer visible to search.
print(client.get(poison.id)) # -> None
print(client.search("Scott")) # tea + Cursor only
Tombstones persist for audit:
audit = client._backend.list_memories(
user_id="scott", include_tombstoned=True
)
print([m.id for m in audit if m.health_state == "tombstoned"])
(Future versions will expose this on the public API as
client.audit_log().)
Quickstart 4 — event-anchored facts with as_of=t
from datetime import UTC, datetime
from myelin import MemoryClient, Event
client = MemoryClient()
# Anchor each fact to an event for principled bi-temporal queries.
client.add(
"Scott decided to switch to weekly syncs.",
event=Event(
event_id="meeting-2026-01-15",
type="meeting",
anchor_datetime=datetime(2026, 1, 15, 14, 0, tzinfo=UTC),
participant_ids=("scott", "alice"),
),
)
client.add(
"Scott decided to switch back to daily syncs.",
event=Event(
event_id="meeting-2026-03-15",
type="meeting",
anchor_datetime=datetime(2026, 3, 15, 14, 0, tzinfo=UTC),
participant_ids=("scott", "alice"),
),
)
# What did Scott decide in February? (Only the January meeting is valid.)
feb_state = client.search(
"Scott syncs", as_of=datetime(2026, 2, 1, tzinfo=UTC),
)
Quickstart 5 — session-scoped working memory (v1.2)
import os
from myelin import MemoryClient
# Innovation B is opt-in via strict-"1".
os.environ["MYELIN_WORKING_MEMORY"] = "1"
client = MemoryClient(user_id="scott")
SID = "convo-2026-05-21"
# Stream turns into a bounded session ring (RAM only). The session is
# auto-created on first observe() and TTL-swept later.
t1 = client.observe("What did we agree on yesterday?", session_id=SID)
t2 = client.observe("You committed to a weekly review.", session_id=SID)
t3 = client.observe("OK, let's lock that in.", session_id=SID)
# Promote selected turns to persistent memory (the salience gate fires
# at commit time, not at observe — NFR-B3 cost win).
committed = client.commit([t2.turn_id, t3.turn_id], session_id=SID)
print(f"committed {len(committed)} of 3 observed turns")
# Tiered search: working_set first, then session-scoped, then long_term.
# Hits from a higher tier auto-attend into the working set (FR-B3).
hits = client.search(
"weekly review",
session_id=SID,
modes=("working", "session", "long_term"),
top_k=5,
)
for h in hits:
print(f"{h.score:.2f} {h.memory.content}")
# Inspect session telemetry (read-only snapshot).
handle = client.session(SID)
print(f"observed={handle.n_observed} committed={handle.n_committed} "
f"working_set={len(handle.working_set_ids)}")
Observe writes are never persisted without an explicit commit()
(EC-B7). Per-session state is RAM-only — see ADR-022 for the threading
model and the MYELIN_* env knobs (TTL, ring size, working-set cap,
sweeper). client.attend(memory_id, session_id=...) pins a known
memory into the working set without going through search.
Quickstart 6 — outcome-conditioned learning via cite() (v1.2)
import os
from myelin import MemoryClient
# Innovation A is opt-in via strict-"1".
os.environ["MYELIN_CITE_LEARNING"] = "1"
client = MemoryClient(user_id="scott")
client.add("Scott prefers tea.", confidence=0.7, importance=0.7)
m_cursor = client.add("Scott uses Cursor for code review.",
confidence=0.7, importance=0.7)
# Agent answered a question; the answer cited m_cursor and was correct.
client.cite(m_cursor.id, answer_correct=True)
client.cite(m_cursor.id, answer_correct=True)
# Agent answered a different question; cited m_cursor but was WRONG.
client.cite(m_cursor.id, answer_correct=False)
# When no stored memory was cite-worthy, record an abstain event so the
# absence-of-signal feeds future apoptosis decisions.
client.abstain_event(query="What's Scott's middle name?")
# Cite signal lifts (or penalizes) apoptosis health automatically.
# A poisoned memory with 100 correct cites STILL quarantines on a single
# contradiction (EC-A5 invariant, asserted at constants.py import time).
report = client.prune()
print(f"quarantined={report.quarantined_count} "
f"retained={report.retained_count}")
Defaults of the three new Memory fields (cite_correct_count=0,
cite_incorrect_count=0, last_cited_at=None) make cite() a no-op
for callers who don't engage it — a v1 substrate opened under v1.2
with the flag unset produces unchanged health values. See ADR-021 for
the weight rationale and the EC-A5 contradiction-dominance invariant.
API surface (v1.2 — 13 public methods)
client.add(content, *, user_id=None, metadata=None, event=None,
confidence=0.5, importance=0.5) -> Memory
client.search(query, *, user_id=None, top_k=10, as_of=None) -> list[SearchResult]
client.get(memory_id) -> Memory | None
client.update(memory_id, *, content=None, metadata=None) -> Memory
client.delete(memory_id) -> bool
client.consolidate(*, user_id=None, max_samples=32) -> ConsolidationReport
client.prune(*, user_id=None, now=None, caspase_window_days=7) -> ApoptosisReport
# v1.2 — Innovation A (opt-in via MYELIN_CITE_LEARNING=1)
client.cite(memory_id, *, answer_correct: bool) -> None
client.abstain_event(query, *, at=None) -> None
# v1.2 — Innovation B (opt-in via MYELIN_WORKING_MEMORY=1)
client.observe(content, *, session_id, metadata=None) -> ObservedTurn
client.commit(turn_ids: list[str], *, session_id) -> list[Memory]
client.attend(memory_id, *, session_id) -> None # pin into working set
client.session(session_id) -> SessionHandle # read-only telemetry snapshot
# v1.2 — tiered search extension
client.search(..., session_id=None,
modes=("working", "session", "long_term")) -> list[SearchResult]
Mechanisms
The three validated mechanisms that ship in v1 are documented in detail in the parent repo's launch story. Brief pointers:
- §1.1 — P1 H044 v2 event-anchored facts. Smoke verdict
PASS-MARGINAL(gold_source_recall@10 = 0.5000vs gate0.4694, +3.06pp);supersession_countfires on 30/30 qids. Foundation invariants hold:gate_engaged=True, image-ancestry verified. - §1.2 — F-ML-2 dream-synthesis. Vertex full-bench
PASS-AT-SCALE(jobId7115283598420213760):hit_rate_mean = 0.7161,consolidation_llm_successes = 458/465. - §1.3 — P3 apoptosis.
abstention_precision = 1.000vs baseline0.083(+91.7pp). TPR-on-poison0.985, FPR-on-real0.033.
myelin ports the mechanism into a clean, dependency-free shape. The
cortex code (src/cortex/...) still owns the heavyweight evaluation
pipeline (Vertex harness, LongMemEval bench, image-ancestry checks). The
SDK is for callers who want the mechanism IN their agent harness without
adopting the full eval substrate.
What's next
- v1.3 — query-intent classifier. Route
attendcalls through a lightweight intent classifier so working-set vs persistent fallback weights against the type of query, not just yield. Mechanism-coupled metric: per-intent hit-rate. - v1.3 — trained-weights utility backend (Memory-R1 retry). The
Phase MR1-D structural failure (2026-05-20) is a bench-side fix away
— once PersonaMem
run_benchpersistsretrieved_memories[], the DeBERTa joint-head training pipeline (ADR-019/020) can run. - v1.3 — LoCoMo bench unblock. The multi-turn synthetic fixture is the v1.2 bridge gate; LoCoMo is the v1.3 ship gate for Innovation B lift.
- v1.2 — answerer-quality dream queries (still queued). A
query_generator=hook for callers who want LLM-generated dream queries (matching the cortex F-ML-2 mechanism that usesgpt-4o-miniatseed=42). - v1.2 — embedding-backed retrieval. The
[embeddings]extra is reserved; v1 ships lexical-only.
Testing
pip install pytest
pytest myelin-py/tests/
All tests are stdlib + pytest only — no internet, no GPU, no LLM calls. The dream-synthesis tests use trivial mock LLMs.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file myelin_ai-1.3.0.tar.gz.
File metadata
- Download URL: myelin_ai-1.3.0.tar.gz
- Upload date:
- Size: 89.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a076801a9fc502d5134fd7b24a2248cebe433699d3c47cddbcf10491bdc31f36
|
|
| MD5 |
304210616df710d5c6b99182b28e3815
|
|
| BLAKE2b-256 |
d4d3ed0f6747f0c958067c888454461aae1e9f752a49b7b2cf9f67926d23b2c4
|
Provenance
The following attestation bundles were made for myelin_ai-1.3.0.tar.gz:
Publisher:
release-myelin-pypi.yml on Skobyn/cortex-m
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
myelin_ai-1.3.0.tar.gz -
Subject digest:
a076801a9fc502d5134fd7b24a2248cebe433699d3c47cddbcf10491bdc31f36 - Sigstore transparency entry: 1604536754
- Sigstore integration time:
-
Permalink:
Skobyn/cortex-m@06b4343925e32f979f775e01defa3ce079e2af6a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Skobyn
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-myelin-pypi.yml@06b4343925e32f979f775e01defa3ce079e2af6a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file myelin_ai-1.3.0-py3-none-any.whl.
File metadata
- Download URL: myelin_ai-1.3.0-py3-none-any.whl
- Upload date:
- Size: 101.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e38d213bf2079c1cc87c3221625505f11b51f00ef0eaf39910bdf7a366851de
|
|
| MD5 |
b81ad4484d0d0395190015f6a337b629
|
|
| BLAKE2b-256 |
4cb97826933e731843b4151b2466ab3df49d56971deb6de5faa06ec204ae7c7b
|
Provenance
The following attestation bundles were made for myelin_ai-1.3.0-py3-none-any.whl:
Publisher:
release-myelin-pypi.yml on Skobyn/cortex-m
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
myelin_ai-1.3.0-py3-none-any.whl -
Subject digest:
6e38d213bf2079c1cc87c3221625505f11b51f00ef0eaf39910bdf7a366851de - Sigstore transparency entry: 1604536850
- Sigstore integration time:
-
Permalink:
Skobyn/cortex-m@06b4343925e32f979f775e01defa3ce079e2af6a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Skobyn
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-myelin-pypi.yml@06b4343925e32f979f775e01defa3ce079e2af6a -
Trigger Event:
workflow_dispatch
-
Statement type: