feat: implement Mem0 memory backend adapter#345
Conversation
Add concrete MemoryBackend implementation using Mem0 (embedded Qdrant + SQLite) as the storage layer, unblocking all downstream memory features. - Mem0MemoryBackend implements MemoryBackend, MemoryCapabilities, and SharedKnowledgeStore protocols with asyncio.to_thread for all sync Mem0 calls - Mapping layer (mappers.py) for bidirectional domain model <-> Mem0 dict conversion with _synthorg_ metadata prefix - Mem0BackendConfig + config builder deriving from CompanyMemoryConfig - Factory wired up with deferred import (no longer raises MemoryConfigError for mem0 backend) - Shared knowledge via reserved __synthorg_shared__ namespace with publisher ownership tracking - 95 unit tests (adapter, mappers, config) + 6 integration tests (retrieval pipeline, shared knowledge flow) Closes #206
Pre-reviewed by 9 agents, 33 findings addressed: - Remove vendor-specific defaults from Mem0EmbedderConfig (now required) - Fix metadata dict mutation in publish() (use spread instead) - Add path traversal validation on Mem0BackendConfig.data_dir - Extract _validate_add_result helper for store/publish result validation - Add explicit ImportError handling in connect() - Make health_check() probe backend with lightweight get_all call - Add defensive category parsing in _extract_category (handle invalid enums) - Add missing publisher check in retract() (not a shared memory entry) - Replace C-style loop in search_shared with generator expression - Use meaningful variable names (raw_entries/filtered vs triple result) - Change logger.exception to logger.warning for self-raised errors - Add structured error/error_type kwargs to all re-raise logs - Add MemoryConnectionError to Raises docstrings for shared methods - Update factory to require embedder config (no vendor defaults) - Remove dead store_request_to_mem0_args function and its tests - Add tests: path traversal, missing id, empty content, malformed datetime, invalid category, no-publisher retract, delete-after-get failure, protocol conformance, health_check probe failure
…iewers - Add MemoryError/RecursionError guards before all except Exception blocks - Capture exceptions in re-raise blocks for structured logging - Move helper functions (validate_add_result, extract_category, extract_publisher) from adapter.py to mappers.py to keep adapter under 800-line limit - Add type/tag/confidence validation in mappers (items 9, 11, 23) - Add logging before all raise statements (items 7, 20, 21) - Fix health_check exception log level from debug to warning - Add search_shared query context to error logs - Strengthen factory.py type annotation (embedder: Mem0EmbedderConfig | None) - Parametrize config traversal tests, add ImportError/MemoryError/publish tests - Update CLAUDE.md, README, roadmap, and design spec for Mem0 adapter status
- Fix dependency review CI: add LicenseRef-scancode-protobuf, ZPL-2.1 to license allow-list and allow-dependencies-licenses for packages with null SPDX metadata (mem0ai, numpy, qdrant-client, posthog) - Fix validate_add_result blank-ID gap: check for None and whitespace IDs, not just missing key (Greptile finding) - Fix factory.py wrong event constant: use MEMORY_BACKEND_CONFIG_INVALID instead of MEMORY_BACKEND_UNKNOWN for embedder config errors (Greptile) - Fix retract() bare except: capture as exc and log error detail - Document count() limitation: capped at max_memories_per_agent - Add 37 new tests: validate_add_result (blank/None/whitespace/numeric ID, non-list results), extract_category, extract_publisher, MemoryError re-raise for all operations, blank-ID through store/retrieve, shared namespace fallback, count empty results, retract delete failure
…ig, and tests - mappers.py: add str() conversion before NotBlankStr for tags, clamp confidence to [0.0, 1.0], log missing/non-dict metadata and unexpected tag types, guard blank publisher strings, document None relevance score behavior in apply_post_filters, clean up historical comment - config.py: add structured logging to _reject_traversal validator, validate unsupported storage overrides in build_config_from_company_config - factory.py: wrap ValueError from config construction as MemoryConfigError - pyproject.toml: move mem0ai to optional-dependencies, add TC to test per-file-ignores - __init__.py: guard Mem0 imports with contextlib.suppress(ImportError) - adapter.py: log during disconnect reset failure instead of bare pass - dependency-review.yml: version-pin allowed PURLs - docs: add embedder config to memory.md YAML example, update roadmap Current Status to mention Mem0 adapter - tests: split test_adapter.py (1118 lines) into conftest.py + test_adapter.py + test_adapter_crud.py + test_adapter_shared.py; add protocol isinstance checks, connect MemoryError/RecursionError propagation, agent_id validation, ownership mismatch, and shared namespace guard tests - Clean up 78 now-unused noqa: TC* directives across test files
mem0ai was moved to optional-dependencies — CI needs --extra mem0 in the uv sync command to install it for tests.
mem0ai is the only memory backend — no reason to make it optional. Reverts the optional-dependencies split and CI extra flag.
…d Gemini - CRITICAL: remove Memory.reset() from disconnect() — it wiped all data - MAJOR: wrap Mem0MemoryBackend init errors in factory as MemoryConfigError - MEDIUM: coerce string scores in normalize_relevance_score - MEDIUM: validate dict structure in validate_add_result - MEDIUM: sanitize source metadata via _coerce_source helper - MEDIUM: update CLAUDE.md — mem0ai is a required dependency, not optional - MEDIUM: extract _coerce_confidence, _coerce_source, _normalize_tags helpers - MINOR: coerce non-string values in extract_publisher - MINOR: add user_id param to mem0_get_result fixture - MINOR: document adapter.py 800-line exemption - Tests: add coverage for string scores, non-dict results, source sanitization, publisher coercion, disconnect-no-reset
- Remove unused noqa A004 directive (ruff auto-fix) - Fix import sorting in adapter.py (ruff auto-fix) - Add connect() idempotency test (verifies no-op when already connected) - Add _validate_agent_id error_cls tests for read ops (retrieve, get, count) - Add expires_at filtering tests in apply_post_filters - Add config validation tests using model_construct to bypass Pydantic - Fix factory init test patch target to match local import
…and factory - Add Mem0Client protocol (TYPE_CHECKING) for structural typing documentation - Add asyncio.Lock + double-check locking to prevent concurrent connect() - Expand docstrings: adapter, config, design spec protocol snippets - Replace inline generator in search_shared with explicit loop + publisher fallback logging - Narrow factory exception catch to (ValueError, ValidationError) - Add YAML enum value annotations in design spec - Update CLAUDE.md logging event domains - Remove unused event constants (MEMORY_BACKEND_NOT_IMPLEMENTED, MEMORY_CAPABILITY_UNSUPPORTED) - Rename _PUBLISHER_KEY → PUBLISHER_KEY (public constant) - Add tests: RecursionError propagation, _validate_mem0_result, _coerce_confidence, _normalize_tags, count truncation warning, category post-filter, publisher fallback
…ig, factory, and tests - Remove @runtime_checkable from TYPE_CHECKING-only Mem0Client protocol - Add _connect_lock to disconnect() to prevent race with connect() - Fix MEMORY_ENTRY_DELETE_FAILED event on non-failure path (unverifiable owner) - Add shared namespace verification in retract() (defense-in-depth) - Add _validate_agent_id() call in retract() - Add math.isfinite() guards in normalize_relevance_score() and _coerce_confidence() - Prefer updated_at over now() as created_at fallback in mem0_result_to_entry() - Reject qdrant-external in build_config_from_company_config() (not supported) - Extract _create_mem0_backend() helper from create_memory_backend() - Fix MemoryQuery class docstring: "after" → "at or after" for since field - Use explicit builtins.MemoryError in all test assertions
…mes, pathlib - get(): return None for memories with no user_id (unverifiable ownership) - delete(): raise MemoryStoreError for orphan memories instead of allowing - search_shared(): reject exclude_agent matching reserved shared namespace - config: replace vendor-specific docstring examples with generic identifiers - config: use PurePosixPath for Docker-targeted path construction - factory: catch ValidationError alongside ValueError in config build - mappers: improved created_at fallback cascade (updated_at → expires_at → now) - tests: add orphan rejection tests (get/delete), namespace guard test - tests: freeze clock in expiry test, parameterize validate_add_result tests
Pre-reviewed by 8 agents, 11 findings addressed: - _require_connected() now returns Mem0Client for type-safe call sites - Replaced truthiness checks with explicit `is not None` in post-filters - Replaced dynamic log_fn dispatch with explicit branches in mappers - Added DEBUG logging for dropped tags in _normalize_tags - Extracted _resolve_publisher helper, replaced intermediate list with generator - Added logging to search_shared namespace guard - Removed dead owner-is-not-None conditions in delete() - Widened factory catch from (ValueError, ValidationError) to Exception - Added tests: orphan rejection, shared namespace guards, fallback dates
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF ScorecardScorecard details
Scanned Files
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a complete Mem0 memory backend: adapter, config, mappers, shared helpers, factory dispatch, docs/packaging updates, package exports, observability events, and extensive unit/integration tests; plus dependency-review and test import cleanup changes. Changes
Sequence Diagram(s)sequenceDiagram
participant Factory as Memory Factory
participant Config as Config Builder
participant Adapter as Mem0MemoryBackend
participant Mem0 as Mem0 Memory Client
participant Mappers as Domain Mappers
rect rgba(100, 150, 200, 0.5)
note over Factory,Mem0: Backend Initialization
Factory->>Config: build_config_from_company_config(config, embedder)
Config->>Config: validate embedder & vector_store
Config-->>Factory: Mem0BackendConfig
Factory->>Adapter: __init__(config)
Adapter-->>Factory: Mem0MemoryBackend instance
end
rect rgba(200, 150, 100, 0.5)
note over Adapter,Mappers: Store Operation
Adapter->>Adapter: connect() via asyncio.to_thread
Adapter->>Mem0: Memory.from_config() / add(...)
Mem0-->>Adapter: add result {id,...}
Adapter->>Mappers: build_mem0_metadata(request)
Mappers-->>Adapter: metadata dict
Adapter->>Mappers: validate_add_result(result)
Mappers-->>Adapter: memory_id
end
rect rgba(150, 200, 100, 0.5)
note over Adapter,Mappers: Retrieve Operation
Adapter->>Mem0: search(...) or get_all(...)
Mem0-->>Adapter: list of raw results
Adapter->>Mappers: mem0_result_to_entry() / parse timestamps / normalize score
Mappers-->>Adapter: MemoryEntry...
Adapter->>Mappers: apply_post_filters(entries, query)
Mappers-->>Adapter: filtered entries
Adapter-->>Factory: tuple[MemoryEntry, ...]
end
rect rgba(200, 100, 150, 0.5)
note over Adapter,Mem0: Shared Knowledge Store
Adapter->>Mem0: add(user_id="_shared", metadata={_synthorg_publisher: agent_id})
Mem0-->>Adapter: memory_id
Adapter->>Mem0: search(user_id="_shared")
Mem0-->>Adapter: shared memories
Adapter->>Mappers: extract_publisher() per entry
Mappers-->>Adapter: agent_id
Adapter->>Adapter: filter by exclude_agent if provided
Adapter-->>Factory: shared entries
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
✨ Simplify code
📝 Coding Plan
Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #345 +/- ##
==========================================
+ Coverage 93.82% 93.90% +0.07%
==========================================
Files 441 447 +6
Lines 20243 20803 +560
Branches 1950 2010 +60
==========================================
+ Hits 18993 19535 +542
- Misses 966 981 +15
- Partials 284 287 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR implements the Mem0 memory backend adapter, satisfying the Key changes:
One logic bug was found in Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant Mem0MemoryBackend
participant shared.py
participant mappers.py
participant Mem0SDK
Caller->>Mem0MemoryBackend: connect()
Mem0MemoryBackend->>Mem0SDK: asyncio.to_thread(Memory.from_config)
Mem0SDK-->>Mem0MemoryBackend: client
Mem0MemoryBackend-->>Caller: connected
Caller->>Mem0MemoryBackend: store(agent_id, request)
Mem0MemoryBackend->>mappers.py: build_mem0_metadata(request)
mappers.py-->>Mem0MemoryBackend: metadata dict
Mem0MemoryBackend->>Mem0SDK: asyncio.to_thread(client.add)
Mem0SDK-->>Mem0MemoryBackend: raw result
Mem0MemoryBackend->>mappers.py: validate_add_result(result)
mappers.py-->>Mem0MemoryBackend: memory_id
Mem0MemoryBackend-->>Caller: memory_id
Caller->>Mem0MemoryBackend: retrieve(agent_id, query)
Mem0MemoryBackend->>Mem0SDK: asyncio.to_thread(client.search / get_all)
Mem0SDK-->>Mem0MemoryBackend: raw results
Mem0MemoryBackend->>mappers.py: validate_mem0_result + mem0_result_to_entry (×N)
Mem0MemoryBackend->>mappers.py: apply_post_filters(entries, query)
mappers.py-->>Mem0MemoryBackend: filtered entries
Mem0MemoryBackend-->>Caller: tuple[MemoryEntry]
Caller->>Mem0MemoryBackend: publish(agent_id, request)
Mem0MemoryBackend->>shared.py: publish_shared(client, agent_id, request)
shared.py->>Mem0SDK: asyncio.to_thread(client.add, user_id=SHARED_NAMESPACE)
Mem0SDK-->>shared.py: raw result
shared.py-->>Mem0MemoryBackend: memory_id
Mem0MemoryBackend-->>Caller: memory_id
Caller->>Mem0MemoryBackend: retract(agent_id, memory_id)
Mem0MemoryBackend->>shared.py: retract_shared(client, agent_id, memory_id)
shared.py->>Mem0SDK: asyncio.to_thread(client.get, memory_id)
Mem0SDK-->>shared.py: raw entry
shared.py->>shared.py: _check_retract_ownership(raw, agent_id, memory_id)
shared.py->>Mem0SDK: asyncio.to_thread(client.delete, memory_id)
shared.py-->>Mem0MemoryBackend: True
Mem0MemoryBackend-->>Caller: True
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/ai_company/memory/backends/mem0/mappers.py
Line: 449
Comment:
**Falsy frozenset bypasses category filter entirely**
`if query.categories` evaluates to `False` for an empty `frozenset()`, which means a caller who explicitly passes `categories=frozenset()` (intending to match no categories) will instead receive **all** entries unfiltered — the opposite of the expected behavior. The equivalent guard for `query.since`/`query.until` correctly uses `is not None` checks; this filter should follow the same pattern.
```suggestion
if query.categories is not None and entry.category not in query.categories:
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 1c20792 |
There was a problem hiding this comment.
Pull request overview
Implements a concrete MemoryBackend for the memory subsystem by introducing a Mem0-based adapter, associated configuration models/builders, mapping utilities, and a factory integration so the backend can be instantiated from CompanyMemoryConfig (with an embedder config provided programmatically).
Changes:
- Add Mem0 backend implementation (
Mem0MemoryBackend) with shared knowledge operations and async wrapping of the Mem0 SDK. - Add Mem0-specific configuration (
Mem0BackendConfig,Mem0EmbedderConfig) plus config-dict builder and factory wiring (create_memory_backend(..., embedder=...)). - Add Mem0 <-> domain model mappers and extensive unit/integration tests; update docs, dependencies, and observability events accordingly.
Reviewed changes
Copilot reviewed 81 out of 84 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Locks new transitive dependencies required by mem0ai (and its stack). |
| pyproject.toml | Adds pinned mem0ai==1.0.5, adjusts ruff ignores, and mypy override for mem0.*. |
| src/ai_company/memory/factory.py | Implements backend dispatch and Mem0 backend construction with config validation/logging. |
| src/ai_company/memory/backends/mem0/adapter.py | New Mem0 adapter implementing backend CRUD + shared knowledge operations with async wrapping and observability. |
| src/ai_company/memory/backends/mem0/config.py | New Pydantic config models and builders for Mem0 embedded Qdrant + SQLite history. |
| src/ai_company/memory/backends/mem0/mappers.py | New mapping/validation utilities and post-filter pipeline for Mem0 results. |
| src/ai_company/memory/backends/mem0/init.py | Exposes Mem0 backend/config public API for imports. |
| src/ai_company/memory/backends/init.py | Adds concrete backend package exports. |
| src/ai_company/memory/init.py | Re-exports Mem0 backend/config for ai_company.memory consumers. |
| src/ai_company/observability/events/memory.py | Updates memory event constants to reflect new backend lifecycle/config errors. |
| src/ai_company/memory/models.py | Clarifies since semantics (inclusive) in MemoryQuery docs/field description. |
| tests/unit/memory/test_init.py | Ensures new Mem0 exports are importable from ai_company.memory. |
| tests/unit/memory/test_factory.py | Updates factory tests for Mem0 creation, embedder requirements, and error wrapping. |
| tests/unit/memory/backends/mem0/conftest.py | Adds shared fixtures for Mem0 backend tests. |
| tests/unit/memory/backends/mem0/test_mappers.py | Adds comprehensive mapper and post-filter unit tests. |
| tests/unit/memory/backends/mem0/test_config.py | Adds Mem0 config model/builder unit tests. |
| tests/unit/memory/backends/mem0/test_adapter.py | Adds adapter lifecycle/capabilities/protocol/guard unit tests. |
| tests/unit/memory/backends/mem0/test_adapter_crud.py | Adds adapter CRUD/count unit tests with error-path coverage. |
| tests/unit/memory/backends/mem0/test_adapter_shared.py | Adds shared knowledge publish/search/retract unit tests. |
| tests/integration/memory/test_mem0_backend.py | Adds retrieval-pipeline integration tests using the adapter with a mocked client. |
| tests/unit/observability/test_events.py | Updates expected event constants list to match new memory events. |
| docs/design/memory.md | Updates design spec to reflect Mem0 adapter now implemented + factory signature requiring embedder. |
| docs/roadmap/index.md | Updates roadmap to remove “Mem0 adapter” as remaining work. |
| README.md | Updates project status to indicate Mem0 backend adapter is implemented. |
| CLAUDE.md | Updates architecture/docs to reflect memory/backends/mem0 implementation and dependency note. |
| .github/workflows/dependency-review.yml | Adjusts dependency review allowlists for new packages and missing SPDX metadata. |
| tests/unit/tools/sandbox/test_protocol.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/tools/sandbox/conftest.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/tools/git/test_git_sandbox_integration.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/tools/git/conftest.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/providers/test_protocol.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/providers/conftest.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/hr/test_registry.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/hr/test_onboarding_service.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/hr/test_offboarding_service.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/hr/test_hiring_service.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/hr/test_full_snapshot_strategy.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_task_engine_mutations.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_routing_models.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_react_loop.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_plan_execute_loop.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_metrics.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_loop_protocol.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_context.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_agent_engine_lifecycle.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_agent_engine_errors.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/test_agent_engine.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/engine/task_engine_helpers.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/meeting/test_structured_phases.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/meeting/test_round_robin.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/meeting/test_protocol.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/meeting/test_position_papers.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/meeting/test_orchestrator.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/meeting/conftest.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/conflict_resolution/test_service.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/conflict_resolution/test_hybrid_strategy.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/conflict_resolution/test_helpers.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/conflict_resolution/test_debate_strategy.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/communication/conflict_resolution/test_authority_strategy.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/budget/test_category_analytics.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/test_middleware.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/test_health.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/test_guards.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/test_app.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_tasks.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_providers.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_projects.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_messages.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_meetings.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_departments.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_company.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_budget.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_autonomy.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_artifacts.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_approvals.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_analytics.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/controllers/test_agents.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/conftest.py | Removes type-checking import suppression comments (TC rules). |
| tests/unit/api/auth/test_controller.py | Removes type-checking import suppression comments (TC rules). |
| tests/integration/tools/test_sandbox_integration.py | Removes type-checking import suppression comments (TC rules). |
| tests/integration/tools/conftest.py | Removes type-checking import suppression comments (TC rules). |
| tests/integration/communication/test_meeting_integration.py | Removes type-checking import suppression comments (TC rules). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if t and str(t).strip(): | ||
| valid.append(NotBlankStr(str(t))) |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the system's memory capabilities by integrating Mem0 as a primary pluggable memory backend. It provides a robust and configurable solution for managing agent and shared knowledge, ensuring data integrity, efficient retrieval, and comprehensive observability. The changes lay the groundwork for a more flexible and scalable memory architecture. Highlights
Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/memory.md`:
- Around line 303-315: The Mem0 docs example misleads by showing configurable
storage.vector_store and storage.history_store even though Mem0 hardcodes Qdrant
+ SQLite; update the docs around CompanyMemoryConfig / MemoryStorageConfig /
MemoryOptionsConfig and the create_memory_backend description to either remove
the storage.vector_store and storage.history_store keys from the Mem0 YAML
example or explicitly annotate them as "not configurable for Mem0 (hardcoded to
Qdrant + SQLite in the Mem0 backend implementation)", and mention that the
embedder kwarg must be a Mem0EmbedderConfig for create_memory_backend; ensure
the text references Mem0's fixed storage behavior so readers don't expect
selectable stores.
In `@src/ai_company/memory/backends/mem0/adapter.py`:
- Around line 276-280: In disconnect(), avoid emitting INFO lifecycle logs when
there is no actual state change: acquire self._connect_lock, check the current
state via self._connected (or self._client) and if already disconnected log a
DEBUG message (using MEMORY_BACKEND_DISCONNECTING or a new DEBUG message) and
return; only when transitioning from connected to disconnected perform the INFO
logs and set self._client = None and self._connected = False; update the method
around the existing async with self._connect_lock block in adapter.disconnect to
gate MEMORY_BACKEND_DISCONNECTING and MEMORY_BACKEND_DISCONNECTED behind the
actual state change.
- Around line 13-17: The Mem0MemoryBackend class is too large—extract the Mem0
call wrappers, ownership checks, and shared-store flows into smaller helper
modules while keeping the core client lifecycle and connection-guard logic
inside Mem0MemoryBackend (which implements MemoryBackend, MemoryCapabilities,
and SharedKnowledgeStore). Create separate modules (e.g., mem0_calls.py for
low-level RPC/wrapper functions referenced by Mem0MemoryBackend, ownership.py
for ownership/permission checks, and shared_store.py for shared knowledge store
flows) and replace the in-class large methods with thin delegating calls to
those helpers; update imports and any tests to use the new helper functions,
ensuring public method signatures on Mem0MemoryBackend remain unchanged.
- Around line 501-509: The current retrieval passes query.limit into Mem0 (via
query_to_mem0_search_args / query_to_mem0_getall_args) then applies client-side
filters (apply_post_filters / exclude_agent), which can yield fewer results if
early pages are all filtered; change the logic in the retrieval flow (the block
that calls client.search / client.get_all, _validate_mem0_result,
mem0_result_to_entry, and apply_post_filters) to page/over-fetch: repeatedly
call client.search or client.get_all with an offset/pagination token and
accumulate validated entries, converting with mem0_result_to_entry and applying
apply_post_filters and exclude_agent as you accumulate until you have at least
query.limit post-filtered entries or the backend is exhausted, then slice to
query.limit before returning; add a regression test that simulates the first
page being entirely filtered out so the caller still receives query.limit
results.
- Around line 10-11: Several methods in the mem0 adapter currently catch
builtins.MemoryError and RecursionError and re-raise them without logging, which
loses operation/backend context; update each catch branch in the adapter module
to log the exception at WARNING or ERROR with contextual fields (operation name,
backend "mem0", relevant ids/params) before re-raising, or add a small helper
(e.g., _log_and_reraise(exc, operation, **ctx)) and call it from each catch to
emit the log and then re-raise the original exception to preserve type and
traceback.
In `@src/ai_company/memory/backends/mem0/config.py`:
- Around line 132-183: build_config_from_company_config currently passes
MemoryStorageConfig.data_dir and leaves collection_name defaulted, causing
multiple companies to share the same Mem0 storage; update
build_config_from_company_config to namespace Mem0 by company (e.g., append or
prefix a stable company identifier from config such as config.company_id or
config.organization to data_dir or to collection_name when constructing
Mem0BackendConfig) so each company gets a unique collection/path, and if no
company identifier is available, raise ValueError to fail fast; ensure you
reference Mem0BackendConfig (where data_dir/collection_name are set) and
consider adapter.py usages that key by agent_id/_SHARED_NAMESPACE so the
namespace change isolates records per company.
- Around line 77-98: The _reject_traversal() validator currently only checks for
".." but must also reject Windows-style paths so build_mem0_config_dict()
doesn't later produce mixed-separator paths; update _reject_traversal() (the
model_validator in class for mem0 config) to detect and reject Windows paths by
checking PureWindowsPath(self.data_dir).drive or the presence of "\\" in
self.data_dir, log MEMORY_BACKEND_CONFIG_INVALID with backend="mem0",
field="data_dir", value=self.data_dir and an appropriate reason message, and
raise ValueError when detected; keep the existing ".." check and return self
when valid.
In `@src/ai_company/memory/backends/mem0/mappers.py`:
- Around line 346-394: The agent_id parameters in query_to_mem0_search_args and
query_to_mem0_getall_args (and similarly in extract_publisher) should use the
NotBlankStr type from core.types instead of plain str; update the function
signatures to accept NotBlankStr (or NotBlankStr | None for optional cases),
import NotBlankStr from core.types, and ensure the returned dict values (user_id
or publisher) preserve the NotBlankStr type (no extra whitespace normalization
logic). Also apply the same change to the other affected functions around the
region mentioned (the functions at ~513-526) so all public agent/identifier
parameters use NotBlankStr consistently.
- Around line 264-343: Reduce mem0_result_to_entry's size by extracting its
raw-field validation and created_at fallback logic into two small helpers: one
to validate/extract the id and content (e.g., _extract_mem0_id(raw) and
_extract_mem0_content(raw)) that perform the logger.warning +
MemoryRetrievalError behavior, and one to compute created_at fallback (e.g.,
_resolve_created_at(created_raw, updated_at, expires_at)) that wraps the
parse_mem0_datetime usage and the fallback selection + warning. Keep the
remainder of mem0_result_to_entry to call parse_mem0_metadata,
normalize_relevance_score, and construct MemoryEntry with the values returned
from these helpers (preserve existing logger messages, constants like
MEMORY_MODEL_INVALID, and types such as NotBlankStr and MemoryRetrievalError) so
the function stays under 50 lines and the recovery paths are easy to unit-test.
- Around line 371-375: The mapper currently sends query.limit to Mem0 before
local post-filtering, which causes under-filled results; update the payloads
returned by the mapping functions that build Mem0 requests (the dicts around the
return at lines ~371 and the similar blocks at ~391-394 and ~397-439) to request
a larger candidate window or enable pagination instead of using query.limit
directly (for example: page_size = max(query.limit * N, query.limit + M) or
fetch pages until enough candidates are retrieved), then run
apply_post_filters() and only slice the final results to query.limit locally;
ensure the change is applied wherever the function returns {"query": ...,
"user_id": ..., "limit": ...} so post-filtering can produce a fully-filled
result set.
- Around line 308-328: The fallback logic for created_at currently always picks
updated_at first which can violate MemoryEntry invariants when expires_at is
earlier; modify the branch that computes fallback (in the code handling
created_at None near variables created_at, updated_at, expires_at,
logger.warning, MEMORY_MODEL_INVALID, raw) to choose the earliest timestamp
among the available candidates (updated_at, expires_at, and datetime.now(UTC))
rather than preferring updated_at always, and set fallback_source to a
descriptive string reflecting which value(s) were used (e.g. "updated_at",
"expires_at", "min(updated_at,expires_at)", or "now()") before assigning
created_at to that earliest fallback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d5aa0f5f-a248-421a-bab8-9a99e5a0b05d
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (83)
.github/workflows/dependency-review.ymlCLAUDE.mdREADME.mddocs/design/memory.mddocs/roadmap/index.mdpyproject.tomlsrc/ai_company/memory/__init__.pysrc/ai_company/memory/backends/__init__.pysrc/ai_company/memory/backends/mem0/__init__.pysrc/ai_company/memory/backends/mem0/adapter.pysrc/ai_company/memory/backends/mem0/config.pysrc/ai_company/memory/backends/mem0/mappers.pysrc/ai_company/memory/factory.pysrc/ai_company/memory/models.pysrc/ai_company/observability/events/memory.pytests/integration/communication/test_meeting_integration.pytests/integration/memory/test_mem0_backend.pytests/integration/tools/conftest.pytests/integration/tools/test_sandbox_integration.pytests/unit/api/auth/test_controller.pytests/unit/api/conftest.pytests/unit/api/controllers/test_agents.pytests/unit/api/controllers/test_analytics.pytests/unit/api/controllers/test_approvals.pytests/unit/api/controllers/test_artifacts.pytests/unit/api/controllers/test_autonomy.pytests/unit/api/controllers/test_budget.pytests/unit/api/controllers/test_company.pytests/unit/api/controllers/test_departments.pytests/unit/api/controllers/test_meetings.pytests/unit/api/controllers/test_messages.pytests/unit/api/controllers/test_projects.pytests/unit/api/controllers/test_providers.pytests/unit/api/controllers/test_tasks.pytests/unit/api/test_app.pytests/unit/api/test_guards.pytests/unit/api/test_health.pytests/unit/api/test_middleware.pytests/unit/budget/test_category_analytics.pytests/unit/communication/conflict_resolution/test_authority_strategy.pytests/unit/communication/conflict_resolution/test_debate_strategy.pytests/unit/communication/conflict_resolution/test_helpers.pytests/unit/communication/conflict_resolution/test_hybrid_strategy.pytests/unit/communication/conflict_resolution/test_service.pytests/unit/communication/meeting/conftest.pytests/unit/communication/meeting/test_orchestrator.pytests/unit/communication/meeting/test_position_papers.pytests/unit/communication/meeting/test_protocol.pytests/unit/communication/meeting/test_round_robin.pytests/unit/communication/meeting/test_structured_phases.pytests/unit/engine/task_engine_helpers.pytests/unit/engine/test_agent_engine.pytests/unit/engine/test_agent_engine_errors.pytests/unit/engine/test_agent_engine_lifecycle.pytests/unit/engine/test_context.pytests/unit/engine/test_loop_protocol.pytests/unit/engine/test_metrics.pytests/unit/engine/test_plan_execute_loop.pytests/unit/engine/test_react_loop.pytests/unit/engine/test_routing_models.pytests/unit/engine/test_task_engine_mutations.pytests/unit/hr/test_full_snapshot_strategy.pytests/unit/hr/test_hiring_service.pytests/unit/hr/test_offboarding_service.pytests/unit/hr/test_onboarding_service.pytests/unit/hr/test_registry.pytests/unit/memory/backends/__init__.pytests/unit/memory/backends/mem0/__init__.pytests/unit/memory/backends/mem0/conftest.pytests/unit/memory/backends/mem0/test_adapter.pytests/unit/memory/backends/mem0/test_adapter_crud.pytests/unit/memory/backends/mem0/test_adapter_shared.pytests/unit/memory/backends/mem0/test_config.pytests/unit/memory/backends/mem0/test_mappers.pytests/unit/memory/test_factory.pytests/unit/memory/test_init.pytests/unit/observability/test_events.pytests/unit/providers/conftest.pytests/unit/providers/test_protocol.pytests/unit/tools/git/conftest.pytests/unit/tools/git/test_git_sandbox_integration.pytests/unit/tools/sandbox/conftest.pytests/unit/tools/sandbox/test_protocol.py
💤 Files with no reviewable changes (1)
- tests/unit/observability/test_events.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Agent
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do NOT usefrom __future__ import annotations— Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:(no parentheses) for exception handling syntax per PEP 758 — ruff enforces this on Python 3.14
Files:
tests/unit/communication/meeting/test_structured_phases.pytests/unit/providers/conftest.pytests/unit/hr/test_registry.pytests/integration/communication/test_meeting_integration.pytests/unit/api/controllers/test_projects.pytests/unit/communication/conflict_resolution/test_hybrid_strategy.pytests/unit/tools/sandbox/conftest.pytests/integration/tools/conftest.pytests/unit/api/test_middleware.pytests/unit/api/controllers/test_providers.pytests/unit/communication/meeting/test_orchestrator.pytests/unit/tools/git/test_git_sandbox_integration.pytests/unit/memory/backends/mem0/test_adapter.pytests/unit/hr/test_onboarding_service.pytests/unit/engine/test_agent_engine_lifecycle.pytests/unit/engine/test_plan_execute_loop.pytests/unit/providers/test_protocol.pytests/unit/tools/git/conftest.pysrc/ai_company/observability/events/memory.pytests/unit/api/controllers/test_company.pytests/unit/engine/test_agent_engine.pytests/unit/api/controllers/test_approvals.pytests/unit/hr/test_hiring_service.pysrc/ai_company/memory/backends/mem0/__init__.pysrc/ai_company/memory/factory.pytests/unit/tools/sandbox/test_protocol.pytests/unit/memory/test_init.pytests/unit/budget/test_category_analytics.pytests/unit/api/controllers/test_agents.pytests/unit/hr/test_offboarding_service.pytests/unit/memory/backends/mem0/test_mappers.pytests/unit/engine/test_metrics.pytests/unit/api/conftest.pysrc/ai_company/memory/models.pytests/unit/engine/test_loop_protocol.pytests/unit/communication/conflict_resolution/test_service.pytests/unit/engine/test_agent_engine_errors.pytests/unit/hr/test_full_snapshot_strategy.pytests/unit/engine/task_engine_helpers.pytests/unit/api/controllers/test_messages.pytests/unit/api/controllers/test_analytics.pytests/unit/communication/meeting/test_position_papers.pytests/unit/communication/meeting/test_round_robin.pytests/unit/api/auth/test_controller.pytests/unit/api/controllers/test_meetings.pytests/unit/api/test_health.pytests/unit/api/controllers/test_tasks.pytests/unit/communication/conflict_resolution/test_authority_strategy.pytests/unit/api/controllers/test_departments.pytests/unit/api/test_app.pytests/unit/engine/test_task_engine_mutations.pytests/unit/api/test_guards.pytests/unit/engine/test_context.pytests/unit/memory/backends/mem0/test_adapter_crud.pytests/unit/communication/conflict_resolution/test_helpers.pytests/unit/communication/conflict_resolution/test_debate_strategy.pytests/unit/api/controllers/test_artifacts.pysrc/ai_company/memory/backends/__init__.pysrc/ai_company/memory/__init__.pytests/unit/engine/test_react_loop.pytests/unit/engine/test_routing_models.pytests/unit/communication/meeting/test_protocol.pytests/unit/memory/test_factory.pytests/integration/memory/test_mem0_backend.pytests/unit/communication/meeting/conftest.pytests/unit/memory/backends/mem0/conftest.pytests/unit/api/controllers/test_budget.pytests/unit/memory/backends/mem0/test_adapter_shared.pytests/unit/api/controllers/test_autonomy.pytests/unit/memory/backends/mem0/test_config.pytests/integration/tools/test_sandbox_integration.pysrc/ai_company/memory/backends/mem0/adapter.pysrc/ai_company/memory/backends/mem0/mappers.pysrc/ai_company/memory/backends/mem0/config.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowfor test markers
Prefer@pytest.mark.parametrizefor testing similar cases
Files:
tests/unit/communication/meeting/test_structured_phases.pytests/unit/providers/conftest.pytests/unit/hr/test_registry.pytests/integration/communication/test_meeting_integration.pytests/unit/api/controllers/test_projects.pytests/unit/communication/conflict_resolution/test_hybrid_strategy.pytests/unit/tools/sandbox/conftest.pytests/integration/tools/conftest.pytests/unit/api/test_middleware.pytests/unit/api/controllers/test_providers.pytests/unit/communication/meeting/test_orchestrator.pytests/unit/tools/git/test_git_sandbox_integration.pytests/unit/memory/backends/mem0/test_adapter.pytests/unit/hr/test_onboarding_service.pytests/unit/engine/test_agent_engine_lifecycle.pytests/unit/engine/test_plan_execute_loop.pytests/unit/providers/test_protocol.pytests/unit/tools/git/conftest.pytests/unit/api/controllers/test_company.pytests/unit/engine/test_agent_engine.pytests/unit/api/controllers/test_approvals.pytests/unit/hr/test_hiring_service.pytests/unit/tools/sandbox/test_protocol.pytests/unit/memory/test_init.pytests/unit/budget/test_category_analytics.pytests/unit/api/controllers/test_agents.pytests/unit/hr/test_offboarding_service.pytests/unit/memory/backends/mem0/test_mappers.pytests/unit/engine/test_metrics.pytests/unit/api/conftest.pytests/unit/engine/test_loop_protocol.pytests/unit/communication/conflict_resolution/test_service.pytests/unit/engine/test_agent_engine_errors.pytests/unit/hr/test_full_snapshot_strategy.pytests/unit/engine/task_engine_helpers.pytests/unit/api/controllers/test_messages.pytests/unit/api/controllers/test_analytics.pytests/unit/communication/meeting/test_position_papers.pytests/unit/communication/meeting/test_round_robin.pytests/unit/api/auth/test_controller.pytests/unit/api/controllers/test_meetings.pytests/unit/api/test_health.pytests/unit/api/controllers/test_tasks.pytests/unit/communication/conflict_resolution/test_authority_strategy.pytests/unit/api/controllers/test_departments.pytests/unit/api/test_app.pytests/unit/engine/test_task_engine_mutations.pytests/unit/api/test_guards.pytests/unit/engine/test_context.pytests/unit/memory/backends/mem0/test_adapter_crud.pytests/unit/communication/conflict_resolution/test_helpers.pytests/unit/communication/conflict_resolution/test_debate_strategy.pytests/unit/api/controllers/test_artifacts.pytests/unit/engine/test_react_loop.pytests/unit/engine/test_routing_models.pytests/unit/communication/meeting/test_protocol.pytests/unit/memory/test_factory.pytests/integration/memory/test_mem0_backend.pytests/unit/communication/meeting/conftest.pytests/unit/memory/backends/mem0/conftest.pytests/unit/api/controllers/test_budget.pytests/unit/memory/backends/mem0/test_adapter_shared.pytests/unit/api/controllers/test_autonomy.pytests/unit/memory/backends/mem0/test_config.pytests/integration/tools/test_sandbox_integration.py
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: Useasyncio_mode = "auto"in pytest configuration—no manual@pytest.mark.asyncioneeded on individual tests
Test timeout: 30 seconds per test
Dependencies: all versions pinned with==inpyproject.toml. Groups:testanddev(includes test). Install viauv sync.
Files:
pyproject.toml
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Type hints: all public functions must have type hints, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Create new objects instead of mutating existing ones—never mutate existing objects. For non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement.
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Use@computed_fieldfor derived values instead of storing + validating redundant fields. UseNotBlankStr(fromcore.types) for all identifier/name fields—including optional and tuple variants—instead of manual whitespace validators.
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task.
Line length: 88 characters (enforced by ruff)
Functions: < 50 lines, files < 800 lines
Handle errors explicitly, never silently swallow them
Validate at system boundaries (user input, external APIs, config files)
Every module with business logic MUST have:from ai_company.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code
Always useloggeras the variable name (not_logger, notlog)
Use event name constants fromai_company.observability.eventsdomain-spe...
Files:
src/ai_company/observability/events/memory.pysrc/ai_company/memory/backends/mem0/__init__.pysrc/ai_company/memory/factory.pysrc/ai_company/memory/models.pysrc/ai_company/memory/backends/__init__.pysrc/ai_company/memory/__init__.pysrc/ai_company/memory/backends/mem0/adapter.pysrc/ai_company/memory/backends/mem0/mappers.pysrc/ai_company/memory/backends/mem0/config.py
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Docs source:
docs/(Markdown, built with Zensical). Design spec:docs/design/(7 pages: index, agents, organization, communication, engine, memory, operations).
Files:
docs/roadmap/index.mddocs/design/memory.md
.github/workflows/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
CI: Jobs (lint + type-check + test in parallel) → ci-pass gate. Pages workflow exports OpenAPI schema, builds Astro landing + Zensical docs, merges, deploys to GitHub Pages on push to main.
Files:
.github/workflows/dependency-review.yml
🧠 Learnings (11)
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to scripts/**/*.py : Scripts: `scripts/` directory with relaxed ruff rules allowing `print` and deferred imports
Applied to files:
pyproject.toml
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : All provider calls go through `BaseCompletionProvider` which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code—it's handled by the base class.
Applied to files:
tests/unit/providers/test_protocol.py
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to docs/**/*.md : Docs source: `docs/` (Markdown, built with Zensical). Design spec: `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations).
Applied to files:
docs/roadmap/index.mdCLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Every module with business logic MUST have: `from ai_company.observability import get_logger` then `logger = get_logger(__name__)`
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Use event name constants from `ai_company.observability.events` domain-specific modules (e.g. `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, etc.) instead of hardcoded string literals
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Always use structured logging kwargs: `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Never use `import logging` / `logging.getLogger()` / `print()` in application code
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Always use `logger` as the variable name (not `_logger`, not `log`)
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : All error paths must log at WARNING or ERROR with context before raising
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : All state transitions must log at INFO
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to pyproject.toml : Dependencies: all versions pinned with `==` in `pyproject.toml`. Groups: `test` and `dev` (includes test). Install via `uv sync`.
Applied to files:
CLAUDE.md
🧬 Code graph analysis (45)
tests/unit/communication/meeting/test_structured_phases.py (1)
src/ai_company/communication/meeting/models.py (1)
MeetingAgenda(77-96)
tests/unit/hr/test_registry.py (3)
tests/unit/hr/promotion/conftest.py (1)
registry(98-100)tests/unit/hr/conftest.py (1)
registry(174-176)src/ai_company/hr/registry.py (1)
AgentRegistryService(32-262)
tests/integration/communication/test_meeting_integration.py (1)
src/ai_company/communication/meeting/protocol.py (1)
MeetingProtocol(61-100)
tests/unit/communication/conflict_resolution/test_hybrid_strategy.py (1)
src/ai_company/communication/delegation/hierarchy.py (1)
HierarchyResolver(16-277)
tests/unit/communication/meeting/test_orchestrator.py (1)
src/ai_company/communication/meeting/protocol.py (1)
MeetingProtocol(61-100)
tests/unit/memory/backends/mem0/test_adapter.py (7)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/backends/mem0/config.py (1)
Mem0BackendConfig(54-98)src/ai_company/memory/errors.py (2)
MemoryConnectionError(17-18)MemoryError(13-14)src/ai_company/memory/models.py (1)
MemoryQuery(153-230)src/ai_company/memory/protocol.py (1)
MemoryBackend(20-182)src/ai_company/memory/shared.py (1)
SharedKnowledgeStore(18-82)tests/unit/memory/backends/mem0/conftest.py (2)
make_store_request(111-117)backend(41-49)
tests/unit/hr/test_onboarding_service.py (1)
tests/unit/hr/conftest.py (2)
onboarding_service(180-182)registry(174-176)
tests/unit/engine/test_agent_engine_lifecycle.py (2)
src/ai_company/core/agent.py (1)
AgentIdentity(266-342)src/ai_company/core/task.py (1)
Task(45-261)
tests/unit/engine/test_agent_engine.py (1)
src/ai_company/core/agent.py (1)
AgentIdentity(266-342)
tests/unit/api/controllers/test_approvals.py (2)
tests/unit/api/conftest.py (1)
approval_store(606-607)src/ai_company/api/approval_store.py (1)
ApprovalStore(27-153)
tests/unit/hr/test_hiring_service.py (3)
tests/unit/hr/conftest.py (2)
onboarding_service(180-182)registry(174-176)src/ai_company/hr/onboarding_service.py (1)
OnboardingService(28-173)src/ai_company/hr/registry.py (1)
AgentRegistryService(32-262)
src/ai_company/memory/backends/mem0/__init__.py (2)
src/ai_company/memory/backends/mem0/adapter.py (1)
Mem0MemoryBackend(194-1073)src/ai_company/memory/backends/mem0/config.py (2)
Mem0BackendConfig(54-98)Mem0EmbedderConfig(23-51)
src/ai_company/memory/factory.py (4)
src/ai_company/memory/config.py (1)
CompanyMemoryConfig(139-195)src/ai_company/memory/backends/mem0/config.py (2)
Mem0EmbedderConfig(23-51)build_config_from_company_config(132-183)src/ai_company/memory/errors.py (1)
MemoryConfigError(40-41)src/ai_company/memory/backends/mem0/adapter.py (2)
Mem0MemoryBackend(194-1073)max_memories_per_agent(361-363)
tests/unit/budget/test_category_analytics.py (1)
src/ai_company/budget/tracker.py (1)
CostTracker(68-455)
tests/unit/api/controllers/test_agents.py (1)
src/ai_company/api/auth/service.py (1)
AuthService(36-245)
tests/unit/hr/test_offboarding_service.py (5)
src/ai_company/communication/channel.py (1)
Channel(14-39)src/ai_company/communication/message.py (1)
Message(88-138)src/ai_company/core/enums.py (2)
AgentStatus(67-73)TaskStatus(198-224)tests/unit/hr/conftest.py (1)
registry(174-176)src/ai_company/hr/registry.py (1)
AgentRegistryService(32-262)
tests/unit/memory/backends/mem0/test_mappers.py (3)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/errors.py (2)
MemoryRetrievalError(25-26)MemoryStoreError(21-22)src/ai_company/memory/models.py (2)
MemoryEntry(82-150)MemoryMetadata(20-52)
tests/unit/engine/test_metrics.py (1)
src/ai_company/engine/context.py (1)
AgentContext(87-307)
tests/unit/api/conftest.py (7)
src/ai_company/budget/cost_record.py (1)
CostRecord(16-57)src/ai_company/communication/channel.py (1)
Channel(14-39)src/ai_company/communication/message.py (1)
Message(88-138)src/ai_company/hr/enums.py (1)
LifecycleEventType(32-41)src/ai_company/hr/performance/models.py (2)
CollaborationMetricRecord(69-124)TaskMetricRecord(24-66)src/ai_company/security/models.py (1)
AuditEntry(112-149)src/ai_company/security/timeout/parked_context.py (1)
ParkedContext(19-64)
tests/unit/engine/test_loop_protocol.py (2)
tests/unit/engine/conftest.py (1)
engine(449-460)src/ai_company/engine/context.py (1)
AgentContext(87-307)
tests/unit/communication/conflict_resolution/test_service.py (1)
src/ai_company/communication/delegation/hierarchy.py (1)
HierarchyResolver(16-277)
tests/unit/engine/test_agent_engine_errors.py (3)
src/ai_company/core/agent.py (1)
AgentIdentity(266-342)src/ai_company/core/enums.py (1)
TaskStatus(198-224)src/ai_company/core/task.py (1)
Task(45-261)
tests/unit/hr/test_full_snapshot_strategy.py (1)
src/ai_company/memory/consolidation/models.py (1)
ArchivalEntry(56-91)
tests/unit/engine/task_engine_helpers.py (1)
src/ai_company/core/task.py (1)
Task(45-261)
tests/unit/communication/meeting/test_position_papers.py (1)
src/ai_company/communication/meeting/models.py (1)
MeetingAgenda(77-96)
tests/unit/communication/meeting/test_round_robin.py (1)
src/ai_company/communication/meeting/models.py (1)
MeetingAgenda(77-96)
tests/unit/api/auth/test_controller.py (1)
src/ai_company/api/auth/service.py (1)
AuthService(36-245)
tests/unit/communication/conflict_resolution/test_authority_strategy.py (1)
src/ai_company/communication/delegation/hierarchy.py (1)
HierarchyResolver(16-277)
tests/unit/engine/test_task_engine_mutations.py (1)
src/ai_company/core/task.py (1)
Task(45-261)
tests/unit/engine/test_context.py (3)
src/ai_company/core/agent.py (1)
AgentIdentity(266-342)src/ai_company/core/enums.py (1)
TaskStatus(198-224)src/ai_company/core/task.py (1)
Task(45-261)
tests/unit/memory/backends/mem0/test_adapter_crud.py (4)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/backends/mem0/adapter.py (6)
_validate_mem0_result(123-175)store(417-474)search(98-100)retrieve(476-535)delete(110-112)delete(622-720)src/ai_company/memory/errors.py (3)
MemoryRetrievalError(25-26)MemoryStoreError(21-22)MemoryError(13-14)src/ai_company/memory/models.py (1)
MemoryQuery(153-230)
tests/unit/communication/conflict_resolution/test_helpers.py (1)
src/ai_company/communication/delegation/hierarchy.py (1)
HierarchyResolver(16-277)
tests/unit/communication/conflict_resolution/test_debate_strategy.py (1)
src/ai_company/communication/delegation/hierarchy.py (1)
HierarchyResolver(16-277)
src/ai_company/memory/backends/__init__.py (1)
src/ai_company/memory/backends/mem0/adapter.py (1)
Mem0MemoryBackend(194-1073)
src/ai_company/memory/__init__.py (2)
src/ai_company/memory/backends/mem0/config.py (1)
Mem0EmbedderConfig(23-51)src/ai_company/memory/backends/mem0/adapter.py (1)
Mem0MemoryBackend(194-1073)
tests/unit/engine/test_react_loop.py (1)
src/ai_company/core/agent.py (1)
AgentIdentity(266-342)
tests/unit/engine/test_routing_models.py (1)
src/ai_company/core/agent.py (1)
AgentIdentity(266-342)
tests/integration/memory/test_mem0_backend.py (8)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/backends/mem0/adapter.py (15)
Mem0MemoryBackend(194-1073)max_memories_per_agent(361-363)add(94-96)search(98-100)store(417-474)retrieve(476-535)publish(813-876)search_shared(878-968)get(106-108)get(537-620)delete(110-112)delete(622-720)retract(970-1073)get_all(102-104)count(722-809)src/ai_company/memory/backends/mem0/config.py (2)
Mem0BackendConfig(54-98)Mem0EmbedderConfig(23-51)src/ai_company/memory/models.py (2)
MemoryQuery(153-230)MemoryStoreRequest(55-79)src/ai_company/memory/retrieval_config.py (1)
MemoryRetrievalConfig(20-132)src/ai_company/memory/retriever.py (1)
ContextInjectionStrategy(97-400)tests/unit/memory/test_factory.py (1)
_test_embedder(21-26)tests/unit/memory/backends/mem0/conftest.py (1)
mem0_config(26-31)
tests/unit/memory/backends/mem0/conftest.py (4)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/backends/mem0/adapter.py (1)
max_memories_per_agent(361-363)src/ai_company/memory/backends/mem0/config.py (2)
Mem0BackendConfig(54-98)Mem0EmbedderConfig(23-51)src/ai_company/memory/models.py (1)
MemoryStoreRequest(55-79)
tests/unit/api/controllers/test_budget.py (2)
src/ai_company/budget/cost_record.py (1)
CostRecord(16-57)src/ai_company/budget/tracker.py (1)
CostTracker(68-455)
tests/unit/memory/backends/mem0/test_adapter_shared.py (4)
src/ai_company/memory/backends/mem0/adapter.py (11)
Mem0MemoryBackend(194-1073)add(94-96)publish(813-876)search(98-100)search_shared(878-968)get_all(102-104)get(106-108)get(537-620)delete(110-112)delete(622-720)retract(970-1073)src/ai_company/memory/errors.py (3)
MemoryRetrievalError(25-26)MemoryStoreError(21-22)MemoryError(13-14)src/ai_company/memory/models.py (1)
MemoryQuery(153-230)src/ai_company/core/enums.py (1)
MemoryCategory(101-108)
tests/unit/memory/backends/mem0/test_config.py (4)
src/ai_company/memory/backends/mem0/config.py (4)
Mem0BackendConfig(54-98)Mem0EmbedderConfig(23-51)build_config_from_company_config(132-183)build_mem0_config_dict(101-129)src/ai_company/memory/config.py (2)
CompanyMemoryConfig(139-195)MemoryStorageConfig(25-104)tests/integration/memory/test_mem0_backend.py (1)
backend(45-54)tests/unit/memory/backends/mem0/conftest.py (1)
mem0_config(26-31)
src/ai_company/memory/backends/mem0/adapter.py (3)
src/ai_company/memory/backends/mem0/config.py (2)
Mem0BackendConfig(54-98)build_mem0_config_dict(101-129)src/ai_company/memory/backends/mem0/mappers.py (8)
apply_post_filters(397-439)build_mem0_metadata(40-59)extract_category(488-510)extract_publisher(513-526)mem0_result_to_entry(264-343)query_to_mem0_getall_args(378-394)query_to_mem0_search_args(346-375)validate_add_result(445-485)src/ai_company/memory/models.py (3)
MemoryEntry(82-150)MemoryQuery(153-230)MemoryStoreRequest(55-79)
src/ai_company/memory/backends/mem0/mappers.py (3)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/errors.py (2)
MemoryRetrievalError(25-26)MemoryStoreError(21-22)src/ai_company/memory/models.py (2)
MemoryEntry(82-150)MemoryMetadata(20-52)
src/ai_company/memory/backends/mem0/config.py (2)
src/ai_company/memory/config.py (1)
CompanyMemoryConfig(139-195)src/ai_company/observability/_logger.py (1)
get_logger(8-28)
🪛 LanguageTool
CLAUDE.md
[style] ~130-~130: A comma is missing here.
Context: ...nder ai_company.observability.events (e.g. PROVIDER_CALL_START from `events.prov...
(EG_NO_COMMA)
docs/design/memory.md
[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)
🔇 Additional comments (60)
tests/unit/api/controllers/test_messages.py (1)
6-6: Import cleanup looks good.This change is non-functional and keeps typing intact for
TestClient[Any].tests/unit/providers/test_protocol.py (1)
3-3: Import cleanup looks good.Removing the inline noqa here is safe and keeps the test module cleaner.
tests/unit/api/test_app.py (1)
7-7: Import cleanup looks good.
TestClientis directly used for the fixture type annotation, and removing the inline# noqadoes not introduce any functional or guideline regressions.tests/unit/api/controllers/test_providers.py (1)
6-6: Looks good — lint suppression removal is safe.Line 6 now uses a clean
TestClientimport with no behavior change and no guideline violations.tests/unit/engine/test_agent_engine_errors.py (1)
8-10: Import cleanup is correct and safe.
AgentIdentityandTaskare both used by test signatures, so removing inlinenoqahere does not introduce lint or runtime risk.tests/unit/api/controllers/test_approvals.py (1)
7-7: Import cleanup looks good.Line 7 and Line 9 keep the same runtime behavior while removing inline lint suppressions, and the imports remain consistent with fixture/type usage in this test module.
Also applies to: 9-9
tests/unit/api/conftest.py (1)
5-39: LGTM: import cleanup is behavior-neutral and safe.Removing the inline
noqasuppressions here does not alter functionality, and the imported symbols are used within this fixture module.tests/unit/api/controllers/test_departments.py (1)
6-6: Import cleanup is good.Removing the inline suppression keeps the test import clean and does not change runtime behavior.
tests/unit/hr/test_registry.py (1)
7-7: Good cleanup: removing inline lint suppression is safe here.This keeps the test import explicit and readable without changing behavior.
tests/unit/tools/sandbox/test_protocol.py (3)
3-3: Import cleanup is correct here.
Mappingis used by the protocol-signature annotation, so removing the inline suppression is safe.
4-4:Pathimport remains necessary and valid.The line is used across type annotations in this file, so this cleanup looks good.
12-12: SubprocessSandbox import change is clean.It is directly used by the typed fixture test, so dropping the suppression comment is appropriate.
tests/unit/engine/test_metrics.py (1)
6-6: Looks good — no issue with removing the suppression comment here.This import remains valid and the change is non-functional.
tests/unit/api/controllers/test_budget.py (1)
7-10: Import cleanup looks good.Removing inline lint suppressions on these imports keeps the test module cleaner without changing behavior.
tests/unit/hr/test_onboarding_service.py (1)
8-9: Import cleanup is good here.This keeps type imports explicit without changing behavior.
tests/unit/budget/test_category_analytics.py (1)
15-15: Import cleanup looks good.
CostTrackeris still used in this file’s type annotations, so removing the inlinenoqais safe and improves lint hygiene.tests/unit/hr/test_full_snapshot_strategy.py (1)
12-12: Import cleanup looks good.Removing
# noqa: TC001on Line 12 is safe and keeps the test file lint-clean without changing behavior.tests/unit/engine/test_context.py (1)
9-11: LGTM!The removal of
# noqa: TC001suppressions is correct. These imports (AgentIdentity,Task) are used at runtime in pytest fixture type hints (e.g.,sample_agent_with_personality: AgentIdentity), so they legitimately belong outside aTYPE_CHECKINGblock. The lint suppression was unnecessary.tests/unit/api/controllers/test_autonomy.py (1)
6-6: LGTM! Lint suppression cleanup is appropriate.The removal of
# noqa: TC002is correct sinceTestClientis used at runtime in test function signatures and cannot be moved to a type-checking block. This aligns with the PR's broader cleanup effort to remove unnecessary lint suppressions from test imports.tests/unit/api/controllers/test_agents.py (1)
31-31: LGTM: removing the inlinenoqahere is a good cleanup.
AuthServiceis still referenced (type annotation at Line 45), so this change is clear and safe.tests/unit/api/controllers/test_tasks.py (1)
6-6: LGTM!Clean removal of the inline lint suppression comment. The import remains valid and the change aligns with the repository-wide cleanup of
noqaannotations mentioned in the PR objectives..github/workflows/dependency-review.yml (2)
32-43: LGTM!The license additions are well-documented with clear explanations. Both
LicenseRef-scancode-protobufandZPL-2.1are permissive licenses appropriate for inclusion.
44-51: LGTM — version-pinned allowlist is appropriate.The purl format and manual license verification are correct. The version pins mean this list will need updating when these dependencies are upgraded (e.g.,
numpy@2.4.3→numpy@2.5.x), but that's the intended secure behavior for packages with missing SPDX metadata.Consider adding a brief inline comment noting this maintenance requirement so future contributors know to update this block during dependency bumps.
tests/unit/hr/test_hiring_service.py (1)
15-16: LGTM: Correct removal of unnecessary lint suppressions.Both
OnboardingServiceandAgentRegistryServiceare used at runtime (constructor arguments and method calls), not just in type annotations, so the# noqa: TC001suppressions were unnecessary. This cleanup correctly aligns with linting standards.tests/unit/api/test_middleware.py (1)
6-6: LGTM! Lint cleanup aligns with test-suite standards.The removal of the
# noqa: TC002suppression is appropriate.TestClientis actively used in type annotations throughout the file (lines 45, 50, 56, 106, 111), and this cleanup aligns with the PR's broader effort to remove unnecessary linting suppressions.tests/unit/api/test_health.py (1)
6-6: LGTM! Lint suppression cleanup.Removing the
# noqa: TC002suppression is appropriate as part of the broader linting cleanup mentioned in the PR objectives. No functional impact.tests/unit/hr/test_offboarding_service.py (1)
8-22: LGTM! Correct removal of unnecessary lint suppressions.All four imports (
Channel,Message,Task,AgentRegistryService) are used at runtime in the test file—not just for type annotations. Removing the# noqa: TC001suppressions is appropriate and aligns with the PR's lint hygiene cleanup objectives.tests/unit/providers/conftest.py (1)
3-3: LGTM!The
noqa: TC003removal is correct.AsyncIteratoris used at runtime in theFakeProvider.stream()return type annotation and generator, so the import cannot be moved to aTYPE_CHECKINGblock.tests/unit/communication/conflict_resolution/test_service.py (1)
22-24: LGTM!The
noqa: TC001removal is appropriate.HierarchyResolveris used at runtime in function signatures (e.g.,_make_service()parameter and fixture type hints), so the import must remain outsideTYPE_CHECKING.tests/unit/engine/test_routing_models.py (1)
5-5: LGTM!The
noqa: TC001removal is correct.AgentIdentityis used at runtime in fixture parameter type hints throughout the test class methods.tests/unit/engine/test_agent_engine.py (1)
12-12: LGTM!The
noqa: TC001removal is correct.AgentIdentityis used at runtime in fixture parameter type hints throughout multiple test classes.tests/unit/api/controllers/test_analytics.py (1)
6-6: LGTM!The
noqa: TC002removal is correct.TestClientis used at runtime in fixture parameter type hints.docs/roadmap/index.md (1)
11-11: LGTM!Documentation correctly updated to reflect the Mem0 adapter implementation. The Memory system entry now accurately lists all completed components including the new Mem0 adapter.
tests/unit/engine/test_loop_protocol.py (1)
9-9: LGTM!The
noqa: TC001removal is correct.AgentContextis used at runtime in fixture parameter type hints throughout the test classes.tests/unit/api/test_guards.py (1)
6-6: LGTM!The
noqa: TC002removal is correct.TestClientis used at runtime in fixture parameter type hints and thebare_clientfixture return type annotation.tests/unit/api/auth/test_controller.py (2)
6-6: LGTM!Lint suppression removal is appropriate. The
TestClientimport is valid and used throughout the test file.
47-47: LGTM!Lint suppression removal is appropriate. The
AuthServiceimport is correctly used within the test method scope.tests/unit/api/controllers/test_artifacts.py (1)
6-6: LGTM!Lint suppression cleanup. The
TestClientimport is properly used for type annotations.tests/unit/communication/meeting/test_position_papers.py (1)
13-13: LGTM!Lint suppression cleanup. The
MeetingAgendaimport is used for type hints in fixture parameters throughout the test class.tests/unit/communication/conflict_resolution/test_debate_strategy.py (1)
14-16: LGTM!Lint suppression cleanup. The
HierarchyResolverimport is used for type hints in test method fixture parameters.tests/unit/communication/meeting/test_structured_phases.py (1)
13-13: LGTM!Lint suppression cleanup. The
MeetingAgendaimport is properly used for type annotations in fixture parameters.tests/unit/api/controllers/test_company.py (1)
6-6: LGTM!Lint suppression cleanup. The
TestClientimport is used for type annotations in test method signatures.tests/unit/communication/conflict_resolution/test_hybrid_strategy.py (1)
17-19: LGTM!Lint suppression cleanup. The
HierarchyResolverimport is used for type annotations in fixture parameters throughout the test classes.tests/integration/tools/test_sandbox_integration.py (1)
4-4: LGTM!Lint suppression cleanup. The
Pathimport is used both for type annotations and runtime operations throughout the test file.tests/unit/engine/task_engine_helpers.py (1)
6-6: Import cleanup is safe.Line 6 only removes an inline suppression; behavior is unchanged and the import remains explicit.
tests/unit/tools/sandbox/conftest.py (1)
3-3: LGTM: non-functional lint cleanup.Line 3 is an import-only cleanup with no behavioral change in fixture setup.
tests/integration/communication/test_meeting_integration.py (1)
29-30: Import cleanup looks good.Lines 29–30 keep the same symbols and only remove lint suppression noise.
tests/unit/communication/meeting/test_round_robin.py (1)
13-13: Safe import-only change.Line 13 is a lint-suppression removal with no effect on protocol test behavior.
tests/unit/api/controllers/test_meetings.py (1)
6-6: LGTM on lint suppression removal.Line 6 preserves the same
TestClientimport and does not alter test execution.tests/unit/communication/meeting/test_orchestrator.py (1)
31-31: No concerns with this import cleanup.Line 31 removes suppression only; protocol typing import remains unchanged.
tests/unit/communication/conflict_resolution/test_authority_strategy.py (1)
12-12: Import cleanup is fine.Line 12 keeps
HierarchyResolverusage intact and only drops inline suppression.src/ai_company/memory/models.py (1)
165-165: Good clarification ofsinceboundary semantics.Lines 165 and 197 make the lower bound explicitly inclusive, improving API clarity without changing behavior.
Also applies to: 197-197
tests/unit/api/controllers/test_projects.py (1)
6-6: Import cleanup is good and non-functional.This keeps the test file cleaner without changing behavior.
tests/unit/engine/test_agent_engine_lifecycle.py (1)
9-11: Looks good — import-only cleanup with no behavior change.tests/unit/communication/meeting/test_protocol.py (1)
6-6: Good cleanup on the import statement.No correctness or maintainability concerns here.
tests/unit/engine/test_react_loop.py (1)
8-8: Import change is clean and safe.tests/unit/engine/test_task_engine_mutations.py (1)
8-8: No issues — import-only cleanup is fine.tests/integration/tools/conftest.py (1)
5-5: Looks good.Pure import cleanup with no runtime impact.
tests/unit/communication/conflict_resolution/test_helpers.py (1)
12-12: Import formatting cleanup is fine.pyproject.toml (1)
24-24: Configuration updates are coherent for Mem0 integration.Pinned dependency, Ruff adjustment, and mypy override align with this backend addition.
Also applies to: 138-138, 189-191
| if query.text is not None: | ||
| kwargs = query_to_mem0_search_args(str(agent_id), query) | ||
| raw_result = await asyncio.to_thread(client.search, **kwargs) | ||
| else: | ||
| kwargs = query_to_mem0_getall_args(str(agent_id), query) | ||
| raw_result = await asyncio.to_thread(client.get_all, **kwargs) | ||
| raw_list = _validate_mem0_result(raw_result, context="retrieve") | ||
| entries = tuple(mem0_result_to_entry(item, agent_id) for item in raw_list) | ||
| entries = apply_post_filters(entries, query) |
There was a problem hiding this comment.
Fetch more than query.limit before applying client-side filters.
query.limit is the caller-visible limit after category/tag/time/relevance/exclude filtering, but both methods hand that limit to Mem0 first and only then run apply_post_filters() / exclude_agent locally. If the first N raw hits are filtered out, you'll return too few results even though later hits match. Please paginate or over-fetch until you have query.limit filtered entries, then slice, and add a regression test where the first page is entirely filtered away.
Also applies to: 910-940
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/memory/backends/mem0/adapter.py` around lines 501 - 509, The
current retrieval passes query.limit into Mem0 (via query_to_mem0_search_args /
query_to_mem0_getall_args) then applies client-side filters (apply_post_filters
/ exclude_agent), which can yield fewer results if early pages are all filtered;
change the logic in the retrieval flow (the block that calls client.search /
client.get_all, _validate_mem0_result, mem0_result_to_entry, and
apply_post_filters) to page/over-fetch: repeatedly call client.search or
client.get_all with an offset/pagination token and accumulate validated entries,
converting with mem0_result_to_entry and applying apply_post_filters and
exclude_agent as you accumulate until you have at least query.limit
post-filtered entries or the backend is exhausted, then slice to query.limit
before returning; add a regression test that simulates the first page being
entirely filtered out so the caller still receives query.limit results.
| def build_config_from_company_config( | ||
| config: CompanyMemoryConfig, | ||
| *, | ||
| embedder: Mem0EmbedderConfig, | ||
| ) -> Mem0BackendConfig: | ||
| """Derive a ``Mem0BackendConfig`` from the top-level memory config. | ||
|
|
||
| Args: | ||
| config: Company-wide memory configuration. | ||
| embedder: Embedder settings (provider and model must be | ||
| supplied explicitly to avoid vendor names in defaults). | ||
|
|
||
| Returns: | ||
| Mem0-specific backend configuration. | ||
|
|
||
| Raises: | ||
| ValueError: If the storage config specifies a vector or | ||
| history store that the Mem0 backend does not support, | ||
| or if ``data_dir`` contains parent-directory traversal | ||
| (``..``) — propagated from ``Mem0BackendConfig`` | ||
| validation. | ||
| """ | ||
| if config.storage.vector_store != "qdrant": | ||
| msg = ( | ||
| f"Mem0 backend only supports embedded qdrant vector store, " | ||
| f"got {config.storage.vector_store!r}" | ||
| ) | ||
| logger.warning( | ||
| MEMORY_BACKEND_CONFIG_INVALID, | ||
| backend="mem0", | ||
| field="vector_store", | ||
| value=config.storage.vector_store, | ||
| reason=msg, | ||
| ) | ||
| raise ValueError(msg) | ||
| if config.storage.history_store != "sqlite": | ||
| msg = ( | ||
| f"Mem0 backend only supports sqlite history store, " | ||
| f"got {config.storage.history_store!r}" | ||
| ) | ||
| logger.warning( | ||
| MEMORY_BACKEND_CONFIG_INVALID, | ||
| backend="mem0", | ||
| field="history_store", | ||
| value=config.storage.history_store, | ||
| reason=msg, | ||
| ) | ||
| raise ValueError(msg) | ||
| return Mem0BackendConfig( | ||
| data_dir=config.storage.data_dir, | ||
| embedder=embedder, | ||
| ) |
There was a problem hiding this comment.
Namespace Mem0 storage by company before building the backend.
build_config_from_company_config() passes storage.data_dir straight through and leaves collection_name at the shared "synthorg_memories" default. With the default MemoryStorageConfig.data_dir="/data/memory", multiple company backends on the same host/volume will point at the same Mem0 store, and src/ai_company/memory/backends/mem0/adapter.py keys records only by agent_id / _SHARED_NAMESPACE. That means overlapping agent IDs can leak both private and shared memories across companies. Please make the Mem0 namespace company-scoped here (collection, data_dir, or user_id prefix) or fail fast until one is provided.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/memory/backends/mem0/config.py` around lines 132 - 183,
build_config_from_company_config currently passes MemoryStorageConfig.data_dir
and leaves collection_name defaulted, causing multiple companies to share the
same Mem0 storage; update build_config_from_company_config to namespace Mem0 by
company (e.g., append or prefix a stable company identifier from config such as
config.company_id or config.organization to data_dir or to collection_name when
constructing Mem0BackendConfig) so each company gets a unique collection/path,
and if no company identifier is available, raise ValueError to fail fast; ensure
you reference Mem0BackendConfig (where data_dir/collection_name are set) and
consider adapter.py usages that key by agent_id/_SHARED_NAMESPACE so the
namespace change isolates records per company.
| def query_to_mem0_search_args( | ||
| agent_id: str, | ||
| query: MemoryQuery, | ||
| ) -> dict[str, Any]: | ||
| """Convert a ``MemoryQuery`` to ``Memory.search()`` kwargs. | ||
|
|
||
| Args: | ||
| agent_id: Owning agent identifier. | ||
| query: Retrieval query. | ||
|
|
||
| Returns: | ||
| Dict of kwargs for ``Memory.search()``. | ||
|
|
||
| Raises: | ||
| ValueError: If ``query.text`` is ``None`` (search requires text). | ||
| """ | ||
| if query.text is None: | ||
| msg = "search requires query.text to be set" | ||
| logger.warning( | ||
| MEMORY_MODEL_INVALID, | ||
| field="query.text", | ||
| raw_value=None, | ||
| reason=msg, | ||
| ) | ||
| raise ValueError(msg) | ||
| return { | ||
| "query": query.text, | ||
| "user_id": agent_id, | ||
| "limit": query.limit, | ||
| } | ||
|
|
||
|
|
||
| def query_to_mem0_getall_args( | ||
| agent_id: str, | ||
| query: MemoryQuery, | ||
| ) -> dict[str, Any]: | ||
| """Convert a ``MemoryQuery`` to ``Memory.get_all()`` kwargs. | ||
|
|
||
| Args: | ||
| agent_id: Owning agent identifier. | ||
| query: Retrieval query. | ||
|
|
||
| Returns: | ||
| Dict of kwargs for ``Memory.get_all()``. | ||
| """ | ||
| return { | ||
| "user_id": agent_id, | ||
| "limit": query.limit, | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Keep public agent identifiers typed as NotBlankStr.
query_to_mem0_search_args(), query_to_mem0_getall_args(), and extract_publisher() expose agent IDs as plain str. Using NotBlankStr/NotBlankStr | None here keeps the Mem0 boundary consistent with the rest of the memory layer and removes the ad hoc whitespace normalization.
Proposed fix
def query_to_mem0_search_args(
- agent_id: str,
+ agent_id: NotBlankStr,
query: MemoryQuery,
) -> dict[str, Any]:
@@
def query_to_mem0_getall_args(
- agent_id: str,
+ agent_id: NotBlankStr,
query: MemoryQuery,
) -> dict[str, Any]:
@@
-def extract_publisher(raw: dict[str, Any]) -> str | None:
+def extract_publisher(raw: dict[str, Any]) -> NotBlankStr | None:
@@
- return coerced or None
+ return NotBlankStr(coerced) if coerced else NoneAlso applies to: 513-526
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/memory/backends/mem0/mappers.py` around lines 346 - 394, The
agent_id parameters in query_to_mem0_search_args and query_to_mem0_getall_args
(and similarly in extract_publisher) should use the NotBlankStr type from
core.types instead of plain str; update the function signatures to accept
NotBlankStr (or NotBlankStr | None for optional cases), import NotBlankStr from
core.types, and ensure the returned dict values (user_id or publisher) preserve
the NotBlankStr type (no extra whitespace normalization logic). Also apply the
same change to the other affected functions around the region mentioned (the
functions at ~513-526) so all public agent/identifier parameters use NotBlankStr
consistently.
| return { | ||
| "query": query.text, | ||
| "user_id": agent_id, | ||
| "limit": query.limit, | ||
| } |
There was a problem hiding this comment.
Don't apply the final limit before post-filtering.
query.limit is sent to Mem0 first, then apply_post_filters() drops expired/category/tag/date/min_relevance misses. That under-fills results whenever qualifying entries exist beyond the first raw limit hits. Fetch a larger candidate window or paginate, then slice to query.limit only after local filtering.
Also applies to: 391-394, 397-439
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/memory/backends/mem0/mappers.py` around lines 371 - 375, The
mapper currently sends query.limit to Mem0 before local post-filtering, which
causes under-filled results; update the payloads returned by the mapping
functions that build Mem0 requests (the dicts around the return at lines ~371
and the similar blocks at ~391-394 and ~397-439) to request a larger candidate
window or enable pagination instead of using query.limit directly (for example:
page_size = max(query.limit * N, query.limit + M) or fetch pages until enough
candidates are retrieved), then run apply_post_filters() and only slice the
final results to query.limit locally; ensure the change is applied wherever the
function returns {"query": ..., "user_id": ..., "limit": ...} so post-filtering
can produce a fully-filled result set.
There was a problem hiding this comment.
Code Review
This is an impressive and comprehensive pull request that implements the Mem0 memory backend. The changes are well-structured, covering configuration, the adapter implementation, data mapping, and a factory function. The code demonstrates strong design principles, including clear protocol adherence, robust error handling, and important security considerations like ownership verification and path traversal prevention. The test suite is extensive and thorough, providing confidence in the implementation. I've identified one critical syntax issue related to exception handling that needs to be addressed, but otherwise, this is an excellent contribution.
| except builtins.MemoryError, RecursionError: | ||
| raise |
There was a problem hiding this comment.
The syntax for catching multiple exceptions is incorrect for Python 3. Using a comma-separated list of exceptions is a Python 2 feature. In Python 3, the exceptions must be enclosed in a tuple. This will cause a SyntaxError at runtime.
This issue is present in multiple locations throughout this file (e.g., lines 305, 456, 518, 601, 701, 778, 859, 950, 1054). Please correct all occurrences.
except (builtins.MemoryError, RecursionError):| except ValueError, TypeError: | ||
| logger.warning( |
There was a problem hiding this comment.
The syntax for catching multiple exceptions is incorrect for Python 3. Using a comma-separated list of exceptions is a Python 2 feature. In Python 3, the exceptions must be enclosed in a tuple. This will cause a SyntaxError at runtime.
This issue is also present in other functions within this file (normalize_relevance_score and _coerce_confidence). Please correct all occurrences.
except (ValueError, TypeError):…rror guards, docs - Extract SharedKnowledgeStore methods from adapter.py to shared.py (797 < 800 line limit) - Move validation helpers (validate_mem0_result, resolve_publisher, check_delete_ownership) to mappers.py - Rename _SHARED_NAMESPACE to SHARED_NAMESPACE (public constant in mappers) - Add MemoryError/RecursionError guards with logger.exception() in adapter, shared, factory - Add MEMORY_BACKEND_SYSTEM_ERROR event constant - Add Windows path rejection (backslashes, drive letters) to config validator - Update CLAUDE.md event exemplar list - Update docs/design/memory.md config comments (hardcoded stores) - Update all test imports for renamed/moved symbols
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (4)
src/ai_company/memory/backends/mem0/config.py (1)
69-72:⚠️ Potential issue | 🔴 CriticalNamespace the Mem0 store per company.
build_config_from_company_config()still forwards the shareddata_dirand leavescollection_nameat the global"synthorg_memories"default. The adapter writes private records underuser_id=str(agent_id)and shared records underSHARED_NAMESPACE, so two companies on the same volume will read and overwrite each other's memories whenever agent IDs overlap. Derive a company-scoped path/collection here, or fail fast until a stable company identifier is available.Also applies to: 199-202
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai_company/memory/backends/mem0/config.py` around lines 69 - 72, The Mem0 config uses a global collection_name ("synthorg_memories") and shared data_dir causing cross-company collisions; update build_config_from_company_config() and the Mem0 config Field for collection_name to derive a company-scoped collection/path (e.g., include the company_id or company_slug) or raise an explicit error if no stable company identifier is available; ensure the adapter behavior that writes private records under user_id=str(agent_id) and shared records under SHARED_NAMESPACE remains compatible by namespacing collection_name (or data_dir) per company so two companies on the same volume cannot read/overwrite each other's memories.src/ai_company/memory/backends/mem0/mappers.py (1)
302-366: 🛠️ Refactor suggestion | 🟠 MajorBreak
mem0_result_to_entry()down a bit more.Even after extracting
_resolve_created_at(), this function is still over the repo's 50-line cap and keeps raw id/content validation, metadata decoding, score normalization, and model construction in one block. Pulling the id/content validation into small helpers would bring it back under the limit and keep the recovery paths easier to test.As per coding guidelines, "Functions: < 50 lines, files < 800 lines".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai_company/memory/backends/mem0/mappers.py` around lines 302 - 366, The function mem0_result_to_entry is over the 50-line limit and mixes id/content validation, metadata decoding, score normalization and object construction; extract the id and content checks into small helpers (e.g., _validate_mem0_id(raw) -> NotBlankStr and _validate_mem0_content(raw) -> NotBlankStr) that perform the logger.warning + MemoryRetrievalError behavior currently in mem0_result_to_entry, call parse_mem0_metadata and normalize_relevance_score as before, and then have mem0_result_to_entry simply call those helpers, parse timestamps via _resolve_created_at/update helpers, and build the MemoryEntry—this will shrink mem0_result_to_entry under 50 lines and keep validation/recovery paths testable.src/ai_company/memory/backends/mem0/shared.py (1)
212-242:⚠️ Potential issue | 🟠 MajorDon't cap the Mem0 fetch before local filtering.
This asks Mem0 for only
query.limitraw hits and then appliesapply_post_filters()plusexclude_agent. If the first N shared hits are expired, category-mismatched, tag-mismatched, outside the time window, or excluded, callers get fewer thanquery.limitresults even when later matches exist. Over-fetch/paginate until you havequery.limitpost-filtered entries, then slice locally, and add a regression test where the first page is fully filtered away.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai_company/memory/backends/mem0/shared.py` around lines 212 - 242, The code currently requests only query.limit raw results from client.search/get_all and then applies validate_mem0_result -> mem0_result_to_entry -> apply_post_filters and exclude_agent, which can yield fewer than query.limit results if early hits are filtered; change the logic around the client.search/client.get_all call so you page/over-fetch until you have at least query.limit post-filtered entries (or no more pages), i.e. repeatedly call client.search/get_all with increasing offsets or next-page tokens for SHARED_NAMESPACE, run validate_mem0_result + mem0_result_to_entry + apply_post_filters + exclude_agent on each page, accumulate filtered entries, stop when accumulated >= query.limit or no more raw results, then slice to query.limit; adjust the code paths that use validate_mem0_result, mem0_result_to_entry, apply_post_filters, and the exclude_agent filter accordingly and add a regression test that simulates a first page whose all items are filtered out to ensure later pages fill the result set.src/ai_company/memory/backends/mem0/adapter.py (1)
425-433:⚠️ Potential issue | 🟠 Major
retrieve()can under-fill results after post-filtering.
query.limitis forwarded to Mem0 beforeapply_post_filters()runs. If the first N hits are expired or fail the category/tag/date/min_relevance filters, the caller receives fewer thanquery.limitentries even though later matches exist. Please page/over-fetch until enough filtered entries are collected, then truncate locally, and add a regression test where the first page is filtered away.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai_company/memory/backends/mem0/adapter.py` around lines 425 - 433, The retrieve() implementation forwards query.limit to Mem0 and then applies apply_post_filters(), causing under-fill when early pages are filtered away; change retrieve() to page/over-fetch: call query_to_mem0_search_args / query_to_mem0_getall_args with a per-page size (e.g., min(max_page_size, query.limit) or a fixed chunk), loop calling client.search or client.get_all via asyncio.to_thread to fetch additional pages (using the Mem0 paging token/offset returned in raw_result or incrementing offset), validate each raw_result with validate_mem0_result, extend the entries by converting with mem0_result_to_entry and filtering with apply_post_filters until you have >= query.limit filtered entries or no more pages, then truncate the final entries tuple to query.limit before returning; also add a regression test that simulates the first page being entirely filtered out and ensures retrieve() returns up to query.limit entries by fetching subsequent pages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/memory.md`:
- 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.
In `@src/ai_company/memory/backends/mem0/adapter.py`:
- Around line 107-109: In the constructor validation that checks
max_memories_per_agent (the block that currently raises ValueError when
max_memories_per_agent < 1), log the invalid value at WARNING level before
raising: ensure a module/class logger is available (import logging and get
logger if needed) and call logger.warning with the supplied limit and context
(e.g., "Invalid max_memories_per_agent=%s, must be >=1"), then raise the
ValueError as before; update the constructor (where max_memories_per_agent is
validated) to include this warning emission.
In `@src/ai_company/memory/backends/mem0/mappers.py`:
- Around line 464-478: The current branch logs legitimate post-filtering results
as MEMORY_MODEL_INVALID; change it to use a filter-specific event (e.g.,
MEMORY_FILTER_APPLIED) and lower severity (DEBUG or INFO) instead of
warning/invalid. Update the branch that checks "if pre_count > 0 and post_count
== 0" to call logger.debug or logger.info with event MEMORY_FILTER_APPLIED
(preserving context keys field="post_filter", reason="all entries filtered out
by post-filters", pre_filter_count=pre_count) and leave the existing elif
(pre_count != post_count) as debug but also switch its event to
MEMORY_FILTER_APPLIED so normal filter misses are not treated as model
invalidation.
In `@tests/unit/memory/backends/mem0/test_adapter_shared.py`:
- Around line 81-107: Replace the two duplicate publish passthrough tests
(test_publish_reraises_memory_error and test_publish_reraises_recursion_error)
with a single parametrized test using pytest.mark.parametrize over exception
types (builtins.MemoryError, RecursionError); inside the test set
mock_client.add.side_effect to the parametrized exception and assert await
backend.publish("test-agent-001", make_store_request()) raises that exception
via pytest.raises. Apply the same refactor pattern to the search_shared tests
(replace
test_search_shared_reraises_memory_error/test_search_shared_reraises_recursion_error
to one parametrized test that sets mock_client.search.side_effect and calls
await backend.search_shared(...)) and to the retract tests (combine
test_retract_reraises_memory_error/test_retract_reraises_recursion_error into
one parametrized test that sets mock_client.remove.side_effect and calls await
backend.retract(...)); keep references to mock_client.add/search/remove,
backend.publish/search_shared/retract,
make_store_request()/make_retract_request(), and use pytest.mark.parametrize to
supply the exception classes.
---
Duplicate comments:
In `@src/ai_company/memory/backends/mem0/adapter.py`:
- Around line 425-433: The retrieve() implementation forwards query.limit to
Mem0 and then applies apply_post_filters(), causing under-fill when early pages
are filtered away; change retrieve() to page/over-fetch: call
query_to_mem0_search_args / query_to_mem0_getall_args with a per-page size
(e.g., min(max_page_size, query.limit) or a fixed chunk), loop calling
client.search or client.get_all via asyncio.to_thread to fetch additional pages
(using the Mem0 paging token/offset returned in raw_result or incrementing
offset), validate each raw_result with validate_mem0_result, extend the entries
by converting with mem0_result_to_entry and filtering with apply_post_filters
until you have >= query.limit filtered entries or no more pages, then truncate
the final entries tuple to query.limit before returning; also add a regression
test that simulates the first page being entirely filtered out and ensures
retrieve() returns up to query.limit entries by fetching subsequent pages.
In `@src/ai_company/memory/backends/mem0/config.py`:
- Around line 69-72: The Mem0 config uses a global collection_name
("synthorg_memories") and shared data_dir causing cross-company collisions;
update build_config_from_company_config() and the Mem0 config Field for
collection_name to derive a company-scoped collection/path (e.g., include the
company_id or company_slug) or raise an explicit error if no stable company
identifier is available; ensure the adapter behavior that writes private records
under user_id=str(agent_id) and shared records under SHARED_NAMESPACE remains
compatible by namespacing collection_name (or data_dir) per company so two
companies on the same volume cannot read/overwrite each other's memories.
In `@src/ai_company/memory/backends/mem0/mappers.py`:
- Around line 302-366: The function mem0_result_to_entry is over the 50-line
limit and mixes id/content validation, metadata decoding, score normalization
and object construction; extract the id and content checks into small helpers
(e.g., _validate_mem0_id(raw) -> NotBlankStr and _validate_mem0_content(raw) ->
NotBlankStr) that perform the logger.warning + MemoryRetrievalError behavior
currently in mem0_result_to_entry, call parse_mem0_metadata and
normalize_relevance_score as before, and then have mem0_result_to_entry simply
call those helpers, parse timestamps via _resolve_created_at/update helpers, and
build the MemoryEntry—this will shrink mem0_result_to_entry under 50 lines and
keep validation/recovery paths testable.
In `@src/ai_company/memory/backends/mem0/shared.py`:
- Around line 212-242: The code currently requests only query.limit raw results
from client.search/get_all and then applies validate_mem0_result ->
mem0_result_to_entry -> apply_post_filters and exclude_agent, which can yield
fewer than query.limit results if early hits are filtered; change the logic
around the client.search/client.get_all call so you page/over-fetch until you
have at least query.limit post-filtered entries (or no more pages), i.e.
repeatedly call client.search/get_all with increasing offsets or next-page
tokens for SHARED_NAMESPACE, run validate_mem0_result + mem0_result_to_entry +
apply_post_filters + exclude_agent on each page, accumulate filtered entries,
stop when accumulated >= query.limit or no more raw results, then slice to
query.limit; adjust the code paths that use validate_mem0_result,
mem0_result_to_entry, apply_post_filters, and the exclude_agent filter
accordingly and add a regression test that simulates a first page whose all
items are filtered out to ensure later pages fill the result set.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 319031a5-a41f-4056-b1c1-0038fcf36921
📒 Files selected for processing (11)
CLAUDE.mddocs/design/memory.mdsrc/ai_company/memory/backends/mem0/adapter.pysrc/ai_company/memory/backends/mem0/config.pysrc/ai_company/memory/backends/mem0/mappers.pysrc/ai_company/memory/backends/mem0/shared.pysrc/ai_company/memory/factory.pysrc/ai_company/observability/events/memory.pytests/integration/memory/test_mem0_backend.pytests/unit/memory/backends/mem0/test_adapter_crud.pytests/unit/memory/backends/mem0/test_adapter_shared.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: Test (Python 3.14)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do NOT usefrom __future__ import annotations— Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:(no parentheses) for exception handling syntax per PEP 758 — ruff enforces this on Python 3.14
Files:
src/ai_company/memory/backends/mem0/config.pytests/unit/memory/backends/mem0/test_adapter_crud.pysrc/ai_company/memory/factory.pysrc/ai_company/memory/backends/mem0/shared.pysrc/ai_company/observability/events/memory.pytests/integration/memory/test_mem0_backend.pysrc/ai_company/memory/backends/mem0/adapter.pytests/unit/memory/backends/mem0/test_adapter_shared.pysrc/ai_company/memory/backends/mem0/mappers.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Type hints: all public functions must have type hints, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Create new objects instead of mutating existing ones—never mutate existing objects. For non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement.
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Use@computed_fieldfor derived values instead of storing + validating redundant fields. UseNotBlankStr(fromcore.types) for all identifier/name fields—including optional and tuple variants—instead of manual whitespace validators.
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task.
Line length: 88 characters (enforced by ruff)
Functions: < 50 lines, files < 800 lines
Handle errors explicitly, never silently swallow them
Validate at system boundaries (user input, external APIs, config files)
Every module with business logic MUST have:from ai_company.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code
Always useloggeras the variable name (not_logger, notlog)
Use event name constants fromai_company.observability.eventsdomain-spe...
Files:
src/ai_company/memory/backends/mem0/config.pysrc/ai_company/memory/factory.pysrc/ai_company/memory/backends/mem0/shared.pysrc/ai_company/observability/events/memory.pysrc/ai_company/memory/backends/mem0/adapter.pysrc/ai_company/memory/backends/mem0/mappers.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowfor test markers
Prefer@pytest.mark.parametrizefor testing similar cases
Files:
tests/unit/memory/backends/mem0/test_adapter_crud.pytests/integration/memory/test_mem0_backend.pytests/unit/memory/backends/mem0/test_adapter_shared.py
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Docs source:
docs/(Markdown, built with Zensical). Design spec:docs/design/(7 pages: index, agents, organization, communication, engine, memory, operations).
Files:
docs/design/memory.md
🧠 Learnings (12)
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Validate at system boundaries (user input, external APIs, config files)
Applied to files:
src/ai_company/memory/backends/mem0/config.py
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Use event name constants from `ai_company.observability.events` domain-specific modules (e.g. `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, etc.) instead of hardcoded string literals
Applied to files:
src/ai_company/observability/events/memory.pyCLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : All error paths must log at WARNING or ERROR with context before raising
Applied to files:
src/ai_company/memory/backends/mem0/adapter.pyCLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) for exception handling syntax per PEP 758 — ruff enforces this on Python 3.14
Applied to files:
src/ai_company/memory/backends/mem0/adapter.pysrc/ai_company/memory/backends/mem0/mappers.py
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them
Applied to files:
src/ai_company/memory/backends/mem0/adapter.py
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to docs/**/*.md : Docs source: `docs/` (Markdown, built with Zensical). Design spec: `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Every module with business logic MUST have: `from ai_company.observability import get_logger` then `logger = get_logger(__name__)`
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Always use structured logging kwargs: `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Always use `logger` as the variable name (not `_logger`, not `log`)
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : Never use `import logging` / `logging.getLogger()` / `print()` in application code
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to src/**/*.py : All state transitions must log at INFO
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-13T06:54:28.702Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T06:54:28.702Z
Learning: Applies to pyproject.toml : Dependencies: all versions pinned with `==` in `pyproject.toml`. Groups: `test` and `dev` (includes test). Install via `uv sync`.
Applied to files:
CLAUDE.md
🧬 Code graph analysis (7)
src/ai_company/memory/backends/mem0/config.py (4)
src/ai_company/memory/config.py (1)
CompanyMemoryConfig(139-195)src/ai_company/observability/_logger.py (1)
get_logger(8-28)tests/integration/memory/test_mem0_backend.py (1)
backend(42-51)tests/unit/memory/backends/mem0/conftest.py (1)
backend(41-49)
tests/unit/memory/backends/mem0/test_adapter_crud.py (3)
src/ai_company/memory/backends/mem0/mappers.py (1)
validate_mem0_result(564-616)src/ai_company/memory/errors.py (3)
MemoryRetrievalError(25-26)MemoryStoreError(21-22)MemoryError(13-14)src/ai_company/memory/models.py (1)
MemoryQuery(153-230)
src/ai_company/memory/factory.py (2)
src/ai_company/memory/config.py (1)
CompanyMemoryConfig(139-195)src/ai_company/memory/backends/mem0/config.py (2)
Mem0EmbedderConfig(23-51)build_config_from_company_config(151-202)
src/ai_company/memory/backends/mem0/shared.py (5)
src/ai_company/memory/backends/mem0/mappers.py (7)
apply_post_filters(420-479)build_mem0_metadata(50-69)extract_publisher(635-648)mem0_result_to_entry(302-366)resolve_publisher(619-632)validate_add_result(485-525)validate_mem0_result(564-616)src/ai_company/memory/errors.py (3)
MemoryRetrievalError(25-26)MemoryStoreError(21-22)MemoryError(13-14)src/ai_company/observability/_logger.py (1)
get_logger(8-28)src/ai_company/memory/backends/mem0/adapter.py (8)
Mem0Client(77-84)get(83-83)get(467-556)add(80-80)search(81-81)get_all(82-82)delete(84-84)delete(558-621)src/ai_company/memory/models.py (3)
MemoryEntry(82-150)MemoryQuery(153-230)MemoryStoreRequest(55-79)
tests/integration/memory/test_mem0_backend.py (5)
src/ai_company/memory/backends/mem0/adapter.py (14)
Mem0MemoryBackend(90-794)add(80-80)search(81-81)store(335-398)retrieve(400-465)publish(723-743)search_shared(745-769)get(83-83)get(467-556)delete(84-84)delete(558-621)retract(771-794)get_all(82-82)count(623-716)src/ai_company/memory/backends/mem0/config.py (2)
Mem0BackendConfig(54-117)Mem0EmbedderConfig(23-51)src/ai_company/memory/models.py (2)
MemoryQuery(153-230)MemoryStoreRequest(55-79)src/ai_company/memory/retrieval_config.py (1)
MemoryRetrievalConfig(20-132)src/ai_company/memory/retriever.py (1)
ContextInjectionStrategy(97-400)
src/ai_company/memory/backends/mem0/adapter.py (5)
src/ai_company/memory/backends/mem0/config.py (2)
Mem0BackendConfig(54-117)build_mem0_config_dict(120-148)src/ai_company/memory/backends/mem0/mappers.py (9)
apply_post_filters(420-479)build_mem0_metadata(50-69)check_delete_ownership(651-697)extract_category(528-561)mem0_result_to_entry(302-366)query_to_mem0_getall_args(401-417)query_to_mem0_search_args(369-398)validate_add_result(485-525)validate_mem0_result(564-616)src/ai_company/memory/backends/mem0/shared.py (3)
publish_shared(114-178)retract_shared(279-343)search_shared_memories(181-276)src/ai_company/memory/errors.py (4)
MemoryConnectionError(17-18)MemoryRetrievalError(25-26)MemoryStoreError(21-22)MemoryError(13-14)src/ai_company/memory/models.py (3)
MemoryEntry(82-150)MemoryQuery(153-230)MemoryStoreRequest(55-79)
src/ai_company/memory/backends/mem0/mappers.py (2)
src/ai_company/core/enums.py (1)
MemoryCategory(101-108)src/ai_company/memory/models.py (4)
MemoryEntry(82-150)MemoryMetadata(20-52)MemoryQuery(153-230)MemoryStoreRequest(55-79)
🪛 LanguageTool
docs/design/memory.md
[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)
CLAUDE.md
[style] ~130-~130: A comma is missing here.
Context: ...nder ai_company.observability.events (e.g. PROVIDER_CALL_START from `events.prov...
(EG_NO_COMMA)
🔇 Additional comments (5)
src/ai_company/memory/backends/mem0/shared.py (1)
155-162: No changes needed—exception syntax is already correct for PEP 758.All three locations (lines 155, 252, 318) correctly use
except (builtins.MemoryError, RecursionError) as exc:with parentheses. Per PEP 758, parentheses are optional only when not usingas; they remain required when usingas. The current code is compliant.> Likely an incorrect or invalid review comment.src/ai_company/memory/factory.py (1)
82-89: No changes needed. These handlers correctly follow PEP 758: the parenthesized formexcept (A, B) as exc:is required when using anasclause. PEP 758 allows omitting parentheses only when there is noastarget (e.g.,except A, B:). The code at lines 82–89 and 105–112 complies with the specification.> Likely an incorrect or invalid review comment.tests/unit/memory/backends/mem0/test_adapter_shared.py (1)
28-447: Strong shared-knowledge coverage.This suite validates success paths, ownership rules, namespace guards, and exception translation comprehensively for publish/search/retract.
docs/design/memory.md (1)
63-69: Mem0 config constraints are now clearly documented.The docs now correctly communicate hardcoded Mem0 storage behavior and the embedder requirement in factory usage.
Also applies to: 295-296, 303-315
tests/integration/memory/test_mem0_backend.py (1)
54-289: Integration coverage is solid and objective-aligned.These tests exercise the Mem0 adapter through the retrieval pipeline and shared-memory lifecycle with meaningful assertions across key public operations.
| (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 |
There was a problem hiding this comment.
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.
| if pre_count > 0 and post_count == 0: | ||
| logger.warning( | ||
| MEMORY_MODEL_INVALID, | ||
| field="post_filter", | ||
| reason="all entries filtered out by post-filters", | ||
| pre_filter_count=pre_count, | ||
| ) | ||
| elif pre_count != post_count: | ||
| logger.debug( | ||
| MEMORY_MODEL_INVALID, | ||
| field="post_filter", | ||
| pre_filter_count=pre_count, | ||
| post_filter_count=post_count, | ||
| reason="entries filtered by post-filters", | ||
| ) |
There was a problem hiding this comment.
Don't log normal filter misses as MEMORY_MODEL_INVALID.
A restrictive category/tag/time query can legitimately filter every entry out, so warning on MEMORY_MODEL_INVALID here makes ordinary searches look like data corruption. This branch should use a filter-specific event such as MEMORY_FILTER_APPLIED and stay at DEBUG/INFO.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ai_company/memory/backends/mem0/mappers.py` around lines 464 - 478, The
current branch logs legitimate post-filtering results as MEMORY_MODEL_INVALID;
change it to use a filter-specific event (e.g., MEMORY_FILTER_APPLIED) and lower
severity (DEBUG or INFO) instead of warning/invalid. Update the branch that
checks "if pre_count > 0 and post_count == 0" to call logger.debug or
logger.info with event MEMORY_FILTER_APPLIED (preserving context keys
field="post_filter", reason="all entries filtered out by post-filters",
pre_filter_count=pre_count) and leave the existing elif (pre_count !=
post_count) as debug but also switch its event to MEMORY_FILTER_APPLIED so
normal filter misses are not treated as model invalidation.
…, test parametrize
| for entry in entries: | ||
| if entry.expires_at is not None and entry.expires_at <= now: | ||
| continue | ||
| if query.categories and entry.category not in query.categories: |
There was a problem hiding this comment.
Falsy frozenset bypasses category filter entirely
if query.categories evaluates to False for an empty frozenset(), which means a caller who explicitly passes categories=frozenset() (intending to match no categories) will instead receive all entries unfiltered — the opposite of the expected behavior. The equivalent guard for query.since/query.until correctly uses is not None checks; this filter should follow the same pattern.
| if query.categories and entry.category not in query.categories: | |
| if query.categories is not None and entry.category not in query.categories: |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/memory/backends/mem0/mappers.py
Line: 449
Comment:
**Falsy frozenset bypasses category filter entirely**
`if query.categories` evaluates to `False` for an empty `frozenset()`, which means a caller who explicitly passes `categories=frozenset()` (intending to match no categories) will instead receive **all** entries unfiltered — the opposite of the expected behavior. The equivalent guard for `query.since`/`query.until` correctly uses `is not None` checks; this filter should follow the same pattern.
```suggestion
if query.categories is not None and entry.category not in query.categories:
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
Implements a concrete MemoryBackend for the memory subsystem by adding a Mem0-based adapter (plus config/mapping/shared-store support) and wiring it into the factory and public exports, along with extensive unit/integration test coverage and dependency/docs updates.
Changes:
- Added Mem0 backend implementation (
adapter.py), shared knowledge store ops (shared.py), and mapping utilities (mappers.py) with Mem0-specific config models (config.py). - Updated backend factory + memory exports to support
backend="mem0"(and updated observability event constants accordingly). - Added/updated comprehensive tests and updated dependencies + documentation to reflect Mem0 as the implemented backend.
Reviewed changes
Copilot reviewed 82 out of 85 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds Mem0 and transitive dependencies to the lockfile. |
| pyproject.toml | Pins mem0ai==1.0.5, adds mypy override for mem0.*, adjusts ruff test ignores. |
| src/ai_company/memory/factory.py | Implements config-driven Mem0 backend creation with validation + logging. |
| src/ai_company/memory/backends/mem0/adapter.py | Adds the main Mem0 backend adapter implementing lifecycle + CRUD + shared store delegation. |
| src/ai_company/memory/backends/mem0/shared.py | Implements shared knowledge store operations (publish/search/retract). |
| src/ai_company/memory/backends/mem0/mappers.py | Adds Mem0 <-> domain model mapping + validation + post-filtering helpers. |
| src/ai_company/memory/backends/mem0/config.py | Adds Mem0BackendConfig / Mem0EmbedderConfig and config builders. |
| src/ai_company/memory/backends/mem0/init.py | Exposes Mem0 backend/config objects from the backend package. |
| src/ai_company/memory/backends/init.py | Exposes concrete backends at the memory.backends package level. |
| src/ai_company/memory/init.py | Re-exports Mem0 backend/config from the top-level ai_company.memory API. |
| src/ai_company/memory/models.py | Clarifies since semantics in docstring/field description. |
| src/ai_company/observability/events/memory.py | Updates memory event constants (adds config/system/agent-id events; removes obsolete ones). |
| tests/unit/memory/test_factory.py | Updates factory tests to assert Mem0 backend creation and error wrapping behavior. |
| tests/unit/memory/test_init.py | Updates memory package export tests for new Mem0 exports. |
| tests/unit/memory/backends/mem0/conftest.py | Adds shared fixtures for Mem0 backend unit tests. |
| tests/unit/memory/backends/mem0/test_adapter.py | Adds unit tests for lifecycle, protocol conformance, and connection guards. |
| tests/unit/memory/backends/mem0/test_adapter_crud.py | Adds unit tests for CRUD + count behavior and result validation. |
| tests/unit/memory/backends/mem0/test_adapter_shared.py | Adds unit tests for publish/search_shared/retract ownership behavior. |
| tests/unit/memory/backends/mem0/test_config.py | Adds unit tests for Mem0 config models and config builders. |
| tests/unit/memory/backends/mem0/test_mappers.py | Adds unit tests for mapping and post-filter functions. |
| tests/integration/memory/test_mem0_backend.py | Adds integration-style tests for adapter + retrieval pipeline flow using a mocked client. |
| tests/unit/observability/test_events.py | Updates expected event constants list for memory events. |
| docs/design/memory.md | Updates design doc to reflect Mem0 adapter is implemented and clarifies constraints (qdrant/sqlite + embedder passed programmatically). |
| docs/roadmap/index.md | Updates roadmap to remove “Mem0 adapter” as a remaining item and reflect implementation. |
| README.md | Updates status section to include Mem0 backend adapter as implemented. |
| CLAUDE.md | Updates repo layout/docs and dependency note to include Mem0 adapter/backend. |
| .github/workflows/dependency-review.yml | Adds allowed-license notes and allowlist entries for dependencies missing SPDX metadata in lockfile. |
| tests/unit/tools/sandbox/test_protocol.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/tools/sandbox/conftest.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/tools/git/test_git_sandbox_integration.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/tools/git/conftest.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/providers/test_protocol.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/providers/conftest.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/hr/test_registry.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/hr/test_onboarding_service.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/hr/test_offboarding_service.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/hr/test_hiring_service.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/hr/test_full_snapshot_strategy.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_task_engine_mutations.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_routing_models.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_react_loop.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_plan_execute_loop.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_metrics.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_loop_protocol.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_context.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_agent_engine_lifecycle.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_agent_engine_errors.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/test_agent_engine.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/engine/task_engine_helpers.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/meeting/test_structured_phases.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/meeting/test_round_robin.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/meeting/test_protocol.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/meeting/test_position_papers.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/meeting/test_orchestrator.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/meeting/conftest.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/conflict_resolution/test_service.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/conflict_resolution/test_hybrid_strategy.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/conflict_resolution/test_helpers.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/conflict_resolution/test_debate_strategy.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/communication/conflict_resolution/test_authority_strategy.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/budget/test_category_analytics.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/test_middleware.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/test_health.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/test_guards.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/test_app.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_tasks.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_providers.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_projects.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_messages.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_meetings.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_departments.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_company.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_budget.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_autonomy.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_artifacts.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_approvals.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_analytics.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/controllers/test_agents.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/conftest.py | Removes noqa: TC* markers after test lint config change. |
| tests/unit/api/auth/test_controller.py | Removes noqa: TC* markers after test lint config change. |
| tests/integration/tools/test_sandbox_integration.py | Removes noqa: TC* markers after test lint config change. |
| tests/integration/tools/conftest.py | Removes noqa: TC* markers after test lint config change. |
| tests/integration/communication/test_meeting_integration.py | Removes noqa: TC* markers after test lint config change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def backend_name(self) -> NotBlankStr: | ||
| """Human-readable backend identifier.""" | ||
| return NotBlankStr("mem0") |
| valid: list[NotBlankStr] = [] | ||
| for t in raw_tags: | ||
| stripped = str(t).strip() if t else "" | ||
| if stripped: | ||
| valid.append(NotBlankStr(stripped)) | ||
| else: |
| raw_id = raw.get("id") | ||
| if raw_id is None or not str(raw_id).strip(): | ||
| msg = f"Mem0 result has missing or blank 'id': keys={list(raw.keys())}" | ||
| logger.warning( | ||
| MEMORY_MODEL_INVALID, | ||
| field="id", | ||
| raw_value=raw_id, | ||
| reason=msg, | ||
| ) | ||
| raise MemoryRetrievalError(msg) | ||
| memory_id = NotBlankStr(str(raw_id)) | ||
|
|
||
| raw_content = raw.get("memory") or raw.get("data") | ||
| if not raw_content or not str(raw_content).strip(): | ||
| msg = f"Mem0 result {raw.get('id', '?')} has empty content" | ||
| logger.warning( | ||
| MEMORY_MODEL_INVALID, | ||
| field="content", | ||
| raw_value=raw_content, | ||
| reason=msg, | ||
| ) | ||
| raise MemoryRetrievalError(msg) | ||
| content = NotBlankStr(str(raw_content)) |
| def validate_add_result(result: Any, *, context: str) -> NotBlankStr: | ||
| """Extract and validate the memory ID from a Mem0 ``add`` result. | ||
|
|
||
| Args: | ||
| result: Raw result from ``Memory.add()`` (expected dict). | ||
| context: Human-readable context for error messages | ||
| (e.g. ``"store"`` or ``"shared publish"``). | ||
|
|
||
| Returns: | ||
| The backend-assigned memory ID. | ||
|
|
||
| Raises: | ||
| MemoryStoreError: If the result is missing or malformed. | ||
| """ | ||
| if not isinstance(result, dict): | ||
| msg = ( | ||
| f"Mem0 add returned unexpected type for {context}: {type(result).__name__}" | ||
| ) | ||
| logger.warning(MEMORY_ENTRY_STORE_FAILED, context=context, error=msg) | ||
| raise MemoryStoreError(msg) | ||
| results_list = result.get("results") | ||
| if not isinstance(results_list, list) or not results_list: | ||
| msg = f"Mem0 add returned no results for {context}" | ||
| logger.warning(MEMORY_ENTRY_STORE_FAILED, context=context, error=msg) | ||
| raise MemoryStoreError(msg) | ||
| first = results_list[0] | ||
| if not isinstance(first, dict): | ||
| msg = ( | ||
| f"Mem0 add result item is not a dict for {context}: {type(first).__name__}" | ||
| ) | ||
| logger.warning(MEMORY_ENTRY_STORE_FAILED, context=context, error=msg) | ||
| raise MemoryStoreError(msg) | ||
| raw_id = first.get("id") | ||
| if raw_id is None or not str(raw_id).strip(): | ||
| msg = ( | ||
| f"Mem0 add result has missing or blank 'id' for {context}: " | ||
| f"keys={list(first.keys())}" | ||
| ) | ||
| logger.warning(MEMORY_ENTRY_STORE_FAILED, context=context, error=msg) | ||
| raise MemoryStoreError(msg) | ||
| return NotBlankStr(str(raw_id)) |
| def extract_publisher(raw: dict[str, Any]) -> NotBlankStr | None: | ||
| """Extract the publisher agent ID from a shared memory dict. | ||
|
|
||
| Returns ``None`` if the publisher key is missing, non-dict | ||
| metadata, or the value is blank after coercion and stripping. | ||
| """ | ||
| metadata = raw.get("metadata", {}) | ||
| if not metadata or not isinstance(metadata, dict): | ||
| return None | ||
| value = metadata.get(PUBLISHER_KEY) | ||
| if value is None: | ||
| return None | ||
| coerced = str(value).strip() | ||
| return NotBlankStr(coerced) if coerced else None |
| NotBlankStr( | ||
| resolve_publisher(item), | ||
| ), |
🤖 I have created a release *beep* *boop* --- ## [0.1.3](v0.1.2...v0.1.3) (2026-03-13) ### Features * add Mem0 memory backend adapter ([#345](#345)) ([2788db8](2788db8)), closes [#206](#206) * centralized single-writer TaskEngine with full CRUD API ([#328](#328)) ([9c1a3e1](9c1a3e1)) * incremental AgentEngine → TaskEngine status sync ([#331](#331)) ([7a68d34](7a68d34)), closes [#323](#323) * web dashboard pages — views, components, tests, and review fixes ([#354](#354)) ([b165ec4](b165ec4)) * web dashboard with Vue 3 + PrimeVue + Tailwind CSS ([#347](#347)) ([06416b1](06416b1)) ### Bug Fixes * harden coordination pipeline with validators, logging, and fail-fast ([#333](#333)) ([2f10d49](2f10d49)), closes [#205](#205) * repo-wide security hardening from ZAP, Scorecard, and CodeQL audit ([#357](#357)) ([27eb288](27eb288)) ### CI/CD * add pip-audit, hadolint, OSSF Scorecard, ZAP DAST, and pre-push hooks ([#350](#350)) ([2802d20](2802d20)) * add workflow_dispatch trigger to PR Preview for Dependabot PRs ([#326](#326)) ([4c7b6d9](4c7b6d9)) * bump astral-sh/setup-uv from 7.4.0 to 7.5.0 in the minor-and-patch group ([#335](#335)) ([98dd8ca](98dd8ca)) ### Maintenance * bump the minor-and-patch group across 1 directory with 3 updates ([#352](#352)) ([031b1c9](031b1c9)) * **deps:** bump devalue from 5.6.3 to 5.6.4 in /site in the npm_and_yarn group across 1 directory ([#324](#324)) ([9a9c600](9a9c600)) * migrate docs build from MkDocs to Zensical ([#330](#330)) ([fa8bf1d](fa8bf1d)), closes [#329](#329) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
MemoryBackend,MemoryCapabilities, andSharedKnowledgeStoreprotocols viaMem0MemoryBackendadapterMem0ConfigandMem0EmbedderConfigPydantic models withbuild_config_from_company_config()factoryMemoryEntrydomain models (mappers.py)create_memory_backend()factory function for config-driven backend instantiationasyncio.to_thread()_require_connected()returningMem0ClientProtocolPurePosixPathfor Docker-targeted path construction (cross-platform safe)Test plan
test_adapter_crud.py— 696 lines)test_adapter_shared.py— 450 lines)test_adapter.py— 347 lines)test_mappers.py— 665 lines)test_config.py— 230 lines)test_factory.py— updated)Review coverage
Pre-reviewed through 9 rounds of automated review (8 specialized agents per round). 106 files changed, 11,261 insertions.
Closes #206