Skip to content
Merged
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
35 changes: 35 additions & 0 deletions packages/data-layer/src/monitor_data/db/qdrant.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,41 @@ def get_client(self) -> QdrantClientLib:
raise RuntimeError("Qdrant client not connected. Call connect() first.")
return self._client

def embed_text(self, text: str) -> list[float]:
"""
Generate vector embedding for text.

WARNING: This is a PLACEHOLDER implementation that returns zero vectors!
In production, this MUST use a real embedding model (OpenAI, Anthropic, local).

Args:
text: Text to embed

Returns:
Vector embedding (list of floats)

Note:
Currently returns a zero vector of DEFAULT_VECTOR_SIZE.
This produces meaningless similarity scores and should NOT be deployed to production.
Set QDRANT_EMBEDDING_DISABLED=true in environment to suppress warning.
"""
import os
import logging

# Production safety check: warn if embeddings are disabled without acknowledgment
if os.getenv("QDRANT_EMBEDDING_DISABLED", "").lower() != "true":
logger = logging.getLogger(__name__)
logger.warning(
"PLACEHOLDER EMBEDDING IN USE! "
"Set QDRANT_EMBEDDING_DISABLED=true to acknowledge zero-vector embeddings, "
"or implement real embedding generation. "
"This will produce meaningless similarity scores!"
)

# Placeholder: return zero vector
# In production, call embedding API (OpenAI, Anthropic, etc.)
return [0.0] * DEFAULT_VECTOR_SIZE

def ensure_collection(self, collection_name: str) -> None:
"""
Ensure collection exists with correct configuration.
Expand Down
21 changes: 14 additions & 7 deletions packages/data-layer/src/monitor_data/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,7 @@
"mongodb_get_proposed_change": ["*"],
"mongodb_list_proposed_changes": ["*"],
"mongodb_update_proposed_change": ["CanonKeeper"],
# =========================================================================
# MONGODB OPERATIONS - Memories
# =========================================================================
"mongodb_create_memory": ["MemoryManager"],
"mongodb_get_memories": ["*"],
"mongodb_update_memory": ["MemoryManager"],
"mongodb_search_memories": ["*"],
# (Memory operations moved to DL-7 section below)
# =========================================================================
# MONGODB OPERATIONS - Character Sheets
# =========================================================================
Expand Down Expand Up @@ -192,6 +186,19 @@
"mongodb_update_resolution": ["Orchestrator", "CanonKeeper"],
"mongodb_delete_resolution": ["CanonKeeper"],
# =========================================================================
# MONGODB OPERATIONS - Character Memories (DL-7)
# =========================================================================
"mongodb_create_memory": ["*"],
"mongodb_get_memory": ["*"],
"mongodb_list_memories": ["*"],
"mongodb_update_memory": ["*"],
"mongodb_delete_memory": ["*"],
# =========================================================================
# QDRANT OPERATIONS - Memory Embeddings (DL-7)
# =========================================================================
"qdrant_embed_memory": ["*"],
"qdrant_search_memories": ["*"],
# =========================================================================
# COMPOSITE OPERATIONS
# =========================================================================
"composite_get_entity_full": ["*"],
Expand Down
25 changes: 24 additions & 1 deletion packages/data-layer/src/monitor_data/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,19 @@
# from monitor_data.schemas.entities import *
# from monitor_data.schemas.facts import *
# from monitor_data.schemas.scenes import *
# from monitor_data.schemas.memories import *
from monitor_data.schemas.memories import (
MemoryCreate,
MemoryUpdate,
MemoryFilter,
MemoryResponse,
MemoryListResponse,
MemoryEmbedRequest,
MemoryEmbedResponse,
MemorySearchRequest,
MemorySearchResult,
MemorySearchResponse,
)

# from monitor_data.schemas.sources import *
# from monitor_data.schemas.queries import *
# from monitor_data.schemas.composite import *
Expand Down Expand Up @@ -172,4 +184,15 @@
"CollectionInfo",
"CollectionInfoRequest",
"CollectionInfoResponse",
# Memory schemas
"MemoryCreate",
"MemoryUpdate",
"MemoryFilter",
"MemoryResponse",
"MemoryListResponse",
"MemoryEmbedRequest",
"MemoryEmbedResponse",
"MemorySearchRequest",
"MemorySearchResult",
"MemorySearchResponse",
]
176 changes: 176 additions & 0 deletions packages/data-layer/src/monitor_data/schemas/memories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""
Pydantic schemas for CharacterMemory operations.

LAYER: 1 (data-layer)
IMPORTS FROM: External libraries (pydantic, uuid, datetime) and base schemas
CALLED BY: mongodb_tools.py, qdrant_tools.py

These schemas define the data contracts for memory CRUD and vector operations.
Memories are subjective records belonging to specific entities (characters).
"""

