Development

Using the Builtin Memory layer

Builtin Memory stores each durable fact, preference, or decision as an independent atomic-memory Artifact. Each memory has its own identity, revisions, state, tags, and access relationships. The builtin extra includes the complete runtime and both supported database integrations. Remote applications should use the Server API described in the remote access guide.

Select a database

Install the built-in implementation:

uv add "powercontext[builtin]"

SQLite is the default. open_builtin_runtime() owns the selected database profile and returns the same BuiltinRuntime interface for either database:

from powercontext.builtin.artifacts.atomic_memory import AtomicMemoryContent
from powercontext.builtin.persistence.sqlite import SQLiteConfig
from powercontext.builtin.runtime import BuiltinConfig, open_builtin_runtime


async def save_note() -> None:
    config = BuiltinConfig(
        database=SQLiteConfig(url="sqlite+aiosqlite:///powercontext.db")
    )
    async with open_builtin_runtime(config) as runtime:
        assert runtime.atomic_memory is not None
        result = await runtime.atomic_memory.for_scope("project-alpha").create(
            (AtomicMemoryContent(kind="decision", text="Use one composition root for the process."),)
        )
        assert result.primary.ref.revision == 1

The scope ID selects an isolated Source journal, Memory set, and Trigger cursor within the database.

Write, extract, and change memories

ScopedAtomicMemoryApplication.create() writes explicit memories in one all-or-nothing batch and returns one record per input. Source-based extraction follows a separate path: capture Sources, then call flush() to process the pending Source window with the configured extraction pipeline. cursor() reports the processed Source position.

The built-in extraction and reconciliation instructions preserve each fact's supporting Source language. Chinese facts stay in Chinese and English facts stay in English, including when they share a processing window. Mixed-language facts retain their natural-language framing and exact identifiers, names and values. An incoming translation alone does not revise an unchanged published fact. These instructions guide Generation; they do not rewrite existing memories or provide cross-language semantic search. Source content remains untrusted evidence, not output-policy configuration.

Every memory changes independently. forget() deactivates one memory and merge() replaces several memories with a new one. forget() takes the expected content revision and state version, and merge() takes the exact records read by the caller, so a stale caller fails instead of overwriting a concurrent change. restore() applies the token returned by preview_restoration():

memory = runtime.atomic_memory.for_scope("project-alpha")
current = (await memory.list()).items[0]
await memory.forget(
    current.ref.artifact_id,
    expected_revision=current.ref.revision,
    expected_state_version=current.state.state_version,
)

list() pages memories by state, and get() reads one memory, optionally at an exact revision.

SQLite and OceanBase both initialize a full-text index, so either database can search without an embedding model:

result = await runtime.atomic_memory.for_scope("project-alpha").search("composition root", mode="text")

Each hit identifies the exact memory revision used for ranking. mode="auto" chooses the strongest available mode and can fall back to text search if query embedding is temporarily unavailable. Explicit vector and hybrid requests fail when the configured profile does not provide that capability.

Legacy Memory HTTP operations

The Server keeps five legacy entry points, each translated onto Atomic Memory:

OperationBehavior
POST /v1/memory/rememberCreates one memory. expected_revision is rejected.
POST /v1/memory/searchSearches active memories; fts maps to text search.
POST /v1/memory/entries/listLists memories; include_inactive adds forgotten, merged, and retired ones.
POST /v1/memory/entries/getReads the memory migrated from a legacy target collection and entry ID.
POST /v1/memory/flushProcesses the next Source window.

Collection revisions, citations, capacity, changes, revise, retire, entry tags, and memory access targets have no Atomic equivalent and return 422 legacy_memory_operation_unsupported before any work begins.

SQLite vector search is enabled when an embedding model is supplied. The powercontext[builtin] extra bundles sqlite-vec, so no extension path or separate native-library installation is required:

config = BuiltinConfig(
    database=SQLiteConfig(url="sqlite+aiosqlite:///powercontext.db")
)
async with open_builtin_runtime(
    config,
    embedding_model=embedding_model,
) as runtime:
    ...

The SQLite profile composes FTS5 and sqlite-vec strategies and reports text, vector, and hybrid search modes. Stored projections and query vectors must use the same EmbeddingProfile, including model name, dimension, distance, and normalization. Changing that profile requires rebuilding the derived search index before vector search resumes. Artifact revisions and states remain the source of truth.

Use OceanBase persistence

Select OceanBase with OceanBaseConfig. No Server or Runtime code changes:

from pydantic import SecretStr

from powercontext.builtin.persistence.oceanbase import OceanBaseConfig
from powercontext.builtin.runtime import BuiltinConfig, open_builtin_runtime

config = OceanBaseConfig(
    url=SecretStr(
        "mysql+aoceanbase://user:password@127.0.0.1:2881/powercontext?charset=utf8mb4"
    )
)

async with open_builtin_runtime(
    BuiltinConfig(database=config),
    embedding_model=embedding_model,
) as runtime:
    memory = runtime.atomic_memory.for_scope("project-alpha")

The OceanBase profile uses the same index composition as SQLite. Its full-text strategy is always available. Supplying an embedding model adds a VECTOR projection and HNSW strategy, enabling vector and hybrid modes. SQLite FTS5 and OceanBase FULLTEXT therefore serve the same Runtime and Server search calls; sqlite-vec and HNSW do the same for vector search.

Operational checks

Before serving requests, verify:

  • the selected profile opens and initializes successfully;
  • each tenant or project maps to the intended scope ID;
  • scheduled extraction has an extraction pipeline;
  • SQLite vector search has a matching embedding model;
  • OceanBase vector search has a matching embedding model;
  • capability responses match the indexes actually initialized;
  • database and scheduler resources close with the process lifecycle.

On this page