Skip to content

feat: support per-agent memory retention overrides (#209)#951

Merged
Aureliolo merged 5 commits intomainfrom
feat/agent-memory-retention
Mar 31, 2026
Merged

feat: support per-agent memory retention overrides (#209)#951
Aureliolo merged 5 commits intomainfrom
feat/agent-memory-retention

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add AgentRetentionRule model and MemoryConfig.retention_overrides field for per-agent, per-category retention overrides
  • Extend RetentionEnforcer with _resolve_categories() implementing a 5-level resolution chain: agent per-category > company per-category > agent global default > company global default > keep forever
  • Pass through agent override params in MemoryConsolidationService.cleanup_retention() and run_maintenance()
  • Add RETENTION_AGENT_OVERRIDE_APPLIED structured log event
  • Update design spec and RetentionConfig docstring to document override mechanism

Test plan

  • 30 new unit tests across 3 test files covering:
    • AgentRetentionRule model validation (frozen, ge=1, invalid values)
    • MemoryConfig.retention_overrides (defaults, duplicates, NONE type guard, coexistence with retention_days)
    • _resolve_categories resolution chain (all 5 priority levels individually + combined)
    • cleanup_expired with agent overrides (cutoff correctness, category expansion, log event)
    • Service pass-through (cleanup_retention, run_maintenance forwarding)
    • AgentRetentionRule/RetentionRule field parity test
  • Full test suite: 11358 passed, ruff clean, mypy clean
  • Pre-reviewed by 6 agents, 8 findings addressed (including critical resolution priority bug fix)

Closes #209

🤖 Generated with Claude Code

Aureliolo and others added 2 commits March 31, 2026 17:40
Add AgentRetentionRule model and retention_overrides field to
MemoryConfig, enabling individual agents to override company-level
retention policies per category. Extend RetentionEnforcer with
_resolve_categories() for merging agent overrides with company
defaults using a 5-level resolution order. Pass through override
params in MemoryConsolidationService.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix critical resolution priority bug where _build_categories_to_check
baked company default_retention_days into base, making agent default
unreachable. Pass only explicit company rules to _resolve_categories.

Also: add WARNING log in uniqueness validator, extract _resolve_for_agent
helper (50-line limit), rename log kwarg to resolved_category_count,
update design spec table, add parity/log/cutoff tests.

Pre-reviewed by 6 agents, 8 findings addressed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ce27bffa-7b14-47e0-a547-bb5374cee174

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce5562 and 905af16.

📒 Files selected for processing (1)
  • tests/unit/memory/consolidation/test_retention.py
📜 Recent 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). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python version 3.14+ with PEP 649 native lazy annotations
Do NOT use from __future__ import annotations -- Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14
Line length: 88 characters (ruff enforced)

Files:

  • tests/unit/memory/consolidation/test_retention.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async tests: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Test timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing in Python: use Hypothesis (@given + @settings); run with HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties (dev profile: 1000 examples)
Hypothesis profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe and carries no timing assumptions

Files:

  • tests/unit/memory/consolidation/test_retention.py
🧠 Learnings (16)
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
🔇 Additional comments (4)
tests/unit/memory/consolidation/test_retention.py (4)

175-340: Consider consolidating _resolve_categories tests using @pytest.mark.parametrize.

The 10 test methods in this class follow the same pattern: define inputs, call _resolve_categories, assert on the result. This repetition could be reduced with parametrization.

As per coding guidelines: "Prefer @pytest.mark.parametrize for testing similar cases."


7-16: LGTM!

The imports correctly use the event constant from the domain-specific module as recommended, and structlog.testing is appropriately added for log capture assertions.


342-536: LGTM!

The test class thoroughly covers agent override scenarios including:

  • Backward compatibility without overrides
  • Cutoff correctness for category and default overrides
  • Category expansion with agent defaults
  • Priority resolution (agent default beats company default)
  • Log event emission verification using the event constant
  • Fast-path caching behavior
  • Combined override scenarios with order-independent assertions

The tests are well-structured and correctly verify the new per-agent retention override functionality.


538-553: LGTM!

The parity test is a good safeguard ensuring AgentRetentionRule and RetentionRule stay synchronized. The local imports keep the test self-documenting about which models are being compared (note: RetentionRule is already imported at module level, but keeping both local here is acceptable for test clarity).


Walkthrough

Adds per-agent memory retention overrides: a frozen Pydantic model AgentRetentionRule(category, retention_days) and MemoryConfig.retention_overrides. Strengthens MemoryConfig validation to require uniqueness of override categories and disallow overrides when MemoryLevel.NONE. RetentionEnforcer.cleanup_expired and consolidation service methods accept agent_category_overrides and agent_default_retention_days and resolve retention with precedence: agent per-category → company per-category → agent default → company default → keep forever. Introduces RETENTION_AGENT_OVERRIDE_APPLIED observability event and updates tests and docs accordingly.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding support for per-agent memory retention overrides.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of the additions and modifications made to support per-agent retention overrides.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #209: adds AgentRetentionRule and MemoryConfig.retention_overrides models, implements the 5-level resolution chain in RetentionEnforcer._resolve_categories(), threads overrides through MemoryConsolidationService methods, and adds the RETENTION_AGENT_OVERRIDE_APPLIED log event.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing per-agent retention overrides as specified in issue #209; no extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 95.35% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 905af16.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 357-363: The admonition currently uses an indented code block
style that triggers MD046; update the "Per-Agent Retention Overrides" admonition
so it uses lint-friendly formatting by removing any leading 4-space indentation
and replacing the indented snippet with either inline `code` or a fenced code
block (```...```) for the references to MemoryConfig.retention_overrides and
MemoryConfig.retention_days, and rewrite the resolution order line as plain text
(or a fenced code line) so it reads: agent per-category rule > company
per-category rule > agent global default > company global default > keep
forever, ensuring no indented code blocks remain.

In `@src/synthorg/memory/consolidation/retention.py`:
- Around line 146-162: The code currently treats an empty dict as an applied
override because has_overrides uses "agent_category_overrides is not None";
change the check to detect actual content by using a truthiness check for the
dict (e.g., bool(agent_category_overrides)) so has_overrides becomes True only
when agent_category_overrides has entries or agent_default_retention_days is
provided, then leave the call to _resolve_categories and the
RETENTION_AGENT_OVERRIDE_APPLIED log intact (still reference
agent_category_overrides, agent_default_retention_days, _resolve_categories,
has_overrides and RETENTION_AGENT_OVERRIDE_APPLIED) so you only suppress the log
when there is no real override.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ad8140a2-6702-49cd-8597-610420d74b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 1367225 and 419a186.

📒 Files selected for processing (10)
  • docs/design/memory.md
  • src/synthorg/core/__init__.py
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/memory/consolidation/retention.py
  • src/synthorg/memory/consolidation/service.py
  • src/synthorg/observability/events/consolidation.py
  • tests/unit/core/test_agent.py
  • tests/unit/memory/consolidation/test_retention.py
  • tests/unit/memory/consolidation/test_service.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). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python version 3.14+ with PEP 649 native lazy annotations
