Skip to content

feat: implement procedural memory auto-generation from agent failures#1048

Merged
Aureliolo merged 5 commits intomainfrom
feat/procedural-memory-auto-gen
Apr 3, 2026
Merged

feat: implement procedural memory auto-generation from agent failures#1048
Aureliolo merged 5 commits intomainfrom
feat/procedural-memory-auto-gen

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

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), ProceduralMemoryConfig
  • proposer.py -- ProceduralMemoryProposer LLM-based failure analysis with structural delimiters, sanitized inputs
  • pipeline.py -- propose_procedural_memory end-to-end pipeline, materialize_skill_md for Agent Skills format output
  • Event constants in observability/events/procedural_memory.py

Engine 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
  • Proposer built once in __init__ (not per-failure)
  • Non-critical: failures logged at WARNING, never block execution result

SKILL.md materialization:

  • When ProceduralMemoryConfig.skill_md_directory is set, proposals are written as portable SKILL.md files
  • Follows Agent Skills format with YAML frontmatter
  • Git-native versioning via filesystem

Pre-PR review findings addressed (14 agents, 29 findings)

  • Sanitized error messages before proposer LLM (sanitize_message)
  • Wrapped _build_payload in try-except (never-raises contract)
  • Narrowed bare except Exception to except 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 (prompt injection defense)
  • Added max_length constraints on proposal string fields
  • Added retry_count <= max_retries cross-field validator
  • Fixed TestProposeProcedualMemory typo

Test plan

  • 26 new tests covering models, proposer, pipeline, engine integration, SKILL.md materialization
  • Tests for: non-dict JSON, validation failure, MemoryError propagation, bare markdown fences, empty tools, CompanyMemoryConfig.procedural
  • Full suite: 13426 passed, 9 skipped (platform/docker)
  • mypy strict: clean
  • ruff: clean

Documentation updated

  • CLAUDE.md: Package Structure (memory/, engine/), Logging section (procedural_memory events)
  • docs/design/memory.md: New "Procedural Memory Auto-Generation" section
  • docs/design/engine.md: Step 12 (procedural memory generation) in pipeline
  • README.md: Memory capabilities

Closes #420

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
Copilot AI review requested due to automatic review settings April 3, 2026 19:39
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 20b2d86e-84c7-4e98-8757-22a866c4514f

📥 Commits

Reviewing files that changed from the base of the PR and between 34d1c1d and 6101455.

📒 Files selected for processing (2)
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/observability/events/procedural_memory.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except 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/procedural/proposer.py
  • src/synthorg/observability/events/procedural_memory.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_logger then logger = get_logger(__name__)
Never use import 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. 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)
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
Use Pydantic v2 with conventions: allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields including optional and tuple variants
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over bare create_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/procedural/proposer.py
  • src/synthorg/observability/events/procedural_memory.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/procedural/proposer.py
  • src/synthorg/observability/events/procedural_memory.py
🧠 Learnings (12)
📓 Common learnings
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)
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
📚 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/procedural/proposer.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/observability/events/procedural_memory.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/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:

  • src/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:

  • src/synthorg/observability/events/procedural_memory.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/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:

  • src/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:

  • src/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-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/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:

  • src/synthorg/observability/events/procedural_memory.py
🔇 Additional comments (8)
src/synthorg/memory/procedural/proposer.py (7)

1-32: LGTM!

Module setup correctly uses get_logger(__name__) and imports event constants from the domain-specific module. No from __future__ import annotations as required for Python 3.14.


34-48: LGTM!

The system prompt clearly specifies the expected JSON structure matching the ProceduralMemoryProposal model fields. Good defensive approach to handle markdown fences anyway despite instructing the LLM not to use them.


56-82: LGTM!

The JSON extraction handles both plain and markdown-fenced responses defensively. The two-level logging (DEBUG for parse details here, WARNING with task context in _parse_response) provides appropriate observability at different log levels.


85-105: LGTM!

Good use of structural delimiters [BEGIN/END FAILURE CONTEXT] to prevent prompt confusion when task descriptions contain prompt-like text. All payload attributes accessed are valid per the FailureAnalysisPayload model.


120-138: LGTM!

