This document describes the four agent-facing operations exposed by the
Moneta handle (ARCHITECTURE.md §2). The signatures are verbatim from
MONETA.md §2.1 and are the locked interface — changes require §9
escalation.
This doc reflects the v1.1.0+ handle API. Pre-v1.1.0 module-level
calls (moneta.init(), moneta.deposit(...)) are gone — see
SURGERY_complete.md for the singleton → handle migration record.
import moneta
from moneta import (
Moneta,
MonetaConfig,
Memory,
EntityState,
MonetaResourceLockedError,
ProtectedQuotaExceededError,
)smoke_check is also exported as a harness-level diagnostic — it is not
part of the agent surface.
Every Moneta consumer constructs a handle from a MonetaConfig and
holds it for the lifetime of its substrate boundary:
import moneta
# Test / ephemeral usage — auto-generated unique storage_uri
with moneta.Moneta(moneta.MonetaConfig.ephemeral()) as m:
...
# Production / explicit storage boundary
config = moneta.MonetaConfig(
storage_uri="moneta-local:///var/moneta/agent-a",
half_life_seconds=3600,
snapshot_path=Path("/var/moneta/ecs.json"),
wal_path=Path("/var/moneta/wal.jsonl"),
use_real_usd=True,
usd_target_path=Path("/var/moneta/cortex"),
)
with moneta.Moneta(config) as m:
...Moneta() (no args) raises TypeError by design — every consumer
declares its storage boundary on line one. There are no implicit
defaults (Round 3 §5.3 ruling).
Two handles on the same storage_uri raise MonetaResourceLockedError
synchronously at the second constructor call. The collision check is
in-memory (_ACTIVE_URIS set), not file-locks. Different storage_uri
values yield physically distinct substrates — see
Multi-instance below.
Deposit a new memory. Returns a newly minted EntityID (UUID).
eid = m.deposit(
payload="The user prefers concise explanations.",
embedding=[0.12, -0.45, 0.78, ...], # from your embedder
protected_floor=0.0, # 0.0 = ephemeral, >0 = pinned
)Fresh memories start at Utility = 1.0 (Phase 1 convention, Pass 2
judgment call #2). If protected_floor > 0.0 and the handle already
holds config.quota_override protected memories (default 100), deposit
raises ProtectedQuotaExceededError. The quota is per-handle in v1.1.0+
(it was a module-level constant in the singleton era).
Retrieve the top-k memories most relevant to embedding. The retrieval
flow is:
- Decay all live entities (ARCHITECTURE.md §4 eval point 1)
- Fetch from the shadow vector index (cosine similarity)
- Rerank by
cosine_similarity × utilityso decayed memories are naturally demoted - Return the top
limitMemoryobjects
results = m.query(embedding=[0.12, -0.45, 0.78, ...], limit=5)
for memory in results:
print(memory.payload, memory.utility, memory.attended_count)Register agent attention for one or more entities. This is an append-only log write — the ECS is not updated synchronously (ARCHITECTURE.md §5.1). Updates apply at the sleep pass.
m.signal_attention({
eid_a: 0.3,
eid_b: 0.1,
})If durability is enabled (snapshot_path + wal_path set on the
config), each signal is also fsync'd to the WAL before return.
Return every memory currently in STAGED_FOR_SYNC state — i.e.
memories that a sleep pass has selected for USD authoring but not yet
committed. Phase 3 also surfaces the intended USD target sublayer via
Memory.usd_link once authored.
pending = m.get_consolidation_manifest()
print(f"{len(pending)} memories pending USD commit")Drains the attention log, applies decay (eval points 2 + 3), classifies volatile entities (prune / stage / keep), executes sequential writes for staged batches (≤ 500 prims per batch). Returns counts of pruned and staged entities for the pass.
result = m.run_sleep_pass()
print(f"pruned={result.pruned} staged={result.staged}")run_sleep_pass() is the harness's lever — it is not one of the four
agent operations and is not part of the locked agent surface.
@dataclass(frozen=True)
class Memory:
entity_id: UUID
payload: str
semantic_vector: list[float] # snake_case of SemanticVector
utility: float # [0.0, 1.0], decay target
attended_count: int # cumulative reinforcement
protected_floor: float # decay floor
last_evaluated: float # wall-clock unix seconds
state: EntityState # VOLATILE / STAGED_FOR_SYNC / CONSOLIDATED
usd_link: Optional[object] = None # Phase 3: SdfPath when consolidatedFrozen — mutations flow through signal_attention, never through this
object.
VOLATILE — fresh deposit, fully in hot tier
↓
STAGED_FOR_SYNC — Consolidation has selected for USD authoring
↓
CONSOLIDATED — successfully authored to disk; in hot tier until pruned
There is no PRUNED member — pruned entities are removed from the ECS
and the vector index outright. The codeless schema reserves a "pruned"
token in priorState allowedTokens for forward use, but no current
code path produces it.
v1.1.0 lifted the singleton constraint — N substrates per Python
process is now first-class:
# Two agents in the same process, each with its own substrate
with moneta.Moneta(moneta.MonetaConfig(storage_uri="moneta-local:///agent-a")) as m_a, \
moneta.Moneta(moneta.MonetaConfig(storage_uri="moneta-local:///agent-b")) as m_b:
m_a.deposit(...) # invisible to m_b
m_b.deposit(...) # invisible to m_aEach handle owns its own ecs, attention, vector_index, and
authoring_target — they are physically distinct objects. A
protected-quota slot consumed in m_a does not consume m_b's quota.
Tests covering multi-instance: tests/test_twin_substrate.py and
tests/test_twin_substrate_adversarial.py.
Moneta's durability model is WAL-lite + periodic snapshot, per the
MONETA.md §5 risk #4 mitigation. The target is: survive kill -9 with
at most ~30 seconds of volatile state lost.
| Operation | Durability coverage | Medium |
|---|---|---|
deposit |
Snapshot-covered | Captured by the next run_sleep_pass that writes the ECS snapshot |
signal_attention |
WAL-covered | Every call fsync'd to the JSONL WAL when durability is enabled |
query |
Not applicable (read-only) | — |
get_consolidation_manifest |
Not applicable (read-only) | — |
Deposits made after the last successful snapshot and before a kill -9
are lost on restart. The window width is whatever wall-clock time
elapsed since the last run_sleep_pass (sleep-pass-driven cadence) or
~30 seconds (background-snapshot cadence).
This is a deliberately accepted risk: signals (high-frequency, cheap) are fully durable; deposits (low-frequency, expensive) accept a small loss window. MONETA.md §5 risk #4 documents this trade-off as "WAL-lite periodic snapshot, accept small volatility window."
When Moneta(config) is constructed with snapshot_path and wal_path
set on the config and those files exist on disk:
- The latest snapshot is loaded into a fresh ECS.
- The shadow vector index is rebuilt from the hydrated ECS rows — the vector index does not persist independently in v1.x; it is a pure shadow.
- WAL entries with
timestamp > snapshot_created_atare replayed into the attention log. They apply on the nextrun_sleep_pass. - Pre-snapshot WAL entries are filtered out (they are already captured by the snapshot).
If no snapshot exists (cold start, or kill -9 before the first
successful pass), construction returns an empty ECS.
- Python ≥ 3.11 — required by
pyproject.toml. - CPython with the GIL enabled —
AttentionLog.__init__raisesRuntimeErroron free-threaded interpreters (PEP 703). The lock-free swap-and-drain correctness argument depends on the GIL; the guard converts a silent correctness failure into a loud construction-time error. Verified bytests/test_attention_log_gil_guard.py. A free-threaded port is carried forward to a future surgery — track via MONETA.md §9 Trigger 2. - OpenUSD 0.25.5 (
pxrPython bindings) — required only whenMonetaConfig.use_real_usd=True. Withoutpxr, theMockUsdTargetfallback is selected and 7 pxr-gated tests skip rather than fail. Run pxr-gated tests underhython(or any pxr-capable interpreter). - Codeless schema registration — for typed-prim recognition in
usdviewand downstream consumers, setPXR_PLUGINPATH_NAME=path/to/Moneta/schemabefore launching. The schema is otherwise schema-blind on write but unrecognized on read.
MonetaResourceLockedError— raised when constructing a second handle on astorage_urithat is already held by a live handle in the same process. Subclass ofRuntimeError.ProtectedQuotaExceededError— raised when a protecteddeposit(protected_floor > 0) would exceedconfig.quota_override(default 100). Subclass ofRuntimeError.TypeError— raised byMoneta()(no args) and by anydeposit/query/signal_attentioncall with a missing required argument.
import moneta
with moneta.Moneta(moneta.MonetaConfig.ephemeral()) as m:
eid = m.deposit("hello world", [1.0, 0.0, 0.0])
results = m.query([1.0, 0.0, 0.0], limit=5)
assert results[0].entity_id == eid
m.signal_attention({eid: 0.5})
# A sleep pass applies the attention and runs selection.
result = m.run_sleep_pass()
print(f"pruned={result.pruned} staged={result.staged}")
print(m.get_consolidation_manifest()) # [] — single fresh memory, not stagedsmoke_check() in src/moneta/api.py exercises the full canonical
flow end-to-end and is the lowest-friction way to sanity-check after a
change. It is NOT a substitute for the test suite.
ARCHITECTURE.md— locked specMONETA.md— blueprint (phasing, lineage, risks)docs/decay-tuning.md— λ tuning guidedocs/substrate-conventions.md— shared conventions with OctaviusSURGERY_complete.md—v1.1.0singleton → handle migration recordSURGERY_complete_codeless_schema.md—v1.2.0-rc1codeless schema migration