Do NOT use from __future__ import annotations -- Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14
Line length: 88 characters (ruff enforced)

Files:

  • src/synthorg/observability/events/consolidation.py
  • src/synthorg/core/__init__.py
  • src/synthorg/memory/consolidation/config.py
  • tests/unit/memory/consolidation/test_service.py
  • tests/unit/core/test_agent.py
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/service.py
  • tests/unit/memory/consolidation/test_retention.py
  • src/synthorg/memory/consolidation/retention.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Type hints: all public functions, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict)
Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time
Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)
Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls); prefer structured concurrency over bare create_task
Functions: < 50 lines, files < 800 lines
Errors: handle explicitly, never silently swallow
Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging / logging.getLogger() / print() in application code (exception: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap and handler-cleanup code)
Variable name for logger: always logger (not _logger, not log)
Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool); import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Structured kwargs for logging: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key...

Files:

  • src/synthorg/observability/events/consolidation.py
  • src/synthorg/core/__init__.py
  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/service.py
  • src/synthorg/memory/consolidation/retention.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async tests: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Test timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing in Python: use Hypothesis (@given + @settings); run with HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties (dev profile: 1000 examples)
Hypothesis profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe and carries no timing assumptions

Files:

  • tests/unit/memory/consolidation/test_service.py
  • tests/unit/core/test_agent.py
  • tests/unit/memory/consolidation/test_retention.py
