Skip to main content

Involuntary, multi-dimensional memory recall for AI agents

Project description

Ember

Involuntary, multi-dimensional memory recall for AI agents.

CI PyPI License Python

Memory is not search. Every AI memory system today retrieves on demand — user asks, system fetches. Ember is the opposite. Memory that happens to you. No intent required.

Multi-dimensional ignition means a single keyword never triggers recall. It requires convergence across semantic, emotional, sensory, temporal, spatial, relational, and musical dimensions — the way human memory actually works.

Quickstart

pip install ember-experiences
from ember import Ember

ember = Ember()

# Index a memory
ember.index(
    "Summers growing up in Philadelphia running around until the street lights come on. "
    "Chasing fireflies, thunder rumbling, BBQ smoke in the air.",
    emotions=["nostalgic", "warm", "alive"],
    sensory={"visual": ["fireflies"], "olfactory": ["bbq", "fireworks"], "auditory": ["thunder"]},
    location="Levittown, PA",
    season="summer",
    era="childhood",
    importance=0.85,
)

# Check for ignition (run on every message)
results = ember.check("Lightning bugs on the porch, someone grilling down the street...")

for r in results:
    print(f"{r.recall_intensity.upper()} — score {r.ignition_score:.2f}, {r.dimensions_fired} dims fired")

Zero config. No API keys. No database server. SQLite + MiniLM out of the box.

Architecture

                         ┌──────────────────────────────────────────┐
                         │            Incoming Message              │
                         └────────────────┬─────────────────────────┘
                                          │
                                          ▼
                         ┌──────────────────────────────────────────┐
                         │        Signal Constellation              │
                         │   Extract 7 dimensions from message:     │
                         │   semantic · emotional · sensory         │
                         │   temporal · spatial · relational        │
                         │   musical                                │
                         └────────────────┬─────────────────────────┘
                                          │
                         ┌────────────────▼─────────────────────────┐
                         │          Vector Search                   │
                         │   Find candidate embers by embedding     │
                         │   similarity (cosine distance)           │
                         └────────────────┬─────────────────────────┘
                                          │
                         ┌────────────────▼─────────────────────────┐
                         │         6 Ignition Gates                 │
                         │                                          │
                         │   1. Refractory period (24h cooldown)    │
                         │   2. Thematic refractory (4h dampen)     │
                         │   3. Semantic floor (min relevance)      │
                         │   4. Min dimensions fired (convergence)  │
                         │   5. Weighted composite + bonuses        │
                         │   6. Final threshold check               │
                         └────────────────┬─────────────────────────┘
                                          │
                         ┌────────────────▼─────────────────────────┐
                         │        IgnitionResult                    │
                         │   score · intensity · dimensions_fired   │
                         │   dimension_scores · original_text       │
                         └──────────────────────────────────────────┘

7 Dimensions

Every message is decomposed into a signal constellation — a multi-dimensional fingerprint:

Dimension What it measures How
Semantic Meaning similarity Cosine distance (MiniLM/OpenAI embeddings)
Emotional Valence, arousal, label overlap Lexicon-based extraction
Sensory Visual, auditory, olfactory, tactile, gustatory overlap Dictionary + pattern matching
Temporal Season, time of day, era alignment Keyword extraction
Spatial Location name or type match Known places + type keywords
Relational Shared people ID overlap
Musical Track, artist, or URI match Exact + fuzzy matching

3 Intensity Tiers

Tier Score Range Description
Faint 0.28 - 0.49 A whisper. Background coloring.
Warm 0.50 - 0.67 Clear recall. Specific details surface.
Vivid 0.68+ Full sensory immersion. The memory takes over.

API Reference

Ember(config=None, backend=None, embedding=None)

Create an Ember instance. All parameters optional — defaults to InMemoryBackend + MiniLM.

from ember import Ember, EmberConfig
from ember.backends.sqlite import SQLiteBackend

# Zero-config
ember = Ember()