Constructor correctly uses the dedicated PROCEDURAL_MEMORY_PROPOSER_INIT event constant for initialization logging, addressing the previous review feedback about event collision.


156-192: LGTM!

Exception handling follows the project convention correctly:

  • MemoryError, RecursionError propagate (PEP 758 syntax)
  • Non-retryable ProviderError propagates
  • Retryable provider errors and unexpected exceptions log at WARNING with exc_info=True and return None

194-244: LGTM!

The response parsing is well-structured with comprehensive validation:

  • Empty response check with DEBUG logging
  • JSON structure validation with WARNING on failure
  • Pydantic model validation with exc_info=True for debugging
  • Confidence threshold gating with INFO logging
  • Success logging with proposal metadata
src/synthorg/observability/events/procedural_memory.py (1)

1-42: LGTM!

Well-organized event constants module with:

  • Comprehensive coverage of the procedural memory pipeline lifecycle
  • Consistent hierarchical naming convention (procedural_memory.{subsystem}.{action})
  • Proper Final[str] typing and descriptive docstrings for each constant
  • Distinct PROCEDURAL_MEMORY_PROPOSER_INIT constant per the commit message addressing event distinguishability

Walkthrough

Adds 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 MemoryCategory.PROCEDURAL with a non-inferable tag and may be materialized as SKILL.md files. Changes include new Pydantic models and config (ProceduralMemoryConfig), a proposer, pipeline (materialization + storage), observability event constants, re-exports under synthorg.memory, and integration into AgentEngine post-execution; procedural-path errors are logged and do not alter the agent result.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.26% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: implement procedural memory auto-generation from agent failures' directly and clearly summarizes the main change—adding automatic procedural memory generation when agents fail.
Description check ✅ Passed The description comprehensively explains the feature implementation, including new modules, engine integration, SKILL.md materialization, pre-PR review fixes, tests, and documentation updates—all directly related to the changeset.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #420: failure analysis payload emission, separate proposer LLM analysis, three-tier progressive disclosure, SKILL.md materialization, retrieval integration, and separation of concerns.
Out of Scope Changes check ✅ Passed All changes are within scope: new procedural memory module, engine integration for post-failure proposer invocation, configuration additions, observability events, and comprehensive test coverage—all directly support issue #420 requirements.

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


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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Dependency Review

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

Snapshot Warnings

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

Scanned Files

None

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces 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.

Comment on lines +609 to 610
recovery_result: RecoveryResult | None = None
if execution_result.termination_reason == TerminationReason.ERROR:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

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

exc_info=True,
)
await self._try_procedural_memory(
execution_result,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Pass the captured failed_result (if available) to the procedural memory pipeline to ensure the failure analysis is based on the turns that actually led to the error, rather than potentially successful turns from a resumed execution.

            failed_result or execution_result,

EXECUTION_RESUME_START,
)
from synthorg.observability.events.procedural_memory import (
PROCEDURAL_MEMORY_DISABLED,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Import PROCEDURAL_MEMORY_ERROR to use it for domain-specific error logging in _try_procedural_memory.

    PROCEDURAL_MEMORY_DISABLED,
    PROCEDURAL_MEMORY_ERROR,

except Exception as exc:
logger.warning(
EXECUTION_ENGINE_ERROR,
agent_id=agent_id,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use the domain-specific PROCEDURAL_MEMORY_ERROR event constant instead of the generic EXECUTION_ENGINE_ERROR to maintain consistency with the observability architecture for procedural memory events.

                PROCEDURAL_MEMORY_ERROR,

filename = f"SKILL-{task_id}-{slug}.md"
path = Path(directory) / filename

tags_str = ", ".join(proposal.tags) if proposal.tags else ""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Quote individual tags to ensure the generated YAML frontmatter remains valid if a tag contains special characters like colons or commas.

    tags_str = ", ".join(f'"{t}"' for t in proposal.tags) if proposal.tags else ""

Comment on lines +147 to +148
f"description: {proposal.discovery}\n"
f"trigger: {proposal.condition}\n"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The 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"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 AgentEngine post-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.

Comment on lines +132 to +158
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}"
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +211
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
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +280
# 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,
)

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +325 to +332
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,
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +39
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."""
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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."""

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 95.95588% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.71%. Comparing base (1111602) to head (6101455).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/engine/agent_engine.py 77.41% 7 Missing ⚠️
src/synthorg/memory/procedural/models.py 95.83% 1 Missing and 1 partial ⚠️
src/synthorg/memory/procedural/pipeline.py 97.84% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1111602 and 11b327c.