🧠 Learnings (20)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/consolidation.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging

Applied to files:

  • src/synthorg/observability/events/consolidation.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/consolidation.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/observability/events/consolidation.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)

Applied to files:

  • src/synthorg/core/__init__.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/core/__init__.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • src/synthorg/core/__init__.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
  • src/synthorg/core/agent.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Required dependencies: `mem0ai` (Mem0 memory backend), `cryptography` (Fernet encryption), `faker` (multi-locale agent name generation)

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/core/__init__.py
  • tests/unit/core/test_agent.py
  • src/synthorg/core/agent.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • docs/design/memory.md
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/service.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
🪛 markdownlint-cli2 (0.22.0)
docs/design/memory.md

[warning] 359-359: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🔇 Additional comments (12)
src/synthorg/observability/events/consolidation.py (1)

32-34: Good addition of a domain-scoped event constant.

RETENTION_AGENT_OVERRIDE_APPLIED is consistent with the existing consolidation event taxonomy.

src/synthorg/memory/consolidation/config.py (1)

17-29: Doc update is aligned with runtime precedence semantics.

The precedence order is now explicit and unambiguous for consumers of RetentionConfig.

src/synthorg/core/agent.py (1)

183-277: Strong validation surface for per-agent retention overrides.

The new model + MemoryConfig validators enforce the key invariants cleanly (NONE-type guard and duplicate-category rejection) with structured validation logs.

src/synthorg/core/__init__.py (1)

5-5: Public API export is correctly wired.

Re-exporting AgentRetentionRule here keeps the core package surface consistent.

Also applies to: 84-84

tests/unit/core/test_agent.py (1)

412-527: Great coverage for override invariants.

These tests exercise the critical acceptance/rejection paths and immutability for the new retention override model.

tests/unit/memory/consolidation/test_service.py (1)

470-532: Nice pass-through coverage for service-level override plumbing.

These tests protect both the new override flow and the no-override backward-compatible behavior.

src/synthorg/memory/consolidation/retention.py (1)

77-128: Resolution chain implementation looks correct and readable.

The 5-level precedence is encoded clearly and matches the intended behavior.

src/synthorg/memory/consolidation/service.py (2)

194-217: Override pass-through in cleanup_retention is clean and backward-compatible.

Keyword-only optional parameters and direct forwarding to RetentionEnforcer.cleanup_expired(...) are correctly implemented.


219-246: run_maintenance correctly threads agent retention overrides into cleanup.

The maintenance orchestration now propagates agent-level override inputs without altering the existing consolidation/max-enforcement flow.

tests/unit/memory/consolidation/test_retention.py (3)

172-337: Resolution-chain tests are comprehensive and well targeted.

This suite exercises override precedence and fallback semantics thoroughly, including empty/no-default paths and full-chain behavior.


339-483: Agent-override cleanup tests validate behavior and observability effectively.

The added cases verify category selection, cutoff propagation, and override-event emission with expected structured fields.


485-500: Field-parity guard is a strong regression test.

Validating field names and annotations between AgentRetentionRule and RetentionRule is a solid safety net for future schema changes.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.99%. Comparing base (7cd1d23) to head (905af16).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #951   +/-   ##
=======================================
  Coverage   91.98%   91.99%           
=======================================
  Files         605      605           
  Lines       32543    32583   +40     
  Branches     3141     3147    +6     
