Skip to content

feat: persistent cost aggregation for project-lifetime budgets#1173

Merged
Aureliolo merged 4 commits intomainfrom
feat/persistent-cost-aggregation
Apr 9, 2026
Merged

feat: persistent cost aggregation for project-lifetime budgets#1173
Aureliolo merged 4 commits intomainfrom
feat/persistent-cost-aggregation

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

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

  • New model + protocol: ProjectCostAggregate (frozen Pydantic model) and ProjectCostAggregateRepository (async protocol with get/increment)
  • SQLite repository: SQLiteProjectCostAggregateRepository with atomic INSERT ... ON CONFLICT DO UPDATE upsert, shared write lock, non-negative delta validation
  • Schema: New project_cost_aggregates table with CHECK constraints matching codebase patterns
  • BudgetEnforcer: _get_project_cost() prefers durable aggregate, falls back to in-memory tracker, returns None on total failure (enforcement skipped)
  • CostTracker: record() best-effort updates durable aggregate alongside in-memory write (failures logged, not propagated)
  • Backend wiring: SQLitePersistenceBackend.project_cost_aggregates property with shared write lock
  • Observability: 3 new budget events + 5 new persistence events, all documented in CLAUDE.md
  • Design spec: Updated operations.md G6 gap status (partially addressed)
  • Package structure docs: Updated budget/ and persistence/ descriptions in claude-reference.md

Test Plan

  • 6 new test files covering model validation, repo CRUD, enforcer durable paths, tracker aggregate writes
  • Boundary tests (exact budget match), error mapping (QueryError from sqlite3.Error), zero-cost increments, negative delta rejection, both-sources-fail fallback
  • All 16,125 tests pass (16,125 passed, 9 skipped)

Review Coverage

Pre-reviewed by 15 agents, 14 valid findings addressed:

  • Docstring completeness (enforcer init, record(), check_project_budget guard clause)
  • Write lock consistency with other repos
  • CHECK constraints matching codebase patterns
  • Non-negative delta validation in increment()
  • Lock scope documentation comment
  • Package structure docs updated
  • 6 new tests for edge cases and error paths

Closes #1156

Copilot AI review requested due to automatic review settings April 8, 2026 22:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5ccb0779-a804-4443-84f8-1441bd2b1b2d

📥 Commits

Reviewing files that changed from the base of the PR and between 2f634d6 and 13c8af6.

📒 Files selected for processing (7)
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/persistence/sqlite/schema.sql
  • tests/unit/budget/test_enforcer_project_durable.py
  • tests/unit/budget/test_project_cost_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use from __future__ import annotations in Python; Python 3.14+ has PEP 649 native lazy annotations

Use PEP 758 except syntax: except A, B: without parentheses in Python 3.14; ruff enforces this

Add type hints to all public functions in Python; enforce with mypy strict mode

Use Google-style docstrings on all public classes and functions in Python; required and enforced by ruff D rules

Create new objects instead of mutating existing ones; use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement of non-Pydantic internal collections

Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence) for dict/list fields in frozen Pydantic models

Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time

Use @computed_field for derived values instead of storing and validating redundant fields in Pydantic models (e.g. TokenUsage.total_tokens)

Use NotBlankStr from core.types for all identifier/name fields in Python, including optional (NotBlankStr | None) and tuple variants, instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new Python code (e.g. multiple tool invocations, parallel agent calls) instead of bare create_task; use structured concurrency

Keep Python functions under 50 lines and files under 800 lines

Set line length to 88 characters in Python (enforced by ruff)

Handle errors explicitly in Python; never silently swallow exceptions

Validate at system boundaries (user input, external APIs, config files) ...

Files:

  • tests/unit/budget/test_project_cost_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
  • src/synthorg/budget/tracker.py
  • tests/unit/budget/test_enforcer_project_durable.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.py
**/test_*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers for Python tests

Prefer @pytest.mark.parametrize for testing similar cases in Python tests

For timing-sensitive Python tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins; for blocking tasks, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/budget/test_project_cost_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
  • tests/unit/budget/test_enforcer_project_durable.py
tests/**/*.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/budget/test_project_cost_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
  • tests/unit/budget/test_enforcer_project_durable.py
**/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in Python project-owned code, docstrings, comments, tests, or config examples; use generic names like example-provider, example-large-001, test-provider, test-small-001, or size aliases large/medium/small