📒 Files selected for processing (20)
  • CLAUDE.md
  • README.md
  • docs/design/engine.md
  • docs/design/memory.md
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/memory/__init__.py
  • src/synthorg/memory/config.py
  • src/synthorg/memory/procedural/__init__.py
  • src/synthorg/memory/procedural/models.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/observability/events/procedural_memory.py
  • tests/unit/engine/test_agent_engine_procedural.py
  • tests/unit/memory/procedural/__init__.py
  • tests/unit/memory/procedural/test_models.py
  • tests/unit/memory/procedural/test_pipeline.py
  • tests/unit/memory/procedural/test_proposer.py
  • tests/unit/memory/test_config.py
  • tests/unit/memory/test_init.py
  • tests/unit/observability/test_events.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (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.md
  • CLAUDE.md
  • docs/design/engine.md
  • docs/design/memory.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except 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.py
  • tests/unit/memory/test_config.py
  • tests/unit/memory/test_init.py
  • src/synthorg/memory/config.py
  • tests/unit/memory/procedural/test_models.py
  • src/synthorg/memory/procedural/proposer.py
  • tests/unit/memory/procedural/test_proposer.py
  • src/synthorg/memory/__init__.py
  • tests/unit/memory/procedural/test_pipeline.py
  • tests/unit/engine/test_agent_engine_procedural.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/procedural_memory.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/synthorg/memory/procedural/__init__.py
  • src/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.slow for test categorization
Maintain 80% minimum code coverage, enforced in CI with --cov-fail-under=80
Always run tests with -n 8 for parallelism via pytest-xdist; never run tests sequentially locally. CI uses -n auto
Use @pytest.mark.parametrize for 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 + @settings for 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.py
  • tests/unit/memory/test_config.py
  • tests/unit/memory/test_init.py
  • tests/unit/memory/procedural/test_models.py
  • tests/unit/memory/procedural/test_proposer.py
  • tests/unit/memory/procedural/test_pipeline.py
  • tests/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.py
  • tests/unit/memory/test_config.py
  • tests/unit/memory/test_init.py
  • tests/unit/memory/procedural/test_models.py
  • tests/unit/memory/procedural/test_proposer.py
  • tests/unit/memory/procedural/test_pipeline.py
  • tests/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_logger then logger = get_logger(__name__)
Never use import 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. 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)
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
Use Pydantic v2 with conventions: allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields including optional and tuple variants
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over bare create_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.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/memory/__init__.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/procedural_memory.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/synthorg/memory/procedural/__init__.py
  • src/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.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/memory/__init__.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/procedural_memory.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/synthorg/memory/procedural/__init__.py
  • src/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.md
  • docs/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.md
  • tests/unit/memory/test_init.py
  • src/synthorg/memory/config.py
  • CLAUDE.md
  • docs/design/memory.md
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/memory/__init__.py
  • tests/unit/memory/procedural/test_pipeline.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/procedural_memory.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/synthorg/memory/procedural/__init__.py
  • 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/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • README.md
  • src/synthorg/memory/config.py
  • docs/design/memory.md
  • src/synthorg/memory/__init__.py
  • src/synthorg/engine/agent_engine.py
  • 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: 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.md
  • CLAUDE.md
  • docs/design/engine.md
  • docs/design/memory.md
  • tests/unit/engine/test_agent_engine_procedural.py
  • 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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • README.md
  • 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: 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.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • src/synthorg/engine/agent_engine.py
  • 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 : 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.py
  • 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:

  • tests/unit/observability/test_events.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • src/synthorg/engine/agent_engine.py
  • 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 : 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.py
  • tests/unit/memory/procedural/test_models.py
  • 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 : 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.py
  • src/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.py
  • src/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.md
  • src/synthorg/memory/__init__.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/memory/procedural/__init__.py
  • src/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.md
  • docs/design/engine.md
  • src/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.py
  • src/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_memory keeps 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 CompanyMemoryConfig without 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 (discovery max 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 ProceduralMemoryProposer once 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_memory method correctly:

  • Short-circuits when proposer is not configured (line 701-702)
  • Logs PROCEDURAL_MEMORY_DISABLED when config exists but is disabled (lines 703-712)
  • Requires both memory_backend and recovery_result to proceed (line 713-714)
  • Swallows non-fatal exceptions with exc_info=True for debugging (lines 727-734)
  • Propagates MemoryError and RecursionError as required (lines 725-726)

908-962: Return type change for _apply_recovery is correctly propagated.

The change from ExecutionResult to tuple[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 in propose() follows project conventions.

The exception handling correctly:

  • Propagates system-level errors (MemoryError, RecursionError) per project convention
  • Distinguishes retryable vs non-retryable ProviderError using is_retryable flag
  • Swallows unexpected exceptions with warning-level logging and exc_info=True

The bare MemoryError reference is the built-in Python MemoryError (OOM conditions), not the domain-specific synthorg.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_response method correctly handles:

  • Empty/whitespace responses (debug log, return None)
  • Malformed JSON (warning log, return None)
  • Schema validation failures via Pydantic (warning with exc_info, return None)
  • 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 exactly min_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, and memory_backend
  • Calls _post_execution_pipeline directly with an ERROR termination result
  • Asserts that memory_backend.store was called

Note: The test relies on the FailAndReassignStrategy returning a RecoveryResult that 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.

Comment on lines +201 to +203
except MemoryError, RecursionError:
raise
except Exception as exc:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.py

Repository: 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 -n

Repository: 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.py

Repository: 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 -20

Repository: 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 -50

Repository: 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 -15

Repository: 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.py

Repository: 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.py

Repository: 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")
EOF

Repository: 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)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@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

📥 Commits

Reviewing files that changed from the base of the PR and between 11b327c and 34d1c1d.

📒 Files selected for processing (9)
  • docs/design/memory.md
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/memory/config.py
  • src/synthorg/memory/procedural/models.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/synthorg/memory/procedural/proposer.py
  • tests/unit/engine/test_agent_engine_procedural.py
  • tests/unit/memory/procedural/test_models.py
  • tests/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: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except 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.py
  • tests/unit/engine/test_agent_engine_procedural.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/memory/procedural/test_models.py
  • src/synthorg/memory/procedural/pipeline.py
  • tests/unit/memory/procedural/test_pipeline.py
  • src/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_logger then logger = get_logger(__name__)
Never use import 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. 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)
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
Use Pydantic v2 with conventions: allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields including optional and tuple variants
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over bare create_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.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/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.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/memory/procedural/pipeline.py
  • src/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.slow for test categorization
Maintain 80% minimum code coverage, enforced in CI with --cov-fail-under=80
Always run tests with -n 8 for parallelism via pytest-xdist; never run tests sequentially locally. CI uses -n auto
Use @pytest.mark.parametrize for 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 + @settings for 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.py
  • tests/unit/memory/procedural/test_models.py
  • tests/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.py
  • tests/unit/memory/procedural/test_models.py
  • tests/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.py
  • docs/design/memory.md
  • tests/unit/engine/test_agent_engine_procedural.py
  • src/synthorg/memory/procedural/proposer.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/memory/procedural/pipeline.py
  • tests/unit/memory/procedural/test_pipeline.py
  • 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 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.py
  • src/synthorg/engine/agent_engine.py
  • src/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.py
  • 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 : 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.py
  • 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: 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.md
  • tests/unit/engine/test_agent_engine_procedural.py
  • src/synthorg/engine/agent_engine.py
  • src/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.md
  • 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/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.py
  • src/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.py
  • src/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 procedural field follows the established pattern for nested config models: frozen parent, default_factory for the nested model, and a descriptive docstring attribute. The past review comment about the stale docstring has been addressed (line 209 now includes the procedural attribute).

docs/design/memory.md (1)

619-669: LGTM!

The documentation accurately describes the procedural memory auto-generation feature:

  • Pipeline steps align with pipeline.py implementation
  • Three-tier progressive disclosure matches _format_procedural_content
  • Configuration fields match ProceduralMemoryConfig model
  • Integration point correctly describes _try_procedural_memory as non-blocking with WARNING-level logging