=======================================
+ Hits        29935    29975   +40     
  Misses       2071     2071           
  Partials      537      537           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces per-agent retention overrides, allowing individual agents to define custom memory retention periods that supersede company-level defaults. The implementation includes the AgentRetentionRule model, updated MemoryConfig validation, and a prioritized resolution logic in RetentionEnforcer. Feedback focuses on the inability to explicitly set a 'keep forever' override due to type constraints and resolution logic in the per-category settings. Additionally, minor suggestions were provided to remove unnecessary parentheses in type hints for consistency with the codebase.

category: MemoryCategory = Field(
description="Memory category this override applies to",
)
retention_days: int = Field(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The retention_days field is currently restricted to integers greater than or equal to 1. This prevents agents from explicitly overriding a company-level retention rule to "keep forever" (which is represented by None in other parts of the memory configuration). To support full override capabilities, this field should allow None values.

Suggested change
retention_days: int = Field(
retention_days: int | None = Field(

Comment on lines +110 to +113
agent_days = agent_overrides.get(category)
if agent_days is not None:
result.append((category, agent_days))
continue
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation uses .get(category) and checks is not None. This logic fails to distinguish between a missing override and an explicit "keep forever" override (if retention_days were allowed to be None). Using an in check would correctly handle explicit None overrides by stopping the resolution chain for that category.

Suggested change
agent_days = agent_overrides.get(category)
if agent_days is not None:
result.append((category, agent_days))
continue
if category in agent_overrides:
agent_days = agent_overrides[category]
if agent_days is not None:
result.append((category, agent_days))
continue

self,
agent_id: NotBlankStr,
*,
agent_category_overrides: (Mapping[MemoryCategory, int] | None) = None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The parentheses around the type hint are unnecessary and inconsistent with standard Python type hinting practices used elsewhere in the codebase.

Suggested change
agent_category_overrides: (Mapping[MemoryCategory, int] | None) = None,
agent_category_overrides: Mapping[MemoryCategory, int] | None = None,

self,
agent_id: NotBlankStr,
*,
agent_category_overrides: (Mapping[MemoryCategory, int] | None) = None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The parentheses around the type hint are unnecessary and inconsistent with standard Python type hinting practices used elsewhere in the codebase.

Suggested change
agent_category_overrides: (Mapping[MemoryCategory, int] | None) = None,
agent_category_overrides: Mapping[MemoryCategory, int] | None = None,

…mini

- Add logging to RetentionConfig and DualModeConfig validators (CLAUDE.md rule)
- Remove unnecessary parentheses in service.py type annotations
- Update stale _validate_retention_consistency docstring
- Fix O(n^2) duplicate detection with set-based approach
- Add runtime validation for agent override retention days (>= 1)
- Standardize level 5 wording to "Keep forever (no expiry)" across all locations
- Add clarifying comment on _explicit_rules contract
- Fix empty dict triggering override path unnecessarily
- Replace Sphinx :meth: ref to private method with plain backticks
- Fix MD046 markdownlint warning in memory design page
- Add retention_overrides to agent YAML example in design spec
- Add 2 new tests: fast-path identity return + combined override params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/memory/consolidation/test_retention.py`:
- Around line 527-529: The test assumes call_args_list[0] is the WORKING
category which is order-dependent; instead, locate the call for
MemoryCategory.WORKING by scanning backend.retrieve.call_args_list for the entry
whose MemoryQuery (second positional arg) has category ==
MemoryCategory.WORKING, then assert that its query.until == _NOW -
timedelta(days=7); update any similar assertions to find calls by matching
query.category rather than relying on the list order (references:
backend.retrieve.call_args_list, MemoryQuery, MemoryCategory).
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01f53f87-4aeb-4a6f-bc1b-bd3f25d0b4ed

📥 Commits

Reviewing files that changed from the base of the PR and between 419a186 and a440253.

📒 Files selected for processing (7)
  • docs/design/agents.md
  • docs/design/memory.md
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/memory/consolidation/retention.py
  • src/synthorg/memory/consolidation/service.py
  • tests/unit/memory/consolidation/test_retention.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). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python version 3.14+ with PEP 649 native lazy annotations
Do NOT use from __future__ import annotations -- Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14
Line length: 88 characters (ruff enforced)

Files:

  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/retention.py
  • src/synthorg/memory/consolidation/service.py
  • tests/unit/memory/consolidation/test_retention.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Type hints: all public functions, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict)
Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time
Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)
Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls); prefer structured concurrency over bare create_task
Functions: < 50 lines, files < 800 lines
Errors: handle explicitly, never silently swallow
Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging / logging.getLogger() / print() in application code (exception: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap and handler-cleanup code)
Variable name for logger: always logger (not _logger, not log)
Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool); import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Structured kwargs for logging: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key...

Files:

  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/retention.py
  • src/synthorg/memory/consolidation/service.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async tests: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Test timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing in Python: use Hypothesis (@given + @settings); run with HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties (dev profile: 1000 examples)
Hypothesis profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe and carries no timing assumptions

Files:

  • tests/unit/memory/consolidation/test_retention.py
🧠 Learnings (15)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • docs/design/memory.md
  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/retention.py
  • src/synthorg/memory/consolidation/service.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/core/agent.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/memory/consolidation/config.py
  • src/synthorg/core/agent.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/core/agent.py
  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/core/agent.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/core/agent.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/memory/consolidation/retention.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
🔇 Additional comments (15)
src/synthorg/core/agent.py (3)

183-204: LGTM! Well-structured frozen model for agent retention overrides.

The AgentRetentionRule model correctly mirrors RetentionRule in the memory package while avoiding a core -> memory import dependency. The ge=1 constraint on retention_days and frozen=True config are appropriate.


231-234: LGTM! Per-category retention overrides field.

Using a tuple of AgentRetentionRule rather than a dict preserves field ordering and immutability consistent with other frozen model patterns in this codebase.


262-283: LGTM! Efficient duplicate detection with proper observability.

The O(n) seen set approach (addressed from earlier O(n²) finding per commit messages) correctly identifies duplicates. Structured logging with CONFIG_VALIDATION_FAILED follows guidelines.

src/synthorg/memory/consolidation/config.py (2)

21-39: LGTM! Clear documentation of the resolution chain.

The docstring accurately documents the 5-level precedence order which aligns with the implementation in retention.py. This helps maintainers understand the override behavior.


53-73: LGTM! Consistent validation pattern with observability.

The duplicate detection mirrors the implementation in MemoryConfig._validate_unique_override_categories, using the efficient O(n) seen/dupes approach with proper structured logging.

src/synthorg/memory/consolidation/retention.py (3)

48-53: LGTM! Critical distinction between explicit rules and default-filled entries.

Storing only explicit per-category rules (without default-filled entries) is essential for correct resolution. The docstring clearly explains this constraint and its importance for _resolve_categories.


80-138: LGTM! Well-implemented 5-level resolution chain.

The implementation correctly follows the documented priority:

  1. Agent per-category override
  2. Company per-category rule (explicit only)
  3. Agent global default
  4. Company global default
  5. Keep forever (skip)

Input validation for retention_days >= 1 at lines 109-115 ensures invalid values are rejected early.


156-172: Past review finding addressed: empty dict no longer triggers override log.

The bool(agent_category_overrides) check correctly treats {} as "no overrides" rather than "overrides applied", preventing spurious observability events.

docs/design/memory.md (1)

357-369: LGTM! Clear documentation of per-agent retention overrides.

The section accurately documents the resolution order and uses lint-friendly markdown formatting (addressing the prior MD046 concern). The numbered list clearly communicates the precedence chain.

docs/design/agents.md (1)

121-122: LGTM! Agent YAML example updated with retention override fields.

The example correctly documents both retention_days as the agent-level global default and retention_overrides for per-category rules.

src/synthorg/memory/consolidation/service.py (2)

194-217: LGTM! Clean pass-through of retention override parameters.

The service layer correctly forwards agent_category_overrides and agent_default_retention_days to the underlying RetentionEnforcer. The backward-compatible None defaults preserve existing behavior.


219-246: Note: No production callers currently populate override parameters.

Per the method signatures, run_maintenance and cleanup_retention accept the new override parameters (agent_category_overrides and agent_default_retention_days), which are threaded through the call chain. However, no external callers currently populate these parameters—they're only used internally within the maintenance cycle. This is likely intentional as a future integration point for custom retention rules (e.g., extracting values from AgentIdentity.memory.retention_overrides), but worth tracking.

tests/unit/memory/consolidation/test_retention.py (3)

172-336: LGTM! Comprehensive tests for the 5-level resolution chain.

TestResolveCategories thoroughly covers all priority levels:

  • Agent override replacing/adding categories
  • Agent default filling gaps
  • Company rules beating agent defaults
  • Company defaults as fallback
  • Full chain integration test

459-482: LGTM! Structured log event assertion.

Using structlog.testing.capture_logs() to validate the RETENTION_AGENT_OVERRIDE_APPLIED event is the correct approach for testing observability.


532-546: LGTM! Clever parity test to prevent model drift.

This test ensures AgentRetentionRule and RetentionRule maintain identical field names and types, catching future divergence between the duplicate models in core and memory packages.

Find the WORKING category call by scanning query.categories instead of
relying on call_args_list ordering, making the test resilient to
iteration order changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/memory/consolidation/test_retention.py`:
- Around line 473-477: Replace the hard-coded event string in the test filter
with the event constant: import RETENTION_AGENT_OVERRIDE_APPLIED from
src/synthorg/observability/events/consolidation.py and use it in the list
comprehension that defines override_logs (the comprehension currently
referencing log.get("event") ==
"consolidation.retention.agent_override_applied"); this makes the assertion
resilient to renames and ensures the test references the canonical event symbol.
- Around line 176-337: Multiple near-duplicate tests exercise
RetentionEnforcer._resolve_categories with different inputs; replace them with a
single parametrized pytest test to remove duplication. Create one test function
(e.g., test_resolve_categories_parametrized) decorated with
`@pytest.mark.parametrize` that supplies tuples of (base, agent_overrides,
agent_default_days, company_default_days, expected_dict) covering each of the 10
scenarios currently implemented (including empty case), call
RetentionEnforcer._resolve_categories for each row, convert result to dict and
assert equality/expectations, and remove the original individual test_*
functions; keep the same scenario names as param ids for readability. Ensure you
import pytest and preserve assertions for per-category checks (use expected_dict
to drive them) so coverage of agent vs company vs defaults remains identical to
the originals.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 814696a7-08cf-4123-8224-ed583f44298e

📥 Commits

Reviewing files that changed from the base of the PR and between a440253 and 6ce5562.

📒 Files selected for processing (1)
  • tests/unit/memory/consolidation/test_retention.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). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python version 3.14+ with PEP 649 native lazy annotations
Do NOT use from __future__ import annotations -- Python 3.14 has PEP 649
PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14
Line length: 88 characters (ruff enforced)

Files:

  • tests/unit/memory/consolidation/test_retention.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async tests: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Test timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing in Python: use Hypothesis (@given + @settings); run with HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties (dev profile: 1000 examples)
Hypothesis profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe and carries no timing assumptions

Files:

  • tests/unit/memory/consolidation/test_retention.py
🧠 Learnings (2)
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
📚 Learning: 2026-03-31T14:40:41.736Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:40:41.736Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/consolidation/test_retention.py
🔇 Additional comments (1)
tests/unit/memory/consolidation/test_retention.py (1)

527-534: Good fix: WORKING-category assertion is now order-independent.

Matching the retrieve call by MemoryCategory.WORKING removes call-order coupling and makes this test substantially less brittle.

Comment on lines +176 to +337
def test_agent_override_replaces_company_rule(self) -> None:
"""Agent per-category rule overrides company per-category rule."""
base = ((MemoryCategory.WORKING, 30),)
agent_overrides: dict[MemoryCategory, int] = {
MemoryCategory.WORKING: 90,
}
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides=agent_overrides,
agent_default_days=None,
company_default_days=None,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.WORKING] == 90

def test_agent_override_adds_new_category(self) -> None:
"""Agent can add a rule for a category not in company config."""
base = ((MemoryCategory.WORKING, 30),)
agent_overrides: dict[MemoryCategory, int] = {
MemoryCategory.SEMANTIC: 365,
}
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides=agent_overrides,
agent_default_days=None,
company_default_days=None,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.WORKING] == 30
assert result_dict[MemoryCategory.SEMANTIC] == 365

