remdb 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of remdb might be problematic. Click here for more details.
- rem/__init__.py +2 -0
- rem/agentic/README.md +650 -0
- rem/agentic/__init__.py +39 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +8 -0
- rem/agentic/context.py +148 -0
- rem/agentic/context_builder.py +329 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +107 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +151 -0
- rem/agentic/providers/phoenix.py +674 -0
- rem/agentic/providers/pydantic_ai.py +572 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +396 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +231 -0
- rem/api/README.md +420 -0
- rem/api/main.py +324 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +536 -0
- rem/api/mcp_router/server.py +213 -0
- rem/api/mcp_router/tools.py +584 -0
- rem/api/routers/auth.py +229 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/completions.py +281 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +124 -0
- rem/api/routers/chat/streaming.py +185 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +26 -0
- rem/auth/middleware.py +100 -0
- rem/auth/providers/__init__.py +13 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +455 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +126 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +566 -0
- rem/cli/commands/configure.py +497 -0
- rem/cli/commands/db.py +493 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1302 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +245 -0
- rem/cli/commands/schema.py +183 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +96 -0
- rem/config.py +237 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +64 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +628 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +243 -0
- rem/models/entities/__init__.py +43 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +35 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +191 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/user.py +85 -0
- rem/py.typed +0 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +128 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +16 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +806 -0
- rem/services/content/service.py +676 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +336 -0
- rem/services/dreaming/moment_service.py +264 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +120 -0
- rem/services/embeddings/worker.py +421 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +686 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +575 -0
- rem/services/postgres/__init__.py +23 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
- rem/services/postgres/register_type.py +352 -0
- rem/services/postgres/repository.py +337 -0
- rem/services/postgres/schema_generator.py +379 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +354 -0
- rem/services/rem/README.md +304 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +145 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +527 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +6 -0
- rem/services/session/compression.py +360 -0
- rem/services/session/reload.py +77 -0
- rem/settings.py +1235 -0
- rem/sql/002_install_models.sql +1068 -0
- rem/sql/background_indexes.sql +42 -0
- rem/sql/install_models.sql +1038 -0
- rem/sql/migrations/001_install.sql +503 -0
- rem/sql/migrations/002_install_models.sql +1202 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +583 -0
- rem/utils/__init__.py +43 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +423 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/markdown.py +16 -0
- rem/utils/model_helpers.py +236 -0
- rem/utils/schema_loader.py +336 -0
- rem/utils/sql_types.py +348 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +330 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +5 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- remdb-0.3.0.dist-info/METADATA +1455 -0
- remdb-0.3.0.dist-info/RECORD +187 -0
- remdb-0.3.0.dist-info/WHEEL +4 -0
- remdb-0.3.0.dist-info/entry_points.txt +2 -0
rem/mcp_server.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
In-process MCP server for agent tool loading.
|
|
3
|
+
|
|
4
|
+
This module creates an MCP server instance that is imported directly by agents.
|
|
5
|
+
NO subprocess, NO stdio transport - just direct function calls in the same process.
|
|
6
|
+
|
|
7
|
+
The server exposes REM tools and resources that agents can call directly.
|
|
8
|
+
Services are initialized lazily on first tool call (see tools.py).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
|
|
13
|
+
from rem.api.mcp_router.server import create_mcp_server
|
|
14
|
+
|
|
15
|
+
# Create MCP server instance (is_local=True for local/in-process usage)
|
|
16
|
+
# No initialization needed - tools handle lazy init of services
|
|
17
|
+
mcp = create_mcp_server(is_local=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
# When run directly via CLI, start in stdio mode with service initialization
|
|
22
|
+
from rem.api.mcp_router.tools import init_services
|
|
23
|
+
from rem.services.postgres import get_postgres_service
|
|
24
|
+
from rem.services.rem import RemService
|
|
25
|
+
|
|
26
|
+
async def run_stdio():
|
|
27
|
+
"""Run MCP server in stdio mode with services."""
|
|
28
|
+
db = get_postgres_service()
|
|
29
|
+
if not db:
|
|
30
|
+
raise RuntimeError("PostgreSQL service not available")
|
|
31
|
+
await db.connect()
|
|
32
|
+
rem_service = RemService(postgres_service=db)
|
|
33
|
+
init_services(postgres_service=db, rem_service=rem_service)
|
|
34
|
+
|
|
35
|
+
# Run server
|
|
36
|
+
await mcp.run_async(transport="stdio")
|
|
37
|
+
|
|
38
|
+
# Cleanup
|
|
39
|
+
await db.disconnect()
|
|
40
|
+
|
|
41
|
+
asyncio.run(run_stdio())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM Core Models
|
|
3
|
+
|
|
4
|
+
Core types and base models for the REM (Resource-Entity-Moment) system.
|
|
5
|
+
|
|
6
|
+
REM is a unified memory infrastructure that enables LLM-augmented iterated retrieval
|
|
7
|
+
through natural language interfaces. Unlike traditional databases that assume single-shot
|
|
8
|
+
queries with known schemas, REM is architected for multi-turn conversations where:
|
|
9
|
+
|
|
10
|
+
1. LLMs don't know internal IDs - they work with natural language labels
|
|
11
|
+
2. Information needs emerge incrementally through exploration
|
|
12
|
+
3. Multi-stage exploration is essential: find entity → explore neighborhood → traverse relationships
|
|
13
|
+
|
|
14
|
+
Key Design Principles:
|
|
15
|
+
- Graph edges reference entity LABELS (natural language), not UUIDs
|
|
16
|
+
- Natural language surface area for all queries
|
|
17
|
+
- Schema-agnostic operations (LOOKUP, FUZZY, TRAVERSE)
|
|
18
|
+
- O(1) performance guarantees for entity resolution
|
|
19
|
+
- Iterated retrieval with stage tracking and memos
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .core_model import CoreModel
|
|
23
|
+
from .inline_edge import InlineEdge, InlineEdges
|
|
24
|
+
from .rem_query import (
|
|
25
|
+
FuzzyParameters,
|
|
26
|
+
LookupParameters,
|
|
27
|
+
QueryType,
|
|
28
|
+
RemQuery,
|
|
29
|
+
SearchParameters,
|
|
30
|
+
SQLParameters,
|
|
31
|
+
TraverseParameters,
|
|
32
|
+
TraverseResponse,
|
|
33
|
+
TraverseStage,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"CoreModel",
|
|
38
|
+
"InlineEdge",
|
|
39
|
+
"InlineEdges",
|
|
40
|
+
"QueryType",
|
|
41
|
+
"LookupParameters",
|
|
42
|
+
"FuzzyParameters",
|
|
43
|
+
"SearchParameters",
|
|
44
|
+
"SQLParameters",
|
|
45
|
+
"TraverseParameters",
|
|
46
|
+
"RemQuery",
|
|
47
|
+
"TraverseStage",
|
|
48
|
+
"TraverseResponse",
|
|
49
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CoreModel - Base model for all REM entities.
|
|
3
|
+
|
|
4
|
+
All REM entities (Resources, Messages, Users, Files, Moments) inherit from CoreModel,
|
|
5
|
+
which provides:
|
|
6
|
+
- Identity (id - UUID or string, generated per model type)
|
|
7
|
+
- Temporal tracking (created_at, updated_at, deleted_at)
|
|
8
|
+
- Multi-tenancy (tenant_id)
|
|
9
|
+
- Ownership (user_id)
|
|
10
|
+
- Graph connectivity (graph_edges)
|
|
11
|
+
- Flexible metadata (metadata dict)
|
|
12
|
+
- Tagging (tags list)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from typing import Optional, Union
|
|
17
|
+
from uuid import UUID
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CoreModel(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
Base model for all REM entities.
|
|
25
|
+
|
|
26
|
+
Provides system-level fields for:
|
|
27
|
+
- Identity management (id)
|
|
28
|
+
- Temporal tracking (created_at, updated_at, deleted_at)
|
|
29
|
+
- Multi-tenancy isolation (tenant_id)
|
|
30
|
+
- Ownership tracking (user_id)
|
|
31
|
+
- Graph connectivity (graph_edges)
|
|
32
|
+
- Flexible metadata storage (metadata, tags)
|
|
33
|
+
|
|
34
|
+
Note: ID generation is handled per model type, not by CoreModel.
|
|
35
|
+
Each entity model should generate IDs with appropriate prefixes or labels.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
id: Union[UUID, str, None] = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="Unique identifier (UUID or string, generated per model type). Generated automatically if not provided."
|
|
41
|
+
)
|
|
42
|
+
created_at: datetime = Field(
|
|
43
|
+
default_factory=lambda: datetime.now(timezone.utc).replace(tzinfo=None), description="Entity creation timestamp"
|
|
44
|
+
)
|
|
45
|
+
updated_at: datetime = Field(
|
|
46
|
+
default_factory=lambda: datetime.now(timezone.utc).replace(tzinfo=None), description="Last update timestamp"
|
|
47
|
+
)
|
|
48
|
+
deleted_at: Optional[datetime] = Field(
|
|
49
|
+
default=None, description="Soft deletion timestamp"
|
|
50
|
+
)
|
|
51
|
+
tenant_id: Optional[str] = Field(
|
|
52
|
+
default=None, description="Tenant identifier for multi-tenancy isolation"
|
|
53
|
+
)
|
|
54
|
+
user_id: Optional[str] = Field(
|
|
55
|
+
default=None, description="Owner user identifier (tenant-scoped)"
|
|
56
|
+
)
|
|
57
|
+
graph_edges: list[dict] = Field(
|
|
58
|
+
default_factory=list,
|
|
59
|
+
description="Knowledge graph edges stored as InlineEdge dicts",
|
|
60
|
+
)
|
|
61
|
+
metadata: dict = Field(
|
|
62
|
+
default_factory=dict, description="Flexible metadata storage"
|
|
63
|
+
)
|
|
64
|
+
tags: list[str] = Field(default_factory=list, description="Entity tags")
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Engram - Core memory model for REM.
|
|
3
|
+
|
|
4
|
+
Engrams are structured memory documents that represent captured experiences,
|
|
5
|
+
observations, or insights. They are fundamentally Resources with optional
|
|
6
|
+
attached Moments, following the unified schema from the engram specification.
|
|
7
|
+
|
|
8
|
+
Key Design Principles:
|
|
9
|
+
- Engrams ARE Resources (category="engram")
|
|
10
|
+
- Human-friendly labels in graph edges (not UUIDs)
|
|
11
|
+
- Upsert with JSON merge behavior (never overwrite)
|
|
12
|
+
- Dual indexing: SQL + vector embeddings handled by repository
|
|
13
|
+
- YAML-first for human readability
|
|
14
|
+
|
|
15
|
+
Data Flow:
|
|
16
|
+
1. Upload YAML/JSON engram (API or S3)
|
|
17
|
+
2. Parse into Resource model
|
|
18
|
+
3. Call repository.upsert() - automatically handles:
|
|
19
|
+
- SQL persistence
|
|
20
|
+
- Vector embedding generation
|
|
21
|
+
- Entity key index population
|
|
22
|
+
4. Create attached Moments (if present)
|
|
23
|
+
5. Link moments to parent engram via graph edges
|
|
24
|
+
|
|
25
|
+
Example Engram Structure:
|
|
26
|
+
```yaml
|
|
27
|
+
kind: engram
|
|
28
|
+
name: "Daily Team Standup"
|
|
29
|
+
category: "meeting"
|
|
30
|
+
summary: "Daily standup discussing sprint progress"
|
|
31
|
+
timestamp: "2025-11-16T09:00:00Z"
|
|
32
|
+
uri: "s3://recordings/2025/11/16/standup.m4a"
|
|
33
|
+
content: |
|
|
34
|
+
Daily standup meeting with engineering team...
|
|
35
|
+
|
|
36
|
+
graph_edges:
|
|
37
|
+
- dst: "Q4 Roadmap Discussion"
|
|
38
|
+
rel_type: "semantic_similar"
|
|
39
|
+
weight: 0.75
|
|
40
|
+
properties:
|
|
41
|
+
dst_name: "Q4 Roadmap Discussion"
|
|
42
|
+
dst_entity_type: "resource/meeting"
|
|
43
|
+
confidence: 0.75
|
|
44
|
+
|
|
45
|
+
moments:
|
|
46
|
+
- name: "Sprint Progress Review"
|
|
47
|
+
content: "Sarah reviewed completed tickets"
|
|
48
|
+
summary: "Sprint progress update from Sarah"
|
|
49
|
+
starts_timestamp: "2025-11-16T09:00:00Z"
|
|
50
|
+
ends_timestamp: "2025-11-16T09:05:00Z"
|
|
51
|
+
moment_type: "meeting"
|
|
52
|
+
emotion_tags: ["focused", "productive"]
|
|
53
|
+
topic_tags: ["sprint-progress", "velocity"]
|
|
54
|
+
present_persons:
|
|
55
|
+
- id: "sarah-chen"
|
|
56
|
+
name: "Sarah Chen"
|
|
57
|
+
role: "VP Engineering"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Graph Edge Labels:
|
|
61
|
+
- Use natural, human-friendly labels: "Sarah Chen", "Q4 Roadmap"
|
|
62
|
+
- NOT kebab-case unless it's a file path: "docs/api-spec.md"
|
|
63
|
+
- NOT UUIDs: "550e8400-e29b-41d4-a716-446655440000"
|
|
64
|
+
- Enables conversational queries: "LOOKUP Sarah Chen"
|
|
65
|
+
|
|
66
|
+
JSON Merge Behavior:
|
|
67
|
+
- Graph edges are MERGED, not replaced
|
|
68
|
+
- Metadata is MERGED, not replaced
|
|
69
|
+
- Arrays (tags) are MERGED and deduplicated
|
|
70
|
+
- Content/summary updated if provided
|
|
71
|
+
- Timestamps preserved if not provided
|
|
72
|
+
|
|
73
|
+
Best Practices:
|
|
74
|
+
- Use natural, descriptive names
|
|
75
|
+
- Include device metadata when available
|
|
76
|
+
- Use standard categories: diary, meeting, note, observation, conversation, media
|
|
77
|
+
- Attach moments for temporal segments
|
|
78
|
+
- Use appropriate relationship types in graph edges
|
|
79
|
+
- Always include timestamps in ISO 8601 format
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
from datetime import datetime, timezone
|
|
83
|
+
from typing import Optional
|
|
84
|
+
|
|
85
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
86
|
+
|
|
87
|
+
from .inline_edge import InlineEdge
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DeviceMetadata(BaseModel):
|
|
91
|
+
"""Device metadata for engram capture context."""
|
|
92
|
+
|
|
93
|
+
imei: Optional[str] = Field(
|
|
94
|
+
default=None,
|
|
95
|
+
description="Device IMEI identifier",
|
|
96
|
+
)
|
|
97
|
+
model: Optional[str] = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
description="Device model (e.g., 'iPhone 15 Pro', 'MacBook Pro')",
|
|
100
|
+
)
|
|
101
|
+
os: Optional[str] = Field(
|
|
102
|
+
default=None,
|
|
103
|
+
description="Operating system (e.g., 'iOS 18.1', 'macOS 14.2')",
|
|
104
|
+
)
|
|
105
|
+
app: Optional[str] = Field(
|
|
106
|
+
default=None,
|
|
107
|
+
description="Application name (e.g., 'Percolate Voice', 'Percolate Desktop')",
|
|
108
|
+
)
|
|
109
|
+
version: Optional[str] = Field(
|
|
110
|
+
default=None,
|
|
111
|
+
description="Application version",
|
|
112
|
+
)
|
|
113
|
+
location: Optional[dict] = Field(
|
|
114
|
+
default=None,
|
|
115
|
+
description="GPS location data (latitude, longitude, accuracy, altitude, etc.)",
|
|
116
|
+
)
|
|
117
|
+
network: Optional[dict] = Field(
|
|
118
|
+
default=None,
|
|
119
|
+
description="Network information (type, carrier, signal_strength)",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class EngramMoment(BaseModel):
|
|
124
|
+
"""
|
|
125
|
+
Moment attached to an engram.
|
|
126
|
+
|
|
127
|
+
Represents a temporal segment within an engram with specific
|
|
128
|
+
temporal boundaries, present persons, and contextual metadata.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
name: str = Field(
|
|
132
|
+
...,
|
|
133
|
+
description="Moment name (human-readable)",
|
|
134
|
+
)
|
|
135
|
+
content: str = Field(
|
|
136
|
+
...,
|
|
137
|
+
description="Moment content/description",
|
|
138
|
+
)
|
|
139
|
+
summary: Optional[str] = Field(
|
|
140
|
+
default=None,
|
|
141
|
+
description="Brief summary of the moment",
|
|
142
|
+
)
|
|
143
|
+
moment_type: Optional[str] = Field(
|
|
144
|
+
default=None,
|
|
145
|
+
description="Moment type (meeting, conversation, reflection, etc.)",
|
|
146
|
+
)
|
|
147
|
+
category: Optional[str] = Field(
|
|
148
|
+
default=None,
|
|
149
|
+
description="Moment category for grouping",
|
|
150
|
+
)
|
|
151
|
+
uri: Optional[str] = Field(
|
|
152
|
+
default=None,
|
|
153
|
+
description="Source URI (can include time fragment, e.g., 's3://file.m4a#t=0,300')",
|
|
154
|
+
)
|
|
155
|
+
starts_timestamp: Optional[datetime] = Field(
|
|
156
|
+
default=None,
|
|
157
|
+
description="Moment start time",
|
|
158
|
+
)
|
|
159
|
+
ends_timestamp: Optional[datetime] = Field(
|
|
160
|
+
default=None,
|
|
161
|
+
description="Moment end time",
|
|
162
|
+
)
|
|
163
|
+
emotion_tags: list[str] = Field(
|
|
164
|
+
default_factory=list,
|
|
165
|
+
description="Emotional context tags (focused, excited, concerned, etc.)",
|
|
166
|
+
)
|
|
167
|
+
topic_tags: list[str] = Field(
|
|
168
|
+
default_factory=list,
|
|
169
|
+
description="Topic tags in kebab-case (sprint-progress, api-design, etc.)",
|
|
170
|
+
)
|
|
171
|
+
present_persons: list[dict] = Field(
|
|
172
|
+
default_factory=list,
|
|
173
|
+
description="People present (Person objects with id, name, role)",
|
|
174
|
+
)
|
|
175
|
+
speakers: Optional[list[dict]] = Field(
|
|
176
|
+
default=None,
|
|
177
|
+
description="Speaker segments with text, speaker_identifier, timestamp, emotion",
|
|
178
|
+
)
|
|
179
|
+
location: Optional[str] = Field(
|
|
180
|
+
default=None,
|
|
181
|
+
description="GPS coordinates or descriptive location",
|
|
182
|
+
)
|
|
183
|
+
background_sounds: Optional[str] = Field(
|
|
184
|
+
default=None,
|
|
185
|
+
description="Ambient sounds description",
|
|
186
|
+
)
|
|
187
|
+
metadata: dict = Field(
|
|
188
|
+
default_factory=dict,
|
|
189
|
+
description="Additional moment metadata",
|
|
190
|
+
)
|
|
191
|
+
graph_edges: list[InlineEdge] = Field(
|
|
192
|
+
default_factory=list,
|
|
193
|
+
description="Knowledge graph edges for this moment",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class Engram(BaseModel):
|
|
198
|
+
"""
|
|
199
|
+
Structured memory document for REM.
|
|
200
|
+
|
|
201
|
+
Engrams are Resources with category="engram", optionally containing
|
|
202
|
+
attached Moments. They represent captured experiences, observations,
|
|
203
|
+
or insights with rich contextual metadata.
|
|
204
|
+
|
|
205
|
+
Processing:
|
|
206
|
+
- Upsert as Resource via repository.upsert()
|
|
207
|
+
- Create attached Moments as separate entities
|
|
208
|
+
- Link moments to parent via graph edges (rel_type="part_of")
|
|
209
|
+
|
|
210
|
+
Chunking:
|
|
211
|
+
- Summary chunking (if summary exists)
|
|
212
|
+
- Content chunking (semantic-text-splitter for long content)
|
|
213
|
+
- Moment chunking (each moment is a separate chunk)
|
|
214
|
+
|
|
215
|
+
Embeddings:
|
|
216
|
+
- Generated for resource summary
|
|
217
|
+
- Generated for resource content (chunked if needed)
|
|
218
|
+
- Generated for each attached moment content
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
kind: str = Field(
|
|
222
|
+
default="engram",
|
|
223
|
+
description="Resource kind (always 'engram')",
|
|
224
|
+
)
|
|
225
|
+
name: str = Field(
|
|
226
|
+
...,
|
|
227
|
+
description="Engram name (human-readable, used as graph label)",
|
|
228
|
+
)
|
|
229
|
+
category: str = Field(
|
|
230
|
+
default="engram",
|
|
231
|
+
description="Engram category (diary, meeting, note, observation, conversation, media)",
|
|
232
|
+
)
|
|
233
|
+
summary: Optional[str] = Field(
|
|
234
|
+
default=None,
|
|
235
|
+
description="Brief summary for semantic search (1-3 sentences)",
|
|
236
|
+
)
|
|
237
|
+
content: str = Field(
|
|
238
|
+
default="",
|
|
239
|
+
description="Full engram content",
|
|
240
|
+
)
|
|
241
|
+
uri: Optional[str] = Field(
|
|
242
|
+
default=None,
|
|
243
|
+
description="Resource URI (s3://, seaweedfs://, etc.)",
|
|
244
|
+
)
|
|
245
|
+
timestamp: datetime = Field(
|
|
246
|
+
default_factory=lambda: datetime.now(timezone.utc).replace(tzinfo=None),
|
|
247
|
+
description="Engram timestamp (content creation time)",
|
|
248
|
+
)
|
|
249
|
+
metadata: dict = Field(
|
|
250
|
+
default_factory=dict,
|
|
251
|
+
description="Engram metadata (device info, etc.)",
|
|
252
|
+
)
|
|
253
|
+
graph_edges: list[InlineEdge] = Field(
|
|
254
|
+
default_factory=list,
|
|
255
|
+
description="Knowledge graph edges",
|
|
256
|
+
)
|
|
257
|
+
moments: list[EngramMoment] = Field(
|
|
258
|
+
default_factory=list,
|
|
259
|
+
description="Attached moments (temporal segments)",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def device(self) -> Optional[DeviceMetadata]:
|
|
264
|
+
"""Get device metadata if present."""
|
|
265
|
+
device_data = self.metadata.get("device")
|
|
266
|
+
if device_data:
|
|
267
|
+
return DeviceMetadata(**device_data)
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
def add_moment(
|
|
271
|
+
self,
|
|
272
|
+
name: str,
|
|
273
|
+
content: str,
|
|
274
|
+
summary: Optional[str] = None,
|
|
275
|
+
moment_type: Optional[str] = None,
|
|
276
|
+
starts_timestamp: Optional[datetime] = None,
|
|
277
|
+
ends_timestamp: Optional[datetime] = None,
|
|
278
|
+
**kwargs,
|
|
279
|
+
) -> EngramMoment:
|
|
280
|
+
"""
|
|
281
|
+
Add a moment to this engram.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
name: Moment name
|
|
285
|
+
content: Moment content
|
|
286
|
+
summary: Brief summary
|
|
287
|
+
moment_type: Moment type
|
|
288
|
+
starts_timestamp: Start time
|
|
289
|
+
ends_timestamp: End time
|
|
290
|
+
**kwargs: Additional moment fields
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Created EngramMoment
|
|
294
|
+
"""
|
|
295
|
+
moment = EngramMoment(
|
|
296
|
+
name=name,
|
|
297
|
+
content=content,
|
|
298
|
+
summary=summary,
|
|
299
|
+
moment_type=moment_type,
|
|
300
|
+
starts_timestamp=starts_timestamp,
|
|
301
|
+
ends_timestamp=ends_timestamp,
|
|
302
|
+
**kwargs,
|
|
303
|
+
)
|
|
304
|
+
self.moments.append(moment)
|
|
305
|
+
return moment
|
|
306
|
+
|
|
307
|
+
def add_graph_edge(
|
|
308
|
+
self,
|
|
309
|
+
dst: str,
|
|
310
|
+
rel_type: str,
|
|
311
|
+
weight: float = 0.5,
|
|
312
|
+
properties: Optional[dict] = None,
|
|
313
|
+
) -> InlineEdge:
|
|
314
|
+
"""
|
|
315
|
+
Add a graph edge to this engram.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
dst: Human-friendly destination label
|
|
319
|
+
rel_type: Relationship type
|
|
320
|
+
weight: Edge weight (0.0-1.0)
|
|
321
|
+
properties: Edge properties
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Created InlineEdge
|
|
325
|
+
"""
|
|
326
|
+
edge = InlineEdge(
|
|
327
|
+
dst=dst,
|
|
328
|
+
rel_type=rel_type,
|
|
329
|
+
weight=weight,
|
|
330
|
+
properties=properties or {},
|
|
331
|
+
)
|
|
332
|
+
self.graph_edges.append(edge)
|
|
333
|
+
return edge
|