Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ desktop.ini
!schema/*.usdc
!schema/**/*.usda
!schema/**/*.usdc
# Bridge package schema is also source (codeless USD schema for Comfy-Cozy).
!bridge/schema/**/*.usda
!bridge/schema/**/*.usdc
snapshots/
data/
cache/
Expand Down
50 changes: 50 additions & 0 deletions bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# moneta-bridge

External adapter package wiring Comfy-Cozy session emissions into the
Moneta substrate. Comfy-Cozy is "frozen as law" — zero edits to that
repo. All wiring lives here.

**Status:** v0.1.0a0 — codeless USD schema and embedder landed.
`ingest.py` and `egress.py` not yet implemented.

## Layout

- `moneta_bridge/embedder.py` — `Embedder` Protocol + default
`SentenceTransformersEmbedder`.
- `schema/CozySchema.usda` — codeless USD schema for Comfy-Cozy
emissions (`CozyRoom`, `CozyMemory`).
- `schema/plugInfo.json` — schema registration.
- `tests/` — unit tests.

See [`../docs/bridge-readiness.md`](../docs/bridge-readiness.md) for
the readiness assessment that scoped this package.

## Embedder

Default: `sentence-transformers/all-MiniLM-L6-v2`, 384-dim,
`normalize_embeddings=True`. Chosen because it is offline (no API
key, no PII egress), deterministic given a fixed model version,
small (~80MB RAM, 384-dim cheap for cosine), and the de facto
standard in the agent-memory ecosystem. The embedder is exposed
behind a `Protocol` so swapping is one line.

```python
from moneta_bridge import SentenceTransformersEmbedder

embedder = SentenceTransformersEmbedder()
vec = embedder.embed("the user prefers concise answers")
assert len(vec) == 384
```

## Usage with Moneta

```python
import moneta
from moneta_bridge import SentenceTransformersEmbedder

embedder = SentenceTransformersEmbedder()
with moneta.Moneta(moneta.MonetaConfig.ephemeral()) as m:
text = "remember this fact"
eid = m.deposit(text, embedder.embed(text))
hits = m.query(embedder.embed("what do I know about facts?"))
```
10 changes: 10 additions & 0 deletions bridge/moneta_bridge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""moneta-bridge — Comfy-Cozy <-> Moneta adapter package.

Re-exports the embedder Protocol and the default
SentenceTransformersEmbedder. The default embedder lazily imports
sentence-transformers at construction, so this module is importable
without sentence-transformers installed.
"""
from .embedder import Embedder, SentenceTransformersEmbedder

__all__ = ["Embedder", "SentenceTransformersEmbedder"]
78 changes: 78 additions & 0 deletions bridge/moneta_bridge/embedder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Embedder Protocol + default SentenceTransformersEmbedder.

The bridge does not assume Comfy-Cozy provides embeddings; the
default path is "bridge embeds the payload itself" using a local
sentence-transformers model.

Choice rationale (Architect-on-deck decision, locked):
- Offline: no API key, no network, no PII egress to a third party.
- Deterministic given a fixed model version + seed.
- 384-dim is cheap for cosine similarity in Moneta's vector index.
- The de facto standard in the agent-memory ecosystem
(mem0, llama-index, langchain).

The Embedder Protocol allows callers to swap in any embedder without
patching bridge code — useful for offline/air-gapped deployments
that ship a different model, or for callers who want OpenAI / BGE
embeddings.

The sentence-transformers import is deferred to construction time so
this module is importable on machines that do not have
sentence-transformers installed (e.g., CI workers running pure-Python
tests against the Protocol shape).
"""
from __future__ import annotations

from typing import List, Protocol, runtime_checkable


@runtime_checkable
class Embedder(Protocol):
"""Contract for any embedder usable by the bridge.

Implementations must:
- Expose ``dim`` as the integer embedding dimensionality.
- Implement ``embed(text)`` returning a ``List[float]`` of
length ``dim``.
- Implement ``embed_batch(texts)`` returning a list of
vectors, each ``List[float]`` of length ``dim``.

Embeddings should be L2-normalized so cosine similarity reduces
to dot product (matches Moneta's vector_index expectations).
"""

@property
def dim(self) -> int: ...

def embed(self, text: str) -> List[float]: ...

def embed_batch(self, texts: List[str]) -> List[List[float]]: ...


class SentenceTransformersEmbedder:
"""Default embedder: sentence-transformers/all-MiniLM-L6-v2.

384-dim, L2-normalized. Lazy-imports sentence-transformers on
construction so the module is importable without the dep.
"""

def __init__(
self,
model_name: str = "sentence-transformers/all-MiniLM-L6-v2",
) -> None:
from sentence_transformers import SentenceTransformer

self._model = SentenceTransformer(model_name)
self._dim = int(self._model.get_sentence_embedding_dimension())

