Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1115087
feat: implement Mem0 memory backend adapter (#206)
Aureliolo Mar 12, 2026
b51c09a
refactor: harden Mem0 adapter with review findings from 9 agents
Aureliolo Mar 12, 2026
d1a552a
fix: address 29 PR review findings from local agents and external rev…
Aureliolo Mar 13, 2026
258d1a0
fix: address round-2 PR review findings and fix dependency review CI
Aureliolo Mar 13, 2026
a7ee906
fix: add non-dict metadata guards and created_at fallback logging
Aureliolo Mar 13, 2026
5aedbc4
fix: remove double-logging in retract() ownership check path
Aureliolo Mar 13, 2026
ab58ccf
fix: address round-3 PR review findings across adapter, mappers, conf…
Aureliolo Mar 13, 2026
5a4bc0a
fix: install mem0 optional extra in CI setup action
Aureliolo Mar 13, 2026
d9e0dee
fix: revert mem0ai to required dependency
Aureliolo Mar 13, 2026
3c0c855
fix: address round-4 PR review findings from CodeRabbit, Greptile, an…
Aureliolo Mar 13, 2026
71469c2
fix: address round-5 PR review findings and add missing test coverage
Aureliolo Mar 13, 2026
3adf18b
fix: address round-6 PR review findings across adapter, docs, tests, …
Aureliolo Mar 13, 2026
16975cb
fix: address round-7 PR review findings across adapter, mappers, conf…
Aureliolo Mar 13, 2026
6b69d77
fix: address round-8 PR review findings — orphan rejection, vendor na…
Aureliolo Mar 13, 2026
fc9fcb7
fix: type-safe client, idiomatic patterns, and expanded test coverage
Aureliolo Mar 13, 2026
155d263
fix: address round-9 PR review findings — module extraction, system e…
Aureliolo Mar 13, 2026
1c20792
fix: address round-10 review findings — event misuse, constructor log…
Aureliolo Mar 13, 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
15 changes: 14 additions & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,24 @@ jobs:
# pycparser 3.0, sse-starlette 3.3.2 — MIT per classifiers, scancode misdetects
# LGPL-*: @img/sharp-libvips-* (Astro image optimization, build-time only)
# BlueOak-1.0.0: lru-cache, sax (Astro transitive deps, permissive)
# LicenseRef-scancode-protobuf: protobuf 5.29.6 (BSD-3-Clause AND scancode-protobuf, permissive)
# ZPL-2.1: pytz 2026.1 (MIT AND ZPL-2.1, Zope Public License — permissive)
# Null: mem0ai, numpy, qdrant-client, posthog — license metadata missing
# from lockfile but all are permissive (Apache-2.0 / BSD-3-Clause / MIT)
allow-licenses: >-
MIT, MIT-0, Apache-2.0, BSD-2-Clause, BSD-3-Clause,
ISC, MPL-2.0, PSF-2.0, Unlicense, 0BSD,
CC0-1.0, Python-2.0, Python-2.0.1,
LicenseRef-scancode-free-unknown,
LicenseRef-scancode-free-unknown, LicenseRef-scancode-protobuf,
ZPL-2.1,
LGPL-2.0-only, LGPL-2.1-only, LGPL-3.0-only, LGPL-3.0-or-later,
BlueOak-1.0.0
# Packages with null/missing SPDX license metadata in lockfile.
# Verified manually: mem0ai (Apache-2.0), numpy (BSD-3-Clause),
# qdrant-client (Apache-2.0), posthog (MIT).
allow-dependencies-licenses: >-
pkg:pypi/mem0ai@1.0.5,
pkg:pypi/numpy@2.4.3,
pkg:pypi/qdrant-client@1.17.0,
pkg:pypi/posthog@7.9.12
comment-summary-in-pr: always
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ src/ai_company/
core/ # Shared domain models, base classes, and resilience config (RetryConfig, RateLimiterConfig)
engine/ # Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration), coordination error classification, and prompt policy validation
hr/ # HR engine: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
memory/ # Persistent agent memory (Mem0 initial, custom stack future — see Decision Log), retrieval pipeline (ranking, injection, context formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (consolidation/)
memory/ # Persistent agent memory (pluggable MemoryBackend protocol), backends/ (Mem0 adapter: backends/mem0/), retrieval pipeline (ranking, injection, context formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (consolidation/)
persistence/ # Operational data persistence — pluggable PersistenceBackend protocol, SQLite initial (see Memory & Persistence design page)
observability/ # Structured logging, correlation tracking, log sinks
providers/ # LLM provider abstraction (LiteLLM adapter)
Expand Down Expand Up @@ -127,7 +127,7 @@ src/ai_company/
- **Every module** with business logic MUST have: `from ai_company.observability import get_logger` then `logger = get_logger(__name__)`
- **Never** use `import logging` / `logging.getLogger()` / `print()` in application code
- **Variable name**: always `logger` (not `_logger`, not `log`)
- **Event names**: always use constants from the domain-specific module under `ai_company.observability.events` (e.g. `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, `CFO_ANOMALY_DETECTED` from `events.cfo`, `CONFLICT_DETECTED` from `events.conflict`, `MEETING_STARTED` from `events.meeting`, `CLASSIFICATION_START` from `events.classification`, `CONSOLIDATION_START` from `events.consolidation`, `ORG_MEMORY_QUERY_START` from `events.org_memory`, `API_REQUEST_STARTED` from `events.api`, `CODE_RUNNER_EXECUTE_START` from `events.code_runner`, `DOCKER_EXECUTE_START` from `events.docker`, `MCP_INVOKE_START` from `events.mcp`, `SECURITY_EVALUATE_START` from `events.security`, `HR_HIRING_REQUEST_CREATED` from `events.hr`, `PERF_METRIC_RECORDED` from `events.performance`, `TRUST_EVALUATE_START` from `events.trust`, `PROMOTION_EVALUATE_START` from `events.promotion`, `PROMPT_BUILD_START` from `events.prompt`, `MEMORY_RETRIEVAL_START` from `events.memory`, `AUTONOMY_ACTION_AUTO_APPROVED` from `events.autonomy`, `TIMEOUT_POLICY_EVALUATED` from `events.timeout`, `PERSISTENCE_AUDIT_ENTRY_SAVED` from `events.persistence`, `TASK_ENGINE_STARTED` from `events.task_engine`, `COORDINATION_STARTED` from `events.coordination`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`
- **Event names**: always use constants from the domain-specific module under `ai_company.observability.events` (e.g. `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, `CFO_ANOMALY_DETECTED` from `events.cfo`, `CONFLICT_DETECTED` from `events.conflict`, `MEETING_STARTED` from `events.meeting`, `CLASSIFICATION_START` from `events.classification`, `CONSOLIDATION_START` from `events.consolidation`, `ORG_MEMORY_QUERY_START` from `events.org_memory`, `API_REQUEST_STARTED` from `events.api`, `CODE_RUNNER_EXECUTE_START` from `events.code_runner`, `DOCKER_EXECUTE_START` from `events.docker`, `MCP_INVOKE_START` from `events.mcp`, `SECURITY_EVALUATE_START` from `events.security`, `HR_HIRING_REQUEST_CREATED` from `events.hr`, `PERF_METRIC_RECORDED` from `events.performance`, `TRUST_EVALUATE_START` from `events.trust`, `PROMOTION_EVALUATE_START` from `events.promotion`, `PROMPT_BUILD_START` from `events.prompt`, `MEMORY_RETRIEVAL_START` from `events.memory`, `MEMORY_BACKEND_CONNECTED` from `events.memory`, `MEMORY_ENTRY_STORED` from `events.memory`, `MEMORY_BACKEND_SYSTEM_ERROR` from `events.memory`, `AUTONOMY_ACTION_AUTO_APPROVED` from `events.autonomy`, `TIMEOUT_POLICY_EVALUATED` from `events.timeout`, `PERSISTENCE_AUDIT_ENTRY_SAVED` from `events.persistence`, `TASK_ENGINE_STARTED` from `events.task_engine`, `COORDINATION_STARTED` from `events.coordination`, `COMMUNICATION_DISPATCH_START` from `events.communication`, `COMPANY_STARTED` from `events.company`, `CONFIG_LOADED` from `events.config`, `CORRELATION_ID_CREATED` from `events.correlation`, `DECOMPOSITION_STARTED` from `events.decomposition`, `DELEGATION_STARTED` from `events.delegation`, `EXECUTION_LOOP_STARTED` from `events.execution`, `GIT_OPERATION_START` from `events.git`, `PARALLEL_EXECUTION_STARTED` from `events.parallel`, `PERSONALITY_LOADED` from `events.personality`, `QUOTA_CHECKED` from `events.quota`, `ROLE_ASSIGNED` from `events.role`, `ROUTING_STARTED` from `events.routing`, `SANDBOX_EXECUTE_START` from `events.sandbox`, `TASK_CREATED` from `events.task`, `TASK_ASSIGNMENT_STARTED` from `events.task_assignment`, `TASK_ROUTING_STARTED` from `events.task_routing`, `TEMPLATE_LOADED` from `events.template`, `TOOL_INVOKE_START` from `events.tool`, `WORKSPACE_CREATED` from `events.workspace`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`
- **Structured kwargs**: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`
- **All error paths** must log at WARNING or ERROR with context before raising
- **All state transitions** must log at INFO
Expand Down Expand Up @@ -205,4 +205,5 @@ src/ai_company/

- **Pinned**: all versions use `==` in `pyproject.toml`
- **Groups**: `test` (pytest + plugins), `dev` (includes test + ruff, mypy, pre-commit, commitizen)
- **Required**: `mem0ai` (Mem0 memory backend — the default and currently only backend)
- **Install**: `uv sync` installs everything (dev group is default)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ graph TB

## Status

Core framework complete — agent engine, multi-agent coordination, API, security, HR, memory, and budget systems are implemented. Remaining: Mem0 adapter backend, approval workflow gates, CLI, web dashboard. See the [roadmap](docs/roadmap/index.md) for details.
Core framework complete — agent engine, multi-agent coordination, API, security, HR, memory (including Mem0 backend adapter), and budget systems are implemented. Remaining: approval workflow gates, CLI, web dashboard. See the [roadmap](docs/roadmap/index.md) for details.

## License

Expand Down
60 changes: 42 additions & 18 deletions docs/design/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ configuration without modifying application code.
| context | decisions| learned | |
+----------+----------+-----------+---------------+
| Storage Backend |
| SQLite / PostgreSQL / File-based |
| + Mem0 (initial) / Custom Stack (future) |
| Mem0 (initial, implemented) / Custom (future) |
| Qdrant (embedded) + SQLite history |
| See Decision Log |
+-------------------------------------------------+
```
Expand Down Expand Up @@ -60,12 +60,12 @@ Memory persistence is configurable per agent, from no persistence to fully persi

```yaml
memory:
level: "persistent" # none, session, project, persistent (default: session)
backend: "mem0" # mem0, custom, cognee, graphiti (future) -- see Decision Log
level: "persistent" # none | session | project | persistent (default: session)
backend: "mem0" # mem0 | custom | cognee | graphiti (future) -- see Decision Log
storage:
data_dir: "/data/memory" # mounted Docker volume path
vector_store: "qdrant" # qdrant (embedded), qdrant-external, etc.
history_store: "sqlite" # sqlite, postgresql
vector_store: "qdrant" # hardcoded to embedded qdrant in Mem0 backend
history_store: "sqlite" # hardcoded to sqlite in Mem0 backend
options:
retention_days: null # null = forever
max_memories_per_agent: 10000
Expand Down Expand Up @@ -206,11 +206,21 @@ class MemoryBackend(Protocol):
@property
def backend_name(self) -> NotBlankStr: ...

async def store(self, agent_id: NotBlankStr, request: MemoryStoreRequest) -> NotBlankStr: ...
async def retrieve(self, agent_id: NotBlankStr, query: MemoryQuery) -> tuple[MemoryEntry, ...]: ...
async def get(self, agent_id: NotBlankStr, memory_id: NotBlankStr) -> MemoryEntry | None: ...
async def delete(self, agent_id: NotBlankStr, memory_id: NotBlankStr) -> bool: ...
async def count(self, agent_id: NotBlankStr, *, category: MemoryCategory | None = None) -> int: ...
async def store(self, agent_id: NotBlankStr, request: MemoryStoreRequest) -> NotBlankStr:
"""Raises: MemoryConnectionError, MemoryStoreError."""
...
async def retrieve(self, agent_id: NotBlankStr, query: MemoryQuery) -> tuple[MemoryEntry, ...]:
"""Raises: MemoryConnectionError, MemoryRetrievalError."""
...
async def get(self, agent_id: NotBlankStr, memory_id: NotBlankStr) -> MemoryEntry | None:
"""Raises: MemoryConnectionError, MemoryRetrievalError."""
...
async def delete(self, agent_id: NotBlankStr, memory_id: NotBlankStr) -> bool:
"""Raises: MemoryConnectionError, MemoryStoreError."""
...
async def count(self, agent_id: NotBlankStr, *, category: MemoryCategory | None = None) -> int:
"""Raises: MemoryConnectionError, MemoryRetrievalError."""
...
```

### MemoryCapabilities Protocol
Expand Down Expand Up @@ -248,9 +258,15 @@ clean.
class SharedKnowledgeStore(Protocol):
"""Cross-agent shared knowledge operations."""

async def publish(self, agent_id: NotBlankStr, request: MemoryStoreRequest) -> NotBlankStr: ...
async def search_shared(self, query: MemoryQuery, *, exclude_agent: NotBlankStr | None = None) -> tuple[MemoryEntry, ...]: ...
async def retract(self, agent_id: NotBlankStr, memory_id: NotBlankStr) -> bool: ...
async def publish(self, agent_id: NotBlankStr, request: MemoryStoreRequest) -> NotBlankStr:
"""Raises: MemoryConnectionError, MemoryStoreError."""
...
async def search_shared(self, query: MemoryQuery, *, exclude_agent: NotBlankStr | None = None) -> tuple[MemoryEntry, ...]:
"""Raises: MemoryConnectionError, MemoryRetrievalError."""
...
async def retract(self, agent_id: NotBlankStr, memory_id: NotBlankStr) -> bool:
"""Raises: MemoryConnectionError, MemoryStoreError."""
...
```

### Error Hierarchy
Expand All @@ -276,19 +292,27 @@ memory:
level: "persistent" # none, session, project, persistent (default: session)
storage:
data_dir: "/data/memory"
vector_store: "qdrant"
history_store: "sqlite"
vector_store: "qdrant" # hardcoded to embedded qdrant in Mem0 backend
history_store: "sqlite" # hardcoded to sqlite in Mem0 backend
options:
retention_days: null # null = forever
max_memories_per_agent: 10000
consolidation_interval: "daily"
shared_knowledge_base: true

# Embedder config is passed programmatically via the factory:
# create_memory_backend(config, embedder=Mem0EmbedderConfig(
# provider="<embedding-provider>",
# model="<embedding-model-id>",
# dims=1536,
# ))
```

Configuration is modeled by `CompanyMemoryConfig` (top-level), `MemoryStorageConfig`
(storage paths/backends), and `MemoryOptionsConfig` (behaviour tuning). All are frozen
Pydantic models. The `create_memory_backend(config)` factory returns an isolated
`MemoryBackend` instance per company.
Pydantic models. The `create_memory_backend(config, *, embedder=...)` factory returns an

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use typographical ellipsis in prose for consistency.

Replace embedder=... with embedder=… in the sentence text (outside code blocks) to satisfy the style lint hint.

🧰 Tools
🪛 LanguageTool

[style] ~313-~313: Consider using the typographical ellipsis character here instead.
Context: ...g). All are frozen Pydantic models. The create_memory_backend(config, *, embedder=...) factory returns an isolated `MemoryBa...

(ELLIPSIS)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/memory.md` at line 313, Replace the ASCII three-dot sequence in
the prose with a typographical ellipsis: change the prose instance of
"embedder=..." to "embedder=…" where it appears in the sentence describing the
create_memory_backend(config, *, embedder=...) factory (leave any code blocks or
inline code literals unchanged if they are intended to remain ASCII); ensure
only the prose text is updated and the rest of the sentence remains identical.

isolated `MemoryBackend` instance per company. The `embedder` kwarg is required for the
Mem0 backend (must be a `Mem0EmbedderConfig`).
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Consolidation and Retention

Expand Down
3 changes: 1 addition & 2 deletions docs/roadmap/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The SynthOrg core framework is complete. The following subsystems are built and
- Budget and cost management (tracking, enforcement, CFO optimization, quotas)
- Agent engine (execution loops, parallel execution, task decomposition, routing, assignment, recovery, shutdown)
- Communication layer (message bus, delegation, loop prevention, conflict resolution, meeting protocol)
- Memory system (pluggable backend protocol, retrieval pipeline, shared org memory, consolidation)
- Memory system (pluggable backend protocol, Mem0 adapter, retrieval pipeline, shared org memory, consolidation)
- Security and approval system (rule engine, output scanning, progressive trust, autonomy levels, timeout policies)
- Tool system (file system, git, code runner, MCP bridge, sandboxing, permissions)
- HR engine (hiring, firing, onboarding, offboarding, registry, performance tracking, promotions)
Expand All @@ -22,7 +22,6 @@ The SynthOrg core framework is complete. The following subsystems are built and

| Area | Description |
|------|-------------|
| **Mem0 adapter** | Concrete `MemoryBackend` implementation using the Mem0 library |
| **Approval workflow gates** | Runtime wiring for human-in-the-loop approval queues |
| **CLI** | Terminal interface wrapping the REST API (may not be needed) |
| **Web dashboard** | Vue 3 frontend for monitoring and managing the synthetic organization |
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies = [
"litellm==1.82.1",
"litestar[standard,structlog,pydantic,brotli,prometheus]==2.21.1",
"mcp==1.26.0",
"mem0ai==1.0.5",
"pydantic==2.12.5",
"pyjwt[crypto]==2.11.0",
"pyyaml==6.0.3",
Expand Down Expand Up @@ -134,6 +135,7 @@ convention = "google"
"PLR2004", # magic values in tests
"SLF001", # private member access in tests
"PLC0415", # local imports in test functions
"TC", # type-checking imports (fixture type hints)
]
"__init__.py" = ["F401"]
"scripts/**/*.py" = [
Expand Down Expand Up @@ -184,6 +186,10 @@ ignore_missing_imports = true
module = "mcp.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "mem0.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "litestar.*"
ignore_missing_imports = true
Expand Down
13 changes: 10 additions & 3 deletions src/ai_company/memory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
Re-exports protocols (``MemoryBackend``, ``MemoryCapabilities``,
``SharedKnowledgeStore``, ``MemoryInjectionStrategy``,
``OrgMemoryBackend``, ``ConsolidationStrategy``, ``ArchivalStore``),
domain models, config models, factory, retrieval pipeline,
consolidation, org memory, and error hierarchy so consumers can
import from ``ai_company.memory`` directly.
concrete backends (``Mem0MemoryBackend``), domain models, config
models, factory, retrieval pipeline, consolidation, org memory, and
error hierarchy so consumers can import from ``ai_company.memory``
directly.
"""

from ai_company.memory.backends.mem0 import (
Mem0EmbedderConfig,
Mem0MemoryBackend,
)
from ai_company.memory.capabilities import MemoryCapabilities
from ai_company.memory.config import (
CompanyMemoryConfig,
Expand Down Expand Up @@ -74,6 +79,8 @@
"DefaultTokenEstimator",
"InjectionPoint",
"InjectionStrategy",
"Mem0EmbedderConfig",
"Mem0MemoryBackend",
"MemoryBackend",
"MemoryCapabilities",
"MemoryCapabilityError",
Expand Down
5 changes: 5 additions & 0 deletions src/ai_company/memory/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Concrete memory backend implementations."""

from ai_company.memory.backends.mem0 import Mem0MemoryBackend

__all__ = ["Mem0MemoryBackend"]
6 changes: 6 additions & 0 deletions src/ai_company/memory/backends/mem0/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Mem0-backed agent memory — adapter, config, and mappers."""

from ai_company.memory.backends.mem0.adapter import Mem0MemoryBackend
from ai_company.memory.backends.mem0.config import Mem0BackendConfig, Mem0EmbedderConfig

__all__ = ["Mem0BackendConfig", "Mem0EmbedderConfig", "Mem0MemoryBackend"]
Loading
Loading