src/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, and memory_backend is not None. This ensures the proposer is only created when the feature is fully configured.


695-730: LGTM!

The _try_procedural_memory method correctly implements non-blocking, non-fatal behavior:

  • Early returns when proposer or recovery_result is missing
  • Re-raises MemoryError/RecursionError per project convention
  • Logs other exceptions at WARNING with exc_info=True without blocking the result

687-692: Correct use of failed_result to preserve original failure context.

Passing failed_result or execution_result ensures 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_recovery signature change to return tuple[ExecutionResult, RecoveryResult | None] is clean and maintains backward compatibility at all call sites. The recovery_result is correctly propagated for procedural memory analysis while remaining None when 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_pipeline testing provides good isolation
tests/unit/memory/procedural/test_models.py (1)

1-293: LGTM!

Thorough model validation tests covering:

  • Frozen immutability enforcement
  • NotBlankStr validation (empty/whitespace rejection)
  • Numeric bounds and NaN rejection
  • Relational constraints (retry_count <= max_retries)
  • Tag deduplication preserving order and applying before cap
  • max_length enforcement for fields and collections
src/synthorg/memory/procedural/proposer.py (2)

168-189: LGTM!

Exception handling follows the correct pattern:

  • MemoryError/RecursionError re-raised unconditionally
  • Non-retryable ProviderError propagates to caller
  • Retryable ProviderError and unexpected exceptions logged at WARNING with exc_info=True and return None

193-243: LGTM!

The _parse_response method 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_id sanitization 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_dump with allow_unicode=True and sort_keys=False correctly 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_memory function is well-structured after refactoring:

  • Orchestrates _try_build_payload and _store_and_materialize helpers
  • Guards proposer.propose with 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: raise pattern correctly uses built-in MemoryError for system-error propagation per the coding guidelines. Domain memory errors (MemoryStoreError, MemoryConnectionError) fall through to the generic Exception handler 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 | None correctly rejects empty/whitespace strings while allowing None, addressing the past review about blank skill_md_directory values.


16-81: LGTM!

FailureAnalysisPayload is well-designed:

  • Frozen and disallows NaN/Inf
  • Uses NotBlankStr for all identifier fields
  • Cross-field validation enforces retry_count <= max_retries
  • Comprehensive docstring documents all attributes including defaults

151-204: LGTM!

ProceduralMemoryConfig correctly:

  • Uses frozen model with allow_inf_nan=False
  • Bounds temperature to [0.0, 2.0] matching provider limits
  • Bounds min_confidence to [0.0, 1.0] matching proposal confidence range
  • Uses NotBlankStr | None for optional skill_md_directory
tests/unit/memory/procedural/test_pipeline.py (13)

1-40: LGTM!

Imports are well-organized and appropriate for the test module. No from __future__ import annotations as required by the coding guidelines.


121-134: LGTM!

The _make_identity helper correctly uses approved generic test identifiers (test-provider, test-small-001) per the coding guidelines. Based on learnings: "Tests must use test-provider, test-small-001, etc."


152-209: LGTM!

The TestBuildPayload class provides good coverage of the _build_payload function, 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_memory including happy path, skip scenarios, error handling, metadata verification, and exception propagation. The use of structlog.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_path fixture 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_directory is configured, and correctly test resilience by ensuring memory storage succeeds even when filesystem writes fail.


545-588: LGTM!

Tests correctly verify exception handling semantics - RuntimeError is caught gracefully while MemoryError propagates, 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 RecursionError propagates, ensuring critical system errors are not swallowed by the pipeline's general exception handling.


619-644: LGTM!

Excellent use of @pytest.mark.parametrize for testing _slugify edge cases as recommended by the coding guidelines. The test ids make failures easy to diagnose.


649-673: LGTM!

Test correctly verifies that _try_build_payload logs the PROCEDURAL_MEMORY_ERROR event 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'.
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 3, 2026 20:18 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 55f5206 into main Apr 3, 2026
32 checks passed
@Aureliolo Aureliolo deleted the feat/procedural-memory-auto-gen branch April 3, 2026 20:22
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 3, 2026 20:23 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 4, 2026
🤖 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: implement procedural memory auto-generation from agent failures

2 participants