def test_agent_default_fills_gaps(self) -> None:
"""Agent default_retention_days fills categories without rules."""
base = () # no company rules
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides={},
agent_default_days=60,
company_default_days=None,
)
result_dict = dict(result)
# All categories should get the agent default
for cat in MemoryCategory:
assert result_dict[cat] == 60

def test_agent_rule_beats_agent_default(self) -> None:
"""Agent per-category rule takes priority over agent default."""
base = ()
agent_overrides: dict[MemoryCategory, int] = {
MemoryCategory.EPISODIC: 180,
}
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides=agent_overrides,
agent_default_days=60,
company_default_days=None,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.EPISODIC] == 180
# Other categories get agent default
assert result_dict[MemoryCategory.WORKING] == 60

def test_company_rule_beats_agent_default(self) -> None:
"""Company per-category rule beats agent global default."""
base = ((MemoryCategory.WORKING, 7),)
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides={},
agent_default_days=365,
company_default_days=None,
)
result_dict = dict(result)
# Company rule for WORKING wins over agent default
assert result_dict[MemoryCategory.WORKING] == 7
# Agent default fills other categories
assert result_dict[MemoryCategory.SEMANTIC] == 365

def test_company_default_used_when_no_agent_default(self) -> None:
"""Company default fills gaps when no agent default is set."""
base = ((MemoryCategory.WORKING, 7),)
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides={},
agent_default_days=None,
company_default_days=30,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.WORKING] == 7
assert result_dict[MemoryCategory.SEMANTIC] == 30

