feat: implement procedural memory auto-generation from agent failures#1048
feat: implement procedural memory auto-generation from agent failures#1048
Conversation
Add a proposer pipeline that analyses failed task executions using a separate LLM call (not the failed agent) and produces structured procedural memory entries (MemoryCategory.PROCEDURAL). New modules: - memory/procedural/models.py: FailureAnalysisPayload, ProceduralMemoryProposal, ProceduralMemoryConfig - memory/procedural/proposer.py: ProceduralMemoryProposer (LLM-based analysis) - memory/procedural/pipeline.py: propose_procedural_memory (end-to-end) - observability/events/procedural_memory.py: event constants Engine integration: - agent_engine._apply_recovery returns (ExecutionResult, RecoveryResult | None) - agent_engine._post_execution_pipeline calls proposer after recovery - ProceduralMemoryConfig wired into CompanyMemoryConfig Three-tier progressive disclosure format: - DISCOVERY (~100 tokens for retrieval ranking) - CONDITION/ACTION/RATIONALE (activation-level detail) Retrieval integration via existing ContextInjectionStrategy/ToolBasedInjectionStrategy. Closes #420
Use Any instead of object for **overrides dict type annotations in test helper functions. Add return type annotation to _make_identity in test_pipeline.py.
Pre-reviewed by 14 agents, 29 findings addressed: - Added three-tier progressive disclosure (discovery/activation/execution) - Added SKILL.md file materialization (agentskills.io format) - Added ProceduralMemoryConfig.skill_md_directory for git-native versioning - Sanitized error messages before proposer LLM (sanitize_message) - Wrapped _build_payload in try-except (never-raises contract) - Extracted _try_procedural_memory helper from _post_execution_pipeline - Moved ProceduralMemoryProposer instantiation to __init__ - Narrowed bare Exception to ValidationError in proposer - Added exc_info=True to all warning-level catch blocks - Wired unused event constants (PAYLOAD_BUILT, DISABLED) - Added structural delimiters in _build_user_message - Added max_length constraints on proposal string fields - Added retry_count <= max_retries cross-field validator - Fixed TestProposeProcedualMemory typo - Added 26 new tests (non-dict JSON, validation failure, SKILL.md, execution steps, MemoryError propagation, _build_user_message, bare markdown fences, CompanyMemoryConfig.procedural) - Updated CLAUDE.md, docs/design/memory.md, docs/design/engine.md, README.md
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📜 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)
🧰 Additional context used📓 Path-based instructions (3)**/*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
src/synthorg/**/*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
src/**/*.py⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (12)📓 Common learnings📚 Learning: 2026-03-19T07:12:14.508ZApplied to files:
📚 Learning: 2026-03-15T18:38:44.202ZApplied to files:
📚 Learning: 2026-04-02T07:18:02.381ZApplied to files:
📚 Learning: 2026-03-15T19:14:27.144ZApplied to files:
📚 Learning: 2026-03-18T21:23:23.586ZApplied to files:
📚 Learning: 2026-03-15T18:28:13.207ZApplied to files:
📚 Learning: 2026-03-20T11:18:48.128ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-03-14T16:18:57.267ZApplied to files:
📚 Learning: 2026-03-31T16:09:24.320ZApplied to files:
📚 Learning: 2026-03-16T06:24:56.341ZApplied to files:
🔇 Additional comments (8)
WalkthroughAdds failure-driven procedural memory auto-generation: after an agent execution fails and recovery completes, an optional proposer LLM is invoked to produce a structured procedural memory proposal (discovery/condition/action/rationale/execution steps). Validated, high-confidence proposals are stored as 🚥 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. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure 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 FilesNone |
There was a problem hiding this comment.
Code Review
This pull request introduces a procedural memory auto-generation pipeline that analyzes agent task failures using a proposer LLM and stores the resulting lessons as structured memory entries. The changes include new domain models, configuration, event constants, and the core pipeline logic, along with comprehensive unit tests. Feedback was provided regarding the need to capture the original failed execution result before recovery, the use of domain-specific observability events, and the need for robust YAML frontmatter generation in the SKILL.md materialization process.
| recovery_result: RecoveryResult | None = None | ||
| if execution_result.termination_reason == TerminationReason.ERROR: |
There was a problem hiding this comment.
The execution_result variable is overwritten by the result of _apply_recovery at line 617. If a checkpoint resume occurs, execution_result will contain the outcome of the resumed execution (which might be a success), while the original failure context is lost. This causes the procedural memory pipeline to analyze the wrong set of turns. We should capture the failed execution result to ensure the proposer LLM receives the correct failure context.
recovery_result: RecoveryResult | None = None
failed_result: ExecutionResult | None = None
if execution_result.termination_reason == TerminationReason.ERROR:
failed_result = execution_result
src/synthorg/engine/agent_engine.py
Outdated
| exc_info=True, | ||
| ) | ||
| await self._try_procedural_memory( | ||
| execution_result, |
There was a problem hiding this comment.
src/synthorg/engine/agent_engine.py
Outdated
| EXECUTION_RESUME_START, | ||
| ) | ||
| from synthorg.observability.events.procedural_memory import ( | ||
| PROCEDURAL_MEMORY_DISABLED, |
| except Exception as exc: | ||
| logger.warning( | ||
| EXECUTION_ENGINE_ERROR, | ||
| agent_id=agent_id, |
| filename = f"SKILL-{task_id}-{slug}.md" | ||
| path = Path(directory) / filename | ||
|
|
||
| tags_str = ", ".join(proposal.tags) if proposal.tags else "" |
| f"description: {proposal.discovery}\n" | ||
| f"trigger: {proposal.condition}\n" |
There was a problem hiding this comment.
The YAML frontmatter is constructed using simple string interpolation. Since proposal.discovery and proposal.condition are LLM-generated and can contain newlines or special characters, this can produce invalid YAML. Consider using block scalars (>-) or proper quoting for these fields to ensure the generated SKILL.md files are robust and parseable.
f"description: >-\n {proposal.discovery}\n"
f"trigger: >-\n {proposal.condition}\n"There was a problem hiding this comment.
Pull request overview
Implements failure-driven “procedural memory” auto-generation: after an agent task fails (post-recovery), a separate proposer LLM analyzes structured failure data and stores a MemoryCategory.PROCEDURAL entry, optionally materializing a portable SKILL.md.
Changes:
- Added
memory/procedural/domain (models, proposer, pipeline) + new observability event constants. - Integrated procedural-memory generation into
AgentEnginepost-execution pipeline with non-blocking failure handling. - Added extensive unit tests and updated docs/README to describe the new pipeline and configuration.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/observability/test_events.py | Ensures procedural_memory event module is discoverable. |
| tests/unit/memory/test_init.py | Extends synthorg.memory.__all__ expectations for procedural exports. |
| tests/unit/memory/test_config.py | Verifies CompanyMemoryConfig includes default procedural config. |
| tests/unit/memory/procedural/test_proposer.py | Unit tests for proposer prompting, parsing, confidence gating, and error handling. |
| tests/unit/memory/procedural/test_pipeline.py | End-to-end pipeline tests: payload build, store path, SKILL.md output. |
| tests/unit/memory/procedural/test_models.py | Validates pydantic constraints for payload/proposal/config. |
| tests/unit/memory/procedural/init.py | Adds procedural test package marker. |
| tests/unit/engine/test_agent_engine_procedural.py | Verifies engine integration triggers/doesn’t block correctly. |
| src/synthorg/observability/events/procedural_memory.py | Introduces procedural-memory event constants. |
| src/synthorg/memory/procedural/proposer.py | Implements LLM proposer (prompting, JSON extraction, validation, logging). |
| src/synthorg/memory/procedural/pipeline.py | Implements payload construction, memory storage, and optional SKILL.md materialization. |
| src/synthorg/memory/procedural/models.py | Adds FailureAnalysisPayload, ProceduralMemoryProposal, ProceduralMemoryConfig. |
| src/synthorg/memory/procedural/init.py | Exposes procedural API surface. |
| src/synthorg/memory/config.py | Adds CompanyMemoryConfig.procedural configuration. |
| src/synthorg/memory/init.py | Re-exports procedural models/proposer/pipeline functions. |
| src/synthorg/engine/agent_engine.py | Integrates procedural memory pipeline; adjusts recovery to return recovery context. |
| README.md | Documents procedural-memory auto-generation capability at a high level. |
| docs/design/memory.md | Adds detailed design section for procedural memory pipeline + config. |
| docs/design/engine.md | Updates engine pipeline steps to include procedural memory generation. |
| CLAUDE.md | Updates repo structure notes + logging event-constants guidance with procedural memory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| slug = _slugify(proposal.discovery[:60]) | ||
| filename = f"SKILL-{task_id}-{slug}.md" | ||
| path = Path(directory) / filename | ||
|
|
||
| tags_str = ", ".join(proposal.tags) if proposal.tags else "" | ||
| steps_block = "" | ||
| if proposal.execution_steps: | ||
| lines = "\n".join( | ||
| f"{i}. {step}" for i, step in enumerate(proposal.execution_steps, 1) | ||
| ) | ||
| steps_block = f"\n## Execution Steps\n\n{lines}\n" | ||
|
|
||
| content = ( | ||
| "---\n" | ||
| f"name: {slug}\n" | ||
| f"description: {proposal.discovery}\n" | ||
| f"trigger: {proposal.condition}\n" | ||
| f"confidence: {proposal.confidence}\n" | ||
| f"tags: [{tags_str}]\n" | ||
| f"source: failure:{task_id}\n" | ||
| "---\n\n" | ||
| f"# {proposal.discovery}\n\n" | ||
| f"## Condition\n\n{proposal.condition}\n\n" | ||
| f"## Action\n\n{proposal.action}\n\n" | ||
| f"## Rationale\n\n{proposal.rationale}\n" | ||
| f"{steps_block}" | ||
| ) |
There was a problem hiding this comment.
materialize_skill_md() builds YAML frontmatter via raw string interpolation without escaping. Because ProceduralMemoryProposal fields (e.g., discovery, condition, tags) can legally contain :, quotes, newlines, or commas (they're NotBlankStr), the produced frontmatter can become invalid YAML or allow frontmatter injection. Consider generating the frontmatter with yaml.safe_dump(..., default_flow_style=False) (or at least quoting/escaping scalar values and emitting tags as a proper YAML list) and add a safe fallback when _slugify(...) returns an empty slug (e.g., all-non-alnum discovery).
| try: | ||
| payload = _build_payload(execution_result, recovery_result) | ||
| except MemoryError, RecursionError: | ||
| raise | ||
| except Exception as exc: | ||
| logger.warning( | ||
| PROCEDURAL_MEMORY_STORE_FAILED, | ||
| agent_id=agent_id, | ||
| task_id=task_id, | ||
| error=f"payload construction failed: {type(exc).__name__}: {exc}", | ||
| exc_info=True, | ||
| ) | ||
| return None |
There was a problem hiding this comment.
In the payload-construction failure path, the code logs PROCEDURAL_MEMORY_STORE_FAILED even though no store was attempted (_build_payload raised). This makes the event stream/metrics ambiguous. Suggest logging PROCEDURAL_MEMORY_SKIPPED with a reason like payload_build_failed, or using the dedicated PROCEDURAL_MEMORY_ERROR constant, and reserving ...STORE_FAILED for memory_backend.store() failures.
| # Materialize SKILL.md file when configured. | ||
| if config is not None and config.skill_md_directory is not None: | ||
| try: | ||
| materialize_skill_md(proposal, task_id, config.skill_md_directory) | ||
| except MemoryError, RecursionError: | ||
| raise | ||
| except Exception as exc: | ||
| logger.warning( | ||
| PROCEDURAL_MEMORY_STORE_FAILED, | ||
| agent_id=agent_id, | ||
| task_id=task_id, | ||
| error=f"SKILL.md write failed: {type(exc).__name__}: {exc}", | ||
| exc_info=True, | ||
| ) | ||
|
|
There was a problem hiding this comment.
The SKILL.md write failure is also logged as PROCEDURAL_MEMORY_STORE_FAILED, which conflates backend storage failures with filesystem materialization failures. Consider emitting a separate event (e.g., PROCEDURAL_MEMORY_ERROR / PROCEDURAL_MEMORY_SKILL_MD with success=false) so dashboards can distinguish store vs materialization problems.
| self._procedural_memory_config = procedural_memory_config | ||
| self._memory_backend = memory_backend | ||
| self._procedural_proposer: ProceduralMemoryProposer | None = None | ||
| if procedural_memory_config is not None and memory_backend is not None: | ||
| self._procedural_proposer = ProceduralMemoryProposer( | ||
| provider=provider, | ||
| config=procedural_memory_config, | ||
| ) |
There was a problem hiding this comment.
AgentEngine.__init__ initializes self._procedural_proposer whenever procedural_memory_config and memory_backend are non-None, even if procedural_memory_config.enabled is False. Since the config is frozen, this proposer can never be used and still incurs setup cost/complexity. Consider only constructing the proposer when procedural_memory_config.enabled is true (and keep the disabled event logging in _try_procedural_memory for completeness).
| PROCEDURAL_MEMORY_ERROR: Final[str] = "procedural_memory.error" | ||
| """Unrecoverable error in procedural memory pipeline.""" | ||
|
|
||
| PROCEDURAL_MEMORY_DISABLED: Final[str] = "procedural_memory.disabled" | ||
| """Procedural memory generation is disabled in config.""" | ||
|
|
||
| PROCEDURAL_MEMORY_SKILL_MD: Final[str] = "procedural_memory.skill_md.written" | ||
| """SKILL.md file materialized from a procedural memory proposal.""" |
There was a problem hiding this comment.
PROCEDURAL_MEMORY_ERROR and PROCEDURAL_MEMORY_SKILL_MD are introduced here but are not emitted anywhere in the PR (search shows only definitions). Either wire them into the pipeline/engine (e.g., log ...SKILL_MD on successful materialization and ...ERROR when the pipeline fails unexpectedly) or remove them to avoid dead/unused event inventory.
| PROCEDURAL_MEMORY_ERROR: Final[str] = "procedural_memory.error" | |
| """Unrecoverable error in procedural memory pipeline.""" | |
| PROCEDURAL_MEMORY_DISABLED: Final[str] = "procedural_memory.disabled" | |
| """Procedural memory generation is disabled in config.""" | |
| PROCEDURAL_MEMORY_SKILL_MD: Final[str] = "procedural_memory.skill_md.written" | |
| """SKILL.md file materialized from a procedural memory proposal.""" | |
| PROCEDURAL_MEMORY_DISABLED: Final[str] = "procedural_memory.disabled" | |
| """Procedural memory generation is disabled in config.""" |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1048 +/- ##
==========================================
+ Coverage 90.67% 90.71% +0.03%
==========================================
Files 697 702 +5
Lines 39593 39858 +265
Branches 3960 3975 +15
==========================================
+ Hits 35902 36158 +256
- Misses 3001 3009 +8
- Partials 690 691 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/memory/config.py`:
- Around line 246-253: The CompanyMemoryConfig class docstring Attributes
section is stale after adding the procedural field; update the Google-style
docstring for CompanyMemoryConfig to include an Attributes entry for procedural
(type ProceduralMemoryConfig) with a brief description like "Procedural memory
auto-generation settings controlling failure-driven skill proposals, model
selection, and quality thresholds" so the docstring matches the new procedural:
ProceduralMemoryConfig Field definition.
In `@src/synthorg/memory/procedural/models.py`:
- Around line 182-183: The Field skill_md_directory currently allows empty
strings; update the model to reject blank values by enforcing a non-empty string
at validation time: either change the type to Optional[constr(min_length=1)] or
add a pydantic `@validator` for skill_md_directory that raises a ValueError when
value == "" (and keep accepting None). Ensure the validator/constraint lives in
the same model class in src/synthorg/memory/procedural/models.py so blank "" is
considered invalid while None remains allowed.
- Around line 126-132: Replace the after-model validator _deduplicate_tags that
mutates the frozen instance with a before-field validator on "tags" using
`@field_validator`(mode="before") so de-duplication happens on input; implement
_deduplicate_tags as a classmethod that returns a tuple (preserving order via
dict.fromkeys) when v is list/tuple and otherwise returns v, and remove the
object.__setattr__ mutation and the `@model_validator` usage.
In `@src/synthorg/memory/procedural/pipeline.py`:
- Around line 145-151: The YAML frontmatter is built by interpolating raw
strings (slug, proposal.discovery, proposal.condition, proposal.confidence,
tags_str, task_id) which can break YAML when values contain newlines or special
chars; instead construct a Python dict for the frontmatter and serialize it
using a YAML serializer (e.g., yaml.safe_dump) or otherwise escape each value
before writing so description/trigger/tags are valid YAML; update the code where
the f-strings are assembled (the block building the frontmatter in pipeline.py)
to produce the serialized YAML from the dict rather than raw f-string
interpolation.
- Around line 165-281: The propose_procedural_memory function is too long; split
it into smaller helper functions to keep each under 50 lines. Extract the
payload construction into a helper (e.g., _construct_payload) that wraps
_build_payload and its error handling, extract the proposer call and formatting
into a helper (e.g., _generate_proposal using proposer.propose and
_format_procedural_content), extract storage into a helper (e.g.,
_store_procedural_memory that calls memory_backend.store and logs errors), and
extract the optional SKILL.md step into a helper (e.g., _materialize_skill_md
calling materialize_skill_md). Make propose_procedural_memory orchestrate these
helpers, passing agent_id, task_id, proposer, memory_backend, config and keeping
existing logging and exception behavior.
- Around line 132-135: The code currently interpolates raw task_id into
filename, allowing path separators or absolute paths to escape the target
directory; update the filename construction to sanitize task_id (e.g., coerce to
str, strip leading slashes, and replace any characters except alphanumerics,
hyphen, and underscore with an underscore, and optionally truncate) before using
it in filename. Implement this near where filename is set (replace the existing
filename = f"SKILL-{task_id}-{slug}.md"), using a sanitized variable like
safe_task_id = re.sub(r'[^A-Za-z0-9_-]', '_', str(task_id).lstrip('/\\')) and
then build filename = f"SKILL-{safe_task_id}-{slug}.md"; keep using
Path(directory) / filename as before. Ensure import of re if missing.
- Line 221: Wrap the call to proposer.propose(payload) in a try/except to catch
any exceptions so failures don't bubble up; on exception, log a warning (using
the module's logger or self.logger if available) including the exception
details, set proposal = None, and continue execution so the pipeline degrades
gracefully instead of throwing (i.e., replace the direct call to
proposer.propose with a guarded try/except that assigns to the existing proposal
variable).
- Around line 201-203: Replace the incorrect bare reference to the built-in
MemoryError in the exception handlers by importing the domain error class from
synthorg.memory.errors (e.g. import MemoryError as DomainMemoryError) and update
the except clauses to use the correct tuple syntax, e.g. except
(DomainMemoryError, RecursionError):; apply this change for the handlers
referenced (the except MemoryError, RecursionError: occurrences near the
handlers in pipeline.py and the similar sites at the other two locations) so
domain MemoryStoreError/MemoryConnectionError subclasses are caught by the
intended branch rather than falling through to the generic Exception handler.
In `@tests/unit/memory/procedural/test_pipeline.py`:
- Line 124: Replace the non-approved model_id value used in the test ModelConfig
instantiation: locate the ModelConfig(...) call (symbol: ModelConfig) that
currently sets model_id="test-model-001" and change it to one of the approved
generic ids (e.g., "test-small-001", "test-medium-001", or "test-large-001") so
the test conforms to the naming policy; ensure only the model_id string is
updated and nothing else in the ModelConfig instantiation is modified.
🪄 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: ccdbf4b6-0d02-49dc-a214-f72208f4eadc
📒 Files selected for processing (20)
CLAUDE.mdREADME.mddocs/design/engine.mddocs/design/memory.mdsrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/__init__.pysrc/synthorg/memory/config.pysrc/synthorg/memory/procedural/__init__.pysrc/synthorg/memory/procedural/models.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/observability/events/procedural_memory.pytests/unit/engine/test_agent_engine_procedural.pytests/unit/memory/procedural/__init__.pytests/unit/memory/procedural/test_models.pytests/unit/memory/procedural/test_pipeline.pytests/unit/memory/procedural/test_proposer.pytests/unit/memory/test_config.pytests/unit/memory/test_init.pytests/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). (8)
- GitHub Check: Agent
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Dependency Review
🧰 Additional context used
📓 Path-based instructions (6)
**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Documentation in docs/ is built with Zensical, configured via mkdocs.yml. Design spec docs are in docs/design/ (12 pages), architecture in docs/architecture/, roadmap in docs/roadmap/. REST API reference auto-generated via scripts/export_openapi.py
Files:
README.mdCLAUDE.mddocs/design/engine.mddocs/design/memory.md
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
Line length: 88 characters, enforced by ruff
Global test timeout: 30 seconds per test (configured in pyproject.toml). Non-default overrides like timeout(60) are allowed on specific tests
Files:
tests/unit/observability/test_events.pytests/unit/memory/test_config.pytests/unit/memory/test_init.pysrc/synthorg/memory/config.pytests/unit/memory/procedural/test_models.pysrc/synthorg/memory/procedural/proposer.pytests/unit/memory/procedural/test_proposer.pysrc/synthorg/memory/__init__.pytests/unit/memory/procedural/test_pipeline.pytests/unit/engine/test_agent_engine_procedural.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/__init__.pysrc/synthorg/memory/procedural/models.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use pytest markers:@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowfor test categorization
Maintain 80% minimum code coverage, enforced in CI with--cov-fail-under=80
Always run tests with-n 8for parallelism via pytest-xdist; never run tests sequentially locally. CI uses-n auto
Use@pytest.mark.parametrizefor testing similar cases instead of repeating test code
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in tests. Use generic names: test-provider, test-small-001, test-medium-001, test-large-001, or small/medium/large aliases
Use Hypothesis with@given+@settingsfor property-based testing. Never skip, dismiss, or ignore flaky tests -- fix them fundamentally. Use mocking for time-sensitive tests to make them deterministic instead of widening timing margins
Files:
tests/unit/observability/test_events.pytests/unit/memory/test_config.pytests/unit/memory/test_init.pytests/unit/memory/procedural/test_models.pytests/unit/memory/procedural/test_proposer.pytests/unit/memory/procedural/test_pipeline.pytests/unit/engine/test_agent_engine_procedural.py
⚙️ CodeRabbit configuration file
Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare
@settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which@given() honors automatically.
Files:
tests/unit/observability/test_events.pytests/unit/memory/test_config.pytests/unit/memory/test_init.pytests/unit/memory/procedural/test_models.pytests/unit/memory/procedural/test_proposer.pytests/unit/memory/procedural/test_pipeline.pytests/unit/engine/test_agent_engine_procedural.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: All public functions and classes must have type hints and Google-style docstrings, enforced by ruff D rules and mypy strict mode
Every module with business logic MUST have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code for logging -- use synthorg.observability.get_logger() instead
Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls
Functions must be < 50 lines, files < 800 lines
Immutability: create new objects, never mutate existing ones. Usecopy.deepcopy()at construction andMappingProxyTypewrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (withfrozen=Truein ConfigDict) for config/identity; use separate mutable-via-copy models (viamodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 with conventions:allow_inf_nan=Falsein all ConfigDict declarations to reject NaN/Inf; use@computed_fieldfor derived values instead of storing redundant fields; useNotBlankStr(from core.types) for all identifier/name fields including optional and tuple variants
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over barecreate_task
Handle errors explicitly, never silently swallow exceptions. All error paths must log at WARNING or ERROR with context before raising
Validate at system boundaries (user input, external APIs, config files)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, or comments. Vendor names m...
Files:
src/synthorg/memory/config.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/memory/__init__.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/__init__.pysrc/synthorg/memory/procedural/models.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/memory/config.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/memory/__init__.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/__init__.pysrc/synthorg/memory/procedural/models.py
docs/design/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS read the relevant docs/design/ page before implementing any feature. Design spec is the starting point for architecture, data models, and behavior. If implementation deviates from spec, alert the user and explain why -- user decides whether to proceed or update the spec. When approved deviations occur, update the relevant docs/design/ page
Files:
docs/design/engine.mddocs/design/memory.md
🧠 Learnings (43)
📚 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:
README.mdtests/unit/memory/test_init.pysrc/synthorg/memory/config.pyCLAUDE.mddocs/design/memory.mdsrc/synthorg/memory/procedural/proposer.pysrc/synthorg/memory/__init__.pytests/unit/memory/procedural/test_pipeline.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/__init__.pysrc/synthorg/memory/procedural/models.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:
README.mdsrc/synthorg/memory/config.pydocs/design/memory.mdsrc/synthorg/memory/__init__.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/pipeline.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...
Applied to files:
README.mdCLAUDE.mddocs/design/engine.mddocs/design/memory.mdtests/unit/engine/test_agent_engine_procedural.pysrc/synthorg/engine/agent_engine.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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations
Applied to files:
README.mdCLAUDE.md
📚 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: 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: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
Applied to files:
README.md
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
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 the domain module
Applied to files:
tests/unit/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.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/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.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/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.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/observability/test_events.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/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.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/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.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/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls
Applied to files:
tests/unit/observability/test_events.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:
tests/unit/observability/test_events.pysrc/synthorg/engine/agent_engine.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/observability/test_events.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.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/config.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/models.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models (with `frozen=True` in ConfigDict) for config/identity; use separate mutable-via-copy models (via `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/config.pytests/unit/memory/procedural/test_models.pysrc/synthorg/memory/procedural/models.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/config.pysrc/synthorg/memory/procedural/models.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/memory/config.pysrc/synthorg/memory/procedural/models.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:
CLAUDE.mdsrc/synthorg/memory/__init__.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/__init__.pysrc/synthorg/memory/procedural/models.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/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)
Applied to files:
CLAUDE.mddocs/design/engine.mdsrc/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)
Applied to files:
CLAUDE.md
📚 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: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.
Applied to files:
CLAUDE.md
📚 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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator
Applied to files:
CLAUDE.md
📚 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/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion
Applied to files:
CLAUDE.md
📚 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:
CLAUDE.md
📚 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 : 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. Variable name: always `logger` (not `_logger`, not `log`).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : 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. Variable name: always `logger`.
Applied to files:
CLAUDE.md
📚 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 : 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.
Applied to files:
CLAUDE.md
📚 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 : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
Applied to files:
CLAUDE.md
📚 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 : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`
Applied to files:
CLAUDE.md
📚 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 : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`
Applied to files:
CLAUDE.md
📚 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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/memory/__init__.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/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/memory/__init__.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/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.
Applied to files:
src/synthorg/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-03-31T16:09:24.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T16:09:24.320Z
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/engine/agent_engine.pysrc/synthorg/observability/events/procedural_memory.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/procedural_memory.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Immutability: create new objects, never mutate existing ones. Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Applied to files:
src/synthorg/memory/procedural/models.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/memory/procedural/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves
Applied to files:
src/synthorg/memory/procedural/models.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/memory/procedural/models.py
🔇 Additional comments (20)
README.md (1)
64-64: Memory capability update is clear and aligned with the feature scope.The added procedural-memory wording reads well and accurately communicates the new behavior.
tests/unit/memory/test_config.py (1)
287-291: Good default-coverage test for procedural memory config.This closes an important config regression gap by asserting default initialization and key defaults.
tests/unit/observability/test_events.py (1)
240-240: Domain discovery list update is correct.Including
procedural_memorykeeps the event-domain contract synchronized with the new observability module.src/synthorg/memory/config.py (1)
18-18: Procedural config import is wired cleanly.Nice integration point for extending
CompanyMemoryConfigwithout affecting existing config defaults.tests/unit/memory/test_init.py (1)
74-79: Public API export contract is updated correctly.These additions keep the
synthorg.memory.__all__assertion aligned with the new procedural-memory surface.CLAUDE.md (1)
99-101: Good documentation sync for procedural-memory architecture and logging events.These updates accurately extend both package-structure guidance and event-constant conventions.
Also applies to: 148-148
docs/design/engine.md (1)
671-677: Pipeline step insertion is well-placed and behaviorally accurate.The new non-critical procedural-memory step is clearly scoped and correctly documents its non-blocking failure semantics.
src/synthorg/memory/procedural/__init__.py (1)
1-26: Procedural package public interface is clean and complete.The re-exports and
__all__definition provide a clear, stable import surface for the new feature.docs/design/memory.md (1)
619-669: Well-documented design section for procedural memory auto-generation.The documentation clearly explains the pipeline steps, three-tier progressive disclosure format, configuration options, and integration point. The non-blocking behavior and separation of concerns (failed agent vs. proposer LLM) are well articulated.
src/synthorg/memory/__init__.py (1)
78-85: LGTM - Procedural memory public API correctly re-exported.The new procedural memory symbols are properly imported from the subpackage and added to
__all__with appropriate grouping comments. The alphabetical ordering in__all__is maintained.Also applies to: 114-115, 150-152, 167-168
tests/unit/memory/procedural/test_models.py (1)
1-232: Comprehensive test coverage for procedural memory models.Excellent coverage of validation rules including:
- Immutability (frozen models)
- String validation (empty/whitespace rejection)
- Numeric bounds (confidence 0-1, temperature 0-2, non-negative counts)
- Cross-field validation (
retry_count <= max_retries)- Collection handling (tag deduplication, empty defaults)
- Length constraints (
discoverymax 600 chars)The use of
"example-small-001"for the default model follows the guideline to avoid real vendor names.tests/unit/memory/procedural/test_proposer.py (1)
1-330: Thorough test coverage for the procedural memory proposer.The tests comprehensively cover:
- Successful proposal generation and parsing
- Message construction with structural delimiters
- JSON extraction (plain and markdown-fenced)
- Low confidence filtering
- Provider error handling (retryable vs non-retryable)
- Malformed/empty responses
- Schema validation failures
- System error propagation
Good use of
structlog.testing.capture_logs()for verifying observability events.src/synthorg/observability/events/procedural_memory.py (1)
1-39: Well-structured observability events for procedural memory pipeline.The event constants follow the project's hierarchical naming convention (
procedural_memory.<category>.<action>) and provide complete coverage of the pipeline lifecycle. Each constant includes a docstring explaining when it's emitted.src/synthorg/engine/agent_engine.py (3)
325-332: Proposer instantiated once at engine construction.Good design choice to create the
ProceduralMemoryProposeronce in__init__rather than per-execution. This avoids repeated object creation and ensures consistent configuration across runs.
689-734: Non-blocking procedural memory integration with proper guard conditions.The
_try_procedural_memorymethod correctly:
- Short-circuits when proposer is not configured (line 701-702)
- Logs
PROCEDURAL_MEMORY_DISABLEDwhen config exists but is disabled (lines 703-712)- Requires both
memory_backendandrecovery_resultto proceed (line 713-714)- Swallows non-fatal exceptions with
exc_info=Truefor debugging (lines 727-734)- Propagates
MemoryErrorandRecursionErroras required (lines 725-726)
908-962: Return type change for_apply_recoveryis correctly propagated.The change from
ExecutionResulttotuple[ExecutionResult, RecoveryResult | None]is properly handled across all code paths:
- Returns
(result, None)when no strategy or no task execution (lines 920, 923)- Returns
(result, recovery_result)after successful recovery (lines 944, 952)- Returns
(result, None)on recovery failure (line 962)All callers have been updated to destructure the tuple appropriately.
src/synthorg/memory/procedural/proposer.py (2)
143-177: Error handling inpropose()follows project conventions.The exception handling correctly:
- Propagates system-level errors (
MemoryError,RecursionError) per project convention- Distinguishes retryable vs non-retryable
ProviderErrorusingis_retryableflag- Swallows unexpected exceptions with warning-level logging and
exc_info=TrueThe bare
MemoryErrorreference is the built-in PythonMemoryError(OOM conditions), not the domain-specificsynthorg.memory.errors.MemoryError. As per coding guidelines, this is the intentional project convention for system-error propagation.
181-231: Robust response parsing with appropriate validation.The
_parse_responsemethod correctly handles:
- Empty/whitespace responses (debug log, return
None)- Malformed JSON (warning log, return
None)- Schema validation failures via Pydantic (warning with
exc_info, returnNone)- Low confidence proposals (info log, return
None)- Valid proposals (info log with metadata, return proposal)
The confidence threshold check uses
<which correctly accepts proposals at exactlymin_confidence.tests/unit/engine/test_agent_engine_procedural.py (2)
111-142: Test validates procedural memory generation after ERROR recovery.The test correctly:
- Creates an engine with
recovery_strategy,procedural_memory_config, andmemory_backend- Calls
_post_execution_pipelinedirectly with an ERROR termination result- Asserts that
memory_backend.storewas calledNote: The test relies on the
FailAndReassignStrategyreturning aRecoveryResultthat enables the procedural memory path. This is a valid integration test approach.
144-279: Good coverage of skip conditions and failure resilience.The tests correctly validate that procedural memory is skipped when:
- Termination is not ERROR (line 144-168)
- Config is
None(line 201-223)- Backend is
None(line 225-251)- Config has
enabled=False(line 253-279)The failure resilience test (line 170-199) confirms that proposer errors don't block the engine result.
| except MemoryError, RecursionError: | ||
| raise | ||
| except Exception as exc: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspect MemoryError references and imports:"
rg -n -C2 'MemoryError|from synthorg.memory.errors import' \
src/synthorg/memory/procedural/pipeline.py \
src/synthorg/memory/errors.py \
tests/unit/memory/procedural/test_pipeline.pyRepository: Aureliolo/synthorg
Length of output: 5274
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check top-level imports in pipeline.py ==="
head -40 src/synthorg/memory/procedural/pipeline.py | cat -n
echo -e "\n=== Check all imports in pipeline.py ==="
grep -n "^from\|^import" src/synthorg/memory/procedural/pipeline.py | head -20
echo -e "\n=== Check test imports for MemoryError ==="
head -50 tests/unit/memory/procedural/test_pipeline.py | cat -nRepository: Aureliolo/synthorg
Length of output: 4646
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Full exception handler context (lines 195-280) ==="
sed -n '195,280p' src/synthorg/memory/procedural/pipeline.py | cat -n
echo -e "\n=== Check if MemoryError is explicitly imported from synthorg.memory.errors ==="
grep -n "from synthorg.memory.errors import\|from . import\|from \.errors import" src/synthorg/memory/procedural/pipeline.pyRepository: Aureliolo/synthorg
Length of output: 3261
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check MemoryBackend protocol definition ==="
grep -A 20 "class MemoryBackend\|async def store" src/synthorg/memory/protocol.py | head -40
echo -e "\n=== Check what MemoryError types can be raised by backends ==="
grep -r "raise.*MemoryError\|except.*MemoryError" src/synthorg/memory/backends/ 2>/dev/null | head -20Repository: Aureliolo/synthorg
Length of output: 3005
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check if any backend's store() method raises domain MemoryError ==="
grep -B5 -A10 "def store\|async def store" src/synthorg/memory/backends/*.py | grep -A10 "def store" | head -50Repository: Aureliolo/synthorg
Length of output: 133
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Look at test that references MemoryError (test_memory_error_propagates_from_store) ==="
sed -n '355,375p' tests/unit/memory/procedural/test_pipeline.py | cat -n
echo -e "\n=== Check test imports for MemoryError at the top ==="
grep -n "import.*MemoryError\|from.*errors import" tests/unit/memory/procedural/test_pipeline.py
echo -e "\n=== Check if test imports domain MemoryError indirectly ==="
grep -n "from synthorg.memory" tests/unit/memory/procedural/test_pipeline.py | head -15Repository: Aureliolo/synthorg
Length of output: 1082
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Verify MemoryStoreError and MemoryConnectionError inherit from domain MemoryError ==="
grep -A2 "class MemoryStoreError\|class MemoryConnectionError" src/synthorg/memory/errors.pyRepository: Aureliolo/synthorg
Length of output: 359
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Verify which MemoryError the test actually uses (check full test file imports) ==="
head -100 tests/unit/memory/procedural/test_pipeline.py | grep -n "MemoryError\|^from\|^import" | head -30
echo -e "\n=== Check if builtins is explicitly used in protocol ==="
grep -n "builtins\|import.*MemoryError" src/synthorg/memory/protocol.pyRepository: Aureliolo/synthorg
Length of output: 1169
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check what happens when MemoryStoreError is raised ==="
python3 << 'EOF'
# Simulate the namespace resolution
# Built-in MemoryError
builtin_mem_err = MemoryError("builtin")
# Simulate domain MemoryError (not imported in pipeline.py)
class DomainMemoryError(Exception):
pass
class MemoryStoreError(DomainMemoryError):
"""Subclass of domain MemoryError"""
pass
# Test 1: What gets caught by bare "except MemoryError"?
print("Test 1: Bare 'except MemoryError' (resolves to builtin):")
try:
raise MemoryStoreError("domain error")
except MemoryError: # This is builtin.MemoryError
print(" → Caught by 'except MemoryError'")
except Exception:
print(" → NOT caught, fell through to 'except Exception'")
# Test 2: What if we explicitly catch builtin?
print("\nTest 2: Explicit 'except builtins.MemoryError':")
import builtins
try:
raise MemoryStoreError("domain error")
except builtins.MemoryError:
print(" → Caught by 'except builtins.MemoryError'")
except Exception as e:
print(f" → NOT caught, fell through to 'except Exception': {type(e).__name__}")
# Test 3: What if we catch domain.MemoryError?
print("\nTest 3: Catch domain MemoryError and subclasses:")
try:
raise MemoryStoreError("domain error")
except DomainMemoryError:
print(" → Caught by 'except DomainMemoryError'")
except Exception:
print(" → NOT caught")
EOFRepository: Aureliolo/synthorg
Length of output: 437
Import and catch domain MemoryError from synthorg.memory.errors; current handler resolves to built-in MemoryError.
Lines 201, 246, and 270 use bare except MemoryError, RecursionError: without importing the domain MemoryError. This resolves to Python's built-in MemoryError, not synthorg.memory.errors.MemoryError. When backend.store() or other calls raise domain MemoryStoreError or MemoryConnectionError (both subclasses of domain MemoryError), they fall through to the generic Exception handler and are silently converted to None, violating the requirement to handle errors explicitly.
🔧 Suggested fix
+import builtins
+from synthorg.memory.errors import MemoryError as DomainMemoryError
...
- except MemoryError, RecursionError:
+ except builtins.MemoryError, RecursionError:
+ raise
+ except DomainMemoryError:
raise🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/memory/procedural/pipeline.py` around lines 201 - 203, Replace
the incorrect bare reference to the built-in MemoryError in the exception
handlers by importing the domain error class from synthorg.memory.errors (e.g.
import MemoryError as DomainMemoryError) and update the except clauses to use
the correct tuple syntax, e.g. except (DomainMemoryError, RecursionError):;
apply this change for the handlers referenced (the except MemoryError,
RecursionError: occurrences near the handlers in pipeline.py and the similar
sites at the other two locations) so domain
MemoryStoreError/MemoryConnectionError subclasses are caught by the intended
branch rather than falling through to the generic Exception handler.
…, and CodeRabbit Source fixes: - YAML frontmatter injection: use yaml.safe_dump instead of f-strings (pipeline.py) - Split propose_procedural_memory into helpers under 50-line limit - Guard proposer.propose() with try/except to match never-raises contract - Capture failed execution result before recovery for correct failure context - Sanitize task_id in SKILL.md filename via _slugify with empty-slug fallback - Change skill_md_directory from str|None to NotBlankStr|None - Replace hardcoded 'non-inferable' with NON_INFERABLE_TAG constant - Fix event constants: payload failure uses PROCEDURAL_MEMORY_ERROR, SKILL.md success uses PROCEDURAL_MEMORY_SKILL_MD - Use PROCEDURAL_MEMORY_ERROR instead of EXECUTION_ENGINE_ERROR - Only construct ProceduralMemoryProposer when config.enabled is True - Remove dead code guards in _try_procedural_memory - Add error_category and missing_capability fields to FailureAnalysisPayload - Fix tags dedup ordering: use field_validator(mode='before') instead of model_validator(mode='after') so dedup runs before max_length validation - Add max_length=50 on execution_steps tuple - Add DEBUG log for ProceduralMemoryProposer construction - Log JSONDecodeError detail at DEBUG in _extract_json - Add success log for SKILL.md materialization - Update CompanyMemoryConfig docstring with procedural attribute - Document model/max_tokens defaults in docs/design/memory.md Test additions: - SKILL.md integration via propose_procedural_memory with config - SKILL.md write failure still returns memory_id - RecursionError propagation from backend.store - MemoryError propagation from proposer.propose - Proposer exception returns None gracefully - _slugify edge cases (empty, unicode, long, special chars) - Error message sanitization in _build_payload - error_category/missing_capability field tests - skill_md_directory empty/whitespace rejection - execution_steps max 50 enforcement - Tags capped at 20 after dedup - Fix test-model-001 to test-small-001 (naming convention)
There was a problem hiding this comment.
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 `@src/synthorg/memory/procedural/proposer.py`:
- Around line 131-137: The constructor currently logs configuration with the
event CONSTANT PROCEDURAL_MEMORY_PROPOSED (via logger.debug), which is also
reused when a proposal is generated; change the constructor logging to use a
distinct event constant (e.g., PROCEDURAL_MEMORY_PROPOSER_INIT) or a different
log level/message to avoid collision with the proposal-produced event—update the
logger.debug call that passes PROCEDURAL_MEMORY_PROPOSED to instead reference
the new PROCEDURAL_MEMORY_PROPOSER_INIT constant (and add that constant to the
event definitions), or alternatively remove the event constant and log a plain
debug message indicating proposer initialization.
🪄 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: 685f148c-b2a1-4fc3-abec-85840cfe4238
📒 Files selected for processing (9)
docs/design/memory.mdsrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/config.pysrc/synthorg/memory/procedural/models.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/proposer.pytests/unit/engine/test_agent_engine_procedural.pytests/unit/memory/procedural/test_models.pytests/unit/memory/procedural/test_pipeline.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). (7)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
Line length: 88 characters, enforced by ruff
Global test timeout: 30 seconds per test (configured in pyproject.toml). Non-default overrides like timeout(60) are allowed on specific tests
Files:
src/synthorg/memory/config.pytests/unit/engine/test_agent_engine_procedural.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/engine/agent_engine.pytests/unit/memory/procedural/test_models.pysrc/synthorg/memory/procedural/pipeline.pytests/unit/memory/procedural/test_pipeline.pysrc/synthorg/memory/procedural/models.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: All public functions and classes must have type hints and Google-style docstrings, enforced by ruff D rules and mypy strict mode
Every module with business logic MUST have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code for logging -- use synthorg.observability.get_logger() instead
Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls
Functions must be < 50 lines, files < 800 lines
Immutability: create new objects, never mutate existing ones. Usecopy.deepcopy()at construction andMappingProxyTypewrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (withfrozen=Truein ConfigDict) for config/identity; use separate mutable-via-copy models (viamodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 with conventions:allow_inf_nan=Falsein all ConfigDict declarations to reject NaN/Inf; use@computed_fieldfor derived values instead of storing redundant fields; useNotBlankStr(from core.types) for all identifier/name fields including optional and tuple variants
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over barecreate_task
Handle errors explicitly, never silently swallow exceptions. All error paths must log at WARNING or ERROR with context before raising
Validate at system boundaries (user input, external APIs, config files)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, or comments. Vendor names m...
Files:
src/synthorg/memory/config.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/models.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/memory/config.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/pipeline.pysrc/synthorg/memory/procedural/models.py
docs/design/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS read the relevant docs/design/ page before implementing any feature. Design spec is the starting point for architecture, data models, and behavior. If implementation deviates from spec, alert the user and explain why -- user decides whether to proceed or update the spec. When approved deviations occur, update the relevant docs/design/ page
Files:
docs/design/memory.md
**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Documentation in docs/ is built with Zensical, configured via mkdocs.yml. Design spec docs are in docs/design/ (12 pages), architecture in docs/architecture/, roadmap in docs/roadmap/. REST API reference auto-generated via scripts/export_openapi.py
Files:
docs/design/memory.md
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use pytest markers:@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowfor test categorization
Maintain 80% minimum code coverage, enforced in CI with--cov-fail-under=80
Always run tests with-n 8for parallelism via pytest-xdist; never run tests sequentially locally. CI uses-n auto
Use@pytest.mark.parametrizefor testing similar cases instead of repeating test code
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in tests. Use generic names: test-provider, test-small-001, test-medium-001, test-large-001, or small/medium/large aliases
Use Hypothesis with@given+@settingsfor property-based testing. Never skip, dismiss, or ignore flaky tests -- fix them fundamentally. Use mocking for time-sensitive tests to make them deterministic instead of widening timing margins
Files:
tests/unit/engine/test_agent_engine_procedural.pytests/unit/memory/procedural/test_models.pytests/unit/memory/procedural/test_pipeline.py
⚙️ CodeRabbit configuration file
Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare
@settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which@given() honors automatically.
Files:
tests/unit/engine/test_agent_engine_procedural.pytests/unit/memory/procedural/test_models.pytests/unit/memory/procedural/test_pipeline.py
🧠 Learnings (53)
📚 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:
src/synthorg/memory/config.pydocs/design/memory.mdtests/unit/engine/test_agent_engine_procedural.pysrc/synthorg/memory/procedural/proposer.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/pipeline.pytests/unit/memory/procedural/test_pipeline.pysrc/synthorg/memory/procedural/models.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/config.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/models.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/memory/config.pysrc/synthorg/memory/procedural/models.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models (with `frozen=True` in ConfigDict) for config/identity; use separate mutable-via-copy models (via `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/config.pysrc/synthorg/memory/procedural/models.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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...
Applied to files:
docs/design/memory.mdtests/unit/engine/test_agent_engine_procedural.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/pipeline.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:
docs/design/memory.mdsrc/synthorg/engine/agent_engine.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/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)
Applied to files:
tests/unit/engine/test_agent_engine_procedural.pysrc/synthorg/engine/agent_engine.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:
src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls
Applied to files:
src/synthorg/engine/agent_engine.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:
src/synthorg/engine/agent_engine.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:
src/synthorg/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-03-31T16:09:24.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T16:09:24.320Z
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/engine/agent_engine.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/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.
Applied to files:
src/synthorg/engine/agent_engine.pysrc/synthorg/memory/procedural/pipeline.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/engine/agent_engine.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
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 the domain module
Applied to files:
src/synthorg/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Functions must be < 50 lines, files < 800 lines
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-04-01T06:12:03.047Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T06:12:03.047Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Keep functions under 50 lines and files under 800 lines
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-28T09:24:37.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T09:24:37.044Z
Learning: Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Functions must be less than 50 lines; files must be less than 800 lines
Applied to files:
src/synthorg/memory/procedural/pipeline.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 src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow exceptions. All error paths must log at WARNING or ERROR with context before raising
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-04-02T21:38:30.127Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T21:38:30.127Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/memory/procedural/pipeline.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to tests/**/*.py : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in tests. Use generic names: test-provider, test-small-001, test-medium-001, test-large-001, or small/medium/large aliases
Applied to files:
tests/unit/memory/procedural/test_pipeline.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to {src,tests}/**/*.py : Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: `example-provider`, `example-large-001`, `example-medium-001`, `example-small-001`, `large`/`medium`/`small` as aliases
Applied to files:
tests/unit/memory/procedural/test_pipeline.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to {src/synthorg/**/*.py,tests/**/*.py,web/src/**/*.{ts,tsx}} : NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples — use generic names: `example-provider`, `example-large-001`, `example-medium-001`, `example-small-001`, `large`/`medium`/`small`
Applied to files:
tests/unit/memory/procedural/test_pipeline.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Tests must use `test-provider`, `test-small-001`, etc. instead of real vendor names
Applied to files:
tests/unit/memory/procedural/test_pipeline.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,tests,web,cli,site}/**/*.{py,ts,tsx,go,astro} : Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001. Vendor names may only appear in: (1) Operations design page provider list (docs/design/operations.md), (2) .claude/ skill/agent files, (3) third-party import paths/module names.
Applied to files:
tests/unit/memory/procedural/test_pipeline.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 : Tests must use test-provider, test-small-001, etc. for vendor-agnostic test data.
Applied to files:
tests/unit/memory/procedural/test_pipeline.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 web/**/*.vue : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in dashboard code — use generic names: `example-provider`, `example-large-001`, etc.
Applied to files:
tests/unit/memory/procedural/test_pipeline.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/procedural/models.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Immutability: create new objects, never mutate existing ones. Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Applied to files:
src/synthorg/memory/procedural/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves
Applied to files:
src/synthorg/memory/procedural/models.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/memory/procedural/models.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/memory/procedural/models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/memory/procedural/models.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 src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/memory/procedural/models.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.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/memory/procedural/models.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.
Applied to files:
src/synthorg/memory/procedural/models.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 **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/memory/procedural/models.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : 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
Applied to files:
src/synthorg/memory/procedural/models.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 `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
src/synthorg/memory/procedural/models.py
🔇 Additional comments (31)
src/synthorg/memory/config.py (1)
247-254: LGTM!The
proceduralfield follows the established pattern for nested config models: frozen parent,default_factoryfor the nested model, and a descriptive docstring attribute. The past review comment about the stale docstring has been addressed (line 209 now includes theproceduralattribute).docs/design/memory.md (1)
619-669: LGTM!The documentation accurately describes the procedural memory auto-generation feature:
- Pipeline steps align with
pipeline.pyimplementation- Three-tier progressive disclosure matches
_format_procedural_content- Configuration fields match
ProceduralMemoryConfigmodel- Integration point correctly describes
_try_procedural_memoryas non-blocking with WARNING-level loggingsrc/synthorg/engine/agent_engine.py (4)
325-336: LGTM!The proposer construction logic correctly guards against all prerequisite conditions:
config is not None,config.enabled, andmemory_backend is not None. This ensures the proposer is only created when the feature is fully configured.
695-730: LGTM!The
_try_procedural_memorymethod correctly implements non-blocking, non-fatal behavior:
- Early returns when proposer or recovery_result is missing
- Re-raises
MemoryError/RecursionErrorper project convention- Logs other exceptions at WARNING with
exc_info=Truewithout blocking the result
687-692: Correct use offailed_resultto preserve original failure context.Passing
failed_result or execution_resultensures the procedural memory pipeline receives the original failure state before recovery potentially modifies the execution result. This is important for accurate failure analysis.
904-958: LGTM!The
_apply_recoverysignature change to returntuple[ExecutionResult, RecoveryResult | None]is clean and maintains backward compatibility at all call sites. The recovery_result is correctly propagated for procedural memory analysis while remainingNonewhen no recovery strategy is configured or recovery fails.tests/unit/engine/test_agent_engine_procedural.py (1)
1-286: LGTM!Comprehensive test coverage for procedural memory integration:
- Verifies storage triggers only on ERROR termination
- Verifies non-blocking behavior when proposer fails
- Verifies all skip conditions (no config, no backend, disabled)
- Correctly uses test-compliant provider/model names
- Direct
_post_execution_pipelinetesting provides good isolationtests/unit/memory/procedural/test_models.py (1)
1-293: LGTM!Thorough model validation tests covering:
- Frozen immutability enforcement
NotBlankStrvalidation (empty/whitespace rejection)- Numeric bounds and NaN rejection
- Relational constraints (
retry_count <= max_retries)- Tag deduplication preserving order and applying before cap
max_lengthenforcement for fields and collectionssrc/synthorg/memory/procedural/proposer.py (2)
168-189: LGTM!Exception handling follows the correct pattern:
MemoryError/RecursionErrorre-raised unconditionally- Non-retryable
ProviderErrorpropagates to caller- Retryable
ProviderErrorand unexpected exceptions logged at WARNING withexc_info=Trueand returnNone
193-243: LGTM!The
_parse_responsemethod correctly implements graceful degradation:
- Empty/whitespace responses logged and skipped
- JSON parse failures logged at WARNING
- Schema validation failures logged with
exc_info- Low-confidence proposals logged at INFO (not WARNING, since this is expected behavior)
- Successful proposals logged with confidence and tags
src/synthorg/memory/procedural/pipeline.py (4)
137-139: LGTM!The
task_idsanitization using_slugify(task_id) or "task"addresses the path traversal concern from the past review. This ensures no path separators or absolute path content can escape the intended directory.
150-155: LGTM!Using
yaml.safe_dumpwithallow_unicode=Trueandsort_keys=Falsecorrectly addresses the YAML escaping concern from the past review. This ensures multiline/special characters in LLM-generated content won't break the frontmatter.
276-351: LGTM!The
propose_procedural_memoryfunction is well-structured after refactoring:
- Orchestrates
_try_build_payloadand_store_and_materializehelpers- Guards
proposer.proposewith try/except (addressing past review)- All non-system exceptions are logged and return
None- Maintains the non-blocking contract documented in the docstring
187-188: Exception handling follows project convention.The
except MemoryError, RecursionError: raisepattern correctly uses built-inMemoryErrorfor system-error propagation per the coding guidelines. Domain memory errors (MemoryStoreError,MemoryConnectionError) fall through to the genericExceptionhandler and are logged as non-fatal, which aligns with the non-blocking design requirement.Also applies to: 229-230, 262-263, 323-324
src/synthorg/memory/procedural/models.py (4)
140-148: LGTM!The
@field_validator("tags", mode="before")correctly implements order-preserving deduplication before the model is frozen, addressing the past review comment. The 20-tag cap is applied after deduplication, ensuring the limit applies to unique tags.
198-204: LGTM!Using
NotBlankStr | Nonecorrectly rejects empty/whitespace strings while allowingNone, addressing the past review about blankskill_md_directoryvalues.
16-81: LGTM!
FailureAnalysisPayloadis well-designed:
- Frozen and disallows NaN/Inf
- Uses
NotBlankStrfor all identifier fields- Cross-field validation enforces
retry_count <= max_retries- Comprehensive docstring documents all attributes including defaults
151-204: LGTM!
ProceduralMemoryConfigcorrectly:
- Uses frozen model with
allow_inf_nan=False- Bounds
temperatureto[0.0, 2.0]matching provider limits- Bounds
min_confidenceto[0.0, 1.0]matching proposal confidence range- Uses
NotBlankStr | Nonefor optionalskill_md_directorytests/unit/memory/procedural/test_pipeline.py (13)
1-40: LGTM!Imports are well-organized and appropriate for the test module. No
from __future__ import annotationsas required by the coding guidelines.
121-134: LGTM!The
_make_identityhelper correctly uses approved generic test identifiers (test-provider,test-small-001) per the coding guidelines. Based on learnings: "Tests must usetest-provider,test-small-001, etc."
152-209: LGTM!The
TestBuildPayloadclass provides good coverage of the_build_payloadfunction, testing task field extraction, error/strategy handling, tool call flattening, and edge cases like empty turns.
214-230: LGTM!Tests appropriately verify the progressive disclosure structure (
[DISCOVERY],[CONDITION],[ACTION],[RATIONALE]) and content inclusion.
235-408: LGTM!Comprehensive test coverage for
propose_procedural_memoryincluding happy path, skip scenarios, error handling, metadata verification, and exception propagation. The use ofstructlog.testing.capture_logs()for event verification is appropriate.
413-430: LGTM!Tests correctly verify the conditional
[EXECUTION]section behavior - present with numbered steps when provided, omitted when empty.
435-470: LGTM!Tests properly use the
tmp_pathfixture for filesystem operations and verify SKILL.md file generation including YAML frontmatter, markdown content sections, and directory creation.
475-540: LGTM!Tests verify the optional SKILL.md materialization when
skill_md_directoryis configured, and correctly test resilience by ensuring memory storage succeeds even when filesystem writes fail.
545-588: LGTM!Tests correctly verify exception handling semantics -
RuntimeErroris caught gracefully whileMemoryErrorpropagates, aligning with the PR objective that procedural-path errors should not block execution results except for critical system errors.
593-614: LGTM!Test correctly verifies that
RecursionErrorpropagates, ensuring critical system errors are not swallowed by the pipeline's general exception handling.
619-644: LGTM!Excellent use of
@pytest.mark.parametrizefor testing_slugifyedge cases as recommended by the coding guidelines. The test ids make failures easy to diagnose.
649-673: LGTM!Test correctly verifies that
_try_build_payloadlogs thePROCEDURAL_MEMORY_ERRORevent when payload construction fails.
678-691: LGTM!Test correctly verifies that sensitive file paths in error messages are redacted to
[REDACTED_PATH], ensuring the sanitization feature works as designed.
…tor log The constructor was reusing PROCEDURAL_MEMORY_PROPOSED which collides with the actual proposal-generated event, making observability consumers unable to distinguish 'proposer created' from 'proposal completed'.
🤖 I have created a release *beep* *boop* --- ## [0.6.1](v0.6.0...v0.6.1) (2026-04-04) ### Features * capability-aware prompt profiles for model tier adaptation ([#1047](#1047)) ([67650c5](67650c5)), closes [#805](#805) * implement procedural memory auto-generation from agent failures ([#1048](#1048)) ([55f5206](55f5206)), closes [#420](#420) * implement quality scoring Layers 2+3 -- LLM judge and human override ([#1057](#1057)) ([4a8adfe](4a8adfe)), closes [#230](#230) * token-based personality trimming via PromptProfile.max_personality_tokens ([#1059](#1059)) ([75afd52](75afd52)), closes [#1045](#1045) * workflow execution lifecycle + editor improvements ([#1058](#1058)) ([7b54262](7b54262)), closes [#1029](#1029) [#1042](#1042) ### Refactoring * **web:** address complexity and logging issues in dashboard ([#1056](#1056)) ([ada997b](ada997b)), closes [#1055](#1055) ### Documentation * comprehensive documentation refresh ([#1050](#1050)) ([c7a4259](c7a4259)) ### Tests * fix Hypothesis fuzzing infra and speed up slow unit tests ([#1044](#1044)) ([1111602](1111602)) ### Maintenance * add text=auto catch-all to .gitattributes ([#1051](#1051)) ([fc65d72](fc65d72)) * bump defu from 6.1.4 to 6.1.6 in /site ([#1062](#1062)) ([f0cc439](f0cc439)) --- 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>
Summary
Implements procedural memory auto-generation when agents fail tasks (#420). A separate proposer LLM call (not the failed agent) analyses structured failure data and produces reusable procedural memory entries with three-tier progressive disclosure and optional SKILL.md file materialization.
What changed
New module:
src/synthorg/memory/procedural/models.py--FailureAnalysisPayload,ProceduralMemoryProposal(three-tier: discovery/activation/execution),ProceduralMemoryConfigproposer.py--ProceduralMemoryProposerLLM-based failure analysis with structural delimiters, sanitized inputspipeline.py--propose_procedural_memoryend-to-end pipeline,materialize_skill_mdfor Agent Skills format outputobservability/events/procedural_memory.pyEngine integration:
AgentEngine._try_procedural_memory()-- extracted helper runs after error recovery in post-execution pipeline_apply_recovery()returns(ExecutionResult, RecoveryResult | None)tuple to feed recovery context to the proposer__init__(not per-failure)SKILL.md materialization:
ProceduralMemoryConfig.skill_md_directoryis set, proposals are written as portable SKILL.md filesPre-PR review findings addressed (14 agents, 29 findings)
sanitize_message)_build_payloadin try-except (never-raises contract)except Exceptiontoexcept ValidationErrorin proposerexc_info=Trueto all warning-level catch blocksPAYLOAD_BUILT,DISABLED)_build_user_message(prompt injection defense)max_lengthconstraints on proposal string fieldsretry_count <= max_retriescross-field validatorTestProposeProcedualMemorytypoTest plan
Documentation updated
Closes #420