Files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/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/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.py
🧠 Learnings (25)
📓 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
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Read the relevant `docs/design/` page before implementing any feature or planning any issue; DESIGN_SPEC.md is a pointer file linking to the 12 design pages
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Alert the user and explain if implementation deviates from the design spec rather than silently diverging; every deviation needs explicit user approval
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations occur during implementation
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Present every implementation plan to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Be critical at every phase of planning and implementation by actively looking for ways to improve design in the spirit of robustness, correctness, simplicity, and future-proofing; surface improvements as suggestions not silent changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
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-08T23:14:13.746Z
Learning: Maintain 80% minimum code coverage in Python tests (enforced in CI)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Use `asyncio_mode = "auto"` in Python pytest configuration; no manual `pytest.mark.asyncio` needed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Set global timeout to 30 seconds per Python test in `pyproject.toml`; do not add per-file `pytest.mark.timeout(30)` markers. Non-default overrides like `timeout(60)` are allowed.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Use `pytest-xdist` via `-n 8` when running Python tests locally; never run tests sequentially. Always include `-n 8` in local test runs.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Control Hypothesis profiles via `HYPOTHESIS_PROFILE` environment variable in Python
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Persist failing Hypothesis examples to `~/.synthorg/hypothesis-examples/` via `_WriteOnlyDatabase` in Python tests; `.hypothesis/` is gitignored
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: When Hypothesis finds a failure in Python, fix the underlying bug and add an explicit `example(...)` decorator so the case is permanently covered in CI; do not skip or dismiss flaky tests
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Commit messages must follow `<type>: <description>` format (types: feat, fix, refactor, docs, test, chore, perf, ci); enforced by commitizen commit-msg hook
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: All commits must be GPG/SSH signed on `main` via branch protection
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Use branch names in format `<type>/<slug>` from main in Git
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Pre-commit hooks include: 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, golangci-lint + go vet (CLI conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard conditional, zero warnings)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Pre-push hooks include: mypy type-check (affected modules), pytest unit tests (affected modules), golangci-lint + go vet + go test (CLI conditional), eslint-web (web dashboard); full runs triggered by foundational module changes or conftest changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Blockify rule `block-pr-create`: blocks direct `gh pr create` (must use `/pre-pr-review`)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Blockify rule `enforce-parallel-tests`: enforces `-n 8` with pytest
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Blockify rule `no-cd-prefix`: blocks `cd` prefix in Bash commands
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Blockify rule `no-local-coverage`: blocks `--cov` flags locally (CI handles coverage)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Never use `cd` in Bash commands in this project -- 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-08T23:14:13.746Z
Learning: For tools without a `-C` flag in Bash, use `bash -c "cd <dir> && <cmd>"` (safe child process) instead of `cd` with side effects; example: `bash -c "cd web && npm install"`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Never use Bash to write or modify files -- use Write or Edit tools; do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)"`, or `tee` for file creation/modification
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Use `gh issue list` via Bash for GitHub issue queries (not MCP tools); MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Use squash merge for all PRs -- PR body becomes the squash commit message on main; preserve trailers (e.g. `Release-As`, `Closes `#N``) in PR body to land in final commit
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Preserve existing `Closes `#NNN`` references in PR issue references; never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: After finishing an issue implementation, create a feature branch (`<type>/<slug>`), commit, and push immediately -- do NOT create a PR automatically. Do NOT leave work uncommitted on main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Never create a PR directly -- `gh pr create` is blocked by hookify; always use `/pre-pr-review` to create PRs, which runs automated checks + review agents + fixes before PR creation. Use `/pre-pr-review quick` for trivial/docs-only changes.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: After PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Fix everything valid found by review agents -- never skip or defer; fix pre-existing issues in surrounding code, suggestions, and findings adjacent to PR changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: Always read the relevant section of `web/CLAUDE.md` for component inventory, design system, and post-training references before implementing web dashboard features
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: See `web/CLAUDE.md` for full component inventory, design token rules, and post-training references (TS6, Storybook 10) for web dashboard development
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: See `cli/CLAUDE.md` for CLI commands, flags, and reference; always use `go -C cli` (never `cd cli`)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T23:14:13.746Z
Learning: See `docs/reference/claude-reference.md` for documentation layout, Docker commands, package structure, releasing, CI pipelines, and dependencies
📚 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/budget/tracker.py
  • tests/unit/budget/test_enforcer_project_durable.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.py
📚 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 handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/enforcer.py
📚 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 : Use PEP 758 except syntax: `except A, B:` without parentheses (ruff enforces this on Python 3.14)

Applied to files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.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: 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/tracker.py
  • src/synthorg/budget/enforcer.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/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/project_cost_aggregate_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/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: 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/persistence/sqlite/project_cost_aggregate_repo.py
📚 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 `allow_inf_nan=False` in all `ConfigDict` declarations to reject `NaN`/`Inf` in numeric fields at validation time

Applied to files:

  • src/synthorg/persistence/sqlite/project_cost_aggregate_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 `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • src/synthorg/budget/enforcer.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/budget/enforcer.py
📚 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 : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional `NotBlankStr | None` and tuple variants `tuple[NotBlankStr, ...]`) instead of manual whitespace validators

Applied to files:

  • src/synthorg/budget/enforcer.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/budget/enforcer.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/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:

  • src/synthorg/budget/enforcer.py
🔇 Additional comments (7)
src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py (1)

1-233: LGTM!

The repository implementation is well-structured with:

  • Atomic upsert via INSERT ... ON CONFLICT DO UPDATE ... RETURNING ensures consistency.
  • Non-finite/non-negative delta validation at line 173 addresses boundary input risks.
  • Readback via RETURNING inside the locked section (lines 189-195) avoids the race condition noted in prior review.
  • Proper error mapping from SQLite errors to QueryError with observability logging.
src/synthorg/budget/tracker.py (1)

591-623: LGTM!

The best-effort aggregate update implementation is sound:

  • Correctly no-ops when repo or project_id is absent.
  • Re-raises system errors (MemoryError, RecursionError) per project convention.
  • Logs failures at WARNING with exc_info=True for observability without blocking in-memory recording.
  • Called outside the _lock (line 173) to avoid blocking other callers on I/O, as documented.
src/synthorg/persistence/sqlite/schema.sql (1)

320-330: LGTM!

The project_cost_aggregates table schema is well-designed:

  • project_id TEXT PRIMARY KEY provides implicit indexing for lookups.
  • CHECK constraints mirror the Pydantic model validation (non-negative costs/tokens, non-blank ID).
  • last_updated UTC constraint matches the pattern used in decision_records.recorded_at.
  • Column definitions align exactly with the repository's _UPSERT_SQL and _SELECT_SQL.
src/synthorg/budget/enforcer.py (1)

880-949: LGTM!

The _get_project_cost helper implements the durable-first strategy correctly:

  • Prefers durable aggregate when project_cost_repo is configured (lines 903-926).
  • Falls back to in-memory CostTracker when aggregate query fails (lines 928-949).
  • Returns None when both sources fail, signaling the caller to skip enforcement.
  • Rounds result to BUDGET_ROUNDING_PRECISION for consistency with other cost values.
  • Logs the baseline source ("aggregate" vs "in_memory") at DEBUG for observability.
tests/unit/budget/test_project_cost_aggregate.py (1)

1-117: LGTM!

Comprehensive test coverage for the ProjectCostAggregate model:

  • Validates construction, immutability (frozen=True), and field constraints.
  • Covers edge cases: blank project_id, negative values, and non-finite floats (NaN, inf).
  • Protocol runtime-checkability test correctly uses isinstance() with a stub implementation, addressing the previous review feedback about avoiding private __protocol_attrs__.
tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py (1)

1-182: LGTM!

Excellent test coverage for the SQLite repository:

  • CRUD operations: creation, updates, accumulation, and reads.
  • Edge cases: zero-cost increments (line 111-122), project isolation (line 85-99), timestamp monotonicity (line 101-109).
  • Error handling: QueryError mapping from sqlite3.OperationalError (lines 124-154).
  • Input validation: parametrized tests for negative and non-finite deltas (lines 156-181).
tests/unit/budget/test_enforcer_project_durable.py (1)

1-193: LGTM!

Comprehensive test coverage for the durable project cost enforcement:

  • TestCheckProjectBudgetDurable: Validates aggregate usage, fallback to in-memory on error, exact boundary enforcement, and zero-budget skip behavior.
  • TestMakeBudgetCheckerDurable: Verifies baseline computation from durable aggregate, fallback paths, and zero-baseline on total failure.
  • Helper functions are well-factored and reusable.

Walkthrough

This change adds durable per-project cost aggregation: a new ProjectCostAggregate model and ProjectCostAggregateRepository protocol, a SQLite project_cost_aggregates table, and a SQLiteProjectCostAggregateRepository implementation with atomic upsert semantics. CostTracker and BudgetEnforcer were extended to consult and optionally update the durable aggregate (with in-memory fallback). New observability event constants for aggregation and baseline source were added. Multiple unit tests and docs were updated to cover the new APIs, persistence layer, and enforcement behavior.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.31% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing persistent cost aggregation for project-lifetime budgets, which is the core feature across all modified files.
Description check ✅ Passed The description comprehensively covers the changeset, explaining the new models, repositories, schema changes, BudgetEnforcer and CostTracker modifications, observability additions, and documentation updates.
Linked Issues check ✅ Passed All coding requirements from #1156 are addressed: durable per-project aggregation via ProjectCostAggregate/Repository, SQLite implementation with atomic upserts, budget enforcer baseline queries using the durable aggregate with fallback, in-memory tracker retained, error handling with observability, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #1156 objectives: implementing durable cost aggregation, integrating it into budget enforcement and tracking, wiring the persistence backend, and documenting the design. No unrelated changes detected.

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


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

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 22:18 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Dependency Review

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

Snapshot Warnings

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

Scanned Files

None

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

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

Comment on lines +889 to +890
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except syntax with a comma is from Python 2 and is a SyntaxError in Python 3. To catch multiple exceptions, you should wrap them in a tuple.

            except (MemoryError, RecursionError):

Comment on lines +913 to +914
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except syntax with a comma is from Python 2 and is a SyntaxError in Python 3. To catch multiple exceptions, you should wrap them in a tuple.

        except (MemoryError, RecursionError):

Comment on lines +614 to +615
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except syntax with a comma is from Python 2 and is a SyntaxError in Python 3. To catch multiple exceptions, you should wrap them in a tuple.

        except (MemoryError, RecursionError):

- **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`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This line is extremely long (over 3000 characters), which makes it very difficult to read, edit, and review. For better maintainability, consider restructuring this list. You could break it down into a bulleted list, with one event or event group per line.

Comment on lines +186 to +187
# Read back the updated aggregate.
aggregate = await self.get(project_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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.

Comment on lines +99 to +109
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

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +127
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

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

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

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

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

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

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

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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

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

codecov bot commented Apr 8, 2026

Codecov Report

❌ Patch coverage is 86.46617% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.88%. Comparing base (581c494) to head (13c8af6).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/budget/enforcer.py 70.37% 6 Missing and 2 partials ⚠️
.../persistence/sqlite/project_cost_aggregate_repo.py 87.30% 8 Missing ⚠️
src/synthorg/budget/tracker.py 91.66% 1 Missing ⚠️
src/synthorg/persistence/sqlite/backend.py 85.71% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 581c494 and 2f634d6.

📒 Files selected for processing (17)
  • CLAUDE.md
  • docs/design/operations.md
  • docs/reference/claude-reference.md
  • src/synthorg/budget/__init__.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/project_cost_aggregate.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/persistence.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/persistence/sqlite/schema.sql
  • tests/unit/budget/test_enforcer_project_durable.py
  • tests/unit/budget/test_project_cost_aggregate.py
  • tests/unit/budget/test_tracker_project_aggregate.py
  • tests/unit/persistence/sqlite/test_migrations.py
  • tests/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.14

No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations

All 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.TaskGroup for fan-out/fan-in parallel operations instead of bare create_task; prefer structured concurrency

Use Pydantic v2 with BaseModel, model_validator, computed_field, and ConfigDict; use allow_inf_nan=False in all ConfigDict declarations

Use NotBlankStr from core.types for all identifier/name fields instead of manual whitespace validators (including optional and tuple variants)

Use @computed_field for 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 and MappingProxyType wrapping

Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves

Keep 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 management

Never 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.py
  • tests/unit/budget/test_project_cost_aggregate.py
  • src/synthorg/budget/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/persistence.py
  • tests/unit/budget/test_tracker_project_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
  • tests/unit/budget/test_enforcer_project_durable.py
  • src/synthorg/budget/project_cost_aggregate.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/synthorg/budget/enforcer.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow markers on tests

Configure asyncio_mode = "auto" for pytest; no manual @pytest.mark.asyncio needed

Set global test timeout to 30 seconds per test (in pyproject.toml); do not add per-file markers unless overriding with different timeout

Use Hypothesis for property-based testing with @given and @settings decorators; configure profiles in tests/conftest.py

When Hypothesis finds a failing property test, fix the underlying bug and add an explicit @example(...) decorator to permanently cover the case in CI

For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins

For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) for cancellation safety

Never 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.py
  • tests/unit/budget/test_project_cost_aggregate.py
  • tests/unit/budget/test_tracker_project_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
  • tests/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.py
  • tests/unit/budget/test_project_cost_aggregate.py
  • tests/unit/budget/test_tracker_project_aggregate.py
  • tests/unit/persistence/sqlite/test_project_cost_aggregate_repo.py
  • tests/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_logger with variable name logger

Use event name constants from synthorg.observability.events domain-specific modules instead of hardcoded strings; import directly like from synthorg.observability.events.<domain> import EVENT_CONSTANT

Apply copy.deepcopy() at system boundaries: tool execution, LLM provider serialization, inter-agent delegation, and persistence serialization

Validate 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-provider or test-provider

Files:

  • src/synthorg/budget/__init__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/persistence.py
  • src/synthorg/budget/project_cost_aggregate.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/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__.py
  • src/synthorg/persistence/sqlite/backend.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/persistence.py
  • src/synthorg/budget/project_cost_aggregate.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py
  • src/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.md
  • src/synthorg/budget/__init__.py
  • src/synthorg/observability/events/budget.py
  • tests/unit/budget/test_tracker_project_aggregate.py
  • tests/unit/budget/test_enforcer_project_durable.py
  • src/synthorg/budget/project_cost_aggregate.py
  • src/synthorg/budget/tracker.py
  • src/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.md
  • src/synthorg/budget/__init__.py
  • src/synthorg/budget/project_cost_aggregate.py
  • src/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.md
  • src/synthorg/budget/__init__.py
  • src/synthorg/observability/events/budget.py
  • docs/design/operations.md
  • src/synthorg/budget/project_cost_aggregate.py
  • src/synthorg/budget/tracker.py
  • src/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__.py
  • src/synthorg/observability/events/budget.py
  • docs/design/operations.md
  • src/synthorg/budget/tracker.py
  • src/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.py
  • src/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.py
  • src/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.py
  • CLAUDE.md
  • src/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.py
  • CLAUDE.md
  • src/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.py
  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : 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.py
  • src/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.py
  • CLAUDE.md
  • src/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.py
  • CLAUDE.md
  • src/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.py
  • src/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.py
  • CLAUDE.md
  • src/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_aggregates to _EXPECTED_TABLES is 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_connected access 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 with Final[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 UPDATE operations
  • last_updated TEXT NOT NULL follows existing timestamp patterns in the schema
src/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_id is None or repo is unconfigured
  • Releases the in-memory lock before I/O to avoid blocking other callers
  • Re-raises MemoryError/RecursionError per 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 from project_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 when project_id is present
  • Confirms skip behavior when project_id is None
  • 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.unit marker 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 QueryError propagation from SQLite failures
  • Input validation confirms negative delta rejection
  • Cross-project isolation test prevents regression bugs
  • Uses pytest.approx for float comparisons and TYPE_CHECKING for type hint imports
tests/unit/budget/test_enforcer_project_durable.py (1)

1-189: LGTM!

Comprehensive test coverage for durable budget enforcement:

  • Tests both check_project_budget() and make_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 TaskType and Priority enums for Task construction
src/synthorg/budget/enforcer.py (2)

258-312: LGTM!

The check_project_budget implementation correctly:

  • Returns early when budget is disabled (<= 0)
  • Uses _get_project_cost to query durable aggregate with in-memory fallback
  • Skips enforcement when cost retrieval fails (None return)
  • 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_cost and only updates project_baseline when a non-None value is returned. This preserves the zero baseline default when both sources fail, matching the test expectation in test_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:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --type=py 'except \(' src/synthorg/persistence/sqlite/project_cost_aggregate_repo.py

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

@Aureliolo
Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Aureliolo
Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Aureliolo Aureliolo merged commit 5c212c5 into main Apr 9, 2026
33 of 34 checks passed
@Aureliolo Aureliolo deleted the feat/persistent-cost-aggregation branch April 9, 2026 06:42
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 9, 2026 06:42 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 9, 2026
🤖 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: persistent cost aggregation for project-lifetime budgets

2 participants