@property
def dim(self) -> int:
return self._dim

def embed(self, text: str) -> List[float]:
vec = self._model.encode(text, normalize_embeddings=True)
return [float(x) for x in vec.tolist()]

def embed_batch(self, texts: List[str]) -> List[List[float]]:
mat = self._model.encode(texts, normalize_embeddings=True)
return [[float(x) for x in row] for row in mat.tolist()]
22 changes: 22 additions & 0 deletions bridge/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "moneta-bridge"
version = "0.1.0a0"
description = "Comfy-Cozy <-> Moneta bridge: codeless USD schema, embedder, ingest/egress (in development)."
requires-python = ">=3.11"
dependencies = [
"sentence-transformers>=2.2.0",
]

[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff"]

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["."]
include = ["moneta_bridge*"]

[tool.pytest.ini_options]
pythonpath = ["."]
109 changes: 109 additions & 0 deletions bridge/schema/CozySchema.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#usda 1.0
(
subLayers = [
@usd/schema.usda@
]
)

over "GLOBAL" (
customData = {
string libraryName = "moneta_bridge"
string libraryPath = "./"
bool skipCodeGeneration = true
}
)
{
}

class CozyRoom "CozyRoom" (
inherits = </Typed>
customData = {
string className = "CozyRoom"
string schemaKind = "concreteTyped"
}
doc = """A Comfy-Cozy session/space container.

Authored by Comfy-Cozy when it flushes a session as USD; consumed by
the Moneta bridge ingester. The prim path is /Cozy/Room_<hex> where
<hex> is a uuid4 hex string (32 chars, no dashes). Substrate
convention #1 (docs/substrate-conventions.md): never construct prim
names from natural language — content lives in string attributes."""
)
{
token roomId (
doc = "UUID hex (32 chars) identifying this room."
)

double createdAt (
doc = "Unix seconds when the room first opened."
)

double closedAt (
doc = "Unix seconds when this stage was flushed."
)

token kind (
allowedTokens = ["conversation", "task", "session", "thread"]
doc = "Coarse classification of the room's purpose."
)

string topic (
doc = "Optional human-facing summary; may be empty."
)
}

class CozyMemory "CozyMemory" (
inherits = </Typed>
customData = {
string className = "CozyMemory"
string schemaKind = "concreteTyped"
}
doc = """A single memory minted inside a CozyRoom.

The prim path is /Cozy/Room_<roomHex>/Memory_<memoryHex>, both
UUID hex (substrate convention #1, never NL in prim names). The
bridge ingester reads each CozyMemory and calls
moneta.deposit(payload, embedding, protected_floor); attentionWeight
is forwarded as a deposit-time attention seed.

embeddingHint contract: empty array means "bridge will embed the
payload using its configured embedder." When non-empty, length MUST
equal the bridge embedder's dim (currently 384 for
sentence-transformers/all-MiniLM-L6-v2). The bridge does NOT pad or
truncate — a length mismatch is a contract violation and the bridge
raises."""
)
{
token memoryId (
doc = "UUID hex (32 chars) identifying this memory."
)

string payload (
doc = "Text content; this is what the bridge embeds and deposits."
)

double createdAt (
doc = "Unix seconds when the memory was minted in Comfy-Cozy."
)

float protectedFloor (
doc = "Pinning hint forwarded to moneta.deposit; 0.0 = unprotected."
)

float[] embeddingHint (
doc = "Optional pre-computed embedding. Empty array = bridge embeds. Non-empty length must match bridge embedder dim (384 for default MiniLM-L6-v2)."
)

float attentionWeight (
doc = "Initial attention seed forwarded to signal_attention; 0.0 = no seed."
)

token kind (
allowedTokens = ["event", "fact", "preference", "task", "observation", "decision"]
doc = "Coarse classification of what this memory captures."
)

string[] sourceRefs (
doc = "Opaque pointers back into Comfy-Cozy. Bridge round-trips them; does not interpret."
)
}
37 changes: 37 additions & 0 deletions bridge/schema/plugInfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"Plugins": [
{
"Info": {
"Types": {
"MonetaBridgeCozyRoom": {
"alias": {
"UsdSchemaBase": "CozyRoom"
},
"autoGenerated": true,
"bases": [
"UsdTyped"
],
"schemaIdentifier": "CozyRoom",
"schemaKind": "concreteTyped"
},
"MonetaBridgeCozyMemory": {
"alias": {
"UsdSchemaBase": "CozyMemory"
},
"autoGenerated": true,
"bases": [
"UsdTyped"
],
"schemaIdentifier": "CozyMemory",
"schemaKind": "concreteTyped"
}
}
},
"LibraryPath": "",
"Name": "moneta_bridge",
"ResourcePath": ".",
"Root": ".",
"Type": "resource"
}
]
}
Empty file added bridge/tests/__init__.py
Empty file.
Loading