feat: persistent cost aggregation for project-lifetime budgets#1173
feat: persistent cost aggregation for project-lifetime budgets#1173
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 (7)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (5)**/*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/test_*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
tests/**/*.py⚙️ CodeRabbit configuration file
Files:
**/synthorg/**/*.py📄 CodeRabbit inference engine (CLAUDE.md)
Files:
src/**/*.py⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (25)📓 Common learnings📚 Learning: 2026-03-19T07:13:44.964ZApplied to files:
📚 Learning: 2026-03-17T06:30:14.180ZApplied 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-14T16:18:57.267ZApplied 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-03-14T15:43:05.601ZApplied to files:
📚 Learning: 2026-04-08T20:20:05.318ZApplied to files:
📚 Learning: 2026-04-08T20:20:05.318ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-03-17T06:30:14.180ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-03-15T16:55:07.730ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-04-08T20:20:05.318ZApplied to files:
📚 Learning: 2026-03-20T21:44:04.528ZApplied to files:
📚 Learning: 2026-03-31T20:07:03.035ZApplied to files:
📚 Learning: 2026-04-08T20:20:05.318ZApplied to files:
📚 Learning: 2026-03-15T19:14:27.144ZApplied to files:
📚 Learning: 2026-03-17T22:08:13.456ZApplied to files:
📚 Learning: 2026-03-19T07:12:14.508ZApplied to files:
🔇 Additional comments (7)
WalkthroughThis change adds durable per-project cost aggregation: a new Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Code Review
This pull request implements durable per-project cost aggregates to support lifetime budget enforcement beyond the in-memory retention window. Key changes include a new SQLite table, repository implementation, and integration into the BudgetEnforcer and CostTracker. Review feedback highlights critical Python 3 syntax errors in exception handling that must be fixed. Additionally, suggestions were made to optimize database operations using the RETURNING clause and to improve the readability of the CLAUDE.md file by restructuring an excessively long line.
| except MemoryError, RecursionError: | ||
| raise |
| except MemoryError, RecursionError: | ||
| raise |
| except MemoryError, RecursionError: | ||
| raise |
| - **Never** use `import logging` / `logging.getLogger()` / `print()` in application code (exception: `observability/setup.py`, `observability/sinks.py`, `observability/syslog_handler.py`, `observability/http_handler.py`, and `observability/otlp_handler.py` may use stdlib `logging` and `print(..., file=sys.stderr)` for handler construction, bootstrap, and error reporting code that runs before or during logging system configuration) | ||
| - **Variable name**: always `logger` (not `_logger`, not `log`) | ||
| - **Event names**: always use constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`, `GIT_COMMAND_START` from `events.git`, `CONTEXT_BUDGET_FILL_UPDATED`, `CONTEXT_BUDGET_COMPACTION_STARTED`, `CONTEXT_BUDGET_COMPACTION_COMPLETED`, `CONTEXT_BUDGET_COMPACTION_FAILED`, `CONTEXT_BUDGET_COMPACTION_SKIPPED`, `CONTEXT_BUDGET_COMPACTION_FALLBACK`, `CONTEXT_BUDGET_INDICATOR_INJECTED`, `CONTEXT_BUDGET_AGENT_COMPACTION_REQUESTED`, `CONTEXT_BUDGET_EPISTEMIC_MARKERS_PRESERVED` from `events.context_budget`, `BACKUP_STARTED` from `events.backup`, `SETUP_COMPLETED` from `events.setup`, `ROUTING_CANDIDATE_SELECTED` from `events.routing`, `SHIPPING_HTTP_BATCH_SENT` from `events.shipping`, `EVAL_REPORT_COMPUTED` from `events.evaluation`, `PROMPT_PROFILE_SELECTED` from `events.prompt`, `PROCEDURAL_MEMORY_START` from `events.procedural_memory`, `PERF_LLM_JUDGE_STARTED` from `events.performance`, `TASK_ENGINE_OBSERVER_FAILED` from `events.task_engine`, `TASK_ASSIGNMENT_PROJECT_FILTERED` and `TASK_ASSIGNMENT_PROJECT_NO_ELIGIBLE` from `events.task_assignment`, `EXECUTION_SHUTDOWN_IMMEDIATE_CANCEL`, `EXECUTION_SHUTDOWN_TOOL_WAIT`, `EXECUTION_SHUTDOWN_CHECKPOINT_SAVE`, `EXECUTION_SHUTDOWN_CHECKPOINT_FAILED`, and `EXECUTION_PROJECT_VALIDATION_FAILED` from `events.execution`, `WORKFLOW_EXEC_COMPLETED` from `events.workflow_execution`, `BLUEPRINT_INSTANTIATE_START` from `events.blueprint`, `WORKFLOW_DEF_ROLLED_BACK` from `events.workflow_definition`, `WORKFLOW_VERSION_SAVED` from `events.workflow_version`, `MEMORY_FINE_TUNE_STARTED`, `MEMORY_SELF_EDIT_TOOL_EXECUTE`, `MEMORY_SELF_EDIT_CORE_READ`, `MEMORY_SELF_EDIT_CORE_WRITE`, `MEMORY_SELF_EDIT_CORE_WRITE_REJECTED`, `MEMORY_SELF_EDIT_ARCHIVAL_SEARCH`, `MEMORY_SELF_EDIT_ARCHIVAL_WRITE`, `MEMORY_SELF_EDIT_RECALL_READ`, `MEMORY_SELF_EDIT_RECALL_WRITE`, `MEMORY_SELF_EDIT_WRITE_FAILED` from `events.memory`, `REPORTING_GENERATION_STARTED` from `events.reporting`, `RISK_BUDGET_SCORE_COMPUTED` from `events.risk_budget`, `BUDGET_PROJECT_COST_QUERIED`, `BUDGET_PROJECT_RECORDS_QUERIED`, `BUDGET_PROJECT_BUDGET_EXCEEDED`, and `BUDGET_PROJECT_ENFORCEMENT_CHECK` from `events.budget`, `LLM_STRATEGY_SYNTHESIZED` and `DISTILLATION_CAPTURED` from `events.consolidation`, `MEMORY_DIVERSITY_RERANKED`, `MEMORY_DIVERSITY_RERANK_FAILED`, and `MEMORY_REFORMULATION_ROUND` from `events.memory`, `NOTIFICATION_DISPATCHED` and `NOTIFICATION_DISPATCH_FAILED` from `events.notification`, `QUALITY_STEP_CLASSIFIED` from `events.quality`, `HEALTH_TICKET_EMITTED` from `events.health`, `TRAJECTORY_SCORING_START` from `events.trajectory`, `COORD_METRICS_AMDAHL_COMPUTED` from `events.coordination_metrics`, `COORDINATION_STARTED`, `COORDINATION_COMPLETED`, `COORDINATION_FAILED`, `COORDINATION_PHASE_STARTED`, `COORDINATION_PHASE_COMPLETED`, `COORDINATION_PHASE_FAILED`, `COORDINATION_WAVE_STARTED`, `COORDINATION_WAVE_COMPLETED`, `COORDINATION_TOPOLOGY_RESOLVED`, `COORDINATION_CLEANUP_STARTED`, `COORDINATION_CLEANUP_COMPLETED`, `COORDINATION_CLEANUP_FAILED`, `COORDINATION_WAVE_BUILT`, `COORDINATION_FACTORY_BUILT`, and `COORDINATION_ATTRIBUTION_BUILT` from `events.coordination`, `WEB_REQUEST_START` and `WEB_SSRF_BLOCKED` from `events.web`, `DB_QUERY_START` and `DB_WRITE_BLOCKED` from `events.database`, `TERMINAL_COMMAND_START` and `TERMINAL_COMMAND_BLOCKED` from `events.terminal`, `SUB_CONSTRAINT_RESOLVED` and `SUB_CONSTRAINT_DENIED` from `events.sub_constraint`, `VERSION_SAVED` and `VERSION_SNAPSHOT_FAILED` from `events.versioning`, `ANALYTICS_AGGREGATION_COMPUTED` and `ANALYTICS_RETRY_RATE_ALERT` from `events.analytics`, `CALL_CLASSIFICATION_COMPUTED` from `events.call_classification`, `QUOTA_THRESHOLD_ALERT` and `QUOTA_POLL_FAILED` from `events.quota`, `CONFLICT_DEBATE_EVALUATOR_FAILED` from `events.conflict`, `DELEGATION_LOOP_CIRCUIT_BACKOFF` and `DELEGATION_LOOP_CIRCUIT_PERSIST_FAILED` from `events.delegation`, `MEETING_EVENT_COOLDOWN_SKIPPED` and `MEETING_TASKS_CAPPED` from `events.meeting`, `PERSISTENCE_CIRCUIT_BREAKER_SAVED`, `PERSISTENCE_CIRCUIT_BREAKER_SAVE_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_LOADED`, `PERSISTENCE_CIRCUIT_BREAKER_LOAD_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETED`, and `PERSISTENCE_CIRCUIT_BREAKER_DELETE_FAILED` from `events.persistence`, `METRICS_SCRAPE_COMPLETED`, `METRICS_SCRAPE_FAILED`, `METRICS_COLLECTOR_INITIALIZED`, `METRICS_COORDINATION_RECORDED`, `METRICS_OTLP_EXPORT_COMPLETED` and `METRICS_OTLP_FLUSHER_STOPPED` from `events.metrics`, `ORG_MEMORY_QUERY_START`, `ORG_MEMORY_QUERY_COMPLETE`, `ORG_MEMORY_QUERY_FAILED`, `ORG_MEMORY_WRITE_START`, `ORG_MEMORY_WRITE_COMPLETE`, `ORG_MEMORY_WRITE_DENIED`, `ORG_MEMORY_WRITE_FAILED`, `ORG_MEMORY_POLICIES_LISTED`, `ORG_MEMORY_BACKEND_CREATED`, `ORG_MEMORY_CONNECT_FAILED`, `ORG_MEMORY_DISCONNECT_FAILED`, `ORG_MEMORY_NOT_CONNECTED`, `ORG_MEMORY_ROW_PARSE_FAILED`, `ORG_MEMORY_CONFIG_INVALID`, `ORG_MEMORY_MODEL_INVALID`, `ORG_MEMORY_MVCC_PUBLISH_APPENDED`, `ORG_MEMORY_MVCC_RETRACT_APPENDED`, `ORG_MEMORY_MVCC_SNAPSHOT_AT_QUERIED`, and `ORG_MEMORY_MVCC_LOG_QUERIED` from `events.org_memory`). Each domain has its own module -- see `src/synthorg/observability/events/` for the full inventory of constants. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT` | ||
| - **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`, `GIT_COMMAND_START` from `events.git`, `CONTEXT_BUDGET_FILL_UPDATED`, `CONTEXT_BUDGET_COMPACTION_STARTED`, `CONTEXT_BUDGET_COMPACTION_COMPLETED`, `CONTEXT_BUDGET_COMPACTION_FAILED`, `CONTEXT_BUDGET_COMPACTION_SKIPPED`, `CONTEXT_BUDGET_COMPACTION_FALLBACK`, `CONTEXT_BUDGET_INDICATOR_INJECTED`, `CONTEXT_BUDGET_AGENT_COMPACTION_REQUESTED`, `CONTEXT_BUDGET_EPISTEMIC_MARKERS_PRESERVED` from `events.context_budget`, `BACKUP_STARTED` from `events.backup`, `SETUP_COMPLETED` from `events.setup`, `ROUTING_CANDIDATE_SELECTED` from `events.routing`, `SHIPPING_HTTP_BATCH_SENT` from `events.shipping`, `EVAL_REPORT_COMPUTED` from `events.evaluation`, `PROMPT_PROFILE_SELECTED` from `events.prompt`, `PROCEDURAL_MEMORY_START` from `events.procedural_memory`, `PERF_LLM_JUDGE_STARTED` from `events.performance`, `TASK_ENGINE_OBSERVER_FAILED` from `events.task_engine`, `TASK_ASSIGNMENT_PROJECT_FILTERED` and `TASK_ASSIGNMENT_PROJECT_NO_ELIGIBLE` from `events.task_assignment`, `EXECUTION_SHUTDOWN_IMMEDIATE_CANCEL`, `EXECUTION_SHUTDOWN_TOOL_WAIT`, `EXECUTION_SHUTDOWN_CHECKPOINT_SAVE`, `EXECUTION_SHUTDOWN_CHECKPOINT_FAILED`, and `EXECUTION_PROJECT_VALIDATION_FAILED` from `events.execution`, `WORKFLOW_EXEC_COMPLETED` from `events.workflow_execution`, `BLUEPRINT_INSTANTIATE_START` from `events.blueprint`, `WORKFLOW_DEF_ROLLED_BACK` from `events.workflow_definition`, `WORKFLOW_VERSION_SAVED` from `events.workflow_version`, `MEMORY_FINE_TUNE_STARTED`, `MEMORY_SELF_EDIT_TOOL_EXECUTE`, `MEMORY_SELF_EDIT_CORE_READ`, `MEMORY_SELF_EDIT_CORE_WRITE`, `MEMORY_SELF_EDIT_CORE_WRITE_REJECTED`, `MEMORY_SELF_EDIT_ARCHIVAL_SEARCH`, `MEMORY_SELF_EDIT_ARCHIVAL_WRITE`, `MEMORY_SELF_EDIT_RECALL_READ`, `MEMORY_SELF_EDIT_RECALL_WRITE`, `MEMORY_SELF_EDIT_WRITE_FAILED` from `events.memory`, `REPORTING_GENERATION_STARTED` from `events.reporting`, `RISK_BUDGET_SCORE_COMPUTED` from `events.risk_budget`, `BUDGET_PROJECT_COST_QUERIED`, `BUDGET_PROJECT_RECORDS_QUERIED`, `BUDGET_PROJECT_BUDGET_EXCEEDED`, `BUDGET_PROJECT_ENFORCEMENT_CHECK`, `BUDGET_PROJECT_COST_AGGREGATED`, `BUDGET_PROJECT_COST_AGGREGATION_FAILED`, and `BUDGET_PROJECT_BASELINE_SOURCE` from `events.budget`, `LLM_STRATEGY_SYNTHESIZED` and `DISTILLATION_CAPTURED` from `events.consolidation`, `MEMORY_DIVERSITY_RERANKED`, `MEMORY_DIVERSITY_RERANK_FAILED`, and `MEMORY_REFORMULATION_ROUND` from `events.memory`, `NOTIFICATION_DISPATCHED` and `NOTIFICATION_DISPATCH_FAILED` from `events.notification`, `QUALITY_STEP_CLASSIFIED` from `events.quality`, `HEALTH_TICKET_EMITTED` from `events.health`, `TRAJECTORY_SCORING_START` from `events.trajectory`, `COORD_METRICS_AMDAHL_COMPUTED` from `events.coordination_metrics`, `COORDINATION_STARTED`, `COORDINATION_COMPLETED`, `COORDINATION_FAILED`, `COORDINATION_PHASE_STARTED`, `COORDINATION_PHASE_COMPLETED`, `COORDINATION_PHASE_FAILED`, `COORDINATION_WAVE_STARTED`, `COORDINATION_WAVE_COMPLETED`, `COORDINATION_TOPOLOGY_RESOLVED`, `COORDINATION_CLEANUP_STARTED`, `COORDINATION_CLEANUP_COMPLETED`, `COORDINATION_CLEANUP_FAILED`, `COORDINATION_WAVE_BUILT`, `COORDINATION_FACTORY_BUILT`, and `COORDINATION_ATTRIBUTION_BUILT` from `events.coordination`, `WEB_REQUEST_START` and `WEB_SSRF_BLOCKED` from `events.web`, `DB_QUERY_START` and `DB_WRITE_BLOCKED` from `events.database`, `TERMINAL_COMMAND_START` and `TERMINAL_COMMAND_BLOCKED` from `events.terminal`, `SUB_CONSTRAINT_RESOLVED` and `SUB_CONSTRAINT_DENIED` from `events.sub_constraint`, `VERSION_SAVED` and `VERSION_SNAPSHOT_FAILED` from `events.versioning`, `ANALYTICS_AGGREGATION_COMPUTED` and `ANALYTICS_RETRY_RATE_ALERT` from `events.analytics`, `CALL_CLASSIFICATION_COMPUTED` from `events.call_classification`, `QUOTA_THRESHOLD_ALERT` and `QUOTA_POLL_FAILED` from `events.quota`, `CONFLICT_DEBATE_EVALUATOR_FAILED` from `events.conflict`, `DELEGATION_LOOP_CIRCUIT_BACKOFF` and `DELEGATION_LOOP_CIRCUIT_PERSIST_FAILED` from `events.delegation`, `MEETING_EVENT_COOLDOWN_SKIPPED` and `MEETING_TASKS_CAPPED` from `events.meeting`, `PERSISTENCE_CIRCUIT_BREAKER_SAVED`, `PERSISTENCE_CIRCUIT_BREAKER_SAVE_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_LOADED`, `PERSISTENCE_CIRCUIT_BREAKER_LOAD_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETE_FAILED`, `PERSISTENCE_PROJECT_COST_AGG_INCREMENTED`, `PERSISTENCE_PROJECT_COST_AGG_INCREMENT_FAILED`, `PERSISTENCE_PROJECT_COST_AGG_FETCHED`, `PERSISTENCE_PROJECT_COST_AGG_FETCH_FAILED`, and `PERSISTENCE_PROJECT_COST_AGG_DESERIALIZE_FAILED` from `events.persistence`, `METRICS_SCRAPE_COMPLETED`, `METRICS_SCRAPE_FAILED`, `METRICS_COLLECTOR_INITIALIZED`, `METRICS_COORDINATION_RECORDED`, `METRICS_OTLP_EXPORT_COMPLETED` and `METRICS_OTLP_FLUSHER_STOPPED` from `events.metrics`, `ORG_MEMORY_QUERY_START`, `ORG_MEMORY_QUERY_COMPLETE`, `ORG_MEMORY_QUERY_FAILED`, `ORG_MEMORY_WRITE_START`, `ORG_MEMORY_WRITE_COMPLETE`, `ORG_MEMORY_WRITE_DENIED`, `ORG_MEMORY_WRITE_FAILED`, `ORG_MEMORY_POLICIES_LISTED`, `ORG_MEMORY_BACKEND_CREATED`, `ORG_MEMORY_CONNECT_FAILED`, `ORG_MEMORY_DISCONNECT_FAILED`, `ORG_MEMORY_NOT_CONNECTED`, `ORG_MEMORY_ROW_PARSE_FAILED`, `ORG_MEMORY_CONFIG_INVALID`, `ORG_MEMORY_MODEL_INVALID`, `ORG_MEMORY_MVCC_PUBLISH_APPENDED`, `ORG_MEMORY_MVCC_RETRACT_APPENDED`, `ORG_MEMORY_MVCC_SNAPSHOT_AT_QUERIED`, and `ORG_MEMORY_MVCC_LOG_QUERIED` from `events.org_memory`). Each domain has its own module -- see `src/synthorg/observability/events/` for the full inventory of constants. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT` |
| # Read back the updated aggregate. | ||
| aggregate = await self.get(project_id) |
There was a problem hiding this comment.
For better efficiency, you could use the RETURNING clause with your UPSERT statement to get the updated row in a single database round-trip, instead of performing a separate SELECT query with self.get(). This would avoid a second query to the database. This is supported in SQLite versions shipped with modern Python (3.10+).
There was a problem hiding this comment.
Pull request overview
This PR adds a durable, per-project lifetime cost aggregate to prevent project budget enforcement from being bypassed when the in-memory CostTracker prunes older records (168-hour window).
Changes:
- Introduces
ProjectCostAggregate+ProjectCostAggregateRepository, plus a SQLite implementation using atomic upsert semantics. - Updates budget enforcement and cost recording paths to prefer / update the durable aggregate with safe fallbacks.
- Adds schema + observability event constants and a focused unit test suite for the new durable paths.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py | Validates SQLite repo get/increment behavior and error mapping. |
| tests/unit/persistence/sqlite/test_migrations.py | Ensures new table is present after migrations/schema apply. |
| tests/unit/budget/test_tracker_project_aggregate.py | Covers best-effort aggregate updates from CostTracker.record(). |
| tests/unit/budget/test_project_cost_aggregate.py | Covers model validation + protocol runtime-checkable behavior. |
| tests/unit/budget/test_enforcer_project_durable.py | Verifies durable-first enforcement/baseline behavior and fallbacks. |
| src/synthorg/persistence/sqlite/schema.sql | Adds project_cost_aggregates table with CHECK constraints. |
| src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py | Implements durable aggregate repo with upsert + shared write lock. |
| src/synthorg/persistence/sqlite/backend.py | Wires the new repo into SQLitePersistenceBackend with shared lock. |
| src/synthorg/observability/events/persistence.py | Adds persistence event constants for the new repo operations. |
| src/synthorg/observability/events/budget.py | Adds budget event constants for aggregation + baseline source logging. |
| src/synthorg/budget/tracker.py | Adds optional durable aggregate write path on record() (best-effort). |
| src/synthorg/budget/project_cost_aggregate.py | Adds the frozen model and repository protocol. |
| src/synthorg/budget/enforcer.py | Prefers durable aggregate for project budget checks + baseline computation. |
| src/synthorg/budget/init.py | Re-exports new model/protocol + project budget error. |
| docs/reference/claude-reference.md | Updates package docs to mention durable project cost aggregates. |
| docs/design/operations.md | Updates ops gap G6 status to reflect partial durable budget history. |
| CLAUDE.md | Updates event-constant inventory list to include new event names. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| cursor = await self._db.execute(_SELECT_SQL, (project_id,)) | ||
| row = await cursor.fetchone() | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: | ||
| logger.exception( | ||
| PERSISTENCE_PROJECT_COST_AGG_FETCH_FAILED, | ||
| project_id=project_id, | ||
| error=str(exc), | ||
| ) | ||
| raise QueryError(str(exc)) from exc | ||
|
|
There was a problem hiding this comment.
QueryError is raised with the raw sqlite/aiosqlite exception string, which loses operation context (e.g., which project_id failed and whether this was a fetch vs. deserialization). Other SQLite repos wrap these with a higher-level message ("Failed to fetch ..." / "Failed to deserialize ...") and keep the low-level details in logs; align here so callers get consistent, actionable errors.
| try: | ||
| aggregate = _row_to_aggregate(row) | ||
| except ValidationError as exc: | ||
| logger.exception( | ||
| PERSISTENCE_PROJECT_COST_AGG_DESERIALIZE_FAILED, | ||
| project_id=project_id, | ||
| error=str(exc), | ||
| ) | ||
| raise QueryError(str(exc)) from exc | ||
|
|
There was a problem hiding this comment.
Same as above: deserialization failures are re-raised as QueryError(str(exc)), which drops context about which model/row failed. Consider raising QueryError with a descriptive message (including project_id) and chaining the original exception, while relying on the existing structured log for the detailed ValidationError text.
| now = datetime.now(UTC).isoformat() | ||
| try: | ||
| async with self._write_lock: | ||
| await self._db.execute( | ||
| _UPSERT_SQL, | ||
| (project_id, cost, input_tokens, output_tokens, now), | ||
| ) | ||
| await self._db.commit() | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: | ||
| logger.exception( | ||
| PERSISTENCE_PROJECT_COST_AGG_INCREMENT_FAILED, | ||
| project_id=project_id, | ||
| cost=cost, | ||
| error=str(exc), | ||
| ) | ||
| raise QueryError(str(exc)) from exc | ||
|
|
||
| # Read back the updated aggregate. | ||
| aggregate = await self.get(project_id) | ||
| if aggregate is None: # pragma: no cover -- defensive |
There was a problem hiding this comment.
increment() releases the shared write_lock before reading back via get(). Under concurrent increments, the read-back can observe additional increments that happened after this call’s upsert, so the returned aggregate may not correspond to the post-state of this increment. If callers rely on the returned totals, keep the read-back within the same lock scope or use a single-statement RETURNING-style approach to return the updated row atomically.
| else: | ||
| cost = aggregate.total_cost if aggregate else 0.0 | ||
| logger.debug( | ||
| BUDGET_PROJECT_BASELINE_SOURCE, | ||
| project_id=project_id, | ||
| source="aggregate", | ||
| cost=cost, | ||
| ) | ||
| return cost |
There was a problem hiding this comment.
The durable aggregate path returns aggregate.total_cost directly, but in-memory aggregation rounds to BUDGET_ROUNDING_PRECISION to avoid IEEE-754 artifacts (see CostTracker._aggregate). Without applying the same rounding here, project budget checks can become inconsistent (e.g., repeated 0.1 increments producing 9.999999999 or 10.000000001 and triggering boundary checks unexpectedly). Consider rounding the aggregate-derived cost (or storing costs in minor units) before comparing/enforcing.
| except MemoryError, RecursionError: | ||
| raise | ||
| except Exception: | ||
| logger.exception( | ||
| BUDGET_PREFLIGHT_ERROR, | ||
| project_id=project_id, | ||
| reason="project_cost_aggregate_query_failed", | ||
| ) | ||
| # Fall through to in-memory. |
There was a problem hiding this comment.
_get_project_cost() logs failures under BUDGET_PREFLIGHT_ERROR even when called from make_budget_checker() baseline computation (which previously logged BUDGET_BASELINE_ERROR). This changes event semantics and can break dashboards/alerts that distinguish baseline errors from preflight errors. Consider parameterizing the helper with the appropriate error event (baseline vs preflight) or keeping separate helpers so each call site logs under the intended event constant.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1173 +/- ##
========================================
Coverage 88.88% 88.88%
========================================
Files 857 859 +2
Lines 50440 50561 +121
Branches 5072 5078 +6
========================================
+ Hits 44835 44943 +108
- Misses 4643 4652 +9
- Partials 962 966 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/budget/enforcer.py`:
- Around line 874-929: The project identifier type is inconsistent: update the
parameter types for _get_project_cost, check_project_budget, and
make_budget_checker from str to NotBlankStr and add the import for NotBlankStr
from synthorg.core.types; specifically, change the signatures of
_get_project_cost, check_project_budget, and make_budget_checker to accept
project_id: NotBlankStr so the value passed into
ProjectCostAggregateRepository.get() matches its protocol, and ensure the new
import statement for NotBlankStr is added at the top of
src/synthorg/budget/enforcer.py.
In `@src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py`:
- Around line 171-187: The current increment path releases self._write_lock
before calling await self.get(project_id), which allows concurrent increments to
race and makes the readback potentially reflect a later update; move the
readback so the await self.get(project_id) call happens while still inside the
async with self._write_lock block (i.e., after await self._db.commit() but
before exiting the with), keeping the exception handling for sqlite3/aiosqlite
and re-raising QueryError unchanged; update references to _UPSERT_SQL,
self._db.execute, await self._db.commit(), and get(project_id) accordingly so
the commit and subsequent get are performed under the same lock.
- Line 102: Update the two exception handlers that currently use a parenthesized
tuple to the PEP 758 comma-separated form: replace the occurrences of "except
(sqlite3.Error, aiosqlite.Error) as exc:" with the new syntax "except
sqlite3.Error, aiosqlite.Error as exc:" wherever they appear (e.g., inside the
ProjectCostAggregateRepo methods that handle DB errors around the try/except
blocks at the two reported locations). Ensure both handlers (the one at the
earlier DB operation and the one later around async DB calls) are changed to the
comma-separated form so they match Python 3.14 syntax.
- Around line 161-167: The current validation only checks cost < 0 and misses
non-finite values (nan/inf); update the validation in the same block that checks
cost, input_tokens, output_tokens to also reject non-finite numbers by importing
and using math.isfinite (e.g., if not math.isfinite(cost) or not
math.isfinite(input_tokens) or not math.isfinite(output_tokens) or cost < 0 or
input_tokens < 0 or output_tokens < 0: raise ValueError(...)), keeping the
existing error message but including the non-finite values so bad upstream
floats cannot be persisted; apply this change in the same function/method where
the cost/input_tokens/output_tokens variables are validated
(ProjectCostAggregateRepo input validation block).
In `@tests/unit/budget/test_project_cost_aggregate.py`:
- Around line 100-101: Replace the private-attribute check in
test_protocol_is_runtime_checkable with a runtime isinstance check: create a
small stub class implementing the protocol methods get and increment,
instantiate it, and assert isinstance(stub_instance,
ProjectCostAggregateRepository); update the test name if needed to reflect
runtime checkability and reference the ProjectCostAggregateRepository protocol
and its get/increment methods when locating the test to modify.
🪄 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: f68a797f-2140-42f8-8cd6-ce164efc35a5
📒 Files selected for processing (17)
CLAUDE.mddocs/design/operations.mddocs/reference/claude-reference.mdsrc/synthorg/budget/__init__.pysrc/synthorg/budget/enforcer.pysrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/tracker.pysrc/synthorg/observability/events/budget.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/persistence/sqlite/project_cost_aggregate_repo.pysrc/synthorg/persistence/sqlite/schema.sqltests/unit/budget/test_enforcer_project_durable.pytests/unit/budget/test_project_cost_aggregate.pytests/unit/budget/test_tracker_project_aggregate.pytests/unit/persistence/sqlite/test_migrations.pytests/unit/persistence/sqlite/test_project_cost_aggregate_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). (5)
- GitHub Check: Agent
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Test (Python 3.14)
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use PEP 758 except syntax: use
except A, B:(no parentheses) for exception handling in Python 3.14No
from __future__ import annotations-- Python 3.14 has PEP 649 native lazy annotationsAll public functions and classes require type hints; mypy strict mode is enforced
Use Google style docstrings on all public classes and functions (enforced by ruff D rules)
Use
asyncio.TaskGroupfor fan-out/fan-in parallel operations instead of barecreate_task; prefer structured concurrencyUse Pydantic v2 with
BaseModel,model_validator,computed_field, andConfigDict; useallow_inf_nan=Falsein all ConfigDict declarationsUse
NotBlankStrfromcore.typesfor all identifier/name fields instead of manual whitespace validators (including optional and tuple variants)Use
@computed_fieldfor derived values instead of storing and validating redundant fields (e.g.,TokenUsage.total_tokens)Create new objects for immutability; never mutate existing ones. For non-Pydantic internal collections, use
copy.deepcopy()at construction andMappingProxyTypewrappingUse frozen Pydantic models for config/identity; separate mutable-via-copy models (using
model_copy(update=...)) for runtime state that evolvesKeep functions under 50 lines and files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Use line length of 88 characters (enforced by ruff)
Use Pydantic
model_copy(update=...)for creating mutable copies of models in runtime state managementNever mix static config fields with mutable runtime fields in one Pydantic model; use frozen models for config/identity and separate mutable-via-copy models for runtime state
Files:
tests/unit/persistence/sqlite/test_migrations.pytests/unit/budget/test_project_cost_aggregate.pysrc/synthorg/budget/__init__.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/observability/events/budget.pysrc/synthorg/observability/events/persistence.pytests/unit/budget/test_tracker_project_aggregate.pytests/unit/persistence/sqlite/test_project_cost_aggregate_repo.pytests/unit/budget/test_enforcer_project_durable.pysrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/tracker.pysrc/synthorg/persistence/sqlite/project_cost_aggregate_repo.pysrc/synthorg/budget/enforcer.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use
@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e, and@pytest.mark.slowmarkers on testsConfigure
asyncio_mode = "auto"for pytest; no manual@pytest.mark.asyncioneededSet global test timeout to 30 seconds per test (in
pyproject.toml); do not add per-file markers unless overriding with different timeoutUse Hypothesis for property-based testing with
@givenand@settingsdecorators; configure profiles intests/conftest.pyWhen Hypothesis finds a failing property test, fix the underlying bug and add an explicit
@example(...)decorator to permanently cover the case in CIFor timing-sensitive tests, mock
time.monotonic()andasyncio.sleep()to make them deterministic instead of widening timing marginsFor tasks that must block indefinitely until cancelled, use
asyncio.Event().wait()instead ofasyncio.sleep(large_number)for cancellation safetyNever use vendor names in Python tests; use
test-provider,test-small-001, etc. instead of real vendor identifiers
Files:
tests/unit/persistence/sqlite/test_migrations.pytests/unit/budget/test_project_cost_aggregate.pytests/unit/budget/test_tracker_project_aggregate.pytests/unit/persistence/sqlite/test_project_cost_aggregate_repo.pytests/unit/budget/test_enforcer_project_durable.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/budget/test_project_cost_aggregate.pytests/unit/budget/test_tracker_project_aggregate.pytests/unit/persistence/sqlite/test_project_cost_aggregate_repo.pytests/unit/budget/test_enforcer_project_durable.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Every module with business logic must import and use
from synthorg.observability import get_loggerwith variable nameloggerUse event name constants from
synthorg.observability.eventsdomain-specific modules instead of hardcoded strings; import directly likefrom synthorg.observability.events.<domain> import EVENT_CONSTANTApply
copy.deepcopy()at system boundaries: tool execution, LLM provider serialization, inter-agent delegation, and persistence serializationValidate inputs at system boundaries: user input, external APIs, and config files
Maintain minimum 80% code coverage (enforced in CI)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names like
example-providerortest-provider
Files:
src/synthorg/budget/__init__.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/observability/events/budget.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/tracker.pysrc/synthorg/persistence/sqlite/project_cost_aggregate_repo.pysrc/synthorg/budget/enforcer.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/budget/__init__.pysrc/synthorg/persistence/sqlite/backend.pysrc/synthorg/observability/events/budget.pysrc/synthorg/observability/events/persistence.pysrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/tracker.pysrc/synthorg/persistence/sqlite/project_cost_aggregate_repo.pysrc/synthorg/budget/enforcer.py
🧠 Learnings (49)
📓 Common learnings
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.
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)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: 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).
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Always use `-n 8` flag with pytest locally for parallel test execution; never run tests sequentially
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Use git commit format `<type>: <description>` with types: feat, fix, refactor, docs, test, chore, perf, ci (enforced by commitizen)
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: All commits must be GPG/SSH signed on main branch (enforced via branch protection)
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Use branch names in format `<type>/<slug>` branching from main
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile), golangci-lint+go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (React dashboard)
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: ALWAYS read the relevant `docs/design/` page before implementing any feature or planning any issue
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: If implementation deviates from the spec, alert the user and explain why -- user decides whether to proceed or update the spec; never silently diverge
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: When a spec topic is referenced, read the relevant `docs/design/` page before coding; when approved deviations occur, update the relevant `docs/design/` page to reflect the new reality
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: At every phase of planning and implementation, be critical and actively look for design improvements; surface improvements as suggestions, not silent changes
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: NEVER use `cd` in Bash commands -- the working directory is already set to project root; use absolute paths or run commands directly
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Exception to the no-`cd` rule: `bash -c "cd <dir> && <cmd>"` is safe (runs in child process); use this for tools without a `-C` flag like `npm install`
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: NEVER use Bash to write or modify files -- use the Write or Edit tools; do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c` with open().write(), or `tee`
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: After finishing an issue implementation, create a feature branch (`<type>/<slug>`), commit, and push -- do NOT create a PR automatically
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Do NOT leave work uncommitted on main -- branch, commit, push immediately after finishing
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: NEVER create a PR directly -- `gh pr create` is blocked by hookify; ALWAYS use `/pre-pr-review` to create PRs
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: For trivial/docs-only changes, use `/pre-pr-review quick` to skip agents but still run automated checks
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
URL:
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T22:18:17.265Z
Learning: Fix everything valid in PR review -- never skip valid issues (including pre-existing issues in surrounding code and adjacent findings); no deferring or "out of scope" skipping
📚 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:
docs/reference/claude-reference.mdsrc/synthorg/budget/__init__.pysrc/synthorg/observability/events/budget.pytests/unit/budget/test_tracker_project_aggregate.pytests/unit/budget/test_enforcer_project_durable.pysrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/tracker.pysrc/synthorg/budget/enforcer.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:
docs/reference/claude-reference.mdsrc/synthorg/budget/__init__.pysrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/enforcer.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:
docs/reference/claude-reference.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:
docs/reference/claude-reference.md
📚 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/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management
Applied to files:
docs/reference/claude-reference.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/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.
Applied to files:
docs/reference/claude-reference.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator
Applied to files:
docs/reference/claude-reference.md
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST API and WebSocket API with JWT + API key + WS ticket authentication, RFC 9457 structured errors, and content negotiation.
Applied to files:
docs/reference/claude-reference.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/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:
docs/reference/claude-reference.mdsrc/synthorg/budget/__init__.pysrc/synthorg/observability/events/budget.pydocs/design/operations.mdsrc/synthorg/budget/project_cost_aggregate.pysrc/synthorg/budget/tracker.pysrc/synthorg/budget/enforcer.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:
docs/reference/claude-reference.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:
docs/reference/claude-reference.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:
docs/reference/claude-reference.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)
Applied to files:
docs/reference/claude-reference.md
📚 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/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
docs/reference/claude-reference.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion
Applied to files:
docs/reference/claude-reference.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
Applied to files:
docs/reference/claude-reference.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.
Applied to files:
docs/reference/claude-reference.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
docs/reference/claude-reference.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: 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/budget/__init__.pysrc/synthorg/observability/events/budget.pydocs/design/operations.mdsrc/synthorg/budget/tracker.pysrc/synthorg/budget/enforcer.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/budget/__init__.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/budget/__init__.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/budget/__init__.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).
Applied to files:
src/synthorg/persistence/sqlite/backend.pysrc/synthorg/persistence/sqlite/project_cost_aggregate_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/backend.pysrc/synthorg/persistence/sqlite/project_cost_aggregate_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/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/budget.pyCLAUDE.mdsrc/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/budget.pyCLAUDE.mdsrc/synthorg/observability/events/persistence.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 src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly
Applied to files:
src/synthorg/observability/events/budget.pyCLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : 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/budget.pysrc/synthorg/observability/events/persistence.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/budget.pyCLAUDE.mdsrc/synthorg/observability/events/persistence.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/budget.pyCLAUDE.mdsrc/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/budget.pysrc/synthorg/observability/events/persistence.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/budget.pyCLAUDE.mdsrc/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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls
Applied to files:
CLAUDE.md
📚 Learning: 2026-04-08T20:20:05.318Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T20:20:05.318Z
Learning: Applies to **/*.py : Always use event constants from `synthorg.observability.events` domain modules (e.g., `API_REQUEST_STARTED` from `events.api`) instead of string literals for log event names
Applied to files:
CLAUDE.md
📚 Learning: 2026-04-06T16:35:12.934Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-06T16:35:12.934Z
Learning: All project conventions, commands, and standards are defined in CLAUDE.md - refer to it for project structure, package layout, code conventions, quick commands, git workflow, testing standards, design specifications, logging, resilience, and security patterns
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : 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:
CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`
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: 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:
CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-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:
CLAUDE.md
📚 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:
CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).
Applied to files:
CLAUDE.md
📚 Learning: 2026-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/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/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/persistence.py
🔇 Additional comments (16)
src/synthorg/budget/project_cost_aggregate.py (1)
16-95: Durable aggregate model/protocol looks correct and well-constrained.The immutability, non-negative guards, and repository contract are consistent with the PR objective.
tests/unit/persistence/sqlite/test_migrations.py (1)
46-46: Schema expectation update is correct.Adding
project_cost_aggregatesto_EXPECTED_TABLESis the right migration assertion update.docs/design/operations.md (1)
1857-1859: G6 status framing is accurate and appropriately scoped.The update correctly marks project-level durability as addressed while preserving the remaining gap scope.
src/synthorg/budget/__init__.py (1)
72-100: Public budget exports are consistent and complete for the new aggregate path.Also applies to: 220-223
src/synthorg/persistence/sqlite/backend.py (1)
289-292: Repository wiring for project aggregates is complete and correctly guarded.Instantiation with the shared write lock plus
_require_connectedaccess is solid.Also applies to: 556-568
CLAUDE.md (1)
93-93: Event-constant guidance update is aligned with the new observability surface.src/synthorg/observability/events/budget.py (1)
57-63: LGTM!The new event constants follow the established
budget.<domain>.<action>naming convention and are properly typed withFinal[str]. The constants align well with the durable aggregation feature being introduced.src/synthorg/persistence/sqlite/schema.sql (1)
320-328: LGTM!The table design is well-structured:
- CHECK constraints ensure non-negative values and non-empty
project_id, consistent with other tables in the schema- Primary key provides implicit index for lookups
- Default values allow atomic
INSERT ... ON CONFLICT DO UPDATEoperationslast_updated TEXT NOT NULLfollows existing timestamp patterns in the schemasrc/synthorg/observability/events/persistence.py (1)
264-280: LGTM!The new constants follow the
persistence.<entity>.<action>naming convention documented in the module docstring. The coverage of operations (incremented, fetched) and failure modes (increment_failed, fetch_failed, deserialize_failed) is comprehensive and consistent with other entity event patterns in this file.src/synthorg/budget/tracker.py (2)
590-622: LGTM!The best-effort aggregate update implementation is well-designed:
- Correctly no-ops when
project_idisNoneor repo is unconfigured- Releases the in-memory lock before I/O to avoid blocking other callers
- Re-raises
MemoryError/RecursionErrorper project convention while swallowing other exceptions- Uses appropriate event constants for success/failure logging
The parameter order in
increment()call (lines 603-607) matches the protocol signature fromproject_cost_aggregate.py.
148-172: LGTM!The
record()method changes are correct:
- In-memory append remains protected by the lock
- Aggregate update runs outside the lock to avoid blocking on I/O
- Docstring accurately describes the best-effort behavior
tests/unit/budget/test_tracker_project_aggregate.py (1)
1-90: LGTM!The test suite provides comprehensive coverage of the aggregate write path:
- Verifies
increment()is called with correct parameters whenproject_idis present- Confirms skip behavior when
project_idisNone- Validates best-effort semantics (repo failure doesn't propagate)
- Tests in-memory functionality works with and without the repo
Test structure follows project conventions with
@pytest.mark.unitmarker and async test methods.tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py (1)
1-162: LGTM!Excellent test coverage for the SQLite repository:
- CRUD operations thoroughly tested (create, read, update, accumulation)
- Error mapping validates
QueryErrorpropagation from SQLite failures- Input validation confirms negative delta rejection
- Cross-project isolation test prevents regression bugs
- Uses
pytest.approxfor float comparisons andTYPE_CHECKINGfor type hint importstests/unit/budget/test_enforcer_project_durable.py (1)
1-189: LGTM!Comprehensive test coverage for durable budget enforcement:
- Tests both
check_project_budget()andmake_budget_checker()with durable aggregate- Validates fallback paths when repo errors occur
- Boundary condition at exact budget correctly expects
ProjectBudgetExhaustedError- Tests zero budget skip behavior (no repo call)
- Properly uses
TaskTypeandPriorityenums for Task constructionsrc/synthorg/budget/enforcer.py (2)
258-312: LGTM!The
check_project_budgetimplementation correctly:
- Returns early when budget is disabled (
<= 0)- Uses
_get_project_costto query durable aggregate with in-memory fallback- Skips enforcement when cost retrieval fails (
Nonereturn)- Uses
>=comparison for budget exhaustion (correct boundary behavior)The docstring accurately describes the durable aggregate and fallback behavior.
634-637: LGTM!The baseline computation correctly uses
_get_project_costand only updatesproject_baselinewhen a non-Nonevalue is returned. This preserves the zero baseline default when both sources fail, matching the test expectation intest_both_sources_fail_uses_zero_baseline.
| try: | ||
| cursor = await self._db.execute(_SELECT_SQL, (project_id,)) | ||
| row = await cursor.fetchone() | ||
| except (sqlite3.Error, aiosqlite.Error) as exc: |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n --type=py 'except \(' src/synthorg/persistence/sqlite/project_cost_aggregate_repo.pyRepository: Aureliolo/synthorg
Length of output: 181
Convert both exception handlers to PEP 758 syntax (comma-separated, no parentheses).
Lines 102 and 177 use the parenthesized form. Change to:
except sqlite3.Error, aiosqlite.Error as exc:
This is the mandatory Python 3.14 syntax for your project.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py` at line 102,
Update the two exception handlers that currently use a parenthesized tuple to
the PEP 758 comma-separated form: replace the occurrences of "except
(sqlite3.Error, aiosqlite.Error) as exc:" with the new syntax "except
sqlite3.Error, aiosqlite.Error as exc:" wherever they appear (e.g., inside the
ProjectCostAggregateRepo methods that handle DB errors around the try/except
blocks at the two reported locations). Ensure both handlers (the one at the
earlier DB operation and the one later around async DB calls) are changed to the
comma-separated form so they match Python 3.14 syntax.
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
🤖 I have created a release *beep* *boop* --- ## [0.6.5](v0.6.4...v0.6.5) (2026-04-09) ### Features * add control-plane API endpoints batch ([#1118](#1118), [#1119](#1119), [#1120](#1120), [#1121](#1121)) ([#1138](#1138)) ([af11f0a](af11f0a)) * engine intelligence v2 -- trace enrichment, compaction, versioning eval ([#1139](#1139)) ([ed57dfa](ed57dfa)), closes [#1123](#1123) [#1125](#1125) [#1113](#1113) * generalize versioning to VersionSnapshot[T] for all entity types ([#1155](#1155)) ([5f563ce](5f563ce)), closes [#1131](#1131) [#1132](#1132) [#1133](#1133) * implement auxiliary tool categories -- design, communication, analytics ([#1152](#1152)) ([b506ba4](b506ba4)) * implement multi-project support -- engine orchestration ([#242](#242)) ([#1153](#1153)) ([74f1362](74f1362)) * implement SharedKnowledgeStore append-only + MVCC consistency model (Phase 1.5) ([#1134](#1134)) ([965d3a1](965d3a1)), closes [#1130](#1130) * implement shutdown strategies and SUSPENDED task status ([#1151](#1151)) ([6a0db11](6a0db11)) * persistent cost aggregation for project-lifetime budgets ([#1173](#1173)) ([5c212c5](5c212c5)), closes [#1156](#1156) * Prometheus /metrics endpoint and OTLP exporter ([#1122](#1122)) ([#1135](#1135)) ([aaeaae9](aaeaae9)), closes [#1124](#1124) * Prometheus metrics -- daily budget %, per-agent cost, per-agent budget % ([#1154](#1154)) ([581c494](581c494)), closes [#1148](#1148) ### Bug Fixes * communication hardening -- meeting cooldown, circuit breaker backoff, debate fallback ([#1140](#1140)) ([fe82894](fe82894)), closes [#1115](#1115) [#1116](#1116) [#1117](#1117) ### CI/CD * bump wrangler from 4.80.0 to 4.81.0 in /.github in the all group ([#1144](#1144)) ([b7c0945](b7c0945)) ### Maintenance * bump python from `6869258` to `5e59aae` in /docker/backend in the all group ([#1141](#1141)) ([01e99c2](01e99c2)) * bump python from `6869258` to `5e59aae` in /docker/sandbox in the all group ([#1143](#1143)) ([ea755bd](ea755bd)) * bump python from `6869258` to `5e59aae` in /docker/web in the all group ([#1142](#1142)) ([5416dd9](5416dd9)) * bump the all group across 1 directory with 2 updates ([#1181](#1181)) ([d3d5adf](d3d5adf)) * bump the all group across 1 directory with 3 updates ([#1146](#1146)) ([c609e6c](c609e6c)) * bump the all group in /cli with 2 updates ([#1177](#1177)) ([afd9cde](afd9cde)) * bump the all group in /site with 3 updates ([#1178](#1178)) ([7cff82a](7cff82a)) * bump the all group with 2 updates ([#1180](#1180)) ([199a1a8](199a1a8)) * bump vitest from 4.1.2 to 4.1.3 in /site in the all group ([#1145](#1145)) ([a8c1194](a8c1194)) * consolidated web deps (11 packages + hono security + test fixes) ([#1150](#1150)) ([63a9390](63a9390)), closes [#1147](#1147) [#1136](#1136) [#1137](#1137) * pin Docker Python base image to 3.14.x ([#1182](#1182)) ([8ffdd86](8ffdd86)) --- 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 durable per-project cost aggregation that survives the in-memory CostTracker's 168-hour retention window, preventing project budgets from being silently evaded when older cost records are pruned.
Changes
ProjectCostAggregate(frozen Pydantic model) andProjectCostAggregateRepository(async protocol withget/increment)SQLiteProjectCostAggregateRepositorywith atomicINSERT ... ON CONFLICT DO UPDATEupsert, shared write lock, non-negative delta validationproject_cost_aggregatestable with CHECK constraints matching codebase patterns_get_project_cost()prefers durable aggregate, falls back to in-memory tracker, returnsNoneon total failure (enforcement skipped)record()best-effort updates durable aggregate alongside in-memory write (failures logged, not propagated)SQLitePersistenceBackend.project_cost_aggregatesproperty with shared write lockTest Plan
Review Coverage
Pre-reviewed by 15 agents, 14 valid findings addressed:
Closes #1156