from datetime import datetime
from typing import Optional, Dict, Any
from uuid import UUID

from pydantic import BaseModel, Field


# =============================================================================
# MEMORY SCHEMAS
# =============================================================================


class MemoryCreate(BaseModel):
"""Request to create a CharacterMemory document."""

entity_id: UUID = Field(description="Entity (character) who owns this memory")
text: str = Field(min_length=1, max_length=5000, description="Memory content")
scene_id: Optional[UUID] = Field(None, description="Scene where memory originated")
linked_fact_id: Optional[UUID] = Field(
None, description="Optional anchor to canonical Fact"
)
emotional_valence: float = Field(
default=0.0,
ge=-1.0,
le=1.0,
description="Emotional charge: -1.0 (negative) to 1.0 (positive)",
)
importance: float = Field(
default=0.5,
ge=0.0,
le=1.0,
description="Importance for recall: 0.0 (trivial) to 1.0 (critical)",
)
certainty: float = Field(
default=1.0,
ge=0.0,
le=1.0,
description="Certainty of memory: 0.0 (false) to 1.0 (certain)",
)
metadata: Dict[str, Any] = Field(
default_factory=dict, description="Additional memory metadata"
)


class MemoryUpdate(BaseModel):
"""Request to update a CharacterMemory document."""

importance: Optional[float] = Field(
None,
ge=0.0,
le=1.0,
description="Update importance (affects recall priority)",
)
certainty: Optional[float] = Field(
None, ge=0.0, le=1.0, description="Update certainty"
)
emotional_valence: Optional[float] = Field(
None, ge=-1.0, le=1.0, description="Update emotional charge"
)
metadata: Optional[Dict[str, Any]] = Field(None, description="Update metadata")


class MemoryFilter(BaseModel):
"""Filter for listing/searching memories."""

entity_id: Optional[UUID] = Field(None, description="Filter by entity")
scene_id: Optional[UUID] = Field(None, description="Filter by scene")
min_importance: Optional[float] = Field(
None, ge=0.0, le=1.0, description="Minimum importance threshold"
)
max_importance: Optional[float] = Field(
None, ge=0.0, le=1.0, description="Maximum importance threshold"
)
min_emotional_valence: Optional[float] = Field(
None, ge=-1.0, le=1.0, description="Minimum emotional valence"
)
max_emotional_valence: Optional[float] = Field(
None, ge=-1.0, le=1.0, description="Maximum emotional valence"
)
limit: int = Field(default=100, ge=1, le=1000)
offset: int = Field(default=0, ge=0)


class MemoryResponse(BaseModel):
"""Response with CharacterMemory data."""

memory_id: UUID
entity_id: UUID
text: str
scene_id: Optional[UUID]
linked_fact_id: Optional[UUID]
emotional_valence: float
importance: float
certainty: float
metadata: Dict[str, Any]
created_at: datetime
last_accessed: datetime
access_count: int


class MemoryListResponse(BaseModel):
"""Response with list of memories."""

memories: list[MemoryResponse]
total: int
limit: int
offset: int


# =============================================================================
# MEMORY VECTOR SCHEMAS (QDRANT)
# =============================================================================


class MemoryEmbedRequest(BaseModel):
"""Request to embed a memory in Qdrant."""

memory_id: UUID = Field(description="Memory UUID")
text: str = Field(min_length=1, max_length=5000, description="Memory text to embed")
entity_id: UUID = Field(description="Entity who owns this memory")
scene_id: Optional[UUID] = Field(None, description="Scene where memory originated")
importance: float = Field(default=0.5, ge=0.0, le=1.0)
metadata: Dict[str, Any] = Field(default_factory=dict)


class MemoryEmbedResponse(BaseModel):
"""Response after embedding a memory."""

memory_id: UUID
point_id: str # Qdrant point ID (typically str(memory_id))
collection: str = "memories"
success: bool


class MemorySearchRequest(BaseModel):
"""Request to search memories semantically."""

query_text: str = Field(min_length=1, max_length=5000, description="Search query")
entity_id: Optional[UUID] = Field(None, description="Filter by entity")
scene_id: Optional[UUID] = Field(None, description="Filter by scene")
min_importance: Optional[float] = Field(None, ge=0.0, le=1.0)
top_k: int = Field(default=10, ge=1, le=100, description="Number of results")


class MemorySearchResult(BaseModel):
"""Single memory search result with score."""

memory_id: UUID
entity_id: UUID
text: Optional[str] = Field(
None,
description="Memory text (not stored in Qdrant, requires MongoDB fetch)",
)
scene_id: Optional[UUID]
importance: float
score: float = Field(description="Similarity score (higher = more relevant)")
metadata: Dict[str, Any]


class MemorySearchResponse(BaseModel):
"""Response with semantic search results."""

results: list[MemorySearchResult]
query: str
top_k: int
Loading