# With SQLite persistence
ember = Ember(backend=SQLiteBackend())

# With custom config
config = EmberConfig(min_dimensions_fired=2, base_ignition_threshold=0.20)
ember = Ember(config=config)

# With OpenAI embeddings + PostgreSQL
from ember.embeddings.openai import OpenAIBackend
from ember.backends.postgres import PostgresBackend

ember = Ember(
    embedding=OpenAIBackend(model='text-embedding-3-small'),
    backend=PostgresBackend(dsn='postgresql://user:pass@host/ember'),
)

ember.index(text, **kwargs)

Index a memory as an ember.

ember.index(
    "The smell of pine and campfire smoke at 6am...",
    emotions=["peaceful", "free"],
    sensory={"olfactory": ["pine", "campfire"], "visual": ["mist", "sunrise"]},
    location="Yosemite",
    location_type="nature",
    season="fall",
    time_of_day="morning",
    era="college",
    importance=0.7,
    music_track="Into the Mystic",
    music_artist="Van Morrison",
)

ember.check(message, context=None)

Check if any stored embers ignite for the given message.

results = ember.check(
    "Early morning in the mountains, you can smell the fire pit from last night",
    context={"turn_count": 5, "people_ids": []}
)

for r in results:
    print(r.recall_intensity)   # 'faint', 'warm', or 'vivid'
    print(r.ignition_score)     # 0.0 - 1.0
    print(r.dimensions_fired)   # how many of 7 dimensions activated
    print(r.dimension_scores)   # per-dimension breakdown
    print(r.original_text)      # the memory that ignited

ember.load_preset(name)

Load a bundled ignition preset. Presets tune thresholds and weights for different use cases.

ember = Ember()
ember.load_preset("creative-writing")   # wider net, sensory-heavy
ember.load_preset("therapy-journal")    # gentle, emotional focus
ember.load_preset("family-archive")     # relational + temporal bias
ember.load_preset("companion")          # balanced for AI companions

ember.load_preset_file(path)

Load a custom preset from a YAML file.

ember.load_preset_file("./my-custom-preset.yaml")

ember.register_places(places) / ember.register_people(people)

Register known places and people for spatial and relational scoring.

ember.register_places({
    'levittown': 'Levittown, PA',
    'el porto': 'El Porto, CA',
})

ember.register_people({
    'dad': 'dad',
    'mom': 'mom',
})

Backends

Storage

Backend Install Use case
InMemoryBackend Built-in Testing, prototyping
SQLiteBackend pip install ember-experiences[sqlite] Production default, zero-config
PostgresBackend pip install ember-experiences[postgres] Production at scale
# SQLite (zero-config, file-based)
from ember.backends.sqlite import SQLiteBackend
backend = SQLiteBackend()  # ~/.ember/ember.db
backend = SQLiteBackend(db_path="/path/to/my.db")

# PostgreSQL + pgvector (production-grade)
from ember.backends.postgres import PostgresBackend
backend = PostgresBackend(dsn="postgresql://user:pass@localhost/ember")
backend = PostgresBackend(
    host="localhost", port=5432, dbname="ember",
    user="ember", password="secret",
    table_prefix="myapp_",  # optional namespace
)

Embeddings

Backend Install Use case
MiniLMBackend Built-in (via sentence-transformers) Default, local, free, ~12ms
OpenAIBackend pip install ember-experiences[openai] Cloud deployments, higher quality
# MiniLM (default — local, CPU, free)
from ember.embeddings.minilm import MiniLMBackend
embedding = MiniLMBackend()  # 384 dimensions

# OpenAI (cloud — requires API key)
from ember.embeddings.openai import OpenAIBackend
embedding = OpenAIBackend()  # reads OPENAI_API_KEY from env
embedding = OpenAIBackend(model='text-embedding-3-small')   # 1536 dims
embedding = OpenAIBackend(model='text-embedding-3-large')   # 3072 dims
embedding = OpenAIBackend(model='text-embedding-ada-002')   # 1536 dims (legacy)