def test_no_overrides_returns_base(self) -> None:
"""When no agent overrides, result matches base plus defaults."""
base = (
(MemoryCategory.WORKING, 30),
(MemoryCategory.EPISODIC, 90),
)
result = RetentionEnforcer._resolve_categories(
base,
agent_overrides={},
agent_default_days=None,
company_default_days=None,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.WORKING] == 30
assert result_dict[MemoryCategory.EPISODIC] == 90
# Categories with no rule at all are not included
assert MemoryCategory.SOCIAL not in result_dict

def test_full_resolution_chain(self) -> None:
"""Test all 5 resolution levels in a single scenario.

- WORKING: agent per-category (7) beats company per-category (30)
- EPISODIC: company per-category (90) beats agent default (60)
- SEMANTIC: agent per-category (365) -- no company rule
- PROCEDURAL: agent default (60) -- no rules, no company rule
- SOCIAL: agent default (60) -- no rules, agent default > co default
"""
# Only explicit company per-category rules (not company default)
explicit_rules = (
(MemoryCategory.WORKING, 30),
(MemoryCategory.EPISODIC, 90),
)
agent_overrides: dict[MemoryCategory, int] = {
MemoryCategory.WORKING: 7,
MemoryCategory.SEMANTIC: 365,
}
result = RetentionEnforcer._resolve_categories(
explicit_rules,
agent_overrides=agent_overrides,
agent_default_days=60,
company_default_days=45,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.WORKING] == 7 # 1. agent rule
assert result_dict[MemoryCategory.EPISODIC] == 90 # 2. company rule
assert result_dict[MemoryCategory.SEMANTIC] == 365 # 1. agent rule
assert result_dict[MemoryCategory.PROCEDURAL] == 60 # 3. agent default
assert result_dict[MemoryCategory.SOCIAL] == 60 # 3. agent default

