feat: v0.7.0 engine foundations -- structured failure diagnosis + auditable decisions#1072
feat: v0.7.0 engine foundations -- structured failure diagnosis + auditable decisions#1072
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
🧰 Additional context used📓 Path-based instructions (3)**/*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
src/synthorg/**/*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
src/**/*.py⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (29)📚 Learning: 2026-03-17T06:30:14.180ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-03-31T21:07:37.470ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-03-16T07:22:28.134ZApplied to files:
📚 Learning: 2026-03-14T15:43:05.601ZApplied to files:
📚 Learning: 2026-03-14T16:18:57.267ZApplied to files:
📚 Learning: 2026-04-05T13:59:09.628ZApplied to files:
📚 Learning: 2026-03-15T16:55:07.730ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-03-15T21:49:53.264ZApplied to files:
📚 Learning: 2026-03-14T16:18:57.267ZApplied to files:
📚 Learning: 2026-03-15T16:55:07.730ZApplied to files:
📚 Learning: 2026-03-15T18:38:44.202ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-04-01T09:09:43.948ZApplied to files:
📚 Learning: 2026-04-01T09:58:27.410ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-04-05T13:59:09.628ZApplied to files:
📚 Learning: 2026-03-15T19:14:27.144ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-03-16T23:05:29.577ZApplied to files:
📚 Learning: 2026-03-15T19:14:27.144ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-03-17T11:41:02.964ZApplied to files:
📚 Learning: 2026-03-26T15:18:16.848ZApplied to files:
📚 Learning: 2026-03-15T19:14:27.144ZApplied to files:
📚 Learning: 2026-03-15T18:42:17.990ZApplied to files:
🔇 Additional comments (15)
WalkthroughIntroduces structured failure-diagnosis and auditable decision capture across engine, persistence, API, tests, and docs: adds Suggested labels
|
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Code Review
This pull request implements a structured failure diagnosis system and an auditable decision record drop-box for the approval gate. It introduces the FailureCategory enum and expands RecoveryResult with machine-readable metadata to improve task reassignment and checkpoint reconciliation. Additionally, the ReviewGateService now enforces a no-self-review policy, supported by new preflight checks in the API layer and an append-only DecisionRepository. Feedback includes a critical fix for invalid exception syntax in the review gate service and a suggestion to improve the readability of the CLAUDE.md file.
src/synthorg/engine/review_gate.py
Outdated
| except MemoryError, RecursionError: | ||
| raise |
| config/ # YAML company config loading and validation | ||
| core/ # Shared domain models, base classes, resilience config | ||
| engine/ # Orchestration, execution loops, task engine (observer registration, background observer dispatch), coordination, checkpoint recovery, approval/review gates, stagnation detection, context budget, compaction, hybrid loop, prompt profiles (tier-based prompt adaptation, personality trimming via max_personality_tokens), procedural memory integration (failure-driven), workspace/ (git worktree isolation, merge orchestration, semantic conflict detection), workflow/ (Kanban board, Agile sprints, WIP limits, sprint lifecycle, velocity tracking, ceremony scheduling, strategy migration, strategies/ (pluggable scheduling strategies), velocity_calculators/ (pluggable velocity calculators), definition (visual workflow graph model, node/edge types, validation, YAML export), blueprint_loader (starter blueprint loading), blueprint_models (blueprint data models), blueprints/ (5 YAML starter templates), diff (version diff computation), version (version snapshot model), execution (workflow activation service, execution models, condition evaluator (compound AND/OR/NOT), graph utilities, execution_observer (TaskEngine bridge for lifecycle transitions), execution_activation_helpers (graph walking, conditional processing, task config parsing))) | ||
| engine/ # Orchestration, execution loops, task engine (observer registration, background observer dispatch), coordination, checkpoint recovery, structured failure diagnosis (FailureCategory, infer_failure_category, RecoveryResult failure_context/criteria_failed/stagnation_evidence), approval/review gates (no-self-review enforcement via SelfReviewError, immutable DecisionRecord drop-box), stagnation detection, context budget, compaction, hybrid loop, prompt profiles (tier-based prompt adaptation, personality trimming via max_personality_tokens), procedural memory integration (failure-driven), workspace/ (git worktree isolation, merge orchestration, semantic conflict detection), workflow/ (Kanban board, Agile sprints, WIP limits, sprint lifecycle, velocity tracking, ceremony scheduling, strategy migration, strategies/ (pluggable scheduling strategies), velocity_calculators/ (pluggable velocity calculators), definition (visual workflow graph model, node/edge types, validation, YAML export), blueprint_loader (starter blueprint loading), blueprint_models (blueprint data models), blueprints/ (5 YAML starter templates), diff (version diff computation), version (version snapshot model), execution (workflow activation service, execution models, condition evaluator (compound AND/OR/NOT), graph utilities, execution_observer (TaskEngine bridge for lifecycle transitions), execution_activation_helpers (graph walking, conditional processing, task config parsing))) |
There was a problem hiding this comment.
This line has become excessively long, which impairs readability. For better clarity and maintainability of this important documentation file, please consider restructuring this summary into a multi-line format, such as a bulleted list.
For example:
engine/ # Orchestration, execution loops, and coordination.
# - Task engine (observer registration, background observer dispatch)
# - Checkpoint recovery and structured failure diagnosis
# - Approval/review gates (no-self-review, decisions drop-box)
# - ... and so on.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1072 +/- ##
==========================================
- Coverage 89.62% 89.57% -0.06%
==========================================
Files 744 750 +6
Lines 43625 43975 +350
Branches 4379 4417 +38
==========================================
+ Hits 39099 39389 +290
- Misses 3751 3797 +46
- Partials 775 789 +14 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR lays down v0.7.0 engine foundations by (1) making recovery outcomes carry structured, machine-readable failure diagnosis for better checkpoint reconciliation/routing, and (2) enforcing no-self-review at the approval gate while persisting an append-only, auditable decision trail.
Changes:
- Extend
RecoveryResultwithFailureCategory, structuredfailure_context, unmet criteria, and (optional) stagnation evidence; include these in checkpoint reconciliation and post-recovery status reasons. - Add decisions “drop-box”:
DecisionRecordmodel +DecisionRepositoryprotocol + SQLite schema/repo + observability events. - Enforce no-self-review via
ReviewGateService.check_can_decide()preflight, API-level 403 mapping, and DB/model invariants.
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/persistence/test_protocol.py | Adds DecisionRepository to persistence protocol conformance fakes/tests. |
| tests/unit/persistence/sqlite/test_migrations.py | Updates migration expectations for new decision_records table + indexes. |
| tests/unit/persistence/sqlite/test_decision_repo.py | New unit tests for SQLite decision repository behavior and invariants. |
| tests/unit/memory/procedural/test_pipeline.py | Updates recovery-result construction for new required diagnosis fields. |
| tests/unit/engine/test_review_gate.py | Expands tests for self-review prevention and decision recording. |
| tests/unit/engine/test_recovery_failure_diagnosis.py | New tests for RecoveryResult diagnosis fields, invariants, and inference helper. |
| tests/unit/engine/test_recovery_checkpoint_fields.py | Updates checkpoint-field tests to include new required recovery fields. |
| tests/unit/engine/test_decisions.py | New tests for DecisionRecord immutability/validation behavior. |
| tests/unit/engine/checkpoint/test_strategy.py | Updates checkpoint strategy tests for new recovery fields. |
| tests/unit/engine/checkpoint/test_resume.py | Extends resume reconciliation tests to include failure category + unmet criteria sanitization. |
| tests/unit/core/test_enums_failure_category.py | New tests for FailureCategory and DecisionOutcome enums. |
| tests/unit/api/fakes.py | Adds in-memory FakeDecisionRepository to support API/service tests. |
| tests/unit/api/controllers/test_approvals_helpers.py | Updates controller helper test to ensure unknown exceptions propagate (no silent 200). |
| src/synthorg/persistence/sqlite/schema.sql | Adds decision_records table and supporting indexes/constraints. |
| src/synthorg/persistence/sqlite/decision_repo.py | Implements append-only SQLite repo with atomic per-task version assignment. |
| src/synthorg/persistence/sqlite/backend.py | Wires decision_records repository into SQLite backend. |
| src/synthorg/persistence/repositories.py | Adds DecisionRepository protocol + DecisionRole type. |
| src/synthorg/persistence/protocol.py | Exposes decision_records on PersistenceBackend protocol. |
| src/synthorg/observability/events/persistence.py | Adds persistence event constants for decision record operations. |
| src/synthorg/observability/events/execution.py | Adds EXECUTION_RECOVERY_DIAGNOSIS event constant. |
| src/synthorg/observability/events/approval_gate.py | Adds approval-gate event constants for self-review + decision recording. |
| src/synthorg/engine/review_gate.py | Enforces no-self-review and appends auditable decision records (best-effort). |
| src/synthorg/engine/recovery.py | Adds structured failure diagnosis to RecoveryResult + inference helper + invariants. |
| src/synthorg/engine/immutable.py | Adds helper to deep-copy dict fields at model construction boundaries. |
| src/synthorg/engine/errors.py | Introduces SelfReviewError. |
| src/synthorg/engine/decisions.py | Adds immutable DecisionRecord model (type-level self-review invariant). |
| src/synthorg/engine/checkpoint/strategy.py | Populates new recovery diagnosis fields for checkpoint-resume results. |
| src/synthorg/engine/checkpoint/resume.py | Injects failure category + unmet criteria into reconciliation messages (sanitized). |
| src/synthorg/engine/agent_engine.py | Embeds failure category into post-recovery transition reasons + passes diagnosis to resume. |
| src/synthorg/core/enums.py | Adds FailureCategory and DecisionOutcome enums. |
| src/synthorg/api/controllers/approvals.py | Adds review-gate preflight; maps typed engine errors to 403/404/409; stops swallowing unknown exceptions. |
| src/synthorg/api/app.py | Only wires ReviewGateService when both task_engine and persistence are available. |
| docs/design/engine.md | Updates engine design docs for diagnosis fields and decisions drop-box behavior. |
| CLAUDE.md | Updates package structure notes to reflect new foundations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/synthorg/engine/recovery.py
Outdated
| result = RecoveryResult( | ||
| task_execution=failed_execution, | ||
| strategy_type=self.STRATEGY_TYPE, | ||
| context_snapshot=snapshot, | ||
| error_message=error_message, | ||
| failure_category=category, | ||
| failure_context={ | ||
| "strategy_type": self.STRATEGY_TYPE, | ||
| }, | ||
| ) | ||
|
|
||
| logger.info( | ||
| EXECUTION_RECOVERY_DIAGNOSIS, | ||
| task_id=task_execution.task.id, | ||
| failure_category=category.value, |
There was a problem hiding this comment.
infer_failure_category() can return categories like QUALITY_GATE_FAILED or STAGNATION that have cross-field invariants (criteria_failed non-empty / stagnation_evidence required). This FailAndReassignStrategy always constructs RecoveryResult without those fields, so certain error messages (e.g. containing "criteria") will raise during RecoveryResult validation and cause recovery to fail entirely (leaving the task execution un-transitioned). Consider either (a) only inferring categories that don’t require extra fields here, (b) downgrading to UNKNOWN when required evidence/criteria isn’t available, or (c) populating the required fields before constructing RecoveryResult.
| result = RecoveryResult( | |
| task_execution=failed_execution, | |
| strategy_type=self.STRATEGY_TYPE, | |
| context_snapshot=snapshot, | |
| error_message=error_message, | |
| failure_category=category, | |
| failure_context={ | |
| "strategy_type": self.STRATEGY_TYPE, | |
| }, | |
| ) | |
| logger.info( | |
| EXECUTION_RECOVERY_DIAGNOSIS, | |
| task_id=task_execution.task.id, | |
| failure_category=category.value, | |
| safe_category = category | |
| if category in ( | |
| FailureCategory.QUALITY_GATE_FAILED, | |
| FailureCategory.STAGNATION, | |
| ): | |
| safe_category = FailureCategory.UNKNOWN | |
| result = RecoveryResult( | |
| task_execution=failed_execution, | |
| strategy_type=self.STRATEGY_TYPE, | |
| context_snapshot=snapshot, | |
| error_message=error_message, | |
| failure_category=safe_category, | |
| failure_context={ | |
| "strategy_type": self.STRATEGY_TYPE, | |
| "inferred_failure_category": category.value, | |
| }, | |
| ) | |
| logger.info( | |
| EXECUTION_RECOVERY_DIAGNOSIS, | |
| task_id=task_execution.task.id, | |
| failure_category=result.failure_category.value, |
| return RecoveryResult( | ||
| task_execution=task_execution, | ||
| strategy_type=self.STRATEGY_TYPE, | ||
| context_snapshot=snapshot, | ||
| error_message=error_message, | ||
| failure_category=infer_failure_category(error_message), | ||
| failure_context={ | ||
| "strategy_type": self.STRATEGY_TYPE, | ||
| "checkpoint_id": checkpoint.id, | ||
| "turn_number": checkpoint.turn_number, | ||
| "resume_attempt": resume_attempt, | ||
| }, |
There was a problem hiding this comment.
_build_resume_result() sets failure_category via infer_failure_category(error_message) but does not populate criteria_failed or stagnation_evidence. Because RecoveryResult enforces cross-field invariants (e.g. QUALITY_GATE_FAILED requires non-empty criteria_failed), certain error messages will make checkpoint recovery raise at construction time and force fallback (or fail recovery entirely). Suggest downgrading inferred categories to UNKNOWN when required structured fields aren’t available, or explicitly populating those fields in this strategy before constructing RecoveryResult.
src/synthorg/engine/review_gate.py
Outdated
| The task (cached so the caller can reuse it for | ||
| ``complete_review`` without re-fetching). |
There was a problem hiding this comment.
check_can_decide()’s docstring says the returned task is “cached so the caller can reuse it for complete_review without re-fetching”, but complete_review() always calls check_can_decide() again and there’s no way to pass the pre-fetched task through. This leads to an extra get_task() call per decision and the documentation is currently inaccurate. Either update the docstring, or add an API that allows complete_review() to accept the preflight task (or otherwise reuse it).
| The task (cached so the caller can reuse it for | |
| ``complete_review`` without re-fetching). | |
| The validated task fetched during preflight. |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/synthorg/engine/checkpoint/strategy.py (1)
274-288:⚠️ Potential issue | 🟠 MajorRecoveryResult validation error: checkpoint resume cannot satisfy QUALITY_GATE_FAILED invariant.
When
error_messagecontains "quality" or "criteria" (case-insensitive),infer_failure_category()returnsFailureCategory.QUALITY_GATE_FAILED. However, this code doesn't populatecriteria_failed, and theRecoveryResultvalidator (recovery.py:195-200) requires non-emptycriteria_failedwhenfailure_categoryisQUALITY_GATE_FAILED. This causes a runtimeValueErrorif checkpoint recovery is triggered by an error matching these keywords (e.g., "quality check failed", "criteria not met").Option A – Downgrade QUALITY_GATE_FAILED to UNKNOWN for checkpoint resume:
+ category = infer_failure_category(error_message) + # Checkpoint resume cannot provide criteria_failed evidence, so + # downgrade QUALITY_GATE_FAILED to UNKNOWN to satisfy the invariant. + if category is FailureCategory.QUALITY_GATE_FAILED: + category = FailureCategory.UNKNOWN + return RecoveryResult( task_execution=task_execution, strategy_type=self.STRATEGY_TYPE, context_snapshot=snapshot, error_message=error_message, - failure_category=infer_failure_category(error_message), + failure_category=category, failure_context={Option B – Relax the validator for checkpoint resume scenarios (requires changes in
recovery.py).src/synthorg/api/controllers/approvals.py (1)
534-569:⚠️ Potential issue | 🟠 MajorA late review-gate failure still leaves the approval permanently decided.
save_if_pending()and the WebSocket publish happen before_signal_resume_intent(). Ifcomplete_review()later raisesTaskNotFoundErrororTaskVersionConflictError, this endpoint returns 404/409 after the approval row is already committed, and the client cannot retry because the approval is no longer pending. Preflight reduces one race, but it does not make the workflow atomic.A safer shape here is to couple the approval update and review-gate transition behind one transactional/compensating boundary, and at minimum delay publish/logging until the downstream transition succeeds.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/approvals.py` around lines 534 - 569, The code currently saves the approval and publishes/logs before calling _signal_resume_intent, which can leave the approval committed if downstream _signal_resume_intent (or complete_review) fails; to fix, delay persisting/publishing until the downstream transition succeeds: call _signal_resume_intent first (using updated.task_id rather than saved.task_id), await it, and only then call app_state.approval_store.save_if_pending(updated) followed by _publish_approval_event and _log_approval_decision; keep the preflight _preflight_review_gate check but ensure save_if_pending, _publish_approval_event, and _log_approval_decision happen after _signal_resume_intent to avoid leaving a decided approval when the task transition fails.
🤖 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/engine/agent_engine.py`:
- Around line 669-688: The category assignment includes a redundant None check;
remove the dead else branch and directly read the value from recovery_result
since this block is only executed when recovery_result is not None—replace the
conditional expression that sets category with a direct access like
recovery_result.failure_category.value and keep the rest of the call to
sync_to_task_engine (refer to recovery_result, category, and
ctx.task_execution.status to locate the change).
In `@src/synthorg/persistence/protocol.py`:
- Around line 167-170: The Persistence protocol was updated with a new property
decision_records (DecisionRepository) but the PersistenceBackend class docstring
Attributes section wasn't updated; update the PersistenceBackend docstring to
include an Attributes entry describing decision_records: name
(decision_records), type (DecisionRepository), and brief purpose ("Repository
for DecisionRecord persistence (decisions drop-box)"), and ensure the wording
matches the `@property` docstring for decision_records so generated API docs
remain accurate.
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Line 202: Replace the parenthesized tuple exception syntax used in the except
clause "except (sqlite3.Error, aiosqlite.Error) as exc:" with the PEP 758
comma-separated form so the handler reads using comma-separated exceptions
without parentheses; locate the try/except in decision_repo.py where that clause
appears (the DB error handling block around sqlite3/aiosqlite operations) and
update the except line accordingly while leaving the handler body and variable
name (exc) unchanged.
- Line 310: Update the except clause in decision_repo.py to use the PEP 758
comma-separated exception syntax by replacing the current "except
(ValidationError, json.JSONDecodeError, TypeError) as exc:" with the
comma-separated form "except ValidationError, json.JSONDecodeError, TypeError as
exc:" so the handler for ValidationError, json.JSONDecodeError and TypeError
(variable exc) is written without parentheses; keep the same handler body
unchanged and run tests to confirm no syntax/regression issues.
- Line 275: Replace the parenthesized exception tuple with PEP 758
comma-separated syntax: change the handler currently written as "except
(sqlite3.Error, aiosqlite.Error) as exc:" to the new form "except sqlite3.Error,
aiosqlite.Error as exc:" so the except block in decision_repo.py (the clause
that catches sqlite3.Error and aiosqlite.Error and binds exc) uses the
comma-separated exception list.
- Line 147: Replace the parenthesized multi-exception clause in the except
handler that catches sqlite3.Error and aiosqlite.Error with the PEP 758
comma-separated form: locate the except block currently written as "except
(sqlite3.Error, aiosqlite.Error) as exc" in decision_repo.py (inside the
repository methods handling DB operations) and change it to use "except
sqlite3.Error, aiosqlite.Error as exc" so the handler follows the project
guideline for Python 3.14.
- Line 223: Replace the parenthesized multi-except clause with the PEP 758
comma-separated form: locate the except block written as "except (sqlite3.Error,
aiosqlite.Error) as exc" in decision_repo.py and change it to "except
sqlite3.Error, aiosqlite.Error as exc" so the clause uses the new syntax while
preserving the same exception handling behavior; ensure the rest of the except
body (logging/raising) remains unchanged.
In `@tests/unit/api/fakes.py`:
- Around line 321-323: The test fake computes next_version by counting records
(existing_for_task and next_version) which breaks when versions are
non-contiguous; change the logic that builds the DecisionRecord so next_version
is computed as max of existing record.version values + 1 (treat empty set as 0)
using the records in self._records for the given task_id; update the code around
existing_for_task / next_version / DecisionRecord to use that max(...,
default=0) + 1 approach so seeded non-contiguous histories produce the correct
next version.
In `@tests/unit/engine/test_decisions.py`:
- Around line 81-122: The test suite lacks a check that executing_agent_id and
reviewer_agent_id cannot be the same; add a new unit test (e.g.,
test_self_review_rejected) that calls _make_record with executing_agent_id and
reviewer_agent_id set to the same value and asserts it raises ValidationError
using pytest.raises(ValidationError); place this alongside the other validators
in tests/unit/engine/test_decisions.py so regressions in the cross-field
validator are caught.
In `@tests/unit/engine/test_recovery_failure_diagnosis.py`:
- Around line 46-64: The test function test_failure_category_required declares
an unused fixture parameter sample_task_with_criteria causing unnecessary setup;
remove sample_task_with_criteria from the test_failure_category_required
signature so the function only accepts sample_agent_context and avoid touching
any other logic (ensure the function still uses sample_agent_context,
TaskStatus, RecoveryResult, etc.).
- Around line 66-84: The test_failure_context_required function declares an
unused fixture parameter sample_task_with_criteria; remove that parameter from
the test signature so only used fixtures (sample_agent_context) remain, updating
the function definition for test_failure_context_required to omit
sample_task_with_criteria to eliminate the unused-fixture warning.
---
Outside diff comments:
In `@src/synthorg/api/controllers/approvals.py`:
- Around line 534-569: The code currently saves the approval and publishes/logs
before calling _signal_resume_intent, which can leave the approval committed if
downstream _signal_resume_intent (or complete_review) fails; to fix, delay
persisting/publishing until the downstream transition succeeds: call
_signal_resume_intent first (using updated.task_id rather than saved.task_id),
await it, and only then call app_state.approval_store.save_if_pending(updated)
followed by _publish_approval_event and _log_approval_decision; keep the
preflight _preflight_review_gate check but ensure save_if_pending,
_publish_approval_event, and _log_approval_decision happen after
_signal_resume_intent to avoid leaving a decided approval when the task
transition fails.
🪄 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: 2c12f1e9-d98e-4d4f-92bf-a10c60fb6fab
📒 Files selected for processing (34)
CLAUDE.mddocs/design/engine.mdsrc/synthorg/api/app.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/core/enums.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/engine/checkpoint/resume.pysrc/synthorg/engine/checkpoint/strategy.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/immutable.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/observability/events/execution.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/persistence/protocol.pysrc/synthorg/persistence/repositories.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/persistence/sqlite/decision_repo.pysrc/synthorg/persistence/sqlite/schema.sqltests/unit/api/controllers/test_approvals_helpers.pytests/unit/api/fakes.pytests/unit/core/test_enums_failure_category.pytests/unit/engine/checkpoint/test_resume.pytests/unit/engine/checkpoint/test_strategy.pytests/unit/engine/test_decisions.pytests/unit/engine/test_recovery_checkpoint_fields.pytests/unit/engine/test_recovery_failure_diagnosis.pytests/unit/engine/test_review_gate.pytests/unit/memory/procedural/test_pipeline.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/persistence/sqlite/test_migrations.pytests/unit/persistence/test_protocol.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: Build Sandbox
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Agent
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Do not usefrom __future__ import annotationsin Python -- Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling in Python 3.14 per PEP 758 -- ruff enforces this
All public functions and methods must have type hints, with mypy strict mode enforcement
Google-style docstrings are required on all public classes and functions, enforced by ruff D rules
Create new objects rather than mutating existing ones. For non-Pydantic internal collections (registries, BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement.
Use frozen Pydantic models for config and identity; use separate mutable-via-copy models (viamodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 with conventions:allow_inf_nan=Falsein all ConfigDict declarations to reject NaN/Inf in numeric fields; use@computed_fieldfor derived values instead of storing redundant fields; useNotBlankStrfor all identifier and name fields including optional and tuple variants, instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task.
Maintain a line length of 88 characters (enforced by ruff)
Functions must be less than 50 lines, files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Files:
tests/unit/engine/checkpoint/test_strategy.pysrc/synthorg/observability/events/execution.pysrc/synthorg/persistence/protocol.pytests/unit/memory/procedural/test_pipeline.pysrc/synthorg/api/app.pysrc/synthorg/engine/errors.pytests/unit/persistence/sqlite/test_migrations.pysrc/synthorg/engine/checkpoint/strategy.pysrc/synthorg/engine/immutable.pytests/unit/core/test_enums_failure_category.pytests/unit/engine/test_recovery_checkpoint_fields.pytests/unit/engine/checkpoint/test_resume.pysrc/synthorg/engine/checkpoint/resume.pytests/unit/engine/test_decisions.pytests/unit/api/controllers/test_approvals_helpers.pysrc/synthorg/engine/agent_engine.pytests/unit/engine/test_review_gate.pysrc/synthorg/observability/events/approval_gate.pytests/unit/api/fakes.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/core/enums.pytests/unit/engine/test_recovery_failure_diagnosis.pytests/unit/persistence/test_protocol.pysrc/synthorg/persistence/repositories.pysrc/synthorg/engine/recovery.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.pytests/unit/persistence/sqlite/test_decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e, and@pytest.mark.slowfor test markers
Maintain 80% minimum code coverage (enforced in CI)
Prefer@pytest.mark.parametrizefor testing similar cases
Test vendor names must usetest-provider,test-small-001, etc. instead of real vendor names
Use Python Hypothesis for property-based testing with@given+@settings. Hypothesis profiles configured intests/conftest.py:ci(deterministic,max_examples=10+derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline). Control viaHYPOTHESIS_PROFILEenv var.
When Hypothesis finds a failing example, it is a real bug. Fix the underlying bug and add an explicit@example(...)decorator to the test so the case is permanently covered in CI.
Never skip, dismiss, or ignore flaky tests. For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic. For tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number).
Files:
tests/unit/engine/checkpoint/test_strategy.pytests/unit/memory/procedural/test_pipeline.pytests/unit/persistence/sqlite/test_migrations.pytests/unit/core/test_enums_failure_category.pytests/unit/engine/test_recovery_checkpoint_fields.pytests/unit/engine/checkpoint/test_resume.pytests/unit/engine/test_decisions.pytests/unit/api/controllers/test_approvals_helpers.pytests/unit/engine/test_review_gate.pytests/unit/api/fakes.pytests/unit/engine/test_recovery_failure_diagnosis.pytests/unit/persistence/test_protocol.pytests/unit/persistence/sqlite/test_decision_repo.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/checkpoint/test_strategy.pytests/unit/memory/procedural/test_pipeline.pytests/unit/persistence/sqlite/test_migrations.pytests/unit/core/test_enums_failure_category.pytests/unit/engine/test_recovery_checkpoint_fields.pytests/unit/engine/checkpoint/test_resume.pytests/unit/engine/test_decisions.pytests/unit/api/controllers/test_approvals_helpers.pytests/unit/engine/test_review_gate.pytests/unit/api/fakes.pytests/unit/engine/test_recovery_failure_diagnosis.pytests/unit/persistence/test_protocol.pytests/unit/persistence/sqlite/test_decision_repo.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Always useloggeras the variable name (not_logger, notlog)
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool). Import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging withlogger.info(EVENT, key=value)-- neverlogger.info("msg %s", val)
Log all error paths at WARNING or ERROR level with context before raising
Log all state transitions at INFO level
Use DEBUG level logging for object creation, internal flow, and entry/exit of key functions
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/smallas aliases. Vendor names allowed only in: (1) Operations design page, (2).claude/files, (3) third-party import paths, (4) provider presets which are user-facing runtime data. Tests must usetest-provider,test-small-001, etc.
Files:
src/synthorg/observability/events/execution.pysrc/synthorg/persistence/protocol.pysrc/synthorg/api/app.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/checkpoint/strategy.pysrc/synthorg/engine/immutable.pysrc/synthorg/engine/checkpoint/resume.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/core/enums.pysrc/synthorg/persistence/repositories.pysrc/synthorg/engine/recovery.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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/observability/events/execution.pysrc/synthorg/persistence/protocol.pysrc/synthorg/api/app.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/checkpoint/strategy.pysrc/synthorg/engine/immutable.pysrc/synthorg/engine/checkpoint/resume.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/core/enums.pysrc/synthorg/persistence/repositories.pysrc/synthorg/engine/recovery.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Documentation is in
docs/(Markdown, built with Zensical, config:mkdocs.yml). Design spec indocs/design/(12 pages). Architecture indocs/architecture/. Roadmap indocs/roadmap/. Security indocs/security.md. Licensing indocs/licensing.md.
Files:
docs/design/engine.md
🧠 Learnings (56)
📚 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/execution.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/observability/events/persistence.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/execution.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/observability/events/persistence.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/execution.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/observability/events/persistence.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/execution.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/observability/events/persistence.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/execution.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/observability/events/persistence.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/execution.pysrc/synthorg/observability/events/persistence.py
📚 Learning: 2026-04-04T19:50:52.893Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:50:52.893Z
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: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
Applied to files:
src/synthorg/observability/events/execution.pysrc/synthorg/observability/events/approval_gate.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/protocol.pytests/unit/persistence/test_protocol.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/persistence/sqlite/decision_repo.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/persistence/protocol.pytests/unit/persistence/test_protocol.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/persistence/sqlite/decision_repo.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/memory/procedural/test_pipeline.pysrc/synthorg/engine/errors.pytests/unit/engine/test_recovery_checkpoint_fields.pysrc/synthorg/engine/agent_engine.pytests/unit/engine/test_review_gate.pytests/unit/api/fakes.pytests/unit/persistence/test_protocol.pysrc/synthorg/persistence/repositories.pysrc/synthorg/engine/review_gate.pyCLAUDE.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: 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:
tests/unit/memory/procedural/test_pipeline.pysrc/synthorg/engine/agent_engine.pytests/unit/persistence/test_protocol.pyCLAUDE.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: Applies to src/synthorg/**/*.py : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.
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 : 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/engine/immutable.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/engine/immutable.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/engine/immutable.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/engine/immutable.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/engine/immutable.pysrc/synthorg/engine/decisions.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 **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/engine/immutable.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/engine/immutable.pysrc/synthorg/engine/decisions.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/engine/immutable.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/engine/immutable.pysrc/synthorg/engine/decisions.py
📚 Learning: 2026-04-04T19:50:52.893Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:50:52.893Z
Learning: Applies to **/*.py : Create new objects rather than mutating existing ones. For non-Pydantic internal collections (registries, BaseTool), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement.
Applied to files:
src/synthorg/engine/immutable.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/engine/immutable.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/engine/immutable.py
📚 Learning: 2026-04-04T19:50:52.893Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:50:52.893Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config and 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/engine/immutable.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
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/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
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/observability/events/approval_gate.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/observability/events/persistence.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.
Applied to files:
src/synthorg/observability/events/approval_gate.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/engine/review_gate.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/observability/events/approval_gate.pysrc/synthorg/observability/events/persistence.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/approval_gate.pysrc/synthorg/observability/events/persistence.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/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:
tests/unit/api/fakes.pytests/unit/persistence/test_protocol.pyCLAUDE.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:
tests/unit/api/fakes.pytests/unit/persistence/test_protocol.pyCLAUDE.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: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).
Applied to files:
src/synthorg/api/controllers/approvals.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/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
src/synthorg/api/controllers/approvals.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 : Retryable errors (`is_retryable=True`): `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry.
Applied to files:
src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-16T19:13:34.746Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:34.746Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry. RetryExhaustedError signals that all retries failed — the engine layer catches this to trigger fallback chains.
Applied to files:
src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed
Applied to files:
src/synthorg/api/controllers/approvals.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/api/controllers/approvals.py
📚 Learning: 2026-04-04T19:50:52.893Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-04T19:50:52.893Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors (`is_retryable=True`) include: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry. `RetryExhaustedError` signals that all retries failed.
Applied to files:
src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.
Applied to files:
src/synthorg/api/controllers/approvals.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 src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit
Applied to files:
src/synthorg/api/controllers/approvals.py
📚 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 : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.
Applied to files:
src/synthorg/api/controllers/approvals.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 : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.
Applied to files:
src/synthorg/api/controllers/approvals.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Applied to files:
src/synthorg/api/controllers/approvals.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/api/controllers/approvals.pytests/unit/persistence/test_protocol.pyCLAUDE.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 : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG
Applied to files:
src/synthorg/api/controllers/approvals.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:
tests/unit/persistence/test_protocol.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/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:
src/synthorg/persistence/repositories.pyCLAUDE.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/**/*.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/persistence.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: 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-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: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).
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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality
Applied to files:
docs/design/engine.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.
Applied to files:
docs/design/engine.md
| @property | ||
| def decision_records(self) -> DecisionRepository: | ||
| """Repository for DecisionRecord persistence (decisions drop-box).""" | ||
| ... |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Sync protocol class doc attributes with new property.
Optional: add decision_records to the PersistenceBackend class docstring’s Attributes section so API documentation stays complete.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/protocol.py` around lines 167 - 170, The Persistence
protocol was updated with a new property decision_records (DecisionRepository)
but the PersistenceBackend class docstring Attributes section wasn't updated;
update the PersistenceBackend docstring to include an Attributes entry
describing decision_records: name (decision_records), type (DecisionRepository),
and brief purpose ("Repository for DecisionRecord persistence (decisions
drop-box)"), and ensure the wording matches the `@property` docstring for
decision_records so generated API docs remain accurate.
| error=str(exc), | ||
| ) | ||
| raise QueryError(msg) from exc | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use PEP 758 comma-separated exception syntax without parentheses.
Per project coding guidelines for Python 3.14, exception handlers should use the comma-separated form without parentheses.
♻️ Proposed fix
- except (sqlite3.Error, aiosqlite.Error) as exc:
+ except sqlite3.Error, aiosqlite.Error as exc:As per coding guidelines: "Use except A, B: syntax (no parentheses) for exception handling in Python 3.14 per PEP 758".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except (sqlite3.Error, aiosqlite.Error) as exc: | |
| except sqlite3.Error, aiosqlite.Error as exc: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 147, Replace the
parenthesized multi-exception clause in the except handler that catches
sqlite3.Error and aiosqlite.Error with the PEP 758 comma-separated form: locate
the except block currently written as "except (sqlite3.Error, aiosqlite.Error)
as exc" in decision_repo.py (inside the repository methods handling DB
operations) and change it to use "except sqlite3.Error, aiosqlite.Error as exc"
so the handler follows the project guideline for Python 3.14.
| (record_id,), | ||
| ) | ||
| row = await cursor.fetchone() | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use PEP 758 comma-separated exception syntax without parentheses.
♻️ Proposed fix
- except (sqlite3.Error, aiosqlite.Error) as exc:
+ except sqlite3.Error, aiosqlite.Error as exc:As per coding guidelines: "Use except A, B: syntax (no parentheses) for exception handling in Python 3.14 per PEP 758".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except (sqlite3.Error, aiosqlite.Error) as exc: | |
| except sqlite3.Error, aiosqlite.Error as exc: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 202, Replace the
parenthesized tuple exception syntax used in the except clause "except
(sqlite3.Error, aiosqlite.Error) as exc:" with the PEP 758 comma-separated form
so the handler reads using comma-separated exceptions without parentheses;
locate the try/except in decision_repo.py where that clause appears (the DB
error handling block around sqlite3/aiosqlite operations) and update the except
line accordingly while leaving the handler body and variable name (exc)
unchanged.
| (task_id,), | ||
| ) | ||
| rows = await cursor.fetchall() | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use PEP 758 comma-separated exception syntax without parentheses.
♻️ Proposed fix
- except (sqlite3.Error, aiosqlite.Error) as exc:
+ except sqlite3.Error, aiosqlite.Error as exc:As per coding guidelines: "Use except A, B: syntax (no parentheses) for exception handling in Python 3.14 per PEP 758".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except (sqlite3.Error, aiosqlite.Error) as exc: | |
| except sqlite3.Error, aiosqlite.Error as exc: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 223, Replace the
parenthesized multi-except clause with the PEP 758 comma-separated form: locate
the except block written as "except (sqlite3.Error, aiosqlite.Error) as exc" in
decision_repo.py and change it to "except sqlite3.Error, aiosqlite.Error as exc"
so the clause uses the new syntax while preserving the same exception handling
behavior; ensure the rest of the except body (logging/raising) remains
unchanged.
| (agent_id,), | ||
| ) | ||
| rows = await cursor.fetchall() | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use PEP 758 comma-separated exception syntax without parentheses.
♻️ Proposed fix
- except (sqlite3.Error, aiosqlite.Error) as exc:
+ except sqlite3.Error, aiosqlite.Error as exc:As per coding guidelines: "Use except A, B: syntax (no parentheses) for exception handling in Python 3.14 per PEP 758".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 275, Replace the
parenthesized exception tuple with PEP 758 comma-separated syntax: change the
handler currently written as "except (sqlite3.Error, aiosqlite.Error) as exc:"
to the new form "except sqlite3.Error, aiosqlite.Error as exc:" so the except
block in decision_repo.py (the clause that catches sqlite3.Error and
aiosqlite.Error and binds exc) uses the comma-separated exception list.
| if isinstance(raw_metadata, str): | ||
| parsed["metadata"] = json.loads(raw_metadata) | ||
| return DecisionRecord.model_validate(parsed) | ||
| except (ValidationError, json.JSONDecodeError, TypeError) as exc: |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use PEP 758 comma-separated exception syntax without parentheses.
♻️ Proposed fix
- except (ValidationError, json.JSONDecodeError, TypeError) as exc:
+ except ValidationError, json.JSONDecodeError, TypeError as exc:As per coding guidelines: "Use except A, B: syntax (no parentheses) for exception handling in Python 3.14 per PEP 758".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except (ValidationError, json.JSONDecodeError, TypeError) as exc: | |
| except ValidationError, json.JSONDecodeError, TypeError as exc: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 310, Update the
except clause in decision_repo.py to use the PEP 758 comma-separated exception
syntax by replacing the current "except (ValidationError, json.JSONDecodeError,
TypeError) as exc:" with the comma-separated form "except ValidationError,
json.JSONDecodeError, TypeError as exc:" so the handler for ValidationError,
json.JSONDecodeError and TypeError (variable exc) is written without
parentheses; keep the same handler body unchanged and run tests to confirm no
syntax/regression issues.
| def test_failure_context_required( | ||
| self, | ||
| sample_agent_context: AgentContext, | ||
| sample_task_with_criteria: Task, | ||
| ) -> None: | ||
| """failure_context is required -- omitting it raises ValidationError.""" | ||
| ctx = sample_agent_context.with_task_transition( | ||
| TaskStatus.IN_PROGRESS, reason="starting" | ||
| ) | ||
| assert ctx.task_execution is not None | ||
| failed = ctx.task_execution.with_transition(TaskStatus.FAILED, reason="crash") | ||
| with pytest.raises(ValidationError, match="failure_context"): | ||
| RecoveryResult( # type: ignore[call-arg] | ||
| task_execution=failed, | ||
| strategy_type="fail_reassign", | ||
| context_snapshot=ctx.to_snapshot(), | ||
| error_message="crash", | ||
| failure_category=FailureCategory.TOOL_FAILURE, | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Remove unused fixture parameter sample_task_with_criteria.
Same issue as above — the fixture is declared but not used.
♻️ Proposed fix
def test_failure_context_required(
self,
sample_agent_context: AgentContext,
- sample_task_with_criteria: Task,
) -> None:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_failure_context_required( | |
| self, | |
| sample_agent_context: AgentContext, | |
| sample_task_with_criteria: Task, | |
| ) -> None: | |
| """failure_context is required -- omitting it raises ValidationError.""" | |
| ctx = sample_agent_context.with_task_transition( | |
| TaskStatus.IN_PROGRESS, reason="starting" | |
| ) | |
| assert ctx.task_execution is not None | |
| failed = ctx.task_execution.with_transition(TaskStatus.FAILED, reason="crash") | |
| with pytest.raises(ValidationError, match="failure_context"): | |
| RecoveryResult( # type: ignore[call-arg] | |
| task_execution=failed, | |
| strategy_type="fail_reassign", | |
| context_snapshot=ctx.to_snapshot(), | |
| error_message="crash", | |
| failure_category=FailureCategory.TOOL_FAILURE, | |
| ) | |
| def test_failure_context_required( | |
| self, | |
| sample_agent_context: AgentContext, | |
| ) -> None: | |
| """failure_context is required -- omitting it raises ValidationError.""" | |
| ctx = sample_agent_context.with_task_transition( | |
| TaskStatus.IN_PROGRESS, reason="starting" | |
| ) | |
| assert ctx.task_execution is not None | |
| failed = ctx.task_execution.with_transition(TaskStatus.FAILED, reason="crash") | |
| with pytest.raises(ValidationError, match="failure_context"): | |
| RecoveryResult( # type: ignore[call-arg] | |
| task_execution=failed, | |
| strategy_type="fail_reassign", | |
| context_snapshot=ctx.to_snapshot(), | |
| error_message="crash", | |
| failure_category=FailureCategory.TOOL_FAILURE, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/engine/test_recovery_failure_diagnosis.py` around lines 66 - 84,
The test_failure_context_required function declares an unused fixture parameter
sample_task_with_criteria; remove that parameter from the test signature so only
used fixtures (sample_agent_context) remain, updating the function definition
for test_failure_context_required to omit sample_task_with_criteria to eliminate
the unused-fixture warning.
Add FailureCategory enum and extend RecoveryResult with machine-readable failure classification, unmet criteria, stagnation evidence, and a structured failure_context bag. Both FailAndReassignStrategy and CheckpointRecoveryStrategy populate these fields on every result. The new infer_failure_category() helper uses keyword matching to map error messages to categories (TOOL_FAILURE, STAGNATION, BUDGET_EXCEEDED, QUALITY_GATE_FAILED, TIMEOUT, DELEGATION_FAILED). Future versions may upgrade this to a proper classification pipeline. Checkpoint reconciliation messages now include the failure category and any unmet acceptance criteria, giving resumed agents structured context about what failed rather than a bare error string. Closes #706
Structurally prevent agents from reviewing their own work and add an immutable, append-only audit trail of every review gate decision. ReviewGateService.complete_review() now looks up the task via TaskEngine and raises SelfReviewError when the decider equals task.assigned_to. The approval controller catches this and returns HTTP 403 Forbidden. New DecisionRecord model captures full context at decision time: executing agent, reviewer, outcome, acceptance-criteria snapshot, monotonic version per task, and forward-compatible metadata. Records are appended via the new DecisionRepository protocol; a SQLite implementation follows the SQLiteAuditRepository pattern with indexes on task, executor, reviewer, and recorded_at. Decision recording is best-effort: the review transition happens first and a failed append is logged at WARNING but does not roll back the transition. Closes #700
Critical fixes: - Reorder self-review check before save_if_pending: the preflight (check_can_decide) runs BEFORE the approval is persisted and broadcast, so a rejected self-review attempt never leaves a decided approval row or a WebSocket event behind - Narrow exception handling in _try_review_gate_transition: map TaskNotFoundError -> 404, TaskVersionConflictError -> 409 instead of swallowing as 200 OK with no state change - Atomic version computation via append_with_next_version: versions are now assigned in a single BEGIN IMMEDIATE transaction using COALESCE(MAX(version), 0) + 1, eliminating the TOCTOU race that could silently drop concurrent audit records - Use generic 403 message for self-review to avoid leaking internal agent identifiers in the response body - Sanitize criteria_failed before injecting into checkpoint reconciliation prompts (prompt injection surface) Type safety + invariants: - DecisionRecord: reason is NotBlankStr | None with empty-string coercion; criteria_snapshot is tuple[NotBlankStr, ...]; model_validator forbids executing_agent_id == reviewer_agent_id - RecoveryResult: cross-field validator enforces stagnation_evidence iff STAGNATION category, criteria_failed non-empty with QUALITY_GATE_FAILED; failure_context deep-copy moves to field_validator(mode='before') via engine.immutable.deep_copy_mapping - DecisionRepository.list_by_agent: role is Literal['executor', 'reviewer'] (new DecisionRole alias); also validated at runtime - FailureCategory: +UNKNOWN member so infer_failure_category returns an honest default instead of masking unknown causes as TOOL_FAILURE Schema hardening: - Add FK decision_records.task_id -> tasks(id) - Add CHECK(decision IN (...)) - Add CHECK(reviewer_agent_id != executing_agent_id) - Drop redundant idx_dr_task_id (covered by UNIQUE index) - Replace separate agent + recorded_at indexes with compound (agent_id, recorded_at DESC) indexes for ordered list_by_agent queries Exception detection: - Replace brittle substring matching with sqlite_errorname for IntegrityError duplicate detection Logging: - WARNING before TaskNotFoundError raise in complete_review - WARNING for task_unassigned anomalous state - APPROVAL_GATE_SELF_REVIEW_PREVENTED at API boundary - logger.exception (not logger.warning + exc_info) in _record_decision Reassignment hint (#706 action 6 minimal hook): - Include failure_category in post-recovery transition reason so the task router can read it from status history when reassigning Tests: - Add CheckpointRecoveryStrategy failure_category assertion - Add infer_failure_category empty/precedence tests - Add 'no criteria' reconciliation message test - Add criteria_failed sanitization test - Add cross-field validator tests (stagnation_evidence, criteria_failed) - Add version monotonicity + per-task independence tests - Add list_by_agent executor ordering test (two matching records) - Add corrupted metadata JSON test - Add self-review rejection at DB CHECK + Pydantic model levels Docs: - CLAUDE.md Package Structure: mention decisions drop-box, no-self-review enforcement, failure diagnosis in engine/; decision record repo in persistence/
Addresses 35 valid findings from 16 local review agents + Gemini, Copilot, and CodeRabbit. Groups the fixes into these themes: Critical bug (C1) -- RecoveryResult validator crash: infer_failure_category could return STAGNATION or QUALITY_GATE_FAILED on any error message containing "stagnation", "quality", or "criteria", but both FailAndReassignStrategy and CheckpointRecoveryStrategy._build_resume_result constructed RecoveryResult without the sidecar evidence those categories require. The cross-field validator then crashed construction on exactly the failure paths that were supposed to be the safety net. Added infer_failure_category_without_evidence which clamps the evidence-requiring categories to UNKNOWN and wired both strategies through it. Added parametrized regression tests feeding stagnation/quality/criteria strings through strategy construction so any future recurrence is caught in CI. Review gate correctness + security: - Refuse to record decisions for tasks without an assigned executor (was jamming a sentinel "unassigned" string through a NotBlankStr field, contaminating the audit trail) - Narrow _record_decision except to (QueryError, DuplicateRecordError) so programming errors (ValidationError, TypeError, AttributeError) propagate instead of being silently swallowed as transient audit loss - Thread approval_id through complete_review -> _record_decision so every audit record carries a back-reference to the triggering ApprovalItem - Add TaskInternalError -> 503 mapping in the preflight and transition helpers, mirroring the tasks controller - Make 404/409 responses use generic messages (never echo task UUIDs) - Remove identifiers from SelfReviewError.__init__ message; keep them only as attributes for structured logs - Log APPROVAL_GATE_REVIEW_COMPLETED AFTER sync_to_task_engine succeeds, so audit logs reflect actual transitions not intended ones Decisions drop-box correctness: - Drop custom BEGIN IMMEDIATE transaction in append_with_next_version; the INSERT with inline SELECT COALESCE(MAX + 1) is a single atomic statement under aiosqlite per-statement serialization, and the UNIQUE(task_id, version) constraint rejects any residual collision. Matches the rest of the codebase implicit-transaction style and eliminates the rollback-masking bug in the old layout. - Replace list_by_agent if/elif column mapping with a closed-set _ROLE_TO_COLUMN dict keyed by DecisionRole; S608 noqa now scoped tightly to the bounded identifier set - Use direct row column access (not .get()) for NOT NULL columns so schema drift surfaces as KeyError instead of ValidationError - Add ON DELETE RESTRICT to the tasks FK so audit trail preservation takes priority over task cleanup - Add schema CHECK constraint enforcing UTC offset on recorded_at - Add prompt-injection marker stripping to sanitize_message to harden the reconciliation message against LLM-authored criteria text Model + test improvements: - RecoveryResult: add NO_STAGNATION verdict rejection, criteria uniqueness validation, and default_factory=dict for failure_context - DecisionRecord: add criteria uniqueness validation and default_factory=dict for metadata - Add missing test_self_review_rejected in test_decisions.py (the invariant was only tested in the persistence layer) - Add TestPreflightReviewGate + TestTryReviewGateTransition covering every typed engine-error -> HTTP status mapping - Add test_concurrent_appends_yield_distinct_versions firing 20 concurrent asyncio.gather appends; pins the TOCTOU-safe version contract - Add test_decision_record_programming_error_propagates regression guard for the narrowed except - Fix FakeDecisionRepository to use max(versions) + 1 instead of len(records) + 1 so it honours the same monotonic contract as the real repo - Replace try/except Exception/return anti-pattern in test_invalid_decision_value_raises_query_error with two explicit tests: one exercising the write-time CHECK constraint, one exercising the read-time deserialization guard via PRAGMA ignore_check_constraints File splits to stay under CLAUDE.md 800-line limit: - persistence/repositories.py (918 -> 703): DecisionRepository + DecisionRole moved to repositories_decisions.py; CheckpointRepository + HeartbeatRepository moved to repositories_checkpoint.py. Re-exported for backwards compatibility. - api/controllers/approvals.py (862 -> 623): review-gate flow helpers moved to _approval_review_gate.py; re-aliased in approvals.py to preserve internal API. - decision_repo.append_with_next_version: extracted _build_insert_params + _execute_insert helpers to stay under the 50-line function limit. Documentation: - docs/design/operations.md: new Review Gate Invariants, Auditable Decisions Drop-Box, and Design Rationale: Append-Only vs Consolidation subsections - docs/design/engine.md: add UNKNOWN to FailureCategory value list, document cross-field invariants, document the (failure_category=value) transition-reason wire format, expand the review gate description with the two-phase preflight/complete split, note the #1076 follow-up for agent charter versioning - docs/design/memory.md: add DecisionRecord / DecisionRepository to the Entities Persisted table - persistence/protocol.py: add decision_records to the PersistenceBackend Attributes docstring - engine/decisions.py module docstring: fix dead pointer Issue resolution: #700 action 4 (Consider versioning agent identities/charters as first-class artifacts) is explicitly scoped out and tracked as a follow-up in #1076, with a pointer from docs/design/engine.md. Test plan: - 14,150 unit tests pass - 167 integration tests pass - ruff check + format clean - mypy strict clean (1542 files)
dcfee25 to
1ce2649
Compare
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
tests/unit/memory/procedural/test_pipeline.py (1)
58-90:⚠️ Potential issue | 🟡 MinorKeep the shared recovery fixture semantically consistent.
test_extracts_error_and_strategy()passes"Budget exhausted", but this helper still stamps the result asFailureCategory.TOOL_FAILURE. That makes the shared fixture internally inconsistent and can hide category-specific regressions once_build_payload()or the proposer starts readingfailure_category. Let callers override the category/context here instead of hard-coding them.♻️ Proposed fix
def _make_recovery_result( *, task: Task | None = None, error_message: str = "Provider timeout", + failure_category: FailureCategory = FailureCategory.TOOL_FAILURE, + failure_context: dict[str, Any] | None = None, ) -> RecoveryResult: t = task or _make_task() agent_id = str(uuid4()) exec_id = str(uuid4()) @@ return RecoveryResult( task_execution=te, strategy_type="fail_reassign", context_snapshot=snapshot, error_message=error_message, - failure_category=FailureCategory.TOOL_FAILURE, - failure_context={}, + failure_category=failure_category, + failure_context={} if failure_context is None else failure_context, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/memory/procedural/test_pipeline.py` around lines 58 - 90, The shared fixture _make_recovery_result currently hard-codes failure_category and failure_context (using FailureCategory.TOOL_FAILURE and {}), which makes it semantically inconsistent with tests that pass different error messages; change _make_recovery_result to accept optional parameters failure_category: FailureCategory = FailureCategory.TOOL_FAILURE and failure_context: dict | None = None (default to {} when None) and use those values when constructing the RecoveryResult, and update callers (e.g., tests like test_extracts_error_and_strategy) to pass the appropriate failure_category/failure_context when needed.src/synthorg/api/controllers/approvals.py (1)
330-365:⚠️ Potential issue | 🔴 CriticalLate review-gate failures still commit the approval decision.
Now that
_signal_resume_intent()propagates late review-gate failures, the ordering here is unsafe:save_if_pending()and_publish_approval_event()both run before the actual review-gate transition. If the task is reassigned or otherwise changes in that window, the endpoint can return 403/409/503 after theApprovalItemis already decided and broadcast. In the self-review race, that breaks the no-self-review guarantee with a classic TOCTOU hole. This needs a transactional/compensating unit of work around approval persistence + review-gate transition, not just a preflight check.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/approvals.py` around lines 330 - 365, The current ordering lets late review-gate failures (from _signal_resume_intent) occur after app_state.approval_store.save_if_pending and _publish_approval_event, creating a TOCTOU where a decided ApprovalItem can be persisted/broadcast then rolled back — fix by making the review-gate transition atomic with persistence: either (A) perform the actual review-gate transition (not just _preflight_review_gate) before calling save_if_pending and only publish after that transition succeeds, or (B) wrap save_if_pending + the call to _signal_resume_intent in a transactional/compensating unit of work so that if _signal_resume_intent fails you rollback the persisted decision and suppress broadcasting; move _publish_approval_event and _log_approval_decision to after the successful transition/commit and ensure _preflight_review_gate remains as an early check.src/synthorg/engine/review_gate.py (1)
147-156:⚠️ Potential issue | 🟡 MinorNormalize blank rejection reasons once and reuse them.
_record_decision()already collapses whitespace-onlyreasontoNone, but the rejection path still appends any truthy string totransition_reason. A" "input therefore leaves task history with a meaningless suffix while the audit row stores no reason for the same decision.Proposed fix
- if approved: + normalized_reason = reason.strip() if reason and reason.strip() else None + + if approved: target = TaskStatus.COMPLETED transition_reason = f"Review approved by {decided_by}" event = APPROVAL_GATE_REVIEW_COMPLETED else: target = TaskStatus.IN_PROGRESS transition_reason = f"Review rejected by {decided_by}" - if reason: - transition_reason += f": {reason}" + if normalized_reason is not None: + transition_reason += f": {normalized_reason}" event = APPROVAL_GATE_REVIEW_REWORK @@ await self._record_decision( task=task, decided_by=decided_by, approved=approved, - reason=reason, + reason=normalized_reason, approval_id=approval_id, )Also applies to: 176-181
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/engine/review_gate.py` around lines 147 - 156, The rejection branch appends the raw reason (which can be whitespace) to transition_reason, causing a meaningless suffix; ensure you use the normalized/processed reason rather than the original input by trimming/collapsing whitespace once (as done in _record_decision()) and checking that normalized_reason is truthy before appending to transition_reason; apply the same change to the second occurrence around APPROVAL_GATE_REVIEW_REWORK (and the corresponding block at 176-181) so transition_reason only gets a real reason string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/engine.md`:
- Around line 721-728: Update the sentence that currently states "Every
completed review is appended to the auditable decisions drop-box
(DecisionRepository) as an immutable DecisionRecord..." to clearly indicate this
is best-effort: change the wording to state that the system attempts to append a
DecisionRecord to the DecisionRepository for every completed review but append
failures are logged and do not block the review transition (i.e., it's a
best-effort append where runtime contract logs failures and continues).
Reference DecisionRepository and DecisionRecord in the revised sentence so
readers can find the implementation and follow-up behavior.
In `@src/synthorg/api/app.py`:
- Around line 847-856: The preflight gating is being disabled whenever
persistence is missing; instead ensure ReviewGateService is created whenever
task_engine exists (even if persistence is None) so _preflight_review_gate()
still runs; change the branch that currently requires both task_engine and
persistence to create a ReviewGateService(task_engine=task_engine,
persistence=persistence) when task_engine is not None and call
app_state.set_review_gate_service(review_gate_service), letting
ReviewGateService handle the absence of persistence (decision recording should
degrade/NO-OP) while check_can_decide continues to use task_engine only.
In `@src/synthorg/engine/agent_engine.py`:
- Around line 681-700: The transition reason sent in agent_engine.py (inside the
post-recovery block where sync_to_task_engine is called) only includes
recovery_result.failure_category and drops the unmet criteria details; update
the reason passed to sync_to_task_engine (the call that uses
target_status=ctx.task_execution.status, task_id=task_id, agent_id=agent_id) to
append a sanitized fragment of recovery_result.criteria_failed (or equivalent
field) when failure_category == QUALITY_GATE_FAILED (or when criteria_failed is
present), ensuring you redact/limit sensitive text and length to avoid bloating
the task history; keep the existing failure_category string intact and add a
short, escaped criteria_summary (e.g. first N chars or joined failing keys) to
the reason so downstream routers can see which criteria failed.
In `@src/synthorg/engine/decisions.py`:
- Around line 85-102: The metadata Field is left as a plain mutable dict so
callers can mutate record.metadata after construction; modify the
_deep_copy_metadata field validator to return an immutable read-only mapping by
wrapping the deep-copied mapping with types.MappingProxyType (or equivalent)
before assignment, ensuring the metadata attribute on the model is a read-only
Mapping; update the metadata annotation if needed to typing.Mapping[str, Any]
and keep using the existing metadata Field and _deep_copy_metadata validator and
deep_copy_mapping helper to perform the deep copy then wrap with
MappingProxyType to enforce immutability.
In `@src/synthorg/engine/errors.py`:
- Around line 172-199: Update the SelfReviewError class to use the NotBlankStr
type for its public identifier fields instead of plain str: import NotBlankStr
from core.types, change the __init__ signature to def __init__(self, *, task_id:
NotBlankStr, agent_id: NotBlankStr) -> None, and assign self.task_id and
self.agent_id accordingly; you may keep the existing runtime non-blank checks
for defense-in-depth but they should accept NotBlankStr inputs and not duplicate
semantics. Ensure the class still calls super().__init__("Self-review is not
permitted") and preserves the EngineError inheritance and documented behavior.
In `@src/synthorg/engine/immutable.py`:
- Around line 3-10: The module doc and contract promise to intercept both dict
and list inputs, but deep_copy_mapping() currently only deep-copies dicts;
change the helper to consistently handle both mapping and sequence inputs
(detect dict and list/tuple and return a deep copy for either), update its
name/comments to reflect "collection" semantics (e.g., deep_copy_collection) and
adjust any callers to use the new behavior/signature, ensuring frozen Pydantic
model fields that are lists also get copy.deepcopy() applied at the construction
boundary so no mutable alias is retained.
In `@src/synthorg/engine/recovery.py`:
- Around line 49-55: The table _FAILURE_CATEGORY_RULES currently lacks any
pattern mapping to FailureCategory.TOOL_FAILURE, so infer_failure_category() can
never return TOOL_FAILURE from error strings; add one or more string patterns
(e.g., "tool", "tool error", "tool failure", "plugin", "external service",
"connector") mapped to FailureCategory.TOOL_FAILURE in the
_FAILURE_CATEGORY_RULES tuple so string-based inference can yield TOOL_FAILURE;
update the tuple entry alongside the existing entries (e.g., insert
(("tool","tool error","tool failure","plugin","external service"),
FailureCategory.TOOL_FAILURE)) and ensure infer_failure_category continues to
iterate this table unchanged.
In `@src/synthorg/engine/review_gate.py`:
- Around line 272-285: Replace the tuple-based multi-except with PEP 758 syntax:
change the handler from "except (QueryError, DuplicateRecordError) as exc:" to
"except QueryError, DuplicateRecordError as exc:" so the block that logs
APPROVAL_GATE_DECISION_RECORD_FAILED via logger.exception (using task.id,
decided_by, approved, error_type, error) uses the repo-expected exception
syntax.
- Around line 249-263: The criteria_snapshot construction is vulnerable to
duplicate acceptance criterion descriptions and must dedupe before appending to
DecisionRecord; update the comprehension in review_gate.py (where
criteria_snapshot is built for
_persistence.decision_records.append_with_next_version inside the
_record_decision flow) to produce a tuple of unique, non-empty c.description
values (preserving original order) from task.acceptance_criteria (e.g., filter
c.description.strip() and skip repeats) so
DecisionRecord.append_with_next_version no longer receives duplicate entries.
In `@src/synthorg/engine/sanitization.py`:
- Around line 38-45: The shared prompt-injection regex _PROMPT_INJECTION_PATTERN
is overbroad and currently strips legitimate templating delimiters like "{{",
"}}", "{%", and "%}", corrupting structured content processed by
sanitize_message() used across checkpoints, summarizers and procedural-memory;
remove the alternation tokens that match "{{", "}}", "{%", and "%}" from the
regex so the sanitizer no longer redacts these templating delimiters while
preserving the other injection patterns, then run tests or sample flows that
exercise sanitize_message() to confirm acceptance criteria, assistant snippets,
and procedural payloads remain intact.
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Around line 160-185: The repository currently constructs and persists a row
before applying DecisionRecord model normalization/validation in
append_with_next_version, causing invalid/unaligned data to be committed; change
append_with_next_version to build a DecisionRecord (using DecisionRecord(...) or
a dedicated _normalize_record helper) and run its validation/normalization
(e.g., blank reason -> None, normalize recorded_at to UTC, deduplicate/normalize
criteria_snapshot, enforce NotBlankStr) before calling _execute_insert, then
pass the validated params to _execute_insert; catch model ValidationError and
convert it into the repository's QueryError or DuplicateRecordError contract
instead of letting it leak, and ensure _row_to_record remains able to read back
persisted rows that match the validated form.
In `@tests/unit/api/fakes.py`:
- Around line 307-359: The fake repository uses broad primitives (str, datetime)
that diverge from DecisionRepository; change the identifier/name parameter
annotations in append_with_next_version, get, list_by_task and list_by_agent to
use NotBlankStr from core.types (including approval_id: NotBlankStr | None and
criteria_snapshot: tuple[NotBlankStr, ...] for tuple variants) and keep other
signatures aligned with DecisionRepository so tests mirror the real contract;
add the NotBlankStr import and adjust any callers in the test file if necessary.
In `@tests/unit/engine/test_review_gate.py`:
- Around line 240-257: The test test_task_without_assignee_proceeds must
exercise the real review-gate branch and assert no decision is persisted when
assigned_to is None: create the task via _make_task with assigned_to=None but
use a realistic pre-review status (e.g., TaskStatus.IN_REVIEW or the status that
triggers ReviewGateService.complete_review path) instead of TaskStatus.CREATED,
instantiate mocks via _make_mock_task_engine and _make_mock_decision_repo and
wrap the repo with _make_mock_persistence, call
ReviewGateService.complete_review, assert mock_te.submit was awaited once AND
assert the decision repo save method (the mock from _make_mock_decision_repo)
was not called to ensure no decision record was persisted.
In `@tests/unit/persistence/sqlite/test_migrations.py`:
- Around line 39-40: Add assertions that validate the actual constraints on the
decision_records table rather than just its existence: query sqlite_master or
PRAGMA table_info/foreign_key_list/index_list to assert the reviewer column has
a CHECK or trigger enforcing reviewer != executor (or validate via a SELECT that
would fail if reviewer==executor exists), assert the foreign key for
executor/reviewer uses ON DELETE RESTRICT, and assert there is a UNIQUE index on
(task_id, version) (or verify via PRAGMA index_list / PRAGMA index_info for the
unique index). Update the tests around decision_records to include these
targeted DDL/PRAGMA checks so regressions in reviewer!=executor, ON DELETE
RESTRICT, and UNIQUE(task_id, version) are detected.
---
Outside diff comments:
In `@src/synthorg/api/controllers/approvals.py`:
- Around line 330-365: The current ordering lets late review-gate failures (from
_signal_resume_intent) occur after app_state.approval_store.save_if_pending and
_publish_approval_event, creating a TOCTOU where a decided ApprovalItem can be
persisted/broadcast then rolled back — fix by making the review-gate transition
atomic with persistence: either (A) perform the actual review-gate transition
(not just _preflight_review_gate) before calling save_if_pending and only
publish after that transition succeeds, or (B) wrap save_if_pending + the call
to _signal_resume_intent in a transactional/compensating unit of work so that if
_signal_resume_intent fails you rollback the persisted decision and suppress
broadcasting; move _publish_approval_event and _log_approval_decision to after
the successful transition/commit and ensure _preflight_review_gate remains as an
early check.
In `@src/synthorg/engine/review_gate.py`:
- Around line 147-156: The rejection branch appends the raw reason (which can be
whitespace) to transition_reason, causing a meaningless suffix; ensure you use
the normalized/processed reason rather than the original input by
trimming/collapsing whitespace once (as done in _record_decision()) and checking
that normalized_reason is truthy before appending to transition_reason; apply
the same change to the second occurrence around APPROVAL_GATE_REVIEW_REWORK (and
the corresponding block at 176-181) so transition_reason only gets a real reason
string.
In `@tests/unit/memory/procedural/test_pipeline.py`:
- Around line 58-90: The shared fixture _make_recovery_result currently
hard-codes failure_category and failure_context (using
FailureCategory.TOOL_FAILURE and {}), which makes it semantically inconsistent
with tests that pass different error messages; change _make_recovery_result to
accept optional parameters failure_category: FailureCategory =
FailureCategory.TOOL_FAILURE and failure_context: dict | None = None (default to
{} when None) and use those values when constructing the RecoveryResult, and
update callers (e.g., tests like test_extracts_error_and_strategy) to pass the
appropriate failure_category/failure_context when needed.
🪄 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: d69b9c2c-9a9f-4557-b017-d46b3f7f6d16
📒 Files selected for processing (40)
CLAUDE.mddocs/design/engine.mddocs/design/memory.mddocs/design/operations.mdsrc/synthorg/api/app.pysrc/synthorg/api/controllers/_approval_review_gate.pysrc/synthorg/api/controllers/approvals.pysrc/synthorg/core/enums.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/engine/checkpoint/resume.pysrc/synthorg/engine/checkpoint/strategy.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/immutable.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.pysrc/synthorg/engine/sanitization.pysrc/synthorg/observability/events/approval_gate.pysrc/synthorg/observability/events/execution.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/persistence/protocol.pysrc/synthorg/persistence/repositories.pysrc/synthorg/persistence/repositories_checkpoint.pysrc/synthorg/persistence/repositories_decisions.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/persistence/sqlite/decision_repo.pysrc/synthorg/persistence/sqlite/schema.sqltests/unit/api/controllers/test_approvals_helpers.pytests/unit/api/fakes.pytests/unit/core/test_enums_failure_category.pytests/unit/engine/checkpoint/test_resume.pytests/unit/engine/checkpoint/test_strategy.pytests/unit/engine/test_decisions.pytests/unit/engine/test_recovery_checkpoint_fields.pytests/unit/engine/test_recovery_failure_diagnosis.pytests/unit/engine/test_review_gate.pytests/unit/memory/procedural/test_pipeline.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/persistence/sqlite/test_migrations.pytests/unit/persistence/test_protocol.py
Addresses the 13 valid findings CodeRabbit posted on the fix commit.
One finding (PEP 758 except-with-as-binding) is rejected as a false
positive since `except A, B as e:` is a SyntaxError in Python.
Critical (2):
- decision_repo.py append_with_next_version now validates the
DecisionRecord model BEFORE executing the INSERT. Previously a
record with duplicate criteria_snapshot entries, blank NotBlankStr
fields, or non-UTC recorded_at could land in the durable log and
then fail to read back. ValidationError is caught and re-raised as
QueryError so the repository's declared error contract is honored.
- review_gate.py criteria_snapshot comprehension now dedupes
acceptance-criteria descriptions while preserving order. Task
does not enforce uniqueness on acceptance_criteria, but
DecisionRecord rejects duplicate criteria_snapshot via the
validate_unique_strings validator; without deduping at the
service boundary the narrowed except would let the resulting
ValidationError propagate out of complete_review.
Major (6):
- review_gate.py ReviewGateService.persistence is now optional. The
preflight / state-transition paths only need task_engine; with the
old gating any task-engine-only deployment/test setup lost the
self-review and missing-task preflight entirely. Decision recording
gracefully degrades to a WARNING-level no-op when persistence is
absent.
- api/app.py constructs ReviewGateService whenever task_engine exists
(not only when both task_engine and persistence exist) so the
fail-fast self-review guarantee still holds without persistence.
- agent_engine.py embeds a sanitized summary of criteria_failed into
the post-recovery transition reason alongside failure_category, so
QUALITY_GATE_FAILED's acceptance-criteria list survives outside
the resumed agent context and is visible to downstream routing /
history. Capped at 5 criteria with "+N more" overflow marker.
- decisions.py metadata is now wrapped in MappingProxyType at
construction so record.metadata["key"] = ... can no longer mutate
an audit record after the fact. Matches CLAUDE.md's "non-Pydantic
internal collections use deepcopy + MappingProxyType wrapping"
convention. arbitrary_types_allowed=True on ConfigDict.
- errors.py SelfReviewError now types task_id / agent_id as
NotBlankStr and drops the local non-blank runtime check. Matches
the project convention of using NotBlankStr for public identifier
fields.
- recovery.py _FAILURE_CATEGORY_RULES now has a TOOL_FAILURE rule
matching "tool invocation" / "tool execution" / "tool error" /
"mcp tool", so infer_failure_category can actually reach the
TOOL_FAILURE category instead of collapsing every tool error to
UNKNOWN.
- sanitization.py prompt-injection pattern no longer filters generic
templating delimiters ({{ }}, {% %}, {% %}). Those are legitimate
structured content in acceptance criteria, assistant snippets, and
procedural-memory payloads; redacting them corrupts valid input
with no meaningful injection-defense benefit.
Minor (5):
- engine.md review gate paragraph rewritten to explicitly call out
that the DecisionRecord append is best-effort -- transient
persistence failures are logged and do not roll back the
transition, while programming errors propagate loudly.
- immutable.py module docstring narrowed to "dict-only" so callers
cannot assume list/tuple deep-copy support that deep_copy_mapping
does not provide.
- fakes.FakeDecisionRepository parameter annotations now use
NotBlankStr for identifier fields to mirror the real
DecisionRepository contract and catch test callers that drift
from the production signature.
- test_review_gate.py unassigned-task regression now asserts
repo.append_with_next_version.assert_not_awaited() so the
"refuse to record unassigned executor" invariant cannot regress
silently. Added new tests covering the persistence=None code
paths for both the preflight and complete_review flows.
- test_migrations.py decision_records test now asserts the
load-bearing DDL constraints explicitly: reviewer != executor
CHECK, ON DELETE RESTRICT on the tasks FK (via PRAGMA
foreign_key_list), and UNIQUE(task_id, version). The old test
only proved the table existed.
False positive rejected:
- CodeRabbit suggested replacing `except (QueryError,
DuplicateRecordError) as exc:` with `except QueryError,
DuplicateRecordError as exc:`. PEP 758 only allows the
comma-separated form WITHOUT an `as` binding; with `as exc` the
parenthesized tuple form is still required. The suggested fix
would be a SyntaxError. This is the inverse of the valid PEP 758
form documented in CLAUDE.md.
Test plan:
- 14,267 unit tests pass (up from 14,150 with new regression tests)
- ruff check + format clean
- mypy strict clean (1549 source files)
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (3)
src/synthorg/persistence/sqlite/decision_repo.py (2)
169-182:⚠️ Potential issue | 🟠 MajorReturn the UTC-normalized timestamp you actually persist.
_build_insert_params()normalizesrecorded_atto UTC, but the returnedrecordis copied from the pre-normalizeddraft_record.append_with_next_version()can therefore return a differentrecorded_atthanget(record_id)immediately afterwards.♻️ Minimal fix
+ normalized_recorded_at = recorded_at.astimezone(UTC) try: draft_record = DecisionRecord( id=record_id, task_id=task_id, approval_id=approval_id, executing_agent_id=executing_agent_id, reviewer_agent_id=reviewer_agent_id, decision=decision, reason=reason, criteria_snapshot=criteria_snapshot, - recorded_at=recorded_at, + recorded_at=normalized_recorded_at, version=1, # placeholder; overwritten after insert metadata=metadata_view, )Also applies to: 205-206
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/persistence/sqlite/decision_repo.py` around lines 169 - 182, The returned DecisionRecord is built from the pre-normalized draft_record so recorded_at can differ from the UTC-normalized value actually persisted; after calling _build_insert_params() (or otherwise obtaining the normalized recorded_at used for insert) update the draft DecisionRecord.recorded_at to that normalized UTC timestamp (so append_with_next_version()/get(record_id) return the same recorded_at); apply the same change where DecisionRecord is constructed later (the other occurrence around the 205-206 area).
253-253: 🛠️ Refactor suggestion | 🟠 MajorUse the repo's PEP 758
except A, B:form consistently.These handlers are still using parenthesized tuples. The project standard for Python 3.14 is the comma-separated spelling, so this file will keep attracting lint churn until the clauses are updated together.
As per coding guidelines, "Use PEP 758 except syntax:
except A, B:(no parentheses) on Python 3.14—ruff enforces this".#!/bin/bash rg -n 'except \([^)]*(sqlite3\.Error|aiosqlite\.Error|ValidationError)[^)]*\) as ' src/synthorg/persistence/sqlite/decision_repo.pyAlso applies to: 290-290, 305-305, 326-326, 382-382, 419-419
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 253, Replace parenthesized multi-exception handlers with the PEP 758 comma-separated form throughout decision_repo.py: change occurrences like "except (sqlite3.Error, aiosqlite.Error) as exc" to the project standard "except sqlite3.Error, aiosqlite.Error as exc" (and similarly for ValidationError combinations). Update every matching except clause (the ones flagged around the current occurrences and at the other positions noted) so they use the comma-separated syntax consistently; search for the exact pattern "except ( ... ) as " in this file and edit each to the form "except A, B as <name>" preserving the existing exception variable name and surrounding logic (e.g., in the exception handlers inside the DecisionRepository methods where sqlite3.Error, aiosqlite.Error, and ValidationError are caught).tests/unit/api/fakes.py (1)
308-320:⚠️ Potential issue | 🟡 MinorTighten the fake to the production repository signature.
recorded_at: datetimeandrole: strare still broader thanDecisionRepository(AwareDatetime/DecisionRoleinsrc/synthorg/persistence/repositories_decisions.py). That lets tests type-check against calls the real repository would reject.Also applies to: 355-360
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/api/fakes.py` around lines 308 - 320, The fake function append_with_next_version accepts broader types (recorded_at: datetime and role: str) than the real DecisionRepository; tighten its signature to match the production repository by replacing recorded_at: datetime with recorded_at: AwareDatetime and role: str with role: DecisionRole (use the same types/imports as in src/synthorg/persistence/repositories_decisions.py), update any references in the fake (and the duplicate at lines ~355-360) to use those types so tests type-check against the real repository signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/engine.md`:
- Around line 1038-1040: The doc currently claims that "task router" and
"reassignment selector" parse the transition-reason wire format, which
overstates shipped behavior; update the paragraph referencing the
transition-reason format ("Post-recovery status: <status>
(failure_category=<value>)" and the embedded field failure_category) to remove
the specific consumer names (task router, reassignment selector) and instead
refer to "downstream consumers" or "future routing components"; also add a brief
note that embedding failure_category is provided by this PR but the
routing/reassignment work is deferred so those parsers are not yet part of this
change (leave references to FailAndReassignStrategy and
CheckpointRecoveryStrategy._build_resume_result /
infer_failure_category_without_evidence() as-is if present).
In `@src/synthorg/engine/decisions.py`:
- Around line 104-123: The current _deep_copy_and_freeze_metadata validator
wraps only the top-level mapping in MappingProxyType but leaves nested dict/list
values mutable; update the logic so after deep_copy_mapping(value) you
recursively freeze nested containers (e.g., convert dict -> MappingProxyType,
list -> tuple, set -> frozenset) before returning so record.metadata and any
nested structures are truly immutable; you can implement a helper like
freeze_recursive(copied) and call it from _deep_copy_and_freeze_metadata (ensure
you still unwrap a MappingProxyType input into dict first, and then re-wrap the
top-level mapping with MappingProxyType after freezing nested contents).
In `@src/synthorg/engine/recovery.py`:
- Around line 421-426: The EXECUTION_RECOVERY_DIAGNOSIS event is being emitted
twice; remove the duplicate logger.info call in recovery.py (the call that
passes EXECUTION_RECOVERY_DIAGNOSIS, task_id=task_execution.task.id,
failure_category=category.value,
criteria_failed_count=len(result.criteria_failed)) so the diagnosis is only
logged by AgentEngine._post_execution_pipeline (see
AgentEngine._post_execution_pipeline that already logs RecoveryResult). Simply
delete that logger.info block (or guard it out) in the recovery logic so the
event is emitted once.
In `@src/synthorg/engine/review_gate.py`:
- Around line 181-186: Normalize the review reason string once inside
complete_review() before passing it to both the transition and
_record_decision(): trim and collapse whitespace-only values to None (the same
normalization _record_decision() does) and reuse that normalized value for the
transition reason and for the approval_id/DecisionRecord.reason payload; update
both the call around await self._record_decision(... approval_id=approval_id,
...) at the first site and the other call near lines 268-269 to pass the
normalized_reason variable instead of the raw reason string so task history and
DecisionRecord.reason stay consistent.
In `@tests/unit/engine/test_review_gate.py`:
- Around line 355-372: Update the unit test
test_decision_includes_criteria_snapshot to exercise the
deduplication/blank-stripping logic by passing a task with duplicate and blank
criteria (e.g., ("JWT login", "JWT login", " ", "Refresh works")) to
_make_task, call ReviewGateService.complete_review as before, then check that
repo.append_with_next_version.call_args.kwargs["criteria_snapshot"] equals the
cleaned tuple ("JWT login", "Refresh works"); this targets
ReviewGateService._record_decision and the repo.append_with_next_version
behavior so the ValidationError regression will be caught.
- Around line 76-80: The helper _make_mock_persistence currently sets
decision_records on type(persistence) using PropertyMock which modifies the
MagicMock class globally; instead assign the provided repo directly to the
instance (persistence.decision_records = repo) so each call returns an
independent instance attribute and avoids cross-test coupling—update the
function to stop using type(persistence) or PropertyMock and attach repo to
persistence.decision_records.
---
Duplicate comments:
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Around line 169-182: The returned DecisionRecord is built from the
pre-normalized draft_record so recorded_at can differ from the UTC-normalized
value actually persisted; after calling _build_insert_params() (or otherwise
obtaining the normalized recorded_at used for insert) update the draft
DecisionRecord.recorded_at to that normalized UTC timestamp (so
append_with_next_version()/get(record_id) return the same recorded_at); apply
the same change where DecisionRecord is constructed later (the other occurrence
around the 205-206 area).
- Line 253: Replace parenthesized multi-exception handlers with the PEP 758
comma-separated form throughout decision_repo.py: change occurrences like
"except (sqlite3.Error, aiosqlite.Error) as exc" to the project standard "except
sqlite3.Error, aiosqlite.Error as exc" (and similarly for ValidationError
combinations). Update every matching except clause (the ones flagged around the
current occurrences and at the other positions noted) so they use the
comma-separated syntax consistently; search for the exact pattern "except ( ...
) as " in this file and edit each to the form "except A, B as <name>" preserving
the existing exception variable name and surrounding logic (e.g., in the
exception handlers inside the DecisionRepository methods where sqlite3.Error,
aiosqlite.Error, and ValidationError are caught).
In `@tests/unit/api/fakes.py`:
- Around line 308-320: The fake function append_with_next_version accepts
broader types (recorded_at: datetime and role: str) than the real
DecisionRepository; tighten its signature to match the production repository by
replacing recorded_at: datetime with recorded_at: AwareDatetime and role: str
with role: DecisionRole (use the same types/imports as in
src/synthorg/persistence/repositories_decisions.py), update any references in
the fake (and the duplicate at lines ~355-360) to use those types so tests
type-check against the real repository signature.
🪄 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: b703e766-df42-4991-a96a-0ff0f1969575
📒 Files selected for processing (14)
docs/design/engine.mdsrc/synthorg/api/app.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/immutable.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.pysrc/synthorg/engine/sanitization.pysrc/synthorg/persistence/sqlite/decision_repo.pytests/unit/api/fakes.pytests/unit/engine/test_review_gate.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/persistence/sqlite/test_migrations.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/errors.pytests/unit/persistence/sqlite/test_migrations.pysrc/synthorg/engine/sanitization.pysrc/synthorg/engine/agent_engine.pytests/unit/engine/test_review_gate.pysrc/synthorg/engine/recovery.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/api/fakes.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.pysrc/synthorg/api/app.pysrc/synthorg/engine/decisions.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/sanitization.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.pysrc/synthorg/api/app.pysrc/synthorg/engine/decisions.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/engine/immutable.pysrc/synthorg/engine/errors.pysrc/synthorg/engine/sanitization.pysrc/synthorg/engine/agent_engine.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.pysrc/synthorg/api/app.pysrc/synthorg/engine/decisions.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/persistence/sqlite/test_migrations.pytests/unit/engine/test_review_gate.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/api/fakes.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/persistence/sqlite/test_migrations.pytests/unit/engine/test_review_gate.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/api/fakes.py
🧠 Learnings (47)
📓 Common learnings
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...
📚 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/engine/immutable.pysrc/synthorg/engine/decisions.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/engine/immutable.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/decisions.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/decisions.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/engine/immutable.pysrc/synthorg/engine/decisions.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/engine/immutable.pysrc/synthorg/engine/decisions.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/decisions.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/engine/immutable.pysrc/synthorg/engine/decisions.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 **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/decisions.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/engine/immutable.pysrc/synthorg/engine/decisions.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/engine/immutable.pysrc/synthorg/engine/decisions.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/engine/immutable.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/engine/immutable.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:
src/synthorg/engine/errors.pysrc/synthorg/engine/agent_engine.pytests/unit/api/fakes.pysrc/synthorg/engine/review_gate.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed
Applied to files:
src/synthorg/engine/errors.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/engine/errors.pytests/unit/api/fakes.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : 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/engine/errors.pytests/unit/api/fakes.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/engine/errors.pytests/unit/api/fakes.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 Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; 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/engine/errors.pytests/unit/api/fakes.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use `computed_field` for derived values instead of storing + validating redundant fields; 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/engine/errors.pytests/unit/api/fakes.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/engine/errors.pysrc/synthorg/engine/review_gate.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/engine/errors.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:
src/synthorg/engine/agent_engine.pydocs/design/engine.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/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
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/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/engine/test_review_gate.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.
Applied to files:
tests/unit/engine/test_review_gate.pysrc/synthorg/api/app.pydocs/design/engine.md
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic; for tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`
Applied to files:
tests/unit/engine/test_review_gate.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/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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
tests/unit/api/fakes.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/api/app.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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/engine/review_gate.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions
Applied to files:
src/synthorg/engine/review_gate.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/engine/review_gate.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.
Applied to files:
src/synthorg/engine/review_gate.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/persistence/sqlite/decision_repo.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: NEVER create a PR directly with `gh pr create`—ALWAYS use `/pre-pr-review` (or `/pre-pr-review quick` for trivial/docs-only); for trivial changes skip agents but still run automated checks; fix all valid issues found by review agents
Applied to files:
src/synthorg/api/app.py
📚 Learning: 2026-04-01T05:46:17.064Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T05:46:17.064Z
Learning: Fix all valid issues found by review agents (including pre-existing issues in surrounding code, suggestions, and adjacent findings) — never skip or defer
Applied to files:
src/synthorg/api/app.pydocs/design/engine.md
🔇 Additional comments (2)
src/synthorg/engine/decisions.py (1)
91-94: No action needed; the code is correct as-is.
MappingProxyType[str, Any]is subscriptable on Python 3.14+ and imports safely. The validator properly deep-copies the input usingcopy.deepcopy()and wraps it inMappingProxyType, which follows the freezing convention. Changing the annotation toMapping[str, Any]would only reduce type precision without solving any real issue.tests/unit/persistence/sqlite/test_decision_repo.py (1)
346-378: This test correctly exercises the atomic version-increment guarantee.The test is designed to verify that concurrent
asyncio.gather()calls toappend_with_next_version()all receive distinct monotonic versions without collisions—exactly as claimed in the docstring. Sinceaiosqliteserializes per-connection operations through a worker thread, concurrent coroutines are queued and executed sequentially at the SQL level. The atomicINSERTstatement withCOALESCE(MAX(version), 0) + 1is the protection mechanism, and the test correctly validates it by asserting no UNIQUE(task_id, version) collisions occur across 20 writes.Note: Multi-connection testing with
:memory:is not feasible; each connection gets an isolated database. A shared in-memory DB would require the special URI formatfile::memory:?cache=sharedand would be a different test scenario.
Addresses 7 valid findings CodeRabbit posted on the round-2 fix
commit. One finding (PEP 758 `except A, B as exc:` suggestion) is
rejected again as a false positive -- that form is a SyntaxError in
Python; only the comma form without an `as` binding is allowed.
Valid fixes:
- docs/design/engine.md: soften the transition-reason wire-format
paragraph. Previously claimed "task router / reassignment
selector" parse the `(failure_category=<value>)` suffix, which
overstates shipped behavior -- the actual routing work is
deferred. Now refers to "future downstream consumers" and
explicitly notes the routing/reassignment implementation is
deferred while keeping the key/format contract stable.
- src/synthorg/engine/decisions.py: recursively freeze nested
containers inside ``DecisionRecord.metadata``. The previous
implementation wrapped only the top-level mapping in
``MappingProxyType``, so ``record.metadata["nested"]["key"] =
...`` still worked. New ``_freeze_recursive`` helper converts
dict -> MappingProxyType, list -> tuple, set -> frozenset at
every depth; called from the existing
``_deep_copy_and_freeze_metadata`` field validator.
- src/synthorg/engine/recovery.py: remove the duplicate
``EXECUTION_RECOVERY_DIAGNOSIS`` log emitted from
``FailAndReassignStrategy.recover``. The event is already
emitted by ``AgentEngine._post_execution_pipeline`` with fuller
context (agent_id); logging it twice made dashboard counts
ambiguous. Dropped the now-unused import.
- src/synthorg/engine/review_gate.py: normalize ``reason`` once at
the top of ``complete_review`` so the task-transition reason
string and the ``DecisionRecord.reason`` audit field carry the
same canonical value. Previously the transition reason used the
raw input while ``_record_decision`` re-normalized, letting
e.g. whitespace-only reasons drift between the two sites.
- src/synthorg/persistence/sqlite/decision_repo.py: normalize
``recorded_at`` to UTC *before* constructing the draft
``DecisionRecord`` so the returned record and any subsequent
``get(record_id)`` read-back carry the same timestamp.
Previously the draft preserved the caller's original timezone
while the persisted row was UTC-normalized, so the record
returned from ``append_with_next_version`` could disagree with
later fetches. Also added ``_unfreeze_for_json`` helper so
``_build_insert_params`` can JSON-serialize metadata that now
contains ``MappingProxyType`` values after recursive freezing.
- tests/unit/engine/test_review_gate.py:
* ``_make_mock_persistence`` now assigns ``decision_records``
directly to the ``MagicMock`` instance instead of using
``type(persistence).decision_records = PropertyMock(...)``.
The old pattern mutated a class-level descriptor shared
across pytest-xdist workers and risked cross-test coupling;
per-instance assignment keeps each fake isolated.
* ``test_decision_includes_criteria_snapshot`` now passes a
task with duplicate criteria (``("JWT login", "JWT login",
"Refresh works", "Refresh works")``) and asserts the snapshot
the service forwards to the repo is the deduped tuple
``("JWT login", "Refresh works")``. Pins the dedup step that
keeps ``DecisionRecord.criteria_snapshot``'s unique-strings
validator from crashing ``complete_review``.
- tests/unit/api/fakes.py: tighten ``FakeDecisionRepository``
signature types -- ``recorded_at: AwareDatetime`` instead of
``datetime``, ``role: DecisionRole`` instead of ``str`` -- so
the fake matches the production ``DecisionRepository`` protocol
and test callers cannot drift from the real contract.
- tests/unit/engine/test_decisions.py: ``test_metadata_deep_copied``
renamed to ``test_metadata_deep_copied_and_frozen`` and updated
to assert the top-level mapping is ``MappingProxyType`` AND
nested dicts are recursively frozen.
- tests/unit/persistence/sqlite/test_decision_repo.py:
``test_criteria_and_metadata_round_trip`` updated to compare the
nested metadata value via ``dict(...)`` so the test does not
depend on the exact container type (now ``MappingProxyType``
after recursive freezing).
False positive rejected:
- CodeRabbit again suggested replacing ``except (sqlite3.Error,
aiosqlite.Error) as exc:`` with ``except sqlite3.Error,
aiosqlite.Error as exc:``. PEP 758 only permits the
comma-separated form WITHOUT an ``as`` binding; the suggested
fix is a SyntaxError in Python. Same false positive as round 2,
same rejection.
Test plan:
- 14,267 unit tests pass
- ruff check + format clean
- mypy strict clean (1549 source files)
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/engine/decisions.py`:
- Around line 30-48: The _freeze_recursive function currently ignores tuples,
leaving mutable objects inside them unfrozen; update _freeze_recursive (the
function named _freeze_recursive) to recognize tuple instances like list
instances and recursively freeze each element, returning a tuple of frozen
elements (i.e., add an isinstance(value, tuple) branch that mirrors the list
handling) so nested mutable dicts/sets/lists inside tuples are converted to
MappingProxyType/frozenset/tuple respectively.
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Around line 194-216: The code currently catches DecisionRecord ValidationError
in the try/except and converts it into a QueryError, which hides schema/input
bugs; change the handler in the DecisionRecord construction block so that
ValidationError from DecisionRecord is not downgraded—either remove the except
that wraps into QueryError or re-raise the original ValidationError after
logging (keep the logger.warning call using
PERSISTENCE_DECISION_RECORD_SAVE_FAILED and the same contextual fields), so
malformed criteria_snapshot/IDs surface as validation failures instead of being
treated as best-effort QueryError.
In `@tests/unit/api/fakes.py`:
- Around line 4-9: The fake decision repo currently preserves the caller
timezone for recorded_at; update the fake implementation (the
append_with_next_version method in the fake decision repository used in
tests/unit/api/fakes.py) to normalize any datetime it produces to UTC (e.g.,
call .astimezone(datetime.timezone.utc) or otherwise convert to an aware UTC
datetime) so recorded_at matches the production behavior of
SQLiteDecisionRepository.append_with_next_version(); ensure AwareDatetime/typing
imports are used consistently and adjust any tests that assert timezone-aware
UTC values if necessary.
In `@tests/unit/persistence/sqlite/test_decision_repo.py`:
- Around line 390-409: Update the
test_self_review_rejected_at_db_check_constraint to assert the database CHECK
rather than relying on append_with_next_version/DecisionRecord validation:
remove the call to _append/SQLiteDecisionRepository.append_with_next_version and
instead perform a direct SQL INSERT that sets reviewer_agent_id ==
executing_agent_id (same values as before) against the decisions table using the
migrated_db connection, then assert that the DB raises the constraint error
(QueryError) — this mirrors the bogus-decision CHECK test pattern so the
schema-level CHECK(reviewer_agent_id != executing_agent_id) is actually
exercised.
🪄 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: 7c84aee7-8a80-424b-9c20-6addae400057
📒 Files selected for processing (9)
docs/design/engine.mdsrc/synthorg/engine/decisions.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.pytests/unit/api/fakes.pytests/unit/engine/test_decisions.pytests/unit/engine/test_review_gate.pytests/unit/persistence/sqlite/test_decision_repo.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
tests/unit/api/fakes.pytests/unit/engine/test_decisions.pysrc/synthorg/engine/recovery.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/engine/test_review_gate.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/api/fakes.pytests/unit/engine/test_decisions.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/engine/test_review_gate.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/api/fakes.pytests/unit/engine/test_decisions.pytests/unit/persistence/sqlite/test_decision_repo.pytests/unit/engine/test_review_gate.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/engine/recovery.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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/engine/recovery.pysrc/synthorg/engine/decisions.pysrc/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
🧠 Learnings (39)
📚 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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : 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:
tests/unit/api/fakes.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use `computed_field` for derived values instead of storing + validating redundant fields; use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
tests/unit/api/fakes.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:
tests/unit/api/fakes.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 Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.
Applied to files:
tests/unit/api/fakes.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/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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
tests/unit/api/fakes.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/api/fakes.pysrc/synthorg/engine/recovery.pysrc/synthorg/engine/review_gate.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 Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/engine/recovery.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/engine/recovery.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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/engine/test_review_gate.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.
Applied to files:
tests/unit/engine/test_review_gate.pysrc/synthorg/engine/review_gate.pydocs/design/engine.md
📚 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/engine/decisions.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/engine/decisions.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/engine/decisions.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/engine/decisions.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/engine/decisions.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/engine/decisions.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 **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/engine/decisions.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/engine/decisions.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/engine/decisions.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/engine/decisions.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/engine/decisions.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/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.
Applied to files:
src/synthorg/engine/review_gate.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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/engine/review_gate.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions
Applied to files:
src/synthorg/engine/review_gate.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/engine/review_gate.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/engine/review_gate.pysrc/synthorg/persistence/sqlite/decision_repo.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/engine/review_gate.py
📚 Learning: 2026-04-01T05:46:17.064Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T05:46:17.064Z
Learning: Fix all valid issues found by review agents (including pre-existing issues in surrounding code, suggestions, and adjacent findings) — never skip or defer
Applied to files:
src/synthorg/engine/review_gate.pydocs/design/engine.md
📚 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/persistence/sqlite/decision_repo.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/engine.md
🔇 Additional comments (7)
src/synthorg/engine/review_gate.py (7)
313-326: Use PEP 758 syntax without parentheses for the multi-except handler.Line 313 still uses the tuple-based form. Per coding guidelines, Python 3.14 requires
except A, B:syntax without parentheses.Proposed fix
- except (QueryError, DuplicateRecordError) as exc: + except QueryError, DuplicateRecordError as exc:
1-41: LGTM — module docstring and imports are well-organized.The updated docstring clearly documents the no-self-review enforcement and audit trail features. Imports are appropriate for the new functionality.
44-72: LGTM — clean API design with required task_engine and optional persistence.The docstring clearly documents the degradation behavior when persistence is absent, and making
task_enginerequired ensures self-review enforcement is always available.
74-111: LGTM — well-designed preflight with appropriate error handling.The method properly logs at WARNING level before raising
TaskNotFoundError, returns the validated task for caller inspection, and delegates to_check_self_reviewfor the self-review enforcement.
150-195: LGTM — reason normalization properly addresses the consistency concern.The single normalization at line 158, reused for both the transition reason (line 168) and the audit record (line 193), ensures they stay in sync. The defense-in-depth pattern of re-running the preflight is well-documented.
197-218: LGTM — proper self-review enforcement with appropriate logging.The handling of
Noneassigned_to as an anomalous state worth operator attention (WARNING log) is a sensible design choice, and the method properly logs before raisingSelfReviewError.
280-292: LGTM — deduplication properly addresses the criteria uniqueness gap.The order-preserving deduplication ensures
DecisionRecord.criteria_snapshotwon't reject duplicate entries from tasks that lack unique criteria enforcement.
Addresses 4 valid findings CodeRabbit posted on the round-3 commit.
1. src/synthorg/engine/decisions.py: ``_freeze_recursive`` now also
handles ``tuple`` inputs. Tuples are immutable but their
*elements* can still be mutable (dicts, sets, lists), so the
recursive freeze must descend into tuples too or a payload like
``metadata={"trace": ({"a": 1},)}`` would leak a mutable dict
through the frozen view.
2. src/synthorg/persistence/sqlite/decision_repo.py: the draft-record
validation block no longer wraps ``ValidationError`` as
``QueryError``. The original round-2 rationale for wrapping (keep
the repo's declared error contract narrow) collided with the
review-gate service's narrowed ``except (QueryError,
DuplicateRecordError)``: any programming error or schema drift
that surfaced as ``ValidationError`` in the draft construction
was being re-wrapped as ``QueryError`` and then silently swallowed
by the service as "transient audit loss". Round 4 correctly
inverts this: log the validation failure with context for
operators, then re-raise the original ``ValidationError`` so it
blows through the service's narrow catch and surfaces loudly in
dev/CI. Docstring updated to list ``ValidationError`` in the
Raises section.
3. tests/unit/api/fakes.py: ``FakeDecisionRepository.append_with_next_version``
now normalizes ``recorded_at`` to UTC via ``.astimezone(UTC)``,
matching the production ``SQLiteDecisionRepository`` behavior.
Previously the fake preserved the caller's timezone while the
real repo stored UTC, so test callers that passed a non-UTC
datetime got different values from the fake vs the real backend.
4. tests/unit/persistence/sqlite/test_decision_repo.py:
``test_self_review_rejected_at_db_check_constraint`` now uses a
direct SQL INSERT (via ``migrated_db.execute``) to bypass the
``DecisionRecord`` Pydantic validator and exercise the
schema-level ``CHECK(reviewer_agent_id != executing_agent_id)``
constraint explicitly. The previous implementation went through
``append_with_next_version`` -> draft ``DecisionRecord`` ->
Pydantic self-review validator, which fires before the SQL layer
is reached, so the test was actually exercising the model
validator (already covered by
``test_self_review_rejected_at_pydantic_model``) and not the DB
CHECK. The direct-INSERT form asserts
``exc.sqlite_errorname == "SQLITE_CONSTRAINT_CHECK"`` to guard
against any future raw-SQL / migration / third-party-backend
path that might bypass the model layer.
Test updates:
- ``test_criteria_and_metadata_round_trip`` already compares via
``dict(...)`` to accommodate the recursive-freeze change; no
further test churn needed for item 1.
- ``test_metadata_deep_copied_and_frozen`` already asserts nested
dicts are ``MappingProxyType``; the tuple branch is covered
implicitly since the existing tests don't stress tuple nesting,
and the new tuple branch is a safety net rather than a regression
fix.
Test plan:
- 14,267 unit tests pass
- ruff check + format clean
- mypy strict clean (1549 source files)
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/synthorg/persistence/sqlite/decision_repo.py (1)
229-240:⚠️ Potential issue | 🟠 MajorLog pre-SQL serialization failures too.
_build_insert_params()can still raiseTypeErroron non-JSON-serializable metadata before the SQL error handling starts. That should still surface as a programming error, but right now it bypasses the structured warning/error log for this write path.🪵 Suggested fix
- params = _build_insert_params( - record_id=record_id, - task_id=task_id, - approval_id=approval_id, - executing_agent_id=executing_agent_id, - reviewer_agent_id=reviewer_agent_id, - decision=decision, - reason=draft_record.reason, - criteria_snapshot=draft_record.criteria_snapshot, - recorded_at=draft_record.recorded_at, - metadata=dict(draft_record.metadata), - ) + try: + params = _build_insert_params( + record_id=record_id, + task_id=task_id, + approval_id=approval_id, + executing_agent_id=executing_agent_id, + reviewer_agent_id=reviewer_agent_id, + decision=decision, + reason=draft_record.reason, + criteria_snapshot=draft_record.criteria_snapshot, + recorded_at=draft_record.recorded_at, + metadata=dict(draft_record.metadata), + ) + except TypeError as exc: + logger.warning( + PERSISTENCE_DECISION_RECORD_SAVE_FAILED, + record_id=record_id, + task_id=task_id, + error=str(exc), + error_type="TypeError", + ) + raiseAs per coding guidelines, "All error paths must log at WARNING or ERROR with context before raising".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/persistence/sqlite/decision_repo.py` around lines 229 - 240, The call to _build_insert_params(...) can raise TypeError (non-JSON-serializable metadata) before the SQL error handling; wrap the params = _build_insert_params(...) invocation in a try/except that catches TypeError, log a WARNING or ERROR with contextual fields (record_id, task_id, approval_id, executing_agent_id, reviewer_agent_id and draft_record.metadata) and the exception, then re-raise the error so it surfaces as a programming error; place this around the existing params assignment (reference: _build_insert_params, draft_record, and the params variable).
🤖 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/persistence/sqlite/decision_repo.py`:
- Around line 156-163: The append path performs INSERT → SELECT →
commit()/rollback() across awaits on a shared aiosqlite.Connection and must be
serialized to avoid cross-coroutine rollbacks wiping other work; add a
per-connection asyncio.Lock (e.g. self._conn_lock) on the DecisionRepo instance
and acquire it around the whole critical section that does the
INSERT/SELECT/commit/_rollback_quietly() (the method implementing the append
sequence and any other blocks noted around the ranges referenced), releasing in
a finally block so commit/rollback and error handling run while holding the
lock; apply the same lock protection to the other affected code regions (the
similar sequences at the other noted ranges) so all DB mutation sequences
against that connection are serialized.
- Around line 448-452: The code in decision_repo.py currently deserializes
raw_criteria with tuple(json.loads(raw_criteria)) which silently accepts
non-array JSON (e.g., strings or objects) and corrupts data; change the read
path that sets parsed["criteria_snapshot"] to first json.loads(raw_criteria) and
then validate that the result is a list/tuple (isinstance(..., list) or tuple)
before converting to tuple; if the loaded value is not an array, raise a
deserialization/ValueError (or propagate an error) so corrupted rows fail
instead of being silently converted—update the handling around
parsed["criteria_snapshot"] in the function that processes
row["criteria_snapshot"] to perform this type check and raise on invalid shapes.
In `@tests/unit/api/fakes.py`:
- Around line 297-381: The tests file exceeds the line limit; extract the
decision-specific fake by moving the FakeDecisionRepository class (and its
helpers like append_with_next_version, get, list_by_task, list_by_agent) into a
new small test-support module (e.g., decision_fakes) and replace the original
class in tests/unit/api/fakes.py with an import from that new module; update any
test imports to reference the new module and ensure the new module preserves the
same public API and uses MappingProxyType, UTC normalization, and the same
exception types (DuplicateRecordError) so behavior remains identical.
---
Duplicate comments:
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Around line 229-240: The call to _build_insert_params(...) can raise TypeError
(non-JSON-serializable metadata) before the SQL error handling; wrap the params
= _build_insert_params(...) invocation in a try/except that catches TypeError,
log a WARNING or ERROR with contextual fields (record_id, task_id, approval_id,
executing_agent_id, reviewer_agent_id and draft_record.metadata) and the
exception, then re-raise the error so it surfaces as a programming error; place
this around the existing params assignment (reference: _build_insert_params,
draft_record, and the params variable).
🪄 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: 51f8c255-5b9b-4d29-b340-6defbe6b7b2f
📒 Files selected for processing (4)
src/synthorg/engine/decisions.pysrc/synthorg/persistence/sqlite/decision_repo.pytests/unit/api/fakes.pytests/unit/persistence/sqlite/test_decision_repo.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Deploy Preview
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
tests/unit/api/fakes.pysrc/synthorg/engine/decisions.pysrc/synthorg/persistence/sqlite/decision_repo.pytests/unit/persistence/sqlite/test_decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/api/fakes.pytests/unit/persistence/sqlite/test_decision_repo.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/api/fakes.pytests/unit/persistence/sqlite/test_decision_repo.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/engine/decisions.pysrc/synthorg/persistence/sqlite/decision_repo.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/engine/decisions.pysrc/synthorg/persistence/sqlite/decision_repo.py
🧠 Learnings (36)
📚 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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : 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:
tests/unit/api/fakes.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use `computed_field` for derived values instead of storing + validating redundant fields; use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.py
📚 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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking
Applied to files:
tests/unit/api/fakes.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 : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic; for tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`
Applied to files:
tests/unit/api/fakes.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
tests/unit/api/fakes.pysrc/synthorg/engine/decisions.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/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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
tests/unit/api/fakes.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/api/fakes.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/engine/decisions.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/engine/decisions.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/engine/decisions.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/engine/decisions.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/engine/decisions.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 **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/engine/decisions.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/engine/decisions.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/engine/decisions.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/engine/decisions.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/engine/decisions.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/persistence/sqlite/decision_repo.pytests/unit/persistence/sqlite/test_decision_repo.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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 src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
🔇 Additional comments (2)
src/synthorg/engine/decisions.py (1)
130-151: Nice hardening of audit-metadata immutability.Rebuilding
MappingProxyTypeinputs and recursively freezing nested containers closes the remaining post-construction mutation paths on append-only decision records.tests/unit/persistence/sqlite/test_decision_repo.py (1)
390-421: Good DB-level coverage for the no-self-review invariant.By bypassing
DecisionRecordand inserting directly, this test now actually exercises the SQLiteCHECK(reviewer_agent_id != executing_agent_id)instead of just re-testing the model validator.
Addresses 4 valid findings CodeRabbit posted on the round-4 commit. 1. src/synthorg/persistence/sqlite/decision_repo.py: serialize the append critical section with a per-instance ``asyncio.Lock``. ``SQLiteDecisionRepository.append_with_next_version`` performs INSERT -> SELECT version -> commit across multiple awaits on a shared ``aiosqlite.Connection``. Without a lock, coroutine B's ``_rollback_quietly`` on an error could wipe coroutine A's in-flight INSERT (or vice versa). A per-instance lock serializes this repository's own writers; cross-repository coordination remains the backend's responsibility but is out of scope here. The lock is acquired via ``async with`` around the ``_execute_insert`` call so the entire transactional scope runs under exclusive access. 2. src/synthorg/persistence/sqlite/decision_repo.py: ``_row_to_record`` now shape-checks the decoded ``criteria_snapshot`` JSON payload and raises ``TypeError`` if it is not a list. Previously ``tuple(json.loads(raw_criteria))`` would silently iterate over a string's characters or a dict's keys if a row somehow contained a non-array JSON value (e.g. from a migration bug or a third-party backend), producing garbage data. The ``TypeError`` is caught alongside the existing ``ValidationError`` / ``JSONDecodeError`` handlers and surfaces as ``QueryError`` with full context. 3. src/synthorg/persistence/sqlite/decision_repo.py: wrap ``_build_insert_params`` in a ``try/except TypeError`` so non-JSON-serializable metadata (datetimes, custom classes, recursive structures) surfaces as a loud programming error with full context (record_id, task_id, approval_id, executing / reviewer agent IDs) instead of crashing mid-INSERT without explanation. ``TypeError`` is re-raised so the review-gate service's narrowed ``except (QueryError, DuplicateRecordError)`` does not silently swallow it. 4. tests/unit/api/fakes.py -> tests/unit/api/fakes_decisions.py: ``FakeDecisionRepository`` extracted into a new module to keep ``fakes.py`` under the 800-line CLAUDE.md budget (926 -> 843). Re-exported from ``fakes`` so existing test imports keep working. The new module preserves the exact public API -- UTC normalization, ``MappingProxyType`` metadata wrapping, and ``DuplicateRecordError`` semantics all unchanged. Test plan: - 14,267 unit tests pass - ruff check + format clean - mypy strict clean (1550 source files)
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
tests/unit/api/fakes.py (1)
291-297: 🛠️ Refactor suggestion | 🟠 MajorThis module is still over the 800-line cap.
The extraction helps, but
tests/unit/api/fakes.pystill ends at Line 843, so the repo's file-size rule is still violated. Please move one more fake/backend cluster out of this module instead of only re-exporting from here.As per coding guidelines: "files must be less than 800 lines".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/api/fakes.py` around lines 291 - 297, This file still exceeds the 800-line limit; pick another large fake/backend cluster class or group (e.g., the next biggest Fake*Repository or Fake*Backend class defined in tests.unit.api.fakes.py), move its full implementation into a new sibling module (following the pattern used for FakeDecisionRepository -> tests.unit.api.fakes_decisions), then import-and-re-export it from tests.unit.api.fakes using the same name (add a from tests.unit.api.fakes_<name> import <ClassName> and re-export line with appropriate noqa comments). Ensure the moved classes, imports, and any dependent helpers are updated so tests still import the same symbol names and that tests/unit/api/fakes.py drops below 800 lines.
🤖 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/persistence/sqlite/decision_repo.py`:
- Around line 204-223: Before normalizing recorded_at to UTC in
append_with_next_version (or the function containing recorded_at_utc =
recorded_at.astimezone(UTC)), add an explicit runtime check that recorded_at is
timezone-aware (e.g., recorded_at.tzinfo is not None) and raise a ValueError
with a clear message like "recorded_at must be timezone-aware" if it is naive;
ensure this validation is applied wherever decisions are constructed in test
fakes as well so tests mirror the production contract.
In `@tests/unit/api/fakes_decisions.py`:
- Around line 70-83: The fake currently stores caller-provided metadata directly
behind MappingProxyType, causing shape and mutability drift vs production;
before constructing DecisionRecord in the fake (where DecisionRecord is created
and self._records[record_id] is set), run the same metadata
normalization/snapshot path used in
src/synthorg/persistence/sqlite/decision_repo.py (the functions that normalize
and JSON-serialize metadata during insert) so the fake stores a deep-copied,
normalized snapshot; i.e., deep-copy the incoming metadata, apply the repo’s
normalization/serialization routine to produce the persisted shape, then wrap
that resulting dict in MappingProxyType and pass it into DecisionRecord so
stored metadata matches production behavior and is immutable.
---
Duplicate comments:
In `@tests/unit/api/fakes.py`:
- Around line 291-297: This file still exceeds the 800-line limit; pick another
large fake/backend cluster class or group (e.g., the next biggest
Fake*Repository or Fake*Backend class defined in tests.unit.api.fakes.py), move
its full implementation into a new sibling module (following the pattern used
for FakeDecisionRepository -> tests.unit.api.fakes_decisions), then
import-and-re-export it from tests.unit.api.fakes using the same name (add a
from tests.unit.api.fakes_<name> import <ClassName> and re-export line with
appropriate noqa comments). Ensure the moved classes, imports, and any dependent
helpers are updated so tests still import the same symbol names and that
tests/unit/api/fakes.py drops below 800 lines.
🪄 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: e7a50eb3-d668-4705-8260-541253153b46
📒 Files selected for processing (3)
src/synthorg/persistence/sqlite/decision_repo.pytests/unit/api/fakes.pytests/unit/api/fakes_decisions.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: Deploy Preview
- 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 (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
tests/unit/api/fakes.pytests/unit/api/fakes_decisions.pysrc/synthorg/persistence/sqlite/decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/api/fakes.pytests/unit/api/fakes_decisions.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/api/fakes.pytests/unit/api/fakes_decisions.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
🧠 Learnings (20)
📓 Common learnings
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...
📚 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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : 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:
tests/unit/api/fakes.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use `computed_field` for derived values instead of storing + validating redundant fields; use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.py
📚 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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking
Applied to files:
tests/unit/api/fakes.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 : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.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/persistence/sqlite/decision_repo.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
Addresses 3 valid findings CodeRabbit posted on the round-5 commit. 1. src/synthorg/persistence/sqlite/decision_repo.py: reject naive datetimes explicitly in ``append_with_next_version``. The parameter type is ``AwareDatetime`` but there is no runtime enforcement at the function boundary -- a naive datetime would silently pass through ``astimezone(UTC)`` (which assumes local time for naive inputs) and produce a wall-clock-disagreeing timestamp in the persisted row. Fail fast with ``ValueError`` and a clear message before any SQL runs. 2. tests/unit/api/fakes_decisions.py: mirror the same naive-datetime rejection in ``FakeDecisionRepository`` so tests observe identical error behavior from the fake and the real backend. Also deep-copy the caller's metadata before wrapping in ``MappingProxyType``, matching production behavior where ``_build_insert_params``'s ``json.dumps`` step produces an independent snapshot decoupled from the caller's dict. Without the deep copy, a test that mutates its source metadata dict after calling the fake would see the mutation reflected in the stored record -- divergent from the real repo which stores a serialized snapshot. 3. tests/unit/api/fakes.py -> tests/unit/api/fakes_backend.py: extract ``FakePersistenceBackend`` (145 lines) into a new sibling module so ``fakes.py`` drops from 843 -> 705 lines, back under the 800-line CLAUDE.md budget. Re-exported from ``fakes`` at the BOTTOM of the module (after all other Fake* classes are defined) so ``from tests.unit.api.fakes import FakePersistenceBackend`` call sites -- of which there are many across the test suite -- keep working unchanged. The sibling-then-reexport pattern avoids a circular import: ``fakes_backend`` imports the repo classes from ``fakes``, and ``fakes`` only imports ``FakePersistenceBackend`` from ``fakes_backend`` at module end, after its own classes are defined. Test plan: - 14,267 unit tests pass - ruff check + format clean - mypy strict clean (1551 source files) - fakes.py: 705 lines (down from 843, under the 800 budget)
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
src/synthorg/persistence/sqlite/decision_repo.py (1)
365-367: 🧹 Nitpick | 🔵 TrivialUse the repo’s Python 3.14 multi-except form here too.
This rollback handler still uses the parenthesized clause.
♻️ Proposed fix
- except (sqlite3.Error, aiosqlite.Error) as rollback_exc: + except sqlite3.Error, aiosqlite.Error as rollback_exc:As per coding guidelines: "Use PEP 758 except syntax:
except A, B:(no parentheses) on Python 3.14—ruff enforces this".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/persistence/sqlite/decision_repo.py` around lines 365 - 367, The except clause after the await self._db.rollback() uses the old parenthesized tuple form; replace "except (sqlite3.Error, aiosqlite.Error) as rollback_exc:" with the Python 3.14 multi-except form (no parentheses) so the handler becomes "except sqlite3.Error, aiosqlite.Error as rollback_exc:" while keeping the same rollback_exc variable and existing handling logic in decision_repo.py around the self._db.rollback() call.
🤖 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/persistence/sqlite/decision_repo.py`:
- Around line 437-448: The code currently only catches KeyError when mapping
role via _ROLE_TO_COLUMN, allowing non-string inputs (e.g., list) to raise
TypeError; update DecisionRepository.list_by_agent to validate the role argument
type first: check isinstance(role, str) (or None handling if allowed) and if
not, build the same msg and call logger.warning(...) then raise ValueError(msg);
keep the existing try/except KeyError for unknown string values (role_str and
_ROLE_TO_COLUMN) so every invalid role input consistently raises ValueError.
- Around line 183-199: The code path that rejects naive datetimes currently
raises ValueError without logging or documenting it; update the method in the
DecisionRepository (the append/append_decision method that performs the insert
and checks recorded_at) to log a warning or error with contextual fields (e.g.,
record_id, task_id, version and the offending recorded_at) immediately before
raising ValueError, and add ValueError to the method's Raises: docstring so the
exception is documented; ensure the log level matches other append failure paths
and the message clearly states "naive datetime rejected" plus the offending
value.
- Around line 201-203: Replace the shallow snapshot of metadata with a deep copy
before making it read-only: instead of dict(metadata or {}), deep-copy the
metadata (e.g., via copy.deepcopy(metadata or {})) and then wrap that result
with MappingProxyType so nested dicts/lists are not aliased; update the code
around metadata_view in append_with_next_version()/decision_repo.py and ensure
record.metadata receives the MappingProxyType-wrapped deep copy.
- Around line 281-282: Reads (get, list_by_task, list_by_agent) can observe
uncommitted rows inserted by _execute_insert because they use the shared
aiosqlite.Connection (self._db) without synchronization; fix this by serializing
reads with the same _write_lock (or alternatively use a dedicated read
connection) so SELECTs do not run while an INSERT→SELECT→COMMIT sequence in
_execute_insert is in progress—wrap the SELECT operations in get, list_by_task,
and list_by_agent with an async with self._write_lock (or switch those methods
to use a separate connection instance) to prevent phantom reads on the shared
connection.
In `@tests/unit/api/fakes_backend.py`:
- Around line 173-175: The PersistenceBackend protocol is missing the
workflow_versions property used by controllers; add a read-only property named
workflow_versions to the PersistenceBackend protocol definition (the
PersistenceBackend class/protocol) with the correct return type (e.g.,
WorkflowVersionRepository or the interface used by
FakeWorkflowVersionRepository) and update the protocol's docstring Attributes
section to list workflow_versions so type checkers and readers know it exists;
ensure the property name matches usages in controllers
(state.app_state.persistence.workflow_versions) and any import/type alias
references are adjusted accordingly.
---
Duplicate comments:
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Around line 365-367: The except clause after the await self._db.rollback()
uses the old parenthesized tuple form; replace "except (sqlite3.Error,
aiosqlite.Error) as rollback_exc:" with the Python 3.14 multi-except form (no
parentheses) so the handler becomes "except sqlite3.Error, aiosqlite.Error as
rollback_exc:" while keeping the same rollback_exc variable and existing
handling logic in decision_repo.py around the self._db.rollback() call.
🪄 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: d2ad440b-6d49-4b29-8206-b8f6ba5fe2a1
📒 Files selected for processing (4)
src/synthorg/persistence/sqlite/decision_repo.pytests/unit/api/fakes.pytests/unit/api/fakes_backend.pytests/unit/api/fakes_decisions.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
tests/unit/api/fakes.pytests/unit/api/fakes_backend.pytests/unit/api/fakes_decisions.pysrc/synthorg/persistence/sqlite/decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/api/fakes.pytests/unit/api/fakes_backend.pytests/unit/api/fakes_decisions.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/api/fakes.pytests/unit/api/fakes_backend.pytests/unit/api/fakes_decisions.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
🧠 Learnings (29)
📓 Common learnings
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...
📚 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:
tests/unit/api/fakes.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : 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:
tests/unit/api/fakes.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use `computed_field` for derived values instead of storing + validating redundant fields; use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.pytests/unit/api/fakes_backend.py
📚 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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.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 tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking
Applied to files:
tests/unit/api/fakes.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 : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
tests/unit/api/fakes.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:
tests/unit/api/fakes.pytests/unit/api/fakes_backend.pysrc/synthorg/persistence/sqlite/decision_repo.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
tests/unit/api/fakes.pytests/unit/api/fakes_backend.pysrc/synthorg/persistence/sqlite/decision_repo.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
tests/unit/api/fakes_decisions.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
tests/unit/api/fakes_decisions.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:
tests/unit/api/fakes_decisions.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:
tests/unit/api/fakes_decisions.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:
tests/unit/api/fakes_decisions.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
tests/unit/api/fakes_decisions.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
tests/unit/api/fakes_decisions.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
tests/unit/api/fakes_decisions.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:
tests/unit/api/fakes_decisions.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
🔇 Additional comments (5)
tests/unit/api/fakes_decisions.py (2)
42-106: Well-implemented fake matching production semantics.The implementation correctly:
- Computes version as
max(..., default=0) + 1matching production'sCOALESCE(MAX(version), 0) + 1- Rejects naive datetimes with clear error message
- Normalizes
recorded_atto UTC viaastimezone(UTC)- Deep-copies metadata before wrapping in
MappingProxyTypeto prevent caller mutations from leakingThe deep-copy approach (lines 84-86) provides adequate snapshot semantics for test purposes. While production JSON-serializes metadata (which would reject non-JSON-serializable objects), test metadata is typically JSON-compatible, so this difference is acceptable for a test double.
108-132: Query methods correctly implement protocol contract.
get: ReturnsNoneon miss as specifiedlist_by_task: Orders by version ascending (oldest first)list_by_agent: Validates role withValueError, orders byrecorded_atdescendingAll match the
DecisionRepositoryprotocol defined insrc/synthorg/persistence/repositories_decisions.py.tests/unit/api/fakes_backend.py (1)
39-67: LGTM - Clean initialization with proper dual settings support.The fake correctly maintains:
_settings_repo(namespacedFakeSettingsRepository) exposed viasettingsproperty_settings(flat dict) for legacyget_setting/set_settingmethodsThis matches the
PersistenceBackendprotocol which supports both patterns.tests/unit/api/fakes.py (2)
286-292: Clean re-export preserving backwards compatibility.The
as Ypattern with# noqa: PLC0414correctly marks an explicit re-export. Existing test imports (from tests.unit.api.fakes import FakeDecisionRepository) continue to work.
696-705: Bottom-of-file import correctly avoids circular dependency.The comment accurately explains why this import must be at the bottom:
fakes_backend.pyimportsFake*Repositoryclasses from this file, so they must be defined beforefakes_backendis loaded. TheE402andPLC0414noqa comments appropriately acknowledge the style exceptions.
Addresses 5 valid findings. One finding (the recurring PEP 758
`except A, B as exc:` suggestion) rejected yet again as a
SyntaxError -- same false positive as rounds 2, 3, 4, 5, 6.
1. src/synthorg/persistence/protocol.py: add ``workflow_versions``
to the ``PersistenceBackend`` protocol as a ``WorkflowVersionRepository``
property. CodeRabbit flagged this against ``fakes_backend.py``
but the real gap is the protocol definition: controllers and
SQLite/Fake backends all expose ``workflow_versions``, only the
protocol was missing it. Added the import, Attributes section
entry, and the ``@property`` stub; also added a matching
``workflow_versions`` stub to the conformance-test
``_FakeBackend`` in ``tests/unit/persistence/test_protocol.py``
so ``isinstance(_FakeBackend(), PersistenceBackend)`` still
passes.
2. src/synthorg/persistence/sqlite/decision_repo.py: the
naive-datetime rejection in ``append_with_next_version`` now
emits a WARNING-level log event (``PERSISTENCE_DECISION_RECORD_SAVE_FAILED``)
with full context (record_id, task_id, error_type, offending
recorded_at) before raising ``ValueError``, matching the
logging shape of the other save-failure paths.
``ValueError`` is now documented in the method's ``Raises:``
docstring.
3. src/synthorg/persistence/sqlite/decision_repo.py: ``metadata_view``
is now built from ``copy.deepcopy(dict(metadata or {}))`` at
the repository boundary instead of a shallow ``dict(metadata
or {})``. The Pydantic field validator on ``DecisionRecord.metadata``
already runs ``deep_copy_mapping`` + ``_freeze_recursive``, so
this is belt-and-suspenders -- but making the deep copy
explicit at the call site prevents a future maintainer from
assuming the ``MappingProxyType`` wrapping alone protects
against nested-dict aliasing.
4. src/synthorg/persistence/sqlite/decision_repo.py: ``list_by_agent``
now validates ``isinstance(role, str)`` FIRST, before the
``_ROLE_TO_COLUMN`` dict lookup. Previously an untyped caller
passing a ``list`` or ``dict`` would hit ``TypeError:
unhashable type`` from the dict lookup; now they get a
``ValueError`` with the same message shape as an
unknown-string role. The ``Literal`` type makes this branch
statically unreachable, so the isinstance check is cast
through ``object`` to defeat mypy's narrowing.
5. src/synthorg/persistence/sqlite/decision_repo.py: all three
read methods (``get``, ``list_by_task``, ``list_by_agent``)
now acquire ``_write_lock`` around their SELECT statements.
Without this, a reader on the shared
``aiosqlite.Connection`` could observe rows from an
in-flight ``INSERT -> SELECT -> commit`` sequence in
``_execute_insert`` that has not yet committed, producing
phantom reads. The lock only serializes this repository's
own traffic; cross-repository coordination against the same
connection remains a backend concern.
Test plan:
- 14,267 unit tests pass
- ruff check + format clean
- mypy strict clean (1551 source files)
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/synthorg/persistence/sqlite/decision_repo.py (1)
353-353:⚠️ Potential issue | 🟠 MajorUse the repo's PEP 758 multi-except syntax here.
These handlers still use the parenthesized form, so this file will keep violating the Python 3.14 exception-style rule.
♻️ Proposed fix
- except (sqlite3.Error, aiosqlite.Error) as exc: + except sqlite3.Error, aiosqlite.Error as exc: @@ - except (ValidationError, json.JSONDecodeError, TypeError) as exc: + except ValidationError, json.JSONDecodeError, TypeError as exc:Run this after the edit; expect no matches:
#!/bin/bash rg -n '^\s*except \(' src/synthorg/persistence/sqlite/decision_repo.pyAs per coding guidelines: "Use PEP 758 except syntax:
except A, B:(no parentheses) on Python 3.14—ruff enforces this".Also applies to: 390-390, 411-411, 438-438, 517-517, 570-570
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/persistence/sqlite/decision_repo.py` at line 353, Replace every parenthesized multi-except in src/synthorg/persistence/sqlite/decision_repo.py with the PEP 758 comma form; specifically change handlers like "except (sqlite3.Error, aiosqlite.Error) as exc" to use the new syntax "except sqlite3.Error, aiosqlite.Error as exc" (apply the same change for the other occurrences noted in the review). Update each except block that references sqlite3.Error and aiosqlite.Error (and any other parenthesized multi-except occurrences in this file) so they use the PEP 758 form without parentheses.
🤖 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/persistence/sqlite/decision_repo.py`:
- Around line 540-543: Wrap the section that indexes into the DB row (the direct
row["col"] accesses driven by _COLS) in a try/except KeyError inside the same
function in decision_repo.py; in the except block emit the
PERSISTENCE_DECISION_RECORD_DESERIALIZE_FAILED log (include the missing column
name from the KeyError and the record id being deserialized) at WARNING or ERROR
level, then re-raise the same KeyError to preserve existing behavior; apply the
same try/except+log pattern to the other similar block referenced around lines
553-570 so all missing-column failures are logged before propagating.
- Around line 327-333: The code commits the transaction before verifying the
read-back guard, so if cursor.fetchone() returns None the method will report
QueryError after a durable insert; move the commit so it happens only after the
read-back guard succeeds: perform await cursor.fetchone(), check row is not None
and extract row["version"] (or appropriate field), and only then call await
self._db.commit(); apply the same change to the other similar block that uses
_INSERT_SQL, cursor.fetchone(), and await self._db.commit() (the block around
lines 362-379) to ensure no commit occurs before the guard check.
In `@tests/unit/persistence/test_protocol.py`:
- Around line 555-560: The comment claiming object() "satisfies the structural
Protocol check" is inaccurate because a bare object() does not implement
WorkflowVersionRepository methods; instead, update the comment on the
workflow_versions property to state that returning object() only satisfies the
PersistenceBackend interface requirement that the workflow_versions attribute
exists for this conformance test, and note that the full
WorkflowVersionRepository fake (which implements save_version/get_version, etc.)
is provided elsewhere.
---
Duplicate comments:
In `@src/synthorg/persistence/sqlite/decision_repo.py`:
- Line 353: Replace every parenthesized multi-except in
src/synthorg/persistence/sqlite/decision_repo.py with the PEP 758 comma form;
specifically change handlers like "except (sqlite3.Error, aiosqlite.Error) as
exc" to use the new syntax "except sqlite3.Error, aiosqlite.Error as exc" (apply
the same change for the other occurrences noted in the review). Update each
except block that references sqlite3.Error and aiosqlite.Error (and any other
parenthesized multi-except occurrences in this file) so they use the PEP 758
form without parentheses.
🪄 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: 33f8ec37-c4d6-414c-8a66-7b76fd293ee0
📒 Files selected for processing (3)
src/synthorg/persistence/protocol.pysrc/synthorg/persistence/sqlite/decision_repo.pytests/unit/persistence/test_protocol.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
src/synthorg/persistence/protocol.pytests/unit/persistence/test_protocol.pysrc/synthorg/persistence/sqlite/decision_repo.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/persistence/protocol.pysrc/synthorg/persistence/sqlite/decision_repo.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/persistence/protocol.pysrc/synthorg/persistence/sqlite/decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/persistence/test_protocol.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/persistence/test_protocol.py
🧠 Learnings (33)
📓 Common learnings
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...
📚 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/persistence/protocol.pysrc/synthorg/persistence/sqlite/decision_repo.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/protocol.pysrc/synthorg/persistence/sqlite/decision_repo.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/persistence/test_protocol.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:
tests/unit/persistence/test_protocol.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/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:
tests/unit/persistence/test_protocol.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:
tests/unit/persistence/test_protocol.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
tests/unit/persistence/test_protocol.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:
tests/unit/persistence/test_protocol.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : All error paths must log at WARNING or ERROR with context before raising
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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 **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
🔇 Additional comments (7)
src/synthorg/persistence/protocol.py (4)
25-42: LGTM!The new imports for
DecisionRepositoryandWorkflowVersionRepositoryfollow the established pattern with propernoqa: TC001annotations.
75-78: Docstring attributes now complete.The
Attributessection has been updated with bothworkflow_versionsanddecision_records, addressing the previously requested documentation sync.
174-177: LGTM!The
decision_recordsproperty follows the established pattern for repository accessors with proper typing and docstring.
234-237: LGTM!The
workflow_versionsproperty correctly extends the protocol with proper typing and documentation.tests/unit/persistence/test_protocol.py (3)
205-224: LGTM!The
_FakeDecisionRepositoryfollows the established pattern for protocol conformance fakes, providing minimal implementations that satisfy theDecisionRepositoryprotocol shape.
507-509: LGTM!The
decision_recordsproperty correctly returns a_FakeDecisionRepositoryinstance, consistent with other repository properties on_FakeBackend.
608-609: LGTM!The conformance test for
_FakeDecisionRepositoryfollows the established pattern and verifies protocol compliance.
Addresses 3 valid findings. The recurring PEP 758 ``except A, B as exc:`` suggestion is rejected again as the same SyntaxError false positive flagged in rounds 2 through 7. 1. src/synthorg/persistence/sqlite/decision_repo.py: move the transaction commit in ``_execute_insert`` to AFTER the read-back guard succeeds. Previously ``commit()`` ran before the ``row is None`` check, so a defective fetchone() result would leave a durable "ghost" row with no corresponding service-layer caller signal. New sequence is INSERT -> SELECT -> fetchone -> guard -> commit, with an explicit ``_rollback_quietly`` on the None-row branch so no uncommitted INSERT leaks into the next operation. The commit call is now wrapped in its own try/except so a commit-time failure is reported as ``QueryError`` with full context. 2. src/synthorg/persistence/sqlite/decision_repo.py: wrap the ``row["criteria_snapshot"]`` / ``row["metadata"]`` indexing in ``_row_to_record`` in a try/except KeyError that logs ``PERSISTENCE_DECISION_RECORD_DESERIALIZE_FAILED`` with the missing column name and record id BEFORE re-raising the original ``KeyError``. Preserves the existing contract (``KeyError`` still propagates loudly on schema drift) while giving operators the context they need to diagnose the failure without tracing the stack. 3. tests/unit/persistence/test_protocol.py: rewrite the comment on the ``_FakeBackend.workflow_versions`` stub. The previous wording claimed ``object()`` "satisfies the structural Protocol check", which was technically inaccurate -- ``object()`` does not implement ``WorkflowVersionRepository``'s methods. The real situation is that ``PersistenceBackend`` is ``@runtime_checkable``, which only verifies attribute EXISTENCE, not the value's conformance to nested protocols, so a bare ``object()`` is sufficient for the backend conformance test in this file. The full ``WorkflowVersionRepository`` fake lives in ``tests/unit/api/fakes_workflow.py`` for tests that exercise the actual workflow-version behavior. Test plan: - 14,267 unit tests pass - ruff check + format clean - mypy strict clean (1551 source files)
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/persistence/sqlite/decision_repo.py`:
- Around line 576-603: The code must explicitly read every required column from
the DB row with row[...] (so missing columns raise KeyError and hit the existing
logger.exception path) before calling DecisionRecord.model_validate; change the
construction of parsed so you read and assign each required field (e.g., id,
task_id, version, decision, criteria_snapshot, metadata, etc.) via row[...]
inside the try that logs PERSISTENCE_DECISION_RECORD_DESERIALIZE_FAILED, then
decode raw_criteria/raw_metadata into parsed["criteria_snapshot"] and
parsed["metadata"] as you already do, and only after those explicit
reads/decodes call DecisionRecord.model_validate(parsed) so schema drift results
in the same logged KeyError path rather than a ValidationError.
- Around line 335-353: The except block currently converts all
sqlite3.IntegrityError into DuplicateRecordError or QueryError using
_is_unique_constraint_error; update it to only treat UNIQUE/PRIMARY KEY
violations as DuplicateRecordError and let structural constraint violations (FK,
CHECK, NOT NULL, foreign key or other schema constraints) propagate as the
original sqlite3.IntegrityError so they surface loudly. Concretely: add or use a
predicate like _is_structural_constraint_error(exc) (or extend
_is_unique_constraint_error to return tri-state) in the except
sqlite3.IntegrityError handler; if _is_unique_constraint_error(exc) -> raise
DuplicateRecordError(msg) from exc, elif _is_structural_constraint_error(exc) ->
re-raise exc (raise), else log and raise QueryError(msg). Keep references to
PERSISTENCE_DECISION_RECORD_SAVE_FAILED, record_id, and sqlite_errorname in logs
unchanged.
- Around line 148-150: SQLiteDecisionRepository creates its own asyncio.Lock()
(attribute _write_lock) which leads to races because the same
aiosqlite.Connection is shared across many repos; instead create a single
asyncio.Lock in SQLitePersistenceBackend (before _create_repositories()) and
inject that same lock into every repository that performs writes (e.g., pass it
into SQLiteDecisionRepository constructor and any other mutating repos), remove
the per-repo asyncio.Lock creation in SQLiteDecisionRepository, and replace uses
of self._write_lock there with the injected lock so all repositories serialize
commits/rollbacks on the shared connection.
🪄 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: cbef8455-5cc3-4721-9dd3-fa4234feecd6
📒 Files selected for processing (2)
src/synthorg/persistence/sqlite/decision_repo.pytests/unit/persistence/test_protocol.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) on Python 3.14—ruff enforces this
All public functions must have type hints; use mypy strict mode
Use Google-style docstrings required on public classes and functions—enforced by ruff D rules
Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields
Use@computed_fieldfor derived values instead of storing + validating redundant fields; useNotBlankStrfromcore.typesfor all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code; prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly, never silently swallow exceptions
Always use variable namelogger(not_logger, notlog)
Always use structured logging kwargs:logger.info(EVENT, key=value)neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO
DEBUG logging for object creation, internal flow, entry/exit of key functions; pure data models, enums, and re-exports do NOT need logging
Files:
tests/unit/persistence/test_protocol.pysrc/synthorg/persistence/sqlite/decision_repo.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Usepytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for test categorization
Useasyncio_mode = "auto"for async tests—no manual@pytest.mark.asyncioneeded
Global timeout: 30 seconds per test inpyproject.toml; non-default overrides (e.g.,timeout(60)) are allowed but do NOT add per-file markers for the default
Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis (@given+@settings) for property-based testing; profiles intests/conftest.py:ci(deterministic, 10 examples +derandomize=True),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples, no deadline); controlled viaHYPOTHESIS_PROFILEenv var
When Hypothesis finds a failure, it is a real bug—fix the underlying bug and add an explicit@example(...)decorator to permanently cover the case in CI; do NOT skip or ignore flaky tests
For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic; for tasks that must block indefinitely until cancelled, useasyncio.Event().wait()instead ofasyncio.sleep(large_number)
Files:
tests/unit/persistence/test_protocol.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/persistence/test_protocol.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST havefrom synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code (exceptions:observability/setup.py,observability/sinks.py,observability/syslog_handler.py,observability/http_handler.pymay use stdlibloggingandprint())
Always use event name constants from domain-specific modules undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api); import directly
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/smallas aliases; vendor names allowed only in: (1) Operations design page, (2).claude/skill/agent files, (3) third-party imports, (4) provider presets
Files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
🧠 Learnings (32)
📓 Common learnings
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...
📚 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:
tests/unit/persistence/test_protocol.pysrc/synthorg/persistence/sqlite/decision_repo.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/persistence/test_protocol.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:
tests/unit/persistence/test_protocol.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/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:
tests/unit/persistence/test_protocol.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:
tests/unit/persistence/test_protocol.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
tests/unit/persistence/test_protocol.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:
tests/unit/persistence/test_protocol.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: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 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 **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) on Python 3.14—ruff enforces this
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : All error paths must log at WARNING or ERROR with context before raising
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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 : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.py
📚 Learning: 2026-04-05T13:59:09.628Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T13:59:09.628Z
Learning: Applies to **/*.py : Create new objects instead of mutating existing ones; for non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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 **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.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/persistence/sqlite/decision_repo.py
🔇 Additional comments (8)
tests/unit/persistence/test_protocol.py (6)
29-29: LGTM!Import correctly added alongside other repository protocol imports.
59-59: LGTM!Import correctly guarded under
TYPE_CHECKINGfor type-hint-only usage.
507-509: LGTM!Property follows the established pattern of other repository accessors in
_FakeBackend.
555-566: LGTM!The comment now accurately explains
@runtime_checkablesemantics and correctly references where the fullWorkflowVersionRepositoryfake resides.
614-615: LGTM!Conformance test follows the established pattern and correctly verifies protocol compliance.
205-224: Verify fake signature matches theDecisionRepositoryprotocol.The
append_with_next_versionmethod uses**_kwargswhich is very permissive. Other fakes in this file mirror their protocol signatures explicitly (e.g.,_FakeTaskRepository.save(self, task: Task)). Also,role: stron line 222 may need to beDecisionRoleif the protocol uses that literal type.If the protocol uses explicit parameters, aligning the fake's signature helps catch protocol drift at test time.
#!/bin/bash # Description: Find the DecisionRepository protocol definition to verify method signatures # Search for DecisionRepository class definition ast-grep --pattern $'class DecisionRepository: $$$ ' # Also search with Protocol inheritance ast-grep --pattern $'class DecisionRepository(Protocol): $$$ 'src/synthorg/persistence/sqlite/decision_repo.py (2)
354-354: Use the mandated Python 3.14 multi-except syntax everywhere in this module.The parenthesized clauses on Lines 354, 388, 408, 429, 456, 535, and 603 are still the old form; this repo now enforces
except A, B:without parentheses.As per coding guidelines: "Use PEP 758 except syntax:
except A, B:(no parentheses) on Python 3.14—ruff enforces this"Also applies to: 388-388, 408-408, 429-429, 456-456, 535-535, 603-603
227-245: Good guard before UTC normalization.Rejecting naive
recorded_atvalues beforeastimezone(UTC)closes the host-local-time drift hole cleanly.
Addresses 3 valid findings. 1. src/synthorg/persistence/sqlite/backend.py + decision_repo.py: create a single shared ``asyncio.Lock`` on ``SQLitePersistenceBackend`` and inject it into ``SQLiteDecisionRepository`` via a new ``write_lock`` constructor keyword argument. The previous per-repo lock only serialized this repository's own callers; since the ``aiosqlite.Connection`` is shared across every SQLite repo, a ``_rollback_quietly()`` in decision_repo could still wipe another repo's in-flight INSERT on the same connection. The shared lock ensures the decisions drop-box coordinates with other writers on the same connection going forward. The ``write_lock`` parameter is optional (defaults to a per-instance lock) to keep direct-instantiation tests ergonomic. Broader rollout of the shared lock to every other SQLite repository is tracked as a follow-up. 2. src/synthorg/persistence/sqlite/decision_repo.py: classify ``sqlite3.IntegrityError`` more precisely in ``_execute_insert``. Previously every non-UNIQUE ``IntegrityError`` was wrapped as generic ``QueryError``, masking CHECK / FOREIGN KEY / NOT NULL / trigger violations behind a "transient persistence failure" label that the review-gate service's narrowed except would silently swallow. Added ``_is_structural_constraint_error`` predicate; these violations now log with full context and re-raise the original ``sqlite3.IntegrityError`` so schema-level invariant failures surface loudly as programming errors. 3. src/synthorg/persistence/sqlite/decision_repo.py: rewrite ``_row_to_record`` to read every required column via explicit ``row["col"]`` indexing inside the KeyError-logging try block. Previously the method built ``parsed`` via ``dict(row)`` which silently copied whatever columns were present; a missing column would surface as a less-informative ``ValidationError`` from ``DecisionRecord.model_validate`` instead of hitting the KeyError log path with the specific missing column name. The new field-by-field assembly makes schema drift deterministic and observable. Test plan: - 14,267 unit tests pass - ruff check + format clean - mypy strict clean (1551 source files)
…t, and Gemini Rebase onto origin/main (resolve #1072 conflict in agent_engine.py). Python backend: - Fix trends active_agents always returning 0 (pass all_tasks) - Update idle/active field descriptions for runtime-state semantics - Add debug logs on _resolve_agent_counts fallback branches - Use safe .get() in notifier failure path to prevent KeyError - Rename note= to reason= for log consistency - Narrow trim_tier to Literal[1, 2, 3] - Extract _read_notify_enabled helper (50-line function limit) - Remove redundant parentheses in logger kwarg Frontend: - Move unnonced <style> to global.css (CSP nonce compliance) - Replace 9 hardcoded MiniMap colors with CSS variables - Extract Framer Motion durations to @/lib/motion presets - Add confirm-dialog double-click re-entry guard - Fix health-popover race condition (latest-response-wins) - Remove dead side/align props from HealthPopover - Restore aria-describedby auto-wiring on health dialog - Guard Slot Children.only() against invalid input - Fall back to agentId URL param in AgentDetailPage - Wrap useGlobalNotifications handler in try/catch - Add debug log for unknown WS event types in agents store - Replace raw button with shared Button in search overlay - Fix approxLength double-counting BEND_OFFSET on straight edges - Wire HierarchyEdge through React Flow generic (remove as cast) - Fix owner-only orgs skipping empty-state layout - Optimize O(E*N) edge filtering to O(E+N) with Set - Narrow LayoutDirection to TB only (LR was unused) - Fix design token violations (space-y, gap, transition durations) - Fix OverBudget story budgetPercent (20 -> 120) - Remove redundant aria-disabled from disabled buttons - Remove onClick from permanently disabled Create Department button - Reinstate agent-count assertion in DepartmentEditDrawer test - Thread currency prop through BudgetBurnChart tooltip - Update stale comments (smoothstep, dimensions, re-render) Infrastructure: - Add Strict-Transport-Security to /assets/ location block
…t, and Gemini Rebase onto origin/main (resolve #1072 conflict in agent_engine.py). Python backend: - Fix trends active_agents always returning 0 (pass all_tasks) - Update idle/active field descriptions for runtime-state semantics - Add debug logs on _resolve_agent_counts fallback branches - Use safe .get() in notifier failure path to prevent KeyError - Rename note= to reason= for log consistency - Narrow trim_tier to Literal[1, 2, 3] - Extract _read_notify_enabled helper (50-line function limit) - Remove redundant parentheses in logger kwarg Frontend: - Move unnonced <style> to global.css (CSP nonce compliance) - Replace 9 hardcoded MiniMap colors with CSS variables - Extract Framer Motion durations to @/lib/motion presets - Add confirm-dialog double-click re-entry guard - Fix health-popover race condition (latest-response-wins) - Remove dead side/align props from HealthPopover - Restore aria-describedby auto-wiring on health dialog - Guard Slot Children.only() against invalid input - Fall back to agentId URL param in AgentDetailPage - Wrap useGlobalNotifications handler in try/catch - Add debug log for unknown WS event types in agents store - Replace raw button with shared Button in search overlay - Fix approxLength double-counting BEND_OFFSET on straight edges - Wire HierarchyEdge through React Flow generic (remove as cast) - Fix owner-only orgs skipping empty-state layout - Optimize O(E*N) edge filtering to O(E+N) with Set - Narrow LayoutDirection to TB only (LR was unused) - Fix design token violations (space-y, gap, transition durations) - Fix OverBudget story budgetPercent (20 -> 120) - Remove redundant aria-disabled from disabled buttons - Remove onClick from permanently disabled Create Department button - Reinstate agent-count assertion in DepartmentEditDrawer test - Thread currency prop through BudgetBurnChart tooltip - Update stale comments (smoothstep, dimensions, re-render) Infrastructure: - Add Strict-Transport-Security to /assets/ location block
🤖 I have created a release *beep* *boop* --- ## [0.6.2](v0.6.1...v0.6.2) (2026-04-06) ### Features * add issue analyzer script for priority/scope management ([#1084](#1084)) ([1ccba27](1ccba27)) * config fixes and deferred improvements from PR [#1058](#1058) review ([#1067](#1067)) ([2cac2d3](2cac2d3)), closes [#1061](#1061) [#1060](#1060) * cumulative risk-unit action budgets ([#806](#806)) and automated reporting ([#245](#245)) ([#1063](#1063)) ([4689816](4689816)) * fine-tuning pipeline + CompositeBackend + workflow lifecycle ([#1065](#1065)) ([85b05bc](85b05bc)), closes [#1001](#1001) [#850](#850) [#1058](#1058) * memory consolidation upgrades (LLM Merge, Search-and-Ask, diversity penalty, distillation capture) ([#1071](#1071)) ([174e2be](174e2be)), closes [#704](#704) * migrate web dashboard from Radix UI to Base UI, activate CSP nonce, rebuild org chart page, and fix agent routing ([#1083](#1083)) ([ebc6921](ebc6921)) * v0.7.0 engine foundations -- structured failure diagnosis + auditable decisions ([#1072](#1072)) ([d341d37](d341d37)) * workflow templates and versioning with diff and rollback ([#1069](#1069)) ([7af94de](7af94de)), closes [#1006](#1006) [#1008](#1008) ### Documentation * unify REST API docs under /docs/openapi/ and patch sitemap ([#1073](#1073)) ([af19382](af19382)) ### Maintenance * bump hypothesis from 6.151.10 to 6.151.11 in the all group ([#1086](#1086)) ([3176318](3176318)) * bump nginxinc/nginx-unprivileged from `f99cc61` to `601c823` in /docker/web in the all group ([#1085](#1085)) ([5eb99ac](5eb99ac)) * bump the all group in /web with 3 updates ([#1087](#1087)) ([8deae44](8deae44)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Implements two v0.7.0 engine foundation issues:
RecoveryResultCloses #706
Closes #700
#706: Structured Failure Diagnosis
RecoveryResultnow carries machine-readable failure classification instead of just an error string:FailureCategoryenum:TOOL_FAILURE,STAGNATION,BUDGET_EXCEEDED,QUALITY_GATE_FAILED,TIMEOUT,DELEGATION_FAILED,UNKNOWNfailure_context: dict[str, Any]-- strategy-specific structured metadata (deep-copied at construction boundary, defaults to{})criteria_failed: tuple[NotBlankStr, ...]-- acceptance criteria that were not met (uniqueness validated)stagnation_evidence: StagnationResult | None-- linked stagnation detection datainfer_failure_category()helper uses keyword matching with first-match-wins semantics; returnsUNKNOWNwhen no rule matches (honest default, not silentTOOL_FAILURElie)infer_failure_category_without_evidence()helper clampsSTAGNATION/QUALITY_GATE_FAILED->UNKNOWNfor callers that cannot supply the required sidecar data, preventing the cross-field validator from crashing on error messages containing keywords like "stagnation", "quality", or "criteria"RecoveryResult:stagnation_evidenceiffSTAGNATIONcategory (and verdict must not beNO_STAGNATION);criteria_failednon-empty whenQUALITY_GATE_FAILEDFailAndReassignStrategyandCheckpointRecoveryStrategypopulate the new fields on every result using the evidence-clamping variantsanitize_message()before injection into the LLM reconciliation prompt;sanitize_messagenow also strips common prompt-injection markers ([INST],<|system|>,{{ }}, "ignore previous instructions", etc.)failure_categoryas a stable(failure_category=<value>)wire-format contract so the downstream task router/reassignment selector can read it from status historyNote on #706 action items: "stagnation_evidence" uses the existing
StagnationResultmodel since the issue-referencedStagnationEvidencedoes not exist --StagnationResultalready carries verdict, repetition ratio, cycle length, and forward-compatible details. The "smarter routing" action item is addressed via the transition reason embedding; a full routing strategy rework is scoped as a follow-up once the data is consumed in production.#700: No-Self-Review + Decisions Drop-Box
Structurally prevents an agent from reviewing its own work, and adds an immutable append-only audit trail of every review gate decision.
No-self-review enforcement:
SelfReviewErrorexception carryingtask_id+agent_idattributes; the exception message is deliberately generic ("Self-review is not permitted") so callers can never leak internal identifiers viastr(exc)in HTTP responsesReviewGateService.check_can_decide()preflight method that runs BEFOREsave_if_pendingin the approval controller -- a rejected self-review attempt or missing task never leaves a decided approval row or a broadcast WebSocket event behindDecisionRecordmodel validator also rejectsexecuting_agent_id == reviewer_agent_idat construction time; SQLiteCHECK(reviewer_agent_id != executing_agent_id)enforces the same invariant at the database level_preflight_review_gate/_try_review_gate_transition--TaskNotFoundError -> 404,TaskVersionConflictError -> 409,TaskInternalError -> 503 ServiceUnavailable(mirroring the tasks controller), instead of silently swallowing errors as 200 OK_record_decisionnarrowed to catch onlyQueryError/DuplicateRecordError-- programming errors (ValidationError,TypeError,AttributeError) propagate loudly so schema drift surfaces in dev/CI instead of being masked as silent audit lossNotBlankStrexecuting_agent_idfieldDecisions drop-box:
DecisionRecordfrozen Pydantic model: id, task_id, approval_id, executing_agent_id, reviewer_agent_id, decision (DecisionOutcomeenum), reason (NotBlankStr | Nonewith empty-string coercion), criteria_snapshot (tuple[NotBlankStr, ...]with uniqueness validation), recorded_at, server-assigned monotonic version, metadata (defaults to{})DecisionRepositoryprotocol (append-only:append_with_next_version,get,list_by_task,list_by_agent) withDecisionRole = Literal["executor", "reviewer"]-- lives inpersistence/repositories_decisions.pyand is re-exported fromrepositories.pyfor backwards compatibilityapproval_idis threaded throughcomplete_review->_record_decisionso every audit record carries a back-reference to the triggeringApprovalItemSQLiteDecisionRepositoryimplementation: single atomicINSERT ... (SELECT COALESCE(MAX(version), 0) + 1 ...)statement computes version atomically under aiosqlite's per-statement serialization, eliminating the TOCTOU race that a read-then-write pattern would create under concurrent reviewers. TheUNIQUE(task_id, version)constraint rejects any residual collision asDuplicateRecordError. Dropped the customBEGIN IMMEDIATEtransaction (inconsistent with the rest of the codebase's implicit-transaction style)list_by_agent's dynamic column name is derived from a closed-set_ROLE_TO_COLUMNdict keyed by theDecisionRoleLiteral, not an if/elif chain, keeping the S608 suppression tightly scoped to a bounded identifier setdecision_recordstable with FKtask_id -> tasks(id) ON DELETE RESTRICT(audit trail preservation takes priority over task cleanup), CHECK constraints ondecisionenum values, reviewer/executor separation,version >= 1, andrecorded_atUTC offset format (LIKE '%+00:00' OR LIKE '%Z'); UNIQUE(task_id, version); compound indexes(executing_agent_id, recorded_at DESC)+(reviewer_agent_id, recorded_at DESC)to serve orderedlist_by_agentqueries without a sort step_record_decisionis best-effort (logged vialogger.exceptionwith full context on failure) since the transition has already committed -- audit integrity failures do not roll back the review transition, but known transient failures are the only ones swallowedFile splits to stay under the 800-line CLAUDE.md limit:
persistence/repositories.py(918 -> 703):DecisionRepository+DecisionRolemoved topersistence/repositories_decisions.py;CheckpointRepository+HeartbeatRepositorymoved topersistence/repositories_checkpoint.py. Both re-exported fromrepositories.pyfor backwards compatibility.api/controllers/approvals.py(862 -> 623): review-gate flow helpers (try_mid_execution_resume,preflight_review_gate,try_review_gate_transition,signal_resume_intent) moved toapi/controllers/_approval_review_gate.py; re-aliased with leading underscore inapprovals.pyto preserve the internal API.persistence/sqlite/decision_repo.py::append_with_next_version: extracted_build_insert_paramsmodule-level helper and_execute_insertprivate method so the public method stays under the 50-line function limit.Scoped out of this PR (tracked as follow-up in #1076): "Consider versioning agent identities/charters as first-class artifacts" (#700 action 4) -- the issue phrases this as "consider" and it warrants its own design discussion. Tracked separately so #700 can close cleanly with the drop-box + no-self-review work delivered.
Test Plan
uv run python -m pytest tests/ -m unit -n 8)uv run python -m pytest tests/ -m integration -n 8)uv run ruff check src/ tests/cleanuv run ruff format --check src/ tests/cleanuv run mypy src/ tests/cleanNew / extended tests:
tests/unit/core/test_enums_failure_category.py--FailureCategory+DecisionOutcomeround-triptests/unit/engine/test_recovery_failure_diagnosis.py--RecoveryResultnew fields, cross-field validators, criteria uniqueness, stagnationNO_STAGNATIONverdict rejection,infer_failure_category_without_evidenceclamping behavior, C1 regression tests that feed"stagnation"/"quality"/"criteria"error strings through the strategy construction pathtests/unit/engine/test_decisions.py--DecisionRecordmodel validation including newtest_self_review_rejected+ criteria uniqueness +metadatadefault factorytests/unit/persistence/sqlite/test_decision_repo.py-- SQLite repo CRUD, monotonic version assignment, duplicate detection, JSON round-trip, corrupted row handling, DB-level self-review CHECK, protocol conformance, new concurrent-writer test that fires 20asyncio.gatherappends and asserts distinct monotonic versionstests/unit/engine/test_review_gate.py-- self-review prevention, decision recording withapproval_id, non-fatal onQueryError, newtest_decision_record_programming_error_propagatesregression guard ensuringTypeError/ValidationErrorbubble up instead of being silently swallowedtests/unit/api/controllers/test_approvals_helpers.py-- newTestPreflightReviewGate+TestTryReviewGateTransitionclasses covering every typed engine-error mapping (SelfReview -> 403, TaskNotFound -> 404, TaskVersionConflict -> 409, TaskInternal -> 503), plus assertions that generic messages never leaktask_idoragent_idtests/unit/engine/checkpoint/test_resume.py-- reconciliation includesfailure_category+criteria_failed, criteria sanitizationReview Coverage
Pre-reviewed by 16 specialized agents (code-reviewer, python-reviewer, docs-consistency, pr-test-analyzer, silent-failure-hunter, comment-analyzer, type-design-analyzer, logging-audit, resilience-audit, conventions-enforcer, security-reviewer, persistence-reviewer, test-quality-reviewer, async-concurrency-reviewer, api-contract-drift, issue-resolution-verifier) plus external feedback from Gemini, Copilot, and CodeRabbit. All valid findings addressed in the fix commit; invalid PEP 758 and "dead code" false positives rejected with justification in the review log.
Documentation
docs/design/engine.md: RecoveryResult model table updated withUNKNOWN+ cross-field invariants + transition-reason wire format; review gate section documents the two-phase preflight/complete split and the generic-message contract for 403/404/409docs/design/operations.md: new "Review Gate Invariants" + "Auditable Decisions Drop-Box" + "Design Rationale: Append-Only vs Consolidation" subsections under "Security and Approval System" (previously had no mention of either feature;engine/decisions.pymodule docstring now points here accurately)docs/design/memory.md:DecisionRecord/DecisionRepositoryadded to the Entities Persisted tableCLAUDE.mdPackage Structure:engine/mentions decisions drop-box and no-self-review enforcement;persistence/mentions decision record repository