Presets

Presets are YAML files that tune Ember's thresholds and weights for specific use cases.

Bundled Presets

Preset Focus Key traits
default Balanced Standard thresholds, 3 dims minimum
creative-writing Sensory, vivid Lower thresholds, 2 dims minimum, 5 max ignitions
therapy-journal Emotional, gentle Emotional weight 0.30, 48h refractory, 2 max ignitions
family-archive Relational, temporal Relational weight 0.20, 12h refractory
companion Emotional, intimate State modifiers tuned for intimate conversations

Custom Presets

Create a YAML file with any EmberConfig fields you want to override:

# my-preset.yaml
base_ignition_threshold: 0.20
min_dimensions_fired: 2

thresholds:
  sensory: 0.05
  emotional: 0.12

weights:
  semantic: 0.20
  emotional: 0.25
  sensory: 0.20
  relational: 0.08
  temporal: 0.10
  spatial: 0.05
  music: 0.12

intensity:
  faint_floor: 0.20
  vivid_floor: 0.60
ember.load_preset_file("my-preset.yaml")

Configuration

All config values are tunable via EmberConfig:

from ember import EmberConfig
from ember.config import ThresholdConfig, WeightConfig

config = EmberConfig(
    thresholds=ThresholdConfig(sensory=0.15, emotional=0.25),
    weights=WeightConfig(sensory=0.20, emotional=0.20, semantic=0.22,
                         relational=0.07, temporal=0.09, spatial=0.05, music=0.17),
    min_dimensions_fired=2,
    base_ignition_threshold=0.25,
)

# Or load from YAML / preset
config = EmberConfig.from_preset("creative-writing")
config = EmberConfig.from_yaml("path/to/config.yaml")

Custom Backend Interface

Storage Backend (4 methods)

from ember.backends.base import StorageBackend

class MyBackend(StorageBackend):
    def vector_search(self, embedding: list[float], threshold: float, limit: int) -> list[dict]: ...
    def get_recent_ignitions(self, hours: int) -> list[dict]: ...
    def store_ember(self, row: dict) -> dict: ...
    def record_ignition(self, ignition: dict) -> None: ...

Embedding Backend (3 methods)

from ember.embeddings.base import EmbeddingBackend

class MyEmbedding(EmbeddingBackend):
    def embed(self, text: str) -> list[float]: ...
    def embed_batch(self, texts: list[str]) -> list[list[float]]: ...
    def dimensions(self) -> int: ...

Development

git clone https://github.com/ember-experiences/ember-experiences.git
cd ember-experiences
python -m venv venv && source venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ember_experiences-0.2.0.tar.gz (44.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ember_experiences-0.2.0-py3-none-any.whl (37.7 kB view details)

Uploaded Python 3

File details

Details for the file ember_experiences-0.2.0.tar.gz.

File metadata

  • Download URL: ember_experiences-0.2.0.tar.gz
  • Upload date:
  • Size: 44.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ember_experiences-0.2.0.tar.gz
Algorithm Hash digest
SHA256 cad4f351e171f9071dbe5c094c55e474723045a6b19d0b5b8f5d97604df77f9b
MD5 9893444c402b1014d03e8aea67280c8c
BLAKE2b-256 57b8a4dbf94a3b1aff15279a986def685f1ffbb0bc32fa65150b888949ff86f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for ember_experiences-0.2.0.tar.gz:

Publisher: publish.yml on ember-experiences/ember-experiences

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ember_experiences-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for ember_experiences-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3306f49e6a9126b8a7ce589a426f86357f95299cc27e28d4ecf8a49d149497e9
MD5 eaa1fd93f9046dc5e10780ec9b7b07a0
BLAKE2b-256 a901af8264ced584fb2254bbf83b92f269a9cd4a6b73be58efc48bcef6fff0f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for ember_experiences-0.2.0-py3-none-any.whl:

Publisher: publish.yml on ember-experiences/ember-experiences

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page