def test_company_default_used_when_agent_default_absent(self) -> None:
"""Priority 4 (company default) used when no agent default."""
explicit_rules = ((MemoryCategory.WORKING, 30),)
result = RetentionEnforcer._resolve_categories(
explicit_rules,
agent_overrides={},
agent_default_days=None,
company_default_days=45,
)
result_dict = dict(result)
assert result_dict[MemoryCategory.WORKING] == 30 # 2. company rule
assert result_dict[MemoryCategory.SOCIAL] == 45 # 4. company default

def test_empty_everything_returns_empty(self) -> None:
"""No rules, no defaults -- returns empty tuple."""
result = RetentionEnforcer._resolve_categories(
(),
agent_overrides={},
agent_default_days=None,
company_default_days=None,
)
assert result == ()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify repetition hotspots around _resolve_categories tests
rg -n "def test_.*|_resolve_categories\(" tests/unit/memory/consolidation/test_retention.py

Repository: Aureliolo/synthorg

Length of output: 2388


🏁 Script executed:

# Check if tests have docstrings explaining distinct scenarios
cat tests/unit/memory/consolidation/test_retention.py | sed -n '176,337p' | head -60

Repository: Aureliolo/synthorg

Length of output: 2418


🏁 Script executed:

# Verify the test methods exist at the specified lines and count assertions
sed -n '176,337p' tests/unit/memory/consolidation/test_retention.py | grep -E "^\s*(def test_|assert |for )" | head -50

Repository: Aureliolo/synthorg

Length of output: 1999


Consolidate _resolve_categories tests using @pytest.mark.parametrize.

These 10 test methods (lines 176–337) all follow the same pattern: define base/agent_overrides, call _resolve_categories, and assert on the result. Converting to parametrized test cases would reduce duplication and make the priority resolution scenarios easier to extend. Per coding guidelines: "Prefer @pytest.mark.parametrize for testing similar cases."

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

In `@tests/unit/memory/consolidation/test_retention.py` around lines 176 - 337,
Multiple near-duplicate tests exercise RetentionEnforcer._resolve_categories
with different inputs; replace them with a single parametrized pytest test to
remove duplication. Create one test function (e.g.,
test_resolve_categories_parametrized) decorated with `@pytest.mark.parametrize`
that supplies tuples of (base, agent_overrides, agent_default_days,
company_default_days, expected_dict) covering each of the 10 scenarios currently
implemented (including empty case), call RetentionEnforcer._resolve_categories
for each row, convert result to dict and assert equality/expectations, and
remove the original individual test_* functions; keep the same scenario names as
param ids for readability. Ensure you import pytest and preserve assertions for
per-category checks (use expected_dict to drive them) so coverage of agent vs
company vs defaults remains identical to the originals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 31, 2026 20:26 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 020c610 into main Mar 31, 2026
34 checks passed
@Aureliolo Aureliolo deleted the feat/agent-memory-retention branch March 31, 2026 20:45
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 31, 2026 20:45 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 1, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.5.4](v0.5.3...v0.5.4)
(2026-04-01)


### Features

* artifact and project management UI in web dashboard
([#954](#954))
([00a0430](00a0430))
* embed MkDocs build output in React web dashboard at /docs
([#948](#948))
([f229fc2](f229fc2))
* personality preset discovery API and user-defined preset CRUD
([#952](#952))
([497848a](497848a))
* support multi-provider model resolution with budget-based selection
([#953](#953))
([146b782](146b782))
* support per-agent memory retention overrides
([#209](#209))
([#951](#951))
([020c610](020c610))


### Documentation

* write user guides and tutorials
([#949](#949))
([1367225](1367225))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support per-agent memory retention overrides

1 participant