Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
14b08fc
fix(okf): preserve provenance on round-trip
6pt6brty57-star Jul 16, 2026
5e53fdd
Normalize Mem0 category strings during migration (#1354)
smwsk1 Aug 17, 2026
96795c0
fix: consolidate migrate tests
Xenogents Aug 17, 2026
2da1fce
fix: properly show error when namespace limit reached
Xenogents Aug 17, 2026
1a5a8da
fix: replace stale OKF export snapshots (#1483)
valencealignment Aug 17, 2026
741892b
Merge pull request #1488 from 6pt6brty57-star/fix-okf-provenance-roun…
6pt6brty57-star Aug 17, 2026
2a9f532
Fix: strip leading whitespace in filtered query when query is empty (…
truongsontung Aug 17, 2026
514abdf
fix(migrate): keep OKF batches valid with long titles (compressed) (#…
jackmercy Aug 17, 2026
0a1e008
fix(app): preserve provenance in update_memory() to prevent metadata …
sirEven Aug 18, 2026
359bc2d
fix(app): decouple conflict detection prompt from embedded query to p…
sirEven Aug 18, 2026
b5ceb0a
fix: preserve Supermemory migration data and revoke deleted-agent ses…
nanguazhou123-star Aug 18, 2026
9506e74
fix(temporal): parse_relative_time silently returns None for 'last we…
KhangYen Aug 18, 2026
6907352
Fix bug bounty 770 edge cases (MemoryError shadowing, conflicts_dir) …
Xenogents Aug 18, 2026
3019dad
fix(recall): include memories with unknown confidence in filtered que…
garochee33 Aug 18, 2026
b7b7c04
fix: migrate deprecated Pydantic V1 @validator to V2 @field_validator…
hardcorexax-source Aug 18, 2026
ea7d374
fix(okf): preserve temporal metadata across round trips (#1630)
jamilahmadzai Aug 18, 2026
fd1b117
fix: rate limiting, timezone fixes, upsert update flow (#1420)
ejspeed-cmd Aug 19, 2026
fa7f8c9
Remove probe_bugs.py
Xenogents Aug 19, 2026
aab29f4
fix: return renewed header session token (#1615)
spoconymacius3879254-ctrl Aug 19, 2026
f6e79d9
fix: expand preference negation lexicon — 'can not' stand + detest/lo…
Thecesar85 Aug 19, 2026
7d3b69a
fix: preserve extra fields but drop cleared schema fields in update_m…
Xenogents Aug 19, 2026
017db0b
fix(ts): authenticate remote management requests (#1499)
lirunjie0510 Aug 19, 2026
71444a7
fix: Prevent LangGraph crashes and silent memory drops, handle invali…
Xenogents Aug 19, 2026
65b5f6c
fix(logging): use real session ids in memory summaries (#863)
VoltVoks Aug 19, 2026
7af4d57
chore: ruff linting and formatting
Xenogents Aug 19, 2026
07931bb
chore: update openapi drift
Xenogents Aug 19, 2026
9d6984c
feat(analysis): compute a bounded digest for embedding massive sessio…
Xenogents Aug 19, 2026
1547f72
fix(data): resolve conflict directory mismatch, preserve flat metadat…
Xenogents Aug 19, 2026
78e96e6
fix(core): fortify SDK session rotation, OKF bundle locks, and orches…
Xenogents Aug 19, 2026
a8c8e0d
Merge remote-tracking branch 'origin/main' into temporal-and-okf/merg…
Xenogents Aug 19, 2026
ac36338
fix: address CodeRabbit reviews for data integrity, path validation, …
Xenogents Aug 19, 2026
228346a
Merge branch 'main' into temporal-and-okf/merge-prs (resolve conflicts)
Xenogents Aug 21, 2026
ea7ea7f
Interpolate truncated session text to LLM context to prevent overflow
Xenogents Aug 21, 2026
682fc99
fix: ruff linting/formatting
Xenogents Aug 24, 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 integrations/langgraph/langgraph_memanto/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ def remember_node(
return {"messages": []}

content = "\n\n".join(messages_to_remember)
max_content = 10_000
if len(content) > max_content:
content = content[-max_content:]
title = content if len(content) <= 50 else content[:47] + "..."

agent_client, agent_lock = _cache.get(resolved_agent_id)
Expand Down
23 changes: 22 additions & 1 deletion integrations/langgraph/langgraph_memanto/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,30 @@ def _do_list_namespaces(self, op: ListNamespacesOp) -> list[tuple[str, ...]]:
logger.warning("MemantoStore: Failed to list agents: %s", e)
return []

if isinstance(agents, dict):
agent_items = agents.get("agents")
if not isinstance(agent_items, list):
logger.warning(
"MemantoStore: unexpected list_agents payload keys: %s",
sorted(agents),
)
return []
elif isinstance(agents, list):
agent_items = agents
else:
logger.warning(
"MemantoStore: unexpected list_agents payload type: %s",
type(agents).__name__,
)
return []

namespaces = []
for agent in agents:
for agent in agent_items:
if not isinstance(agent, dict):
continue
Comment thread
coderabbitai[bot] marked this conversation as resolved.
agent_id = agent.get("agent_id") or agent.get("id") or ""
if not isinstance(agent_id, str):
continue
if agent_id.startswith(self._agent_prefix):
ns_str = agent_id[len(self._agent_prefix) :]
if ns_str == "default":
Expand Down
18 changes: 18 additions & 0 deletions memanto/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,21 @@ def get_data_dir() -> Path:
d.mkdir(parents=True, exist_ok=True)
return d
return base


def get_conflicts_dir() -> Path:
"""Return the shared directory for conflict reports."""
d = get_data_dir() / "conflicts"
d.mkdir(parents=True, exist_ok=True)
return d


def get_conflict_report_path(agent_id: str, date: str) -> Path:
"""Return a safely constructed path for a conflict report, validating components against traversal."""
import re

if not re.match(r"^[\w\-]+$", agent_id):
raise ValueError(f"Invalid agent_id format: {agent_id}")
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date):
raise ValueError(f"Invalid date format: {date}")
return get_conflicts_dir() / f"{agent_id}_{date}_conflicts.json"
Comment thread
Xenogents marked this conversation as resolved.
11 changes: 11 additions & 0 deletions memanto/app/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,14 @@
}

VALID_PATTERNS = {"support", "project", "tool"}

# Trust fields removed from the schema. Must not be resurrected during update.
REMOVED_TRUST_FIELDS = frozenset(
{
"superseded_by",
"supersedes",
"validated_at",
"validation_count",
"contradiction_detected",
}
)
6 changes: 5 additions & 1 deletion memanto/app/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import re
import uuid
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Annotated, Any

from pydantic import BaseModel, Field, StringConstraints, field_validator
Expand Down Expand Up @@ -130,6 +130,10 @@ def _normalize_title_newlines(cls, value: Any) -> Any:
expired_at: datetime | None = None
expired_by: BoundedExpiredBy | None = None

def set_ttl(self, ttl_seconds: int) -> None:
"""Set expiration time based on a TTL in seconds."""
self.expired_at = datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)

def to_moorcheh_document(self) -> dict[str, Any]:
"""
Convert to Moorcheh document format with flat metadata fields.
Expand Down
2 changes: 2 additions & 0 deletions memanto/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def _validate_cors_settings(
allow_credentials=settings.CORS_ALLOW_CREDENTIALS,
allow_methods=["*"],
allow_headers=["*"],
# Header-authenticated API clients must be able to read an auto-renewed
# token from the response. Custom response headers are not CORS-safelisted.
expose_headers=["X-Session-Token"],
)

Expand Down
5 changes: 5 additions & 0 deletions memanto/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class BatchRememberItem(BaseModel):
"explicit_statement",
description="How memory was obtained (explicit_statement, inferred, observed, etc.)",
)
ttl_seconds: int | None = Field(
None,
ge=1,
description="Time-to-live in seconds. Memory expires after this duration.",
)

@field_validator("content")
@classmethod
Expand Down
13 changes: 9 additions & 4 deletions memanto/app/routes/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from memanto.app.services.policy_presets import PRESETS, list_presets, load_preset
from memanto.app.utils.errors import (
AuthorizationError,
MemoryError,
MemoryOperationError,
map_error_to_http_exception,
)
from memanto.app.utils.temporal_helpers import (
Expand Down Expand Up @@ -462,6 +462,9 @@ async def remember(
provenance=cast(ProvenanceType, request.provenance),
)

if request.ttl_seconds:
memory.set_ttl(request.ttl_seconds)

# Store memory in agent's namespace.
result = await asyncio.to_thread(write_service.store_memory, memory)
status = str(result.get("status", "unknown"))
Expand Down Expand Up @@ -541,6 +544,8 @@ async def batch_remember(
source=item.source,
provenance=cast(ProvenanceType, item.provenance),
)
if item.ttl_seconds:
memory.set_ttl(item.ttl_seconds)
memory_records.append(memory)

# Store in batch
Expand Down Expand Up @@ -724,14 +729,14 @@ async def extract_memories_from_conversation(
session_service = get_session_service()

if not isinstance(result, dict):
raise MemoryError(
raise MemoryOperationError(
message="Data corruption detected: Received malformed batch result from storage layer.",
details={"item_preview": str(result)[:100]},
)

batch_results = result.get("results", [])
if not isinstance(batch_results, list):
raise MemoryError(
raise MemoryOperationError(
message="Data corruption detected: Received malformed batch result array from storage layer.",
details={"item_preview": str(batch_results)[:100]},
)
Expand All @@ -741,7 +746,7 @@ async def extract_memories_from_conversation(
if item_result is not None and (
not isinstance(item_result, dict) or not item_result
):
raise MemoryError(
raise MemoryOperationError(
message="Data corruption detected: Received malformed batch result from storage layer.",
details={"item_preview": str(item_result)[:100]},
)
Expand Down
5 changes: 4 additions & 1 deletion memanto/app/routes/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ async def delete_agent(
# and continue removing local metadata.
pass

agent_service.delete_agent(agent_id)
# Revoke the persisted token before removing agent metadata. If local
# session cleanup fails, abort the deletion so an apparently deleted
# agent cannot keep authorizing requests with its old token.
get_session_service().delete_session(agent_id)
agent_service.delete_agent(agent_id)
return {
"message": (
f"Agent '{agent_id}' successfully deleted"
Expand Down
18 changes: 11 additions & 7 deletions memanto/app/services/agent_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
from memanto.app.core import agent_namespace
from memanto.app.models.session import AgentCreate, AgentInfo, AgentList
from memanto.app.utils.atomic_write import atomic_write_text
from memanto.app.utils.errors import AgentAlreadyExistsError, AgentNotFoundError
from memanto.app.utils.errors import (
AgentAlreadyExistsError,
AgentNotFoundError,
NamespaceError,
)
from memanto.app.utils.temporal_helpers import as_utc_aware
from memanto.app.utils.validation import validate_safe_id

Expand Down Expand Up @@ -96,18 +100,18 @@ def create_agent(
try:
client.namespaces.create(namespace, type="text")
print(f"[OK] Namespace created in Moorcheh: {namespace}")
except ConflictError:
print(f"[OK] Namespace already exists in Moorcheh: {namespace}")
except Exception as exc:
message = str(exc).lower()
if (
if "limit" in message or "tier" in message or "quota" in message:
raise NamespaceError(f"Moorcheh namespace limit reached: {exc}")
if isinstance(exc, ConflictError) or (
"namespace" in message and "already exists" in message
) or "conflict" in message:
):
print(f"[OK] Namespace already exists in Moorcheh: {namespace}")
else:
raise Exception(
raise NamespaceError(
f"Failed to create namespace '{namespace}' in Moorcheh: {exc}"
)
) from exc

agent = AgentInfo(
agent_id=agent_create.agent_id,
Expand Down
Loading
Loading