Skip to content

Bucha11/axor-memory-sqlite

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

axor-memory-sqlite

CI PyPI Python License: MIT

SQLite memory provider for axor-core.

Persistent cross-session memory for governed agents. Zero extra dependencies — uses Python's built-in sqlite3.


Installation

pip install axor-memory-sqlite

Requires axor-core >= 0.1.0.


Quick Start

import axor_claude
from axor_core import AgentDefinition, AgentDomain, FragmentValue, MemoryFragment
from axor_memory_sqlite import SQLiteMemoryProvider

provider = SQLiteMemoryProvider("~/.axor/memory.db")

session = axor_claude.make_session(
    api_key="sk-ant-...",
    agent_def=AgentDefinition(
        name="my-agent",
        domain=AgentDomain.CODING,
        personality="You are an expert Python engineer.",
        memory_namespaces=("my-agent",),   # loaded at every session start
    ),
    memory_provider=provider,
)

result = await session.run("refactor the auth module")

# save what you want to remember next time
await provider.save([
    MemoryFragment(
        namespace="my-agent",
        key="auth_module_status",
        content="Auth module refactored to use JWT. Entry point: auth/jwt.py.",
        value=FragmentValue.KNOWLEDGE,
    ),
])

FragmentValue — what gets remembered how

Every MemoryFragment has a value that controls how the compressor treats it when it appears in ContextView:

Value Compressor behavior Typical use
PINNED Never touched — survives all turns User preferences, system rules, agent personality
KNOWLEDGE Dedup + error collapse only — no truncation Project docs, domain context, API specs
WORKING Normal compression pipeline Task findings, recent tool results
EPHEMERAL Aggressive compression — evicted first Debug output, one-turn scratch

Eviction priority: EPHEMERALWORKINGKNOWLEDGEPINNED (never evicted).


API

SQLiteMemoryProvider(db_path)

from axor_memory_sqlite import SQLiteMemoryProvider

provider = SQLiteMemoryProvider("~/.axor/memory.db")  # persistent
provider = SQLiteMemoryProvider(":memory:")            # in-memory, tests only

All methods are async. I/O runs in a thread pool — async callers are never blocked.

save(fragments)

Upsert by (namespace, key) — existing fragments are overwritten:

await provider.save([
    MemoryFragment(
        namespace="my-agent",
        key="project_stack",
        content="FastAPI + async SQLAlchemy + PostgreSQL",
        value=FragmentValue.PINNED,
        tags=["stack", "tech"],
    ),
])

load(query)

from axor_core import MemoryQuery, FragmentValue

# load all from namespace, pinned first
fragments = await provider.load(MemoryQuery(
    namespaces=("my-agent",),
    max_results=20,
))

# filter by value
pinned = await provider.load(MemoryQuery(
    namespaces=("my-agent",),
    values=(FragmentValue.PINNED, FragmentValue.KNOWLEDGE),
    max_results=10,
))

Results are ordered by priority (PINNED first) then by accessed_at descending.

delete(namespace, keys)

n = await provider.delete("my-agent", ["stale_key_1", "stale_key_2"])
print(f"deleted {n} fragments")

evict(namespace, values, max_age_seconds)

Remove stale fragments by value and/or age:

# evict all ephemeral fragments
await provider.evict("my-agent", values=(FragmentValue.EPHEMERAL,))

# evict working fragments older than 24 hours
await provider.evict(
    "my-agent",
    values=(FragmentValue.WORKING,),
    max_age_seconds=86400,
)

namespaces()

ns = await provider.namespaces()
# ["my-agent", "shared", "project-x"]

close()

await provider.close()

Call on shutdown. The provider can be used as an async context manager in tests:

async with SQLiteMemoryProvider(":memory:") as provider:
    await provider.save([...])

Namespaces

Namespaces are logical groupings — they do not require explicit creation. A namespace exists as soon as you save a fragment with that name.

Recommended pattern:

Namespace Content
{agent-name} Agent-specific memory — not shared
shared Shared across all agents in a project
project:{name} Project-specific facts
user:{id} Per-user preferences
# agent reads from its own namespace + shared
agent = AgentDefinition(
    name="billing-agent",
    memory_namespaces=("billing-agent", "shared"),
)

Schema

CREATE TABLE memory_fragments (
    namespace    TEXT    NOT NULL,
    key          TEXT    NOT NULL,
    content      TEXT    NOT NULL,
    value        TEXT    NOT NULL DEFAULT 'working',
    token_count  INTEGER NOT NULL DEFAULT 0,
    tags         TEXT    NOT NULL DEFAULT '[]',   -- JSON array
    created_at   TEXT    NOT NULL,
    accessed_at  TEXT    NOT NULL,
    metadata     TEXT    NOT NULL DEFAULT '{}',   -- JSON object
    PRIMARY KEY (namespace, key)
);

The database file is a standard SQLite file — readable with any SQLite tool.


Testing

Use :memory: for tests — no file created, no cleanup needed:

import pytest
from axor_memory_sqlite import SQLiteMemoryProvider
from axor_core import MemoryFragment, FragmentValue, MemoryQuery

@pytest.mark.asyncio
async def test_memory():
    p = SQLiteMemoryProvider(":memory:")

    await p.save([
        MemoryFragment(namespace="test", key="k1",
                       content="hello", value=FragmentValue.WORKING),
    ])
    results = await p.load(MemoryQuery(namespaces=("test",), max_results=5))
    assert len(results) == 1
    assert results[0].content == "hello"

    await p.close()

Requirements

  • Python 3.11+
  • axor-core >= 0.1.0
  • No extra dependencies — uses stdlib sqlite3 + asyncio

License

MIT

About

SQLite memory provider for axor-core with persistent cross-session storage and zero external dependencies.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages