Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bc5647e
feat: Initialize data structures and class for managing memory versio…
bittergreen Jan 30, 2026
57b3cf6
fix: avoid adding fileurl to memoryvalue (#995)
whipser030 Feb 2, 2026
c750c3c
feat: add delete_node_by_mem_cube_id && recover_memory_by_mem_kube_id…
wustzdy Feb 2, 2026
658eb3d
feat: add PostgreSQL + pgvector graph database backend (#966)
anatolykoptev Feb 3, 2026
3ed7413
fix: The "tags" lack the key words related to the memory content. (#999)
whipser030 Feb 4, 2026
58363aa
fix: add ruff check code && poetry run ruff format (#1007)
wustzdy Feb 4, 2026
40bb20b
fix: parallel chunk large memory items and process those chunks, but …
endxxxx Feb 4, 2026
5856451
fix: polardb parameter bug (#1013)
wustzdy Feb 4, 2026
eaf15ef
feat: opimize branch 2.0.5 (#1016)
Wang-Daoji Feb 5, 2026
da91ffc
fix: The "total nodes" information returned by the "get memory" inter…
whipser030 Feb 5, 2026
08d4f44
Revert "fix: The "total nodes" information returned by the "get memor…
CarltonXiang Feb 5, 2026
b354faa
feat: add neo4j and neo4j_community function (#1028)
wustzdy Feb 6, 2026
2550b93
feat: Add correlation threshold filtering and maximum limit for retri…
hijzy Feb 6, 2026
f7453ec
feat: skill memory (#1031)
Wang-Daoji Feb 6, 2026
5e17c52
refactor: (scheduler) modularize handlers and search pipelines (#1004)
fancyboi999 Feb 6, 2026
f435327
fix: set default relativity (#1036)
hijzy Feb 6, 2026
62ac2be
fix: The "total nodes" information returned by the "get memory" inter…
whipser030 Feb 6, 2026
c2bc36b
feat: change code struct && update neo4j (#1040)
wustzdy Feb 6, 2026
81e8db4
feat: modify sdk (#1034)
anpulin Feb 6, 2026
b4fbfa3
Fix: format error (#1044)
CarltonXiang Feb 6, 2026
8ae67ba
hotfix: Code restoration, with the original content of the knowledge …
whipser030 Feb 8, 2026
eec601f
feat: skill memory (#1042)
Wang-Daoji Feb 9, 2026
15921be
Fix/format error (#1046)
CarltonXiang Feb 9, 2026
2be48da
feat: skill memory (#1056)
Wang-Daoji Feb 9, 2026
78b18a7
feat: mem read rawfile handling (#1058)
glin93 Feb 9, 2026
f690dad
feat: Recall the original text of the knowledge base content rather t…
tangg555 Feb 9, 2026
d10b794
feat: skill memory (#1062)
Wang-Daoji Feb 9, 2026
4c66722
feat: opimize branch 2.0.5 (#1064)
Wang-Daoji Feb 9, 2026
2aa6253
feat: skill memory (#1065)
Wang-Daoji Feb 9, 2026
01fcece
feat: fix RawFileMemory bug (#1066)
Wang-Daoji Feb 9, 2026
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ Get Free API: [Try API](https://memos-dashboard.openmem.net/quickstart/?source=g
- **Awesome-AI-Memory**
This is a curated repository dedicated to resources on memory and memory systems for large language models. It systematically collects relevant research papers, frameworks, tools, and practical insights. The repository aims to organize and present the rapidly evolving research landscape of LLM memory, bridging multiple research directions including natural language processing, information retrieval, agentic systems, and cognitive science.
- **Get started** 👉 [IAAR-Shanghai/Awesome-AI-Memory](https://github.com/IAAR-Shanghai/Awesome-AI-Memory)
- **MemOS Cloud OpenClaw Plugin**
Official OpenClaw lifecycle plugin for MemOS Cloud. It automatically recalls context from MemOS before the agent starts and saves the conversation back to MemOS after the agent finishes.
- **Get started** 👉 [MemTensor/MemOS-Cloud-OpenClaw-Plugin](https://github.com/MemTensor/MemOS-Cloud-OpenClaw-Plugin)

<br>

Expand Down
65 changes: 65 additions & 0 deletions docker/Dockerfile.krolik
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# MemOS with Krolik Security Extensions
#
# This Dockerfile builds MemOS with authentication, rate limiting, and admin API.
# It uses the overlay pattern to keep customizations separate from base code.

FROM python:3.11-slim

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
build-essential \
libffi-dev \
python3-dev \
curl \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd -r memos && useradd -r -g memos -u 1000 memos

WORKDIR /app

# Use official Hugging Face
ENV HF_ENDPOINT=https://huggingface.co

# Copy base MemOS source
COPY src/ ./src/
COPY pyproject.toml ./

# Install base dependencies
RUN pip install --upgrade pip && \
pip install --no-cache-dir poetry && \
poetry config virtualenvs.create false && \
poetry install --no-dev --extras "tree-mem mem-scheduler"

# Install additional dependencies for Krolik
RUN pip install --no-cache-dir \
sentence-transformers \
torch \
transformers \
psycopg2-binary \
redis

# Apply Krolik overlay (AFTER base install to allow easy updates)
COPY overlays/krolik/ ./src/memos/

# Create data directory
RUN mkdir -p /data/memos && chown -R memos:memos /data/memos
RUN chown -R memos:memos /app

# Set Python path
ENV PYTHONPATH=/app/src

# Switch to non-root user
USER memos

EXPOSE 8000

# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=60s \
CMD curl -f http://localhost:8000/health || exit 1

# Use extended entry point with security features
CMD ["gunicorn", "memos.api.server_api_ext:app", "--preload", "-w", "2", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--timeout", "120"]
2 changes: 1 addition & 1 deletion examples/mem_agent/deepsearch_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def factory_initialization() -> tuple[DeepSearchMemAgent, dict[str, Any]]:


def main():
agent_factory, components_factory = factory_initialization()
agent_factory, _components_factory = factory_initialization()
results = agent_factory.run(
"Caroline met up with friends, family, and mentors in early July 2023.",
user_id="locomo_exp_user_0_speaker_b_ct-1118",
Expand Down
27 changes: 8 additions & 19 deletions examples/mem_scheduler/memos_w_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ def init_task():
return conversations, questions


working_memories = []
default_mem_update_handler = mem_scheduler.handlers.get(MEM_UPDATE_TASK_LABEL)
if default_mem_update_handler is None:
logger.warning("Default MEM_UPDATE handler not found; custom handler will be a no-op.")


# Define custom query handler function
Expand All @@ -100,24 +102,11 @@ def custom_query_handler(messages: list[ScheduleMessageItem]):

# Define custom memory update handler function
def custom_mem_update_handler(messages: list[ScheduleMessageItem]):
global working_memories
search_args = {}
top_k = 2
for msg in messages:
# Search for memories relevant to the current content in text memory (return top_k=2)
results = mem_scheduler.retriever.search(
query=msg.content,
user_id=msg.user_id,
mem_cube_id=msg.mem_cube_id,
mem_cube=mem_scheduler.current_mem_cube,
top_k=top_k,
method=mem_scheduler.search_method,
search_args=search_args,
)
working_memories.extend(results)
working_memories = working_memories[-5:]
for mem in results:
print(f"\n[scheduler] Retrieved memory: {mem.memory}")
if default_mem_update_handler is None:
logger.error("Default MEM_UPDATE handler missing; cannot process messages.")
return
# Delegate to the built-in handler to keep behavior aligned with scheduler refactor.
default_mem_update_handler(messages)


async def run_with_scheduler():
Expand Down
9 changes: 5 additions & 4 deletions examples/mem_scheduler/try_schedule_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ def add_msgs(
label=MEM_UPDATE_TASK_LABEL,
content=query,
)
# Run one session turn manually to get search candidates
mem_scheduler._memory_update_consumer(
messages=[message],
)
# Run one session turn manually via registered handler (public surface)
handler = mem_scheduler.handlers.get(MEM_UPDATE_TASK_LABEL)
if handler is None:
raise RuntimeError("MEM_UPDATE handler not registered on mem_scheduler.")
handler([message])
Empty file added src/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions src/memos/api/README_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# MemOS API

## Default entry and deployment

- Use **`server_api.py`** as the API service entry for **public open-source usage**.
- You can deploy via **`docker/Dockerfile`**.

The above is the default, general way to run and deploy the API.

## Extensions and reference implementations

- **`server_api_ext.py`** and **`Dockerfile.krolik`** are one developer’s extended API and deployment setup, **for reference only**. They are not yet integrated with cloud services and are still in testing.
- If you need extensions or custom behavior, you can refer to these and use or adapt them as you like.
Empty file added src/memos/api/__init__.py
Empty file.
25 changes: 21 additions & 4 deletions src/memos/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,27 @@
class MemOSClient:
"""MemOS API client"""

def __init__(self, api_key: str | None = None, base_url: str | None = None):
self.base_url = (
base_url or os.getenv("MEMOS_BASE_URL") or "https://memos.memtensor.cn/api/openmem/v1"
def __init__(
self,
api_key: str | None = None,
base_url: str | None = None,
is_global: str | bool = "false",
):
# Priority:
# 1. base_url argument
# 2. MEMOS_BASE_URL environment variable (direct URL)
# 3. MEMOS_IS_GLOBAL environment variable (True/False toggle)
arg_is_global = str(is_global).lower() in ("true", "1", "yes")
memos_is_global = os.getenv("MEMOS_IS_GLOBAL", "false").lower() in ("true", "1", "yes")
final_is_global = arg_is_global or memos_is_global
default_url = (
"https://api.memt.ai/platform/api/openmem/v1"
if final_is_global
else "https://memos.memtensor.cn/api/openmem/v1"
)

self.base_url = base_url or os.getenv("MEMOS_BASE_URL") or default_url

api_key = api_key or os.getenv("MEMOS_API_KEY")

if not api_key:
Expand All @@ -56,7 +73,7 @@ def get_message(
message_limit_number: int = 6,
source: str | None = None,
) -> MemOSGetMessagesResponse | None:
"""Get messages"""
"""Get message"""
# Validate required parameters
self._validate_required_params(user_id=user_id)

Expand Down
53 changes: 50 additions & 3 deletions src/memos/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,10 @@ def get_internet_config() -> dict[str, Any]:
"chunker": {
"backend": "sentence",
"config": {
"save_rawfile": os.getenv(
"MEM_READER_SAVE_RAWFILENODE", "true"
).lower()
== "true",
"tokenizer_or_token_counter": "gpt2",
"chunk_size": 512,
"chunk_overlap": 128,
Expand Down Expand Up @@ -676,6 +680,30 @@ def get_polardb_config(user_id: str | None = None) -> dict[str, Any]:
"embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 1024)),
}

@staticmethod
def get_postgres_config(user_id: str | None = None) -> dict[str, Any]:
"""Get PostgreSQL + pgvector configuration for MemOS graph storage.

Uses standard PostgreSQL with pgvector extension.
Schema: memos.memories, memos.edges
"""
user_name = os.getenv("MEMOS_USER_NAME", "default")
if user_id:
user_name = f"memos_{user_id.replace('-', '')}"

return {
"host": os.getenv("POSTGRES_HOST", "postgres"),
"port": int(os.getenv("POSTGRES_PORT", "5432")),
"user": os.getenv("POSTGRES_USER", "n8n"),
"password": os.getenv("POSTGRES_PASSWORD", ""),
"db_name": os.getenv("POSTGRES_DB", "n8n"),
"schema_name": os.getenv("MEMOS_SCHEMA", "memos"),
"user_name": user_name,
"use_multi_db": False,
"embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", "384")),
"maxconn": int(os.getenv("POSTGRES_MAX_CONN", "20")),
}

@staticmethod
def get_mysql_config() -> dict[str, Any]:
"""Get MySQL configuration."""
Expand Down Expand Up @@ -780,6 +808,8 @@ def get_product_default_config() -> dict[str, Any]:
"chunker": {
"backend": "sentence",
"config": {
"save_rawfile": os.getenv("MEM_READER_SAVE_RAWFILENODE", "true").lower()
== "true",
"tokenizer_or_token_counter": "gpt2",
"chunk_size": 512,
"chunk_overlap": 128,
Expand All @@ -797,7 +827,12 @@ def get_product_default_config() -> dict[str, Any]:
"oss_config": APIConfig.get_oss_config(),
"skills_dir_config": {
"skills_oss_dir": os.getenv("SKILLS_OSS_DIR", "skill_memory/"),
"skills_local_dir": os.getenv("SKILLS_LOCAL_DIR", "/tmp/skill_memory/"),
"skills_local_tmp_dir": os.getenv(
"SKILLS_LOCAL_TMP_DIR", "/tmp/skill_memory/"
),
"skills_local_dir": os.getenv(
"SKILLS_LOCAL_DIR", "/tmp/upload_skill_memory/"
),
},
},
},
Expand Down Expand Up @@ -895,6 +930,8 @@ def create_user_config(user_name: str, user_id: str) -> tuple["MOSConfig", "Gene
"chunker": {
"backend": "sentence",
"config": {
"save_rawfile": os.getenv("MEM_READER_SAVE_RAWFILENODE", "true").lower()
== "true",
"tokenizer_or_token_counter": "gpt2",
"chunk_size": 512,
"chunk_overlap": 128,
Expand Down Expand Up @@ -937,13 +974,18 @@ def create_user_config(user_name: str, user_id: str) -> tuple["MOSConfig", "Gene
if os.getenv("ENABLE_INTERNET", "false").lower() == "true"
else None
)
postgres_config = APIConfig.get_postgres_config(user_id=user_id)
graph_db_backend_map = {
"neo4j-community": neo4j_community_config,
"neo4j": neo4j_config,
"nebular": nebular_config,
"polardb": polardb_config,
"postgres": postgres_config,
}
graph_db_backend = os.getenv("NEO4J_BACKEND", "neo4j-community").lower()
# Support both GRAPH_DB_BACKEND and legacy NEO4J_BACKEND env vars
graph_db_backend = os.getenv(
"GRAPH_DB_BACKEND", os.getenv("NEO4J_BACKEND", "neo4j-community")
).lower()
if graph_db_backend in graph_db_backend_map:
# Create MemCube config

Expand Down Expand Up @@ -1011,18 +1053,23 @@ def get_default_cube_config() -> "GeneralMemCubeConfig | None":
neo4j_config = APIConfig.get_neo4j_config(user_id="default")
nebular_config = APIConfig.get_nebular_config(user_id="default")
polardb_config = APIConfig.get_polardb_config(user_id="default")
postgres_config = APIConfig.get_postgres_config(user_id="default")
graph_db_backend_map = {
"neo4j-community": neo4j_community_config,
"neo4j": neo4j_config,
"nebular": nebular_config,
"polardb": polardb_config,
"postgres": postgres_config,
}
internet_config = (
APIConfig.get_internet_config()
if os.getenv("ENABLE_INTERNET", "false").lower() == "true"
else None
)
graph_db_backend = os.getenv("NEO4J_BACKEND", "neo4j-community").lower()
# Support both GRAPH_DB_BACKEND and legacy NEO4J_BACKEND env vars
graph_db_backend = os.getenv(
"GRAPH_DB_BACKEND", os.getenv("NEO4J_BACKEND", "neo4j-community")
).lower()
if graph_db_backend in graph_db_backend_map:
return GeneralMemCubeConfig.model_validate(
{
Expand Down
Loading