Skip to content

feat: implement multi-project support -- engine orchestration (#242)#1153

Merged
Aureliolo merged 5 commits intomainfrom
feat/multi-project-support
Apr 8, 2026
Merged

feat: implement multi-project support -- engine orchestration (#242)#1153
Aureliolo merged 5 commits intomainfrom
feat/multi-project-support

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Implements the orchestration layer for multi-project support (#242). The data layer already existed (Project model, CRUD API, SQLite persistence, WS events) -- this PR adds engine-level project awareness so multiple projects can run concurrently with isolated budgets and team-scoped agent allocation.

Changes

Engine -- Project Validation (agent_engine.py)

  • New project_repo dependency on AgentEngine
  • _validate_project() method validates project existence and agent team membership before execution
  • Project budget pre-flight check via BudgetEnforcer.check_project_budget()
  • Project context (project_id, project_budget) threaded through the entire execution pipeline: run() -> _execute() -> _post_execution_pipeline() -> _apply_recovery() -> _resume_from_checkpoint() -> _finalize_resume()
  • ProjectNotFoundError and ProjectAgentNotMemberError propagate with generic messages (identifiers stored as attributes for structured logging only, following SelfReviewError pattern)

Budget -- Project-Level Isolation (enforcer.py, tracker.py, _enforcer_helpers.py)

  • CostRecord.project_id field tags every cost record with its project
  • CostTracker.get_project_cost() and get_project_records() for project-scoped queries
  • BudgetEnforcer.check_project_budget() -- pre-flight project budget enforcement
  • make_budget_checker() extended with project_id/project_budget params for in-flight checking
  • _check_project_limit() in the budget checker closure enforces project budget during execution

Assignment -- Project Team Filtering (assignment/models.py, assignment/service.py)

  • AssignmentRequest.project_team field (tuple of agent IDs)
  • TaskAssignmentService.assign() filters available agents to project team members before strategy dispatch
  • Empty team = no filtering (all agents eligible)

Error Types (budget/errors.py, engine/errors.py)

  • ProjectBudgetExhaustedError(BudgetExhaustedError) with project_id, project_budget, project_spent
  • ProjectNotFoundError(EngineError) with project_id attribute
  • ProjectAgentNotMemberError(EngineError) with project_id, agent_id attributes

Observability (events/budget.py, events/execution.py, events/task_assignment.py)

  • 7 new event constants for project budget checks, validation, and assignment filtering

Documentation

  • docs/design/engine.md -- new pipeline step 3 (Project validation) with full description
  • CLAUDE.md -- new event constants added to logging section

Test Plan

37 new tests across 6 new test files + 3 tests added to existing file:

  • test_cost_record_project.py -- CostRecord project_id field (4 tests)
  • test_tracker_project.py -- CostTracker project queries (9 tests)
  • test_enforcer_project.py -- Pre-flight project budget enforcement (7 tests)
  • test_enforcer_project_inflight.py -- In-flight project budget checking (3 tests)
  • test_agent_engine_project.py -- Engine project validation + budget integration (6 tests)
  • test_assignment_project_team.py -- Assignment team filtering (5 tests)
  • test_cost_recording.py -- Project ID propagation (3 new tests)

Full suite: 16033 passed, 9 skipped. mypy clean. ruff clean.

Review Coverage

Pre-reviewed by 13 agents, 6 findings addressed:

  1. check_project_budget() -- added try/except for consistency with make_budget_checker() pattern
  2. Baseline error log -- added project_id kwarg for debuggability
  3. Assignment "no eligible" log -- added available_agents count
  4. Error messages -- switched to generic messages with IDs as attributes (SelfReviewError pattern)
  5. Design docs -- added pipeline step 3 and event constants to CLAUDE.md
  6. Exception handler ordering -- added clarifying comment

Closes #242

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

coderabbitai bot commented Apr 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9920f01d-a8ab-4521-8f4e-c9de92716920

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1a639 and 07683dc.

📒 Files selected for processing (1)
  • src/synthorg/engine/agent_engine.py

Walkthrough

Adds project-scoped functionality across the codebase: AgentEngine gains project validation and project-aware run/resume paths with ProjectNotFoundError and ProjectAgentNotMemberError; budget subsystem gains project-level pre-flight and in-flight checks with ProjectBudgetExhaustedError, project cost queries, and project-aware BudgetChecker closures; CostRecord now includes optional project_id and cost recording propagates it; Task assignment filters by project_team; new observability event constants for project queries/enforcement/validation; and numerous unit tests for tracker, enforcer, cost recording, assignment, and engine behaviors.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: implement multi-project support -- engine orchestration (#242)' directly and specifically describes the main change: adding multi-project support at the engine/orchestration layer.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, detailing engine, budget, assignment, error, observability, and documentation changes that all support the multi-project orchestration objective.
Linked Issues check ✅ Passed The PR fully addresses issue #242 objectives: engine project context validation [agent_engine.py], cross-project budget isolation [enforcer.py, tracker.py, _enforcer_helpers.py], project team filtering [assignment service], and concurrent project support through isolated budget enforcement and project_id threading throughout the pipeline.
Out of Scope Changes check ✅ Passed All changes are in-scope: project validation, budget isolation, team filtering, error types, observability events, and documentation updates directly support the multi-project orchestration objectives from issue #242. README updates accurately reflect the new multi-project capabilities.
Docstring Coverage ✅ Passed Docstring coverage is 58.23% which is sufficient. The required threshold is 40.00%.

✏️ 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 17:43 — 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 07683dc.
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 project-level budget enforcement and team membership validation. It introduces project_id tracking in cost records, adds project-specific query methods to the CostTracker, and updates the AgentEngine to perform pre-flight project validation and budget checks. Additionally, the task assignment service now includes project team filtering. Feedback identifies several critical syntax errors involving outdated Python 2 exception handling and recommends adding safeguards for null project identifiers to avoid runtime validation failures.

Comment on lines +270 to +271
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 is for Python 2. In Python 3, multiple exceptions must be grouped in a tuple, like except (MemoryError, RecursionError):. This will cause a SyntaxError.

This outdated syntax appears multiple times in this pull request (e.g., here and on line 633, and in agent_engine.py). Please correct all occurrences.

        except (MemoryError, RecursionError):

Comment on lines 597 to 598
except MemoryError, RecursionError:
logger.exception(
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 is for Python 2 and will raise a SyntaxError in Python 3. To catch multiple exceptions, they should be grouped in a tuple.

            except (MemoryError, RecursionError):

Comment on lines +605 to +609
except ProjectNotFoundError, ProjectAgentNotMemberError:
# ProjectBudgetExhaustedError (from _validate_project)
# is a BudgetExhaustedError subclass -- intentionally
# caught by the handler below, not here.
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 is for Python 2 and will cause a SyntaxError in Python 3. Multiple exceptions should be grouped in a tuple, like except (ProjectNotFoundError, ProjectAgentNotMemberError):.

            except (ProjectNotFoundError, ProjectAgentNotMemberError):
                # ProjectBudgetExhaustedError (from _validate_project)
                # is a BudgetExhaustedError subclass -- intentionally
                # caught by the handler below, not here.
                raise

ProjectAgentNotMemberError: When the agent is not in the
project team (non-empty team only).
"""
project = await self._project_repo.get(task.project) # type: ignore[union-attr]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If task.project can be None, this could lead to a runtime error. self._project_repo.get(None) might be called, and if it returns None, ProjectNotFoundError would be raised with project_id=None. This would violate the NotBlankStr constraint in its constructor.

Please add a check at the beginning of this method to handle cases where task.project is None or empty, for example by raising a ValueError or returning early.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements the engine-layer orchestration needed for multi-project support by threading project_id/project_budget through execution, enforcing project-scoped budgets, and restricting agent assignment to project teams.

Changes:

  • Add AgentEngine project validation (existence + team membership) and project budget preflight/in-flight enforcement.
  • Tag cost records with project_id and extend CostTracker/BudgetEnforcer APIs for project-scoped aggregation and enforcement.
  • Add assignment-time filtering to restrict eligible agents to a project’s team, plus new observability events and documentation/tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/synthorg/engine/agent_engine.py Adds project validation + threads project context through execution pipeline for budget checking and cost recording.
src/synthorg/engine/errors.py Introduces project-specific engine errors with generic messages + structured attributes.
src/synthorg/engine/cost_recording.py Records execution costs with optional project_id.
src/synthorg/engine/assignment/models.py Adds project_team to assignment request model.
src/synthorg/engine/assignment/service.py Filters available agents by project_team and emits new assignment events.
src/synthorg/budget/cost_record.py Adds optional project_id to CostRecord.
src/synthorg/budget/tracker.py Adds project-scoped cost/records query helpers and project_id filtering in _filter_records.
src/synthorg/budget/enforcer.py Adds preflight project budget enforcement and extends in-flight budget checker inputs.
src/synthorg/budget/_enforcer_helpers.py Adds project budget enforcement to the checker closure.
src/synthorg/budget/errors.py Introduces ProjectBudgetExhaustedError.
src/synthorg/observability/events/budget.py Adds project-level budget event constants.
src/synthorg/observability/events/execution.py Adds project validation/cost event constants.
src/synthorg/observability/events/task_assignment.py Adds project team filtering event constants.
docs/design/engine.md Documents new project validation pipeline step and project-aware budget checking.
CLAUDE.md Updates logging/event-constant guidance with new project-related events.
tests/unit/budget/conftest.py Updates test helper to support project_id on cost records.
tests/unit/budget/test_cost_record_project.py Tests CostRecord.project_id behavior/validation/immutability.
tests/unit/budget/test_tracker_project.py Tests project-scoped tracker cost and record queries.
tests/unit/budget/test_enforcer_project.py Tests BudgetEnforcer.check_project_budget() behavior.
tests/unit/budget/test_enforcer_project_inflight.py Tests in-flight project budget checking via make_budget_checker().
tests/unit/engine/test_cost_recording.py Tests propagation of project_id through cost recording.
tests/unit/engine/test_agent_engine_project.py Tests engine project validation + project budget integration.
tests/unit/engine/test_assignment_project_team.py Tests assignment filtering by project team membership.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +394 to +414
def _check_project_limit(
running_cost: float,
project_budget: float,
project_baseline: float,
agent_id: str,
) -> bool:
"""Return True if project budget is exhausted."""
if project_budget <= 0:
return False
total_project = round(
project_baseline + running_cost,
BUDGET_ROUNDING_PRECISION,
)
if total_project >= project_budget:
logger.warning(
BUDGET_PROJECT_BUDGET_EXCEEDED,
agent_id=agent_id,
total_project=total_project,
project_budget=project_budget,
)
return True
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.

In the in-flight budget checker, project budget exhaustion is logged without the project identifier (only agent_id/total_project/project_budget). With multi-project concurrency this makes the event hard to attribute. Consider threading project_id into the checker closure (e.g., as an additional _build_checker_closure arg) and include it in the BUDGET_PROJECT_BUDGET_EXCEEDED log fields.

Copilot uses AI. Check for mistakes.
Comment on lines +298 to +304
_validate_time_range(start, end)
logger.debug(
BUDGET_PROJECT_COST_QUERIED,
project_id=project_id,
start=start,
end=end,
)
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_records() logs BUDGET_PROJECT_COST_QUERIED, which is semantically a “cost” aggregation event. This makes it difficult to distinguish “cost sum” queries from “records list” queries in observability. Either introduce a dedicated event constant for record queries (e.g., BUDGET_PROJECT_RECORDS_QUERIED) or rename/reuse the existing event to reflect both usages.

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +107
# -- Project validation events --
EXECUTION_PROJECT_VALIDATION_FAILED: Final[str] = "execution.project.validation_failed"
EXECUTION_PROJECT_COST_RECORDED: Final[str] = "execution.project.cost_recorded"
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.

EXECUTION_PROJECT_COST_RECORDED is introduced here but is not referenced anywhere else in the codebase (only its definition exists). If it’s intended to be emitted during cost recording, wire it into the relevant logging path; otherwise, consider removing it to avoid dead/unused event constants.

Copilot uses AI. Check for mistakes.
Comment on lines 1409 to 1463
*,
completion_config: CompletionConfig | None = None,
effective_autonomy: EffectiveAutonomy | None = None,
provider: CompletionProvider | None = None,
project_id: str | None = None,
) -> ExecutionResult:
"""Resume execution from a checkpoint.

Policy: resumed executions run without a wall-clock timeout.
The loop's per-turn budget and max_turns still constrain
execution.
"""
checkpoint_json = self._validate_checkpoint_json(
recovery_result,
agent_id,
task_id,
)
logger.info(
EXECUTION_RESUME_START,
agent_id=agent_id,
task_id=task_id,
resume_attempt=recovery_result.resume_attempt,
)

try:
result, execution_id = await self._reconstruct_and_run_resume(
checkpoint_json,
recovery_result.error_message,
agent_id,
task_id,
failure_category=recovery_result.failure_category,
criteria_failed=recovery_result.criteria_failed,
completion_config=completion_config,
effective_autonomy=effective_autonomy,
provider=provider,
)
except MemoryError, RecursionError:
raise
except Exception as exc:
logger.exception(
EXECUTION_RESUME_FAILED,
agent_id=agent_id,
task_id=task_id,
error=f"{type(exc).__name__}: {exc}",
)
raise
else:
return await self._finalize_resume(
result,
identity,
execution_id,
agent_id,
task_id,
project_id=project_id,
)
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.

Project validation is now threaded into the resume pipeline via project_id, but the resumed execution loop’s budget checker is still created without any project budget context. This means a recovery/resume run may not enforce project-level budgets in-flight, potentially allowing overspend after a crash/retry. Consider threading the project budget into the resume path and passing project_id/project_budget into BudgetEnforcer.make_budget_checker() when resuming.

Copilot uses AI. Check for mistakes.
Comment on lines +1288 to +1293
logger.warning(
EXECUTION_PROJECT_VALIDATION_FAILED,
agent_id=agent_id,
task_id=task_id,
project=task.project,
reason="project_not_found",
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 structured log fields here use project=task.project, while other budget/observability paths use project_id. For consistency and easier querying across domains, consider using project_id as the key name in these validation failure logs as well.

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

codecov bot commented Apr 8, 2026

Codecov Report

❌ Patch coverage is 82.65306% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (965d3a1) to head (07683dc).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/budget/enforcer.py 65.38% 9 Missing ⚠️
src/synthorg/engine/agent_engine.py 66.66% 7 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1153      +/-   ##
==========================================
- Coverage   88.88%   88.86%   -0.02%     
==========================================
  Files         856      856              
  Lines       50114    50208      +94     
  Branches     5035     5047      +12     
==========================================
+ Hits        44544    44618      +74     
- Misses       4614     4631      +17     
- Partials      956      959       +3     

☔ 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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/engine/agent_engine.py (1)

1403-1463: ⚠️ Potential issue | 🔴 Critical

Re-run project validation before checkpoint resume.

This path still goes straight from checkpoint reconstruction into the resumed loop without calling _validate_project(). If the project is deleted or the agent is removed from the team after the original run, the resumed execution continues anyway; it also resumes without refreshed project-budget context. Please revalidate the checkpoint task’s project here and thread the resolved budget into the resumed checker and finalizer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/agent_engine.py` around lines 1403 - 1463, Before
reconstructing the checkpoint in _resume_from_checkpoint, call
self._validate_project(project_id) to re-run project validation (raise/fail if
validation returns error) and capture the resolved budget (e.g.,
project_budget). Thread that project_budget into the resumed flow by adding a
project_budget parameter to the calls and signatures of
_reconstruct_and_run_resume and _finalize_resume (pass
project_budget=<resolved_budget>), so the resumed checker and finalizer use the
refreshed budget/context; ensure you update their definitions to accept and use
the new project_budget argument.
🤖 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 249-306: check_project_budget currently relies on the volatile
_cost_tracker.get_project_cost which prunes old records (168h), so project
budgets can be evaded once pruning runs; change enforcement to use a durable
lifetime aggregate or a cost query with an explicit window. Update the call site
in check_project_budget (and the corresponding in-flight baseline reader
referenced elsewhere) to call a new stable API on the tracker such as
get_project_lifetime_cost(project_id) or get_project_cost(project_id,
window="lifetime"), or compute/maintain a persisted Project.total_spent field
and read that instead; ensure the new API is implemented on CostTracker (or
persist aggregate on Project) to return non-pruned lifetime spend before
enforcing and use that method name in place of _cost_tracker.get_project_cost.

In `@src/synthorg/budget/tracker.py`:
- Around line 240-245: Change the type of public project identifier parameters
to NotBlankStr: update the get_project_cost signature (project_id: str ->
project_id: NotBlankStr) and the other public project_id query function in this
module to use NotBlankStr from core.types, add the import for NotBlankStr, and
keep the rest of the signatures/logic unchanged so blank/whitespace IDs validate
at call time rather than becoming silent misses.

In `@src/synthorg/engine/cost_recording.py`:
- Line 34: Change the project_id parameter type from str | None to NotBlankStr |
None and import NotBlankStr from core.types; update both occurrences of the
project_id annotation in this module (the two locations referenced) so
identifier contracts are consistent and whitespace validation is delegated to
NotBlankStr instead of manual checks.

In `@tests/unit/engine/test_cost_recording.py`:
- Around line 325-364: Combine the three tests (test_project_id_set_on_records,
test_project_id_none_by_default, test_project_id_applied_to_all_turns) into a
single parametrized pytest function using `@pytest.mark.parametrize` that drives
different (turns, project_id, expected_count, expected_project_ids) cases and
calls record_execution_costs with the same pattern (use _FakeTracker and _result
as before); inside the test assert len(tracker.records) == expected_count and
assert each tracker.records[i].project_id matches expected_project_ids (or is
None) so all three scenarios (single turn with "proj-100", default None, and two
turns with "proj-200") are covered by parameters while reusing the existing
helpers record_execution_costs, _FakeTracker, _result, and _turn.

---

Outside diff comments:
In `@src/synthorg/engine/agent_engine.py`:
- Around line 1403-1463: Before reconstructing the checkpoint in
_resume_from_checkpoint, call self._validate_project(project_id) to re-run
project validation (raise/fail if validation returns error) and capture the
resolved budget (e.g., project_budget). Thread that project_budget into the
resumed flow by adding a project_budget parameter to the calls and signatures of
_reconstruct_and_run_resume and _finalize_resume (pass
project_budget=<resolved_budget>), so the resumed checker and finalizer use the
refreshed budget/context; ensure you update their definitions to accept and use
the new project_budget argument.
🪄 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: c1ee888e-22c6-4961-a693-e20fa036f055

📥 Commits

Reviewing files that changed from the base of the PR and between 63a9390 and e720759.

📒 Files selected for processing (23)
  • CLAUDE.md
  • docs/design/engine.md
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/cost_record.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/errors.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/assignment/models.py
  • src/synthorg/engine/assignment/service.py
  • src/synthorg/engine/cost_recording.py
  • src/synthorg/engine/errors.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/task_assignment.py
  • tests/unit/budget/conftest.py
  • tests/unit/budget/test_cost_record_project.py
  • tests/unit/budget/test_enforcer_project.py
  • tests/unit/budget/test_enforcer_project_inflight.py
  • tests/unit/budget/test_tracker_project.py
  • tests/unit/engine/test_agent_engine_project.py
  • tests/unit/engine/test_assignment_project_team.py
  • tests/unit/engine/test_cost_recording.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 Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name), and NEVER use import logging or print() in application code

Use event name constants from synthorg.observability.events domain-specific modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool) instead of arbitrary log messages

Always use structured logging with logger.info(EVENT, key=value) format - never use logger.info('msg %s', val)

Use NotBlankStr from core.types for all identifier/name fields instead of manual whitespace validators

Use frozen Pydantic models for config/identity and separate mutable-via-copy models for runtime state that evolves, never mixing static config fields with mutable runtime fields in one model

For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement

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 in Pydantic models instead of storing and validating redundant fields (e.g. TokenUsage.total_tokens)

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

All public functions must have type hints and pass mypy strict mode type-checking

Google-style docstrings required on all public classes and functions (enforced by ruff D rules)

Do NOT use from future import annotations - Python 3.14 has PEP 649 native lazy annotations

Use except A, B: syntax without parentheses for exception handling (PEP 758) - ruff enforces this on Python 3.14

Keep functions under 50 lines and files under 800 lines

Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for Pydantic models to maintain ...

Files:

  • src/synthorg/budget/cost_record.py
  • src/synthorg/observability/events/task_assignment.py
  • src/synthorg/engine/assignment/service.py
  • src/synthorg/observability/events/execution.py
  • src/synthorg/engine/cost_recording.py
  • src/synthorg/engine/assignment/models.py
  • src/synthorg/budget/errors.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/engine/errors.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/engine/agent_engine.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/cost_record.py
  • src/synthorg/observability/events/task_assignment.py
  • src/synthorg/engine/assignment/service.py
  • src/synthorg/observability/events/execution.py
  • src/synthorg/engine/cost_recording.py
  • src/synthorg/engine/assignment/models.py
  • src/synthorg/budget/errors.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/engine/errors.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/engine/agent_engine.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Mark tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow as appropriate

Always run tests with -n 8 parallelism via pytest-xdist, never run tests sequentially locally

Use @pytest.mark.parametrize for testing similar cases instead of duplicating test code

Use Hypothesis @given decorator with profiles configured in tests/conftest.py (ci for CI with 10 examples, dev for 1000 examples, fuzz for 10000 examples), controlled via HYPOTHESIS_PROFILE env var; failing examples are saved to ~/.synthorg/hypothesis-examples/

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

NEVER skip, dismiss, or ignore flaky tests - always fix them fully and fundamentally; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins

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

Maintain 80% minimum code coverage (enforced in CI); asyncio_mode is set to auto so @pytest.mark.asyncio is not needed for async tests; 30-second timeout per test is global in pyproject.toml (non-default overrides like timeout(60) are allowed)

Files:

  • tests/unit/budget/conftest.py
  • tests/unit/engine/test_cost_recording.py
  • tests/unit/budget/test_cost_record_project.py
  • tests/unit/budget/test_tracker_project.py
  • tests/unit/budget/test_enforcer_project.py
  • tests/unit/budget/test_enforcer_project_inflight.py
  • tests/unit/engine/test_assignment_project_team.py
  • tests/unit/engine/test_agent_engine_project.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/conftest.py
  • tests/unit/engine/test_cost_recording.py
  • tests/unit/budget/test_cost_record_project.py
  • tests/unit/budget/test_tracker_project.py
  • tests/unit/budget/test_enforcer_project.py
  • tests/unit/budget/test_enforcer_project_inflight.py
  • tests/unit/engine/test_assignment_project_team.py
  • tests/unit/engine/test_agent_engine_project.py
docs/design/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant docs/design/ page before implementing any feature or planning any issue, as the design spec is the starting point for architecture, data models, and behavior

When implementation deviates from the spec, alert the user and explain why before proceeding - every deviation needs explicit user approval and must be documented by updating the relevant docs/design/ page

Files:

  • docs/design/engine.md
🧠 Learnings (41)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...
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)
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-08T17:42:54.041Z
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-08T17:42:54.041Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design in the spirit of robustness, correctness, simplicity, and future-proofing - surface improvements as suggestions, not silent changes, and let the user decide
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
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-08T17:42:54.041Z
Learning: Use feature branch naming convention <type>/<slug> where type is: feat, fix, refactor, docs, test, chore, perf, ci
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
Learning: Signed commits are required on main via branch protection - all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
Learning: NEVER create a PR directly using gh pr create - use /pre-pr-review command to run automated checks and review agents before creating the PR; use /pre-pr-review quick for trivial/docs-only changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
Learning: After finishing an issue implementation, create a feature branch, commit, and push - do NOT create a PR automatically or leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR changes), fix them all - never skip or defer fixes as out of scope
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
Learning: Never use cd in Bash commands - the working directory is already set to the project root; use absolute paths or run commands directly; bash -c 'cd <dir> && <cmd>' is safe for tools without a -C flag
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T17:42:54.041Z
Learning: NEVER use Bash to write or modify files - use the Write or Edit tools instead; do not use cat, echo, sed -i, or tee for file creation or modification (read-only/inspection uses are fine)
📚 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/cost_record.py
  • src/synthorg/budget/errors.py
  • src/synthorg/observability/events/budget.py
  • tests/unit/budget/test_tracker_project.py
  • tests/unit/budget/test_enforcer_project.py
  • tests/unit/budget/test_enforcer_project_inflight.py
  • src/synthorg/budget/tracker.py
  • tests/unit/engine/test_agent_engine_project.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • docs/design/engine.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/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/cost_record.py
  • src/synthorg/budget/errors.py
  • src/synthorg/observability/events/budget.py
  • tests/unit/budget/test_enforcer_project.py
  • tests/unit/budget/test_enforcer_project_inflight.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • docs/design/engine.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

  • src/synthorg/engine/assignment/service.py
  • docs/design/engine.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/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/engine/assignment/service.py
  • src/synthorg/engine/errors.py
  • tests/unit/engine/test_agent_engine_project.py
  • src/synthorg/engine/agent_engine.py
  • docs/design/engine.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 : 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/execution.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/budget/_enforcer_helpers.py
  • CLAUDE.md
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly from the domain module

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
  • CLAUDE.md
📚 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/execution.py
  • CLAUDE.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/observability/events/execution.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/execution.py
  • CLAUDE.md
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/**/*.py : Event names must always use constants from domain-specific modules under `synthorg.observability.events`. Import directly from specific domain modules.

Applied to files:

  • src/synthorg/observability/events/execution.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/execution.py
  • src/synthorg/observability/events/budget.py
  • 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: 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/errors.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • docs/design/engine.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to 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
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/budget/test_tracker_project.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed

Applied to files:

  • src/synthorg/engine/errors.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/engine/agent_engine.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/enforcer.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/enforcer.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors include: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry.

Applied to files:

  • src/synthorg/budget/enforcer.py
📚 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-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-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-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-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-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-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-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-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-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 : Structured kwargs in logging: always `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: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • docs/design/engine.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/agent_engine.py
🔇 Additional comments (14)
tests/unit/budget/conftest.py (1)

351-364: Good fixture extension for project-scoped records.

The optional project_id plumb-through is correct and backward-compatible for existing tests.

src/synthorg/observability/events/task_assignment.py (1)

25-27: Event constants are clean and consistent.

Names and values align with the existing task-assignment event namespace.

src/synthorg/observability/events/execution.py (1)

105-107: Looks good.

These additions fit the existing execution event structure and support project-scoped observability cleanly.

src/synthorg/budget/errors.py (1)

64-85: Well-structured specialized error type.

Subclassing BudgetExhaustedError with project_id/project_budget/project_spent is the right shape for downstream handling and structured logging.

src/synthorg/engine/errors.py (1)

72-108: Good security-conscious exception design.

Generic messages plus structured project_id/agent_id attributes are implemented cleanly.

src/synthorg/budget/cost_record.py (1)

50-53: project_id field addition is solid.

Typing and default behavior are appropriate for optional project attribution.

src/synthorg/engine/assignment/models.py (1)

119-125: Good addition for project-team eligibility input.

The immutable typed tuple is a clean interface for assignment filtering.

CLAUDE.md (1)

93-93: Good update to event-constant guidance.

The added project-budget and project-assignment examples keep the logging policy aligned with the new observability events introduced in this PR.

tests/unit/budget/test_cost_record_project.py (1)

13-52: Solid focused coverage for project_id behavior.

These tests correctly validate defaults, validation failure for blank values, and frozen-model immutability for the new field.

src/synthorg/engine/assignment/service.py (1)

17-18: Project-team eligibility filtering is correctly integrated.

The pre-strategy filtering and explicit no-eligible early return behavior are clear, deterministic, and properly instrumented with domain event constants.

Also applies to: 81-112

src/synthorg/observability/events/budget.py (1)

52-55: Event additions are consistent with the budget domain taxonomy.

The three new project-budget constants follow the existing naming and scoping conventions cleanly.

tests/unit/budget/test_tracker_project.py (1)

35-149: Comprehensive unit coverage for project-scoped tracker queries.

The cases for isolation, unknown IDs, and start-time filtering are well targeted for get_project_cost() and get_project_records().

tests/unit/budget/test_enforcer_project_inflight.py (1)

57-119: In-flight project-budget behavior is tested at the right boundaries.

The three scenarios map cleanly to the intended closure logic (exceeded, under, and disabled project budget).

tests/unit/budget/test_enforcer_project.py (1)

22-90: Strong scenario coverage for check_project_budget().

The boundary and isolation cases, plus subclass-compatibility assertion, give good confidence in the new project-budget enforcement path.

Comment on lines +249 to +306
async def check_project_budget(
self,
project_id: str,
project_budget: float,
) -> None:
"""Check project-level budget and raise if exceeded.

Args:
project_id: Project identifier for cost lookup.
project_budget: Total project budget (from Project.budget).

Raises:
ProjectBudgetExhaustedError: When project spend >= budget.
"""
if project_budget <= 0:
return

try:
project_cost = await self._cost_tracker.get_project_cost(
project_id,
)
except MemoryError, RecursionError:
raise
except Exception:
logger.exception(
BUDGET_PREFLIGHT_ERROR,
project_id=project_id,
reason="project_cost_query_failed",
)
return

logger.debug(
BUDGET_PROJECT_ENFORCEMENT_CHECK,
project_id=project_id,
project_cost=project_cost,
project_budget=project_budget,
)

if project_cost >= project_budget:
logger.warning(
BUDGET_PROJECT_BUDGET_EXCEEDED,
project_id=project_id,
project_cost=project_cost,
project_budget=project_budget,
)
_fmt = format_cost
_cur = self._budget_config.currency
msg = (
f"Project {project_id!r} budget exhausted: "
f"{_fmt(project_cost, _cur)} >= "
f"{_fmt(project_budget, _cur)}"
)
raise ProjectBudgetExhaustedError(
msg,
project_id=project_id,
project_budget=project_budget,
project_spent=project_cost,
)
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.

⚠️ Potential issue | 🟠 Major

Project budgets currently inherit the tracker’s 168-hour retention window.

Both the new pre-flight check and the in-flight project baseline read from CostTracker.get_project_cost(), but that tracker auto-prunes old records. Once pruning runs, older project spend disappears and a long-lived project can spend again even though its total project budget was already exhausted. This needs a durable lifetime aggregate, or an explicit project-budget window, before the feature can enforce total project budgets correctly.

Also applies to: 627-655

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/enforcer.py` around lines 249 - 306, check_project_budget
currently relies on the volatile _cost_tracker.get_project_cost which prunes old
records (168h), so project budgets can be evaded once pruning runs; change
enforcement to use a durable lifetime aggregate or a cost query with an explicit
window. Update the call site in check_project_budget (and the corresponding
in-flight baseline reader referenced elsewhere) to call a new stable API on the
tracker such as get_project_lifetime_cost(project_id) or
get_project_cost(project_id, window="lifetime"), or compute/maintain a persisted
Project.total_spent field and read that instead; ensure the new API is
implemented on CostTracker (or persist aggregate on Project) to return
non-pruned lifetime spend before enforcing and use that method name in place of
_cost_tracker.get_project_cost.

task_id: str,
*,
tracker: CostTracker | None,
project_id: str | None = None,
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

Use NotBlankStr | None for project_id to keep identifier contracts consistent.

project_id is an identifier field but is currently typed as str | None. Tightening this to NotBlankStr | None keeps the engine boundary consistent with the project-wide identifier typing rules and reduces accidental whitespace propagation.

♻️ Proposed fix
 from synthorg.budget.cost_record import CostRecord
+from synthorg.core.types import NotBlankStr
 from synthorg.observability import get_logger
@@
 async def record_execution_costs(  # noqa: PLR0913
@@
-    project_id: str | None = None,
+    project_id: NotBlankStr | None = None,
 ) -> None:

As per coding guidelines, "Use NotBlankStr from core.types for all identifier/name fields instead of manual whitespace validators".

Also applies to: 72-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/cost_recording.py` at line 34, Change the project_id
parameter type from str | None to NotBlankStr | None and import NotBlankStr from
core.types; update both occurrences of the project_id annotation in this module
(the two locations referenced) so identifier contracts are consistent and
whitespace validation is delegated to NotBlankStr instead of manual checks.

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: 2

♻️ Duplicate comments (2)
src/synthorg/budget/enforcer.py (2)

277-279: ⚠️ Potential issue | 🟠 Major

Project budget enforcement still relies on a retention-pruned aggregate.

Both pre-flight and in-flight project checks query get_project_cost(), which can undercount after pruning and allow overspend on long-lived projects. This keeps project-budget enforcement non-authoritative beyond retention.

Also applies to: 640-642

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/enforcer.py` around lines 277 - 279, The project budget
checks call self._cost_tracker.get_project_cost which relies on retention-pruned
aggregates and can undercount long-lived projects; update the enforcement code
(both the pre-flight and in-flight callers of get_project_cost) to use an
authoritative, un-pruned cost source instead of the pruned aggregate—either by
calling a new method like get_project_cost_unpruned (or passing an explicit
include_pruned=False/authoritative=True flag to get_project_cost) on
_cost_tracker and implementing that path to query the raw/ledger-backed totals;
ensure the _cost_tracker API, its implementation, and the preflight/inflight
check callers are changed consistently so enforcement uses the
un-pruned/authoritative totals.

600-601: ⚠️ Potential issue | 🟠 Major

Do not coerce missing project context to an empty project ID.

Line 665 silently converts missing project_id to "", which can hide invalid caller state and misattribute enforcement/logging. Require a real project ID whenever project_budget > 0, and keep the type as NotBlankStr | None at this boundary.

♻️ Proposed fix
+from synthorg.core.types import NotBlankStr  # noqa: TC001
@@
-        project_id: str | None = None,
+        project_id: NotBlankStr | None = None,
         project_budget: float = 0.0,
@@
+        if project_budget > 0 and project_id is None:
+            msg = "project_id is required when project_budget > 0"
+            raise ValueError(msg)
@@
-            project_id=project_id or "",
+            project_id=project_id,
         )

As per coding guidelines, "use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) ... variants."

Also applies to: 665-665

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/enforcer.py` around lines 600 - 601, The code currently
types project_id as str | None and silently coerces missing project_id to ""
(the project_id or "" pattern around the project_budget check), which can hide
invalid caller state; change the parameter annotation to NotBlankStr | None
(imported from core.types), remove the coercion-to-empty-string logic, and add a
runtime validation in the enforcer function/method that raises a clear exception
(e.g., ValueError) when project_budget > 0 but project_id is None; allow
project_id to remain None only when project_budget == 0. Ensure any
logging/enforcement uses the validated NotBlankStr value and not a coerced empty
string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/budget/_enforcer_helpers.py`:
- Around line 287-293: The project limit check in _check_project_limit (called
with running_cost, project_baseline, agent_id, project_id) uses only the
per-execution running_cost + project_baseline snapshot and thus misses
concurrent in-flight spend; replace the baseline+local check with a query to a
shared in-flight project accumulator/live tracker (e.g.,
get_or_create_project_inflight(project_id)) and compare running_cost +
live_inflight_cost against project_budget, performing the check-and-reserve
atomically (use a lock or atomic increment) and ensure you decrement/release the
reserved amount when the run finishes/fails; update both call sites (the
_check_project_limit invocation around running_cost/project_baseline and the
similar logic at the 397-420 region) to use the shared tracker and atomic
reserve/release pattern, referencing ctx.accumulated_cost.cost_usd, project_id,
and agent_id for locating and updating the tracker.

In `@src/synthorg/engine/agent_engine.py`:
- Around line 559-565: The code currently skips project validation when
self._project_repo is None, allowing a task with task.project set to proceed
without checks; update the agent engine to fail closed by adding an explicit
guard where task.project is truthy: if task.project and self._project_repo is
None, raise or return an error/abort before calling _validate_project or
continuing execution (same change should be applied to the other two sites where
the pattern appears around the blocks that call _validate_project at the noted
locations); ensure the guard prevents further execution and cost recording for
tasks referencing a project when no project repo is configured.

---

Duplicate comments:
In `@src/synthorg/budget/enforcer.py`:
- Around line 277-279: The project budget checks call
self._cost_tracker.get_project_cost which relies on retention-pruned aggregates
and can undercount long-lived projects; update the enforcement code (both the
pre-flight and in-flight callers of get_project_cost) to use an authoritative,
un-pruned cost source instead of the pruned aggregate—either by calling a new
method like get_project_cost_unpruned (or passing an explicit
include_pruned=False/authoritative=True flag to get_project_cost) on
_cost_tracker and implementing that path to query the raw/ledger-backed totals;
ensure the _cost_tracker API, its implementation, and the preflight/inflight
check callers are changed consistently so enforcement uses the
un-pruned/authoritative totals.
- Around line 600-601: The code currently types project_id as str | None and
silently coerces missing project_id to "" (the project_id or "" pattern around
the project_budget check), which can hide invalid caller state; change the
parameter annotation to NotBlankStr | None (imported from core.types), remove
the coercion-to-empty-string logic, and add a runtime validation in the enforcer
function/method that raises a clear exception (e.g., ValueError) when
project_budget > 0 but project_id is None; allow project_id to remain None only
when project_budget == 0. Ensure any logging/enforcement uses the validated
NotBlankStr value and not a coerced empty string.
🪄 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: 23f61f0e-47f4-45e1-9d57-658543a653f0

📥 Commits

Reviewing files that changed from the base of the PR and between e720759 and a0aedf6.

📒 Files selected for processing (10)
  • CLAUDE.md
  • README.md
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/cost_recording.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/execution.py
  • tests/unit/engine/test_cost_recording.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: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
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/engine/test_cost_recording.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/cost_recording.py
  • src/synthorg/budget/enforcer.py
🧠 Learnings (66)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...
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)
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).
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • README.md
  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • README.md
  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • README.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).

Applied to files:

  • README.md
📚 Learning: 2026-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:

  • README.md
  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/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:

  • README.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/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

  • README.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:

  • README.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • README.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • README.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-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-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-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 : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-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-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-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
  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • 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 tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/engine/test_cost_recording.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 tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/engine/test_cost_recording.py
📚 Learning: 2026-03-31T20:29:10.177Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:29:10.177Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/engine/test_cost_recording.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/test_cost_recording.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/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/**/*.py : Event names must always use constants from domain-specific modules under `synthorg.observability.events`. Import directly from specific domain modules.

Applied to files:

  • src/synthorg/observability/events/execution.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/execution.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/budget/_enforcer_helpers.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly from the domain module

Applied to files:

  • src/synthorg/observability/events/execution.py
  • src/synthorg/observability/events/budget.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/observability/events/execution.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/observability/events/budget.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/cost_recording.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/observability/events/budget.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.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/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
📚 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
📚 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
📚 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/observability/events/budget.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/_enforcer_helpers.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 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/tracker.py
  • src/synthorg/engine/cost_recording.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to **/*.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/tracker.py
  • src/synthorg/engine/cost_recording.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/tracker.py
  • src/synthorg/engine/cost_recording.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/tracker.py
  • src/synthorg/engine/cost_recording.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/tracker.py
  • src/synthorg/engine/cost_recording.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/budget/tracker.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/budget/tracker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/budget/tracker.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) -- PEP 758 except syntax, enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/engine/agent_engine.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/enforcer.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/enforcer.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors include: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry.

Applied to files:

  • src/synthorg/budget/enforcer.py
🔇 Additional comments (8)
README.md (1)

76-78: Documentation update is aligned with implementation.

The updated orchestration and budget descriptions accurately reflect project-scoped team filtering and project-level budget controls added in this PR.

src/synthorg/engine/cost_recording.py (1)

12-12: Project ID typing and propagation look correct.

project_id is now type-constrained, stored on each CostRecord, and emitted in the success event payload, which keeps cost attribution and observability consistent.

Also applies to: 35-35, 73-73, 134-134

src/synthorg/observability/events/execution.py (1)

105-106: New execution event constant is correctly defined.

The added project-validation event follows the existing execution event convention and placement.

tests/unit/engine/test_cost_recording.py (1)

321-375: Parametrized project-id coverage is well-structured.

This is a clean consolidation of similar scenarios and validates propagation across both single-turn and multi-turn paths.

CLAUDE.md (1)

93-93: Logging convention update is consistent with new event surface.

The added project assignment, project budget, and project validation event examples are aligned with this PR’s observability changes.

src/synthorg/observability/events/budget.py (1)

52-56: Project budget event constants are cleanly added.

The new constants are well-scoped and match the established budget event naming pattern.

src/synthorg/budget/tracker.py (1)

241-313: Project-scoped tracker APIs are implemented consistently.

get_project_cost / get_project_records follow existing query patterns (time-range validation, snapshot filtering, structured event logging), and the project_id filter integration is straightforward.

Also applies to: 706-721

src/synthorg/engine/agent_engine.py (1)

1429-1437: Good stale-checkpoint protection.

Re-validating the project before resume and re-threading project_id / project_budget into the resumed loop closes the checkpoint hole after team or budget changes.

Also applies to: 1452-1464

Comment on lines +287 to +293
or _check_project_limit(
running_cost,
project_budget,
project_baseline,
agent_id,
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.

⚠️ Potential issue | 🟠 Major

Project budget checks miss concurrent spend.

running_cost here is the current execution's ctx.accumulated_cost.cost_usd, so each closure only sees its own in-flight spend. If two agents run in the same project concurrently, both can reuse the same project_baseline snapshot and stay under project_budget individually while the combined project total overshoots it. That makes this a best-effort per-execution stop, not a true project-wide in-flight limit.

Please compare against a shared project accumulator/live tracker instead of project_baseline + local running_cost.

Also applies to: 397-420

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/_enforcer_helpers.py` around lines 287 - 293, The project
limit check in _check_project_limit (called with running_cost, project_baseline,
agent_id, project_id) uses only the per-execution running_cost +
project_baseline snapshot and thus misses concurrent in-flight spend; replace
the baseline+local check with a query to a shared in-flight project
accumulator/live tracker (e.g., get_or_create_project_inflight(project_id)) and
compare running_cost + live_inflight_cost against project_budget, performing the
check-and-reserve atomically (use a lock or atomic increment) and ensure you
decrement/release the reserved amount when the run finishes/fails; update both
call sites (the _check_project_limit invocation around
running_cost/project_baseline and the similar logic at the 397-420 region) to
use the shared tracker and atomic reserve/release pattern, referencing
ctx.accumulated_cost.cost_usd, project_id, and agent_id for locating and
updating the tracker.

Comment on lines +559 to +565
# Project validation and project-level budget check
if self._project_repo is not None:
_project_budget = await self._validate_project(
task=task,
agent_id=agent_id,
task_id=task_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.

⚠️ Potential issue | 🟠 Major

Fail closed when a project task arrives without project_repo.

If task.project is set and self._project_repo is None, this validation is skipped but the project id still flows into execution and cost recording. A wiring mistake then silently disables project existence checks, team membership checks, and project budget enforcement for that run.

🔒 Minimal guard
-                if self._project_repo is not None:
+                if task.project and self._project_repo is None:
+                    logger.warning(
+                        EXECUTION_PROJECT_VALIDATION_FAILED,
+                        agent_id=agent_id,
+                        task_id=task_id,
+                        project_id=task.project,
+                        reason="project_repo_not_configured",
+                    )
+                    msg = "project_repo is required for project-scoped tasks"
+                    raise ExecutionStateError(msg)
+                if self._project_repo is not None:
                     _project_budget = await self._validate_project(
                         task=task,
                         agent_id=agent_id,
                         task_id=task_id,
                     )

Also applies to: 595-596, 695-696

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/agent_engine.py` around lines 559 - 565, The code
currently skips project validation when self._project_repo is None, allowing a
task with task.project set to proceed without checks; update the agent engine to
fail closed by adding an explicit guard where task.project is truthy: if
task.project and self._project_repo is None, raise or return an error/abort
before calling _validate_project or continuing execution (same change should be
applied to the other two sites where the pattern appears around the blocks that
call _validate_project at the noted locations); ensure the guard prevents
further execution and cost recording for tasks referencing a project when no
project repo is configured.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/engine/agent_engine.py (1)

1357-1395: ⚠️ Potential issue | 🟠 Major

Let resume-time project/budget failures escape _apply_recovery().

_resume_from_checkpoint() can now raise ProjectNotFoundError, ProjectAgentNotMemberError, and ProjectBudgetExhaustedError, but the blanket except Exception converts them back into the original ERROR result. That hides the real stop reason and keeps the resumed run on the generic error path instead of using the dedicated handlers in run().

⚙️ Minimal fix
         except MemoryError, RecursionError:
             raise
+        except BudgetExhaustedError:
+            raise
+        except ProjectNotFoundError, ProjectAgentNotMemberError:
+            raise
         except Exception as exc:
             logger.exception(
                 EXECUTION_RECOVERY_FAILED,

If you convert the missing-project_repo path to an ExecutionStateError, re-raise that here too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/agent_engine.py` around lines 1357 - 1395, The broad
except Exception in _apply_recovery is swallowing ProjectNotFoundError,
ProjectAgentNotMemberError, and ProjectBudgetExhaustedError raised by
_resume_from_checkpoint, causing resumed runs to be treated as generic errors;
update _apply_recovery to allow these to propagate (either by not catching them
here or by converting the missing-project_repo path to raise ExecutionStateError
in _resume_from_checkpoint and then re-raising ExecutionStateError in
_apply_recovery), i.e., add an explicit except ExecutionStateError (or the three
project exceptions) that re-raises before the generic except Exception block so
run()’s dedicated handlers can run.
♻️ Duplicate comments (3)
src/synthorg/engine/agent_engine.py (1)

559-574: ⚠️ Potential issue | 🟠 Major

Don't let project-scoped tasks run without project_repo.

This only warns and continues. The run still threads task.project into execution and cost recording, so a wiring mistake disables project existence checks, team membership checks, and project budget enforcement while still charging the project. The same fail-open path exists during checkpoint resume.

🔒 Minimal guard
-                if self._project_repo is not None:
+                if task.project and self._project_repo is None:
+                    logger.warning(
+                        EXECUTION_PROJECT_VALIDATION_FAILED,
+                        agent_id=agent_id,
+                        task_id=task_id,
+                        project_id=task.project,
+                        reason="project_repo_not_configured",
+                    )
+                    msg = "project_repo is required for project-scoped tasks"
+                    raise ExecutionStateError(msg)
+                if self._project_repo is not None:
                     _project_budget = await self._validate_project(
                         task=task,
                         agent_id=agent_id,
                         task_id=task_id,
                     )
-                elif task.project:
-                    logger.warning(
-                        EXECUTION_PROJECT_VALIDATION_FAILED,
-                        agent_id=agent_id,
-                        task_id=task_id,
-                        project_id=task.project,
-                        reason="project_repo_not_configured",
-                    )
-        if self._project_repo is not None:
+        if task.project and self._project_repo is None:
+            logger.warning(
+                EXECUTION_PROJECT_VALIDATION_FAILED,
+                agent_id=agent_id,
+                task_id=task_id,
+                project_id=task.project,
+                reason="project_repo_not_configured",
+            )
+            msg = "project_repo is required for project-scoped tasks"
+            raise ExecutionStateError(msg)
+        if self._project_repo is not None:
             project_budget = await self._validate_project(
                 task=task,
                 agent_id=agent_id,
                 task_id=task_id,
             )

Also applies to: 1437-1446

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/agent_engine.py` around lines 559 - 574, The code
currently only logs a warning when task.project is set but self._project_repo is
None, allowing project-scoped tasks to continue; change this to fail fast: where
you check self._project_repo before calling _validate_project (the block that
currently emits EXECUTION_PROJECT_VALIDATION_FAILED), instead raise or return a
project-validation failure (e.g., raise a ProjectValidationError or set the task
run to failed and stop execution) so the run does not proceed or record costs
against the project; apply the same guard in the checkpoint-resume path that
also calls _validate_project so both normal run and resume paths abort when
project_repo is not configured.
src/synthorg/budget/_enforcer_helpers.py (1)

284-309: ⚠️ Potential issue | 🟠 Major

The in-flight project stop is still per-execution, not per-project.

project_baseline + running_cost only includes this execution's local spend. Two agents in the same project can share the same baseline, both stay under project_budget individually, and still overshoot it together. This needs a shared project accumulator/reservation, not a per-closure snapshot.

Also applies to: 398-420

src/synthorg/budget/enforcer.py (1)

256-264: ⚠️ Potential issue | 🟠 Major

Project budgets still depend on a pruned aggregate.

Both check_project_budget() and the in-flight baseline in make_budget_checker() still read CostTracker.get_project_cost(), and this file now documents that the tracker drops records after 168 hours. Once pruning runs, a long-lived project can spend again under an already-exhausted project budget. Please switch project-budget enforcement to a lifetime aggregate or an explicit project-budget window.

Also applies to: 276-279, 637-642

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/enforcer.py` around lines 256 - 264,
check_project_budget() and the in-flight baseline in make_budget_checker() still
use CostTracker.get_project_cost(), which is subject to 168h pruning and causes
under-reporting for long-lived projects; replace those reads with a
lifetime-safe aggregate or an explicit project-window query. Update calls in
check_project_budget(), make_budget_checker(), and any other usages in this file
that call CostTracker.get_project_cost() to either use a new/alternative API
such as CostTracker.get_project_lifetime_cost() or pass an explicit long-lived
window parameter (e.g., window=None or lifetime=True) so the returned cost is
not pruned; if that API doesn't exist, add a lifetime-aggregate method on
CostTracker and use it in these locations. Ensure unit tests and callers
expecting pruned behavior are adjusted accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/synthorg/engine/agent_engine.py`:
- Around line 1357-1395: The broad except Exception in _apply_recovery is
swallowing ProjectNotFoundError, ProjectAgentNotMemberError, and
ProjectBudgetExhaustedError raised by _resume_from_checkpoint, causing resumed
runs to be treated as generic errors; update _apply_recovery to allow these to
propagate (either by not catching them here or by converting the
missing-project_repo path to raise ExecutionStateError in
_resume_from_checkpoint and then re-raising ExecutionStateError in
_apply_recovery), i.e., add an explicit except ExecutionStateError (or the three
project exceptions) that re-raises before the generic except Exception block so
run()’s dedicated handlers can run.

---

Duplicate comments:
In `@src/synthorg/budget/enforcer.py`:
- Around line 256-264: check_project_budget() and the in-flight baseline in
make_budget_checker() still use CostTracker.get_project_cost(), which is subject
to 168h pruning and causes under-reporting for long-lived projects; replace
those reads with a lifetime-safe aggregate or an explicit project-window query.
Update calls in check_project_budget(), make_budget_checker(), and any other
usages in this file that call CostTracker.get_project_cost() to either use a
new/alternative API such as CostTracker.get_project_lifetime_cost() or pass an
explicit long-lived window parameter (e.g., window=None or lifetime=True) so the
returned cost is not pruned; if that API doesn't exist, add a lifetime-aggregate
method on CostTracker and use it in these locations. Ensure unit tests and
callers expecting pruned behavior are adjusted accordingly.

In `@src/synthorg/engine/agent_engine.py`:
- Around line 559-574: The code currently only logs a warning when task.project
is set but self._project_repo is None, allowing project-scoped tasks to
continue; change this to fail fast: where you check self._project_repo before
calling _validate_project (the block that currently emits
EXECUTION_PROJECT_VALIDATION_FAILED), instead raise or return a
project-validation failure (e.g., raise a ProjectValidationError or set the task
run to failed and stop execution) so the run does not proceed or record costs
against the project; apply the same guard in the checkpoint-resume path that
also calls _validate_project so both normal run and resume paths abort when
project_repo is not configured.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3b76f17-59bd-453c-bb69-6adb6c38c1b8

📥 Commits

Reviewing files that changed from the base of the PR and between a0aedf6 and 9e1a639.

📒 Files selected for processing (4)
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/engine/test_agent_engine_project.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). (4)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Use except A, B: syntax (no parentheses) for exception handling -- ruff enforces PEP 758 on Python 3.14

Type hints: all public functions, mypy strict mode

Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)

Create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement

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

Pydantic v2: 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 + validating redundant fields in Pydantic models

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

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code over bare create_task

Line length: 88 characters (ruff)

Functions: < 50 lines, files < 800 lines

Handle errors explicitly, never silently swallow

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

Files:

  • src/synthorg/engine/agent_engine.py
  • tests/unit/engine/test_agent_engine_project.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Always use logger as the variable name (not _logger, not log)

Use event name constants from synthorg.observability.events domain modules instead of raw strings

Always use structured kwargs in logging: logger.info(EVENT, key=value) -- never logger.info("msg %s", val)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO level

Use DEBUG level for object creation, internal flow, entry/exit of key functions

All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001. Tests must use test-provider, test-small-001, etc.

Pure data models, enums, and re-exports do NOT need logging

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.py
src/synthorg/!(observability)/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use import logging / logging.getLogger() / print() in application code (exception: observability modules may use stdlib logging)

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Test markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow

Use asyncio_mode = "auto" for async tests -- no manual @pytest.mark.asyncio needed

Prefer @pytest.mark.parametrize for testing similar cases

Use Hypothesis for property-based testing with @given + @settings; profiles: ci (deterministic), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples)

Files:

  • tests/unit/engine/test_agent_engine_project.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/engine/test_agent_engine_project.py
🧠 Learnings (27)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...
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)
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
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-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
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
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-08T18:35:19.613Z
Learning: If implementation deviates from the design spec, alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
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-08T18:35:19.613Z
Learning: At every phase of planning and implementation, be critical -- actively look for ways to improve the design in the spirit of what we're building
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
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-08T18:35:19.613Z
Learning: Coverage: 80% minimum (enforced in CI)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Global timeout: 30 seconds per test in `pyproject.toml` -- do not add per-file markers unless overriding with different timeout
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Always include `-n 8` when running pytest locally, never run tests sequentially
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Commits: `<type>: <description>` -- 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-08T18:35:19.613Z
Learning: Signed commits: required on `main` via branch protection -- all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Branches: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: ALWAYS use `/pre-pr-review` to create PRs -- `gh pr create` is blocked by hookify
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Never create a PR directly -- create a feature branch, commit, and push instead. Do not leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Never use `cd` in Bash commands -- use absolute paths or run commands directly. Exception: `bash -c "cd <dir> && <cmd>"` is safe
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
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 "open(...).write(...)"`, or `tee`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Design spec is MANDATORY -- always read relevant `docs/design/` pages before implementing features. When deviations occur, update the design pages to reflect new reality
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T18:35:19.613Z
Learning: Fix everything valid that review agents find -- including pre-existing issues in surrounding code, suggestions, and adjacent findings. Never skip or defer
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • tests/unit/engine/test_agent_engine_project.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Engine: Agent orchestration, execution loops, parallel execution, task decomposition, routing, task assignment, centralized single-writer task state engine (TaskEngine), task lifecycle, recovery, shutdown, workspace isolation, coordination (multi-agent pipeline: TopologyDispatcher protocol, 4 dispatchers — SAS/centralized/decentralized/context-dependent, wave execution, workspace lifecycle integration, CoordinationSectionConfig company config bridge, build_coordinator factory), coordination error classification, prompt policy validation, checkpoint recovery (checkpoint/, per-turn persistence, heartbeat detection, CheckpointRecoveryStrategy), approval gate (escalation detection, context parking/resume, EscalationInfo/ResumePayload models), stagnation detection (stagnation/, StagnationDetector protocol, ToolRepetitionDetector, dual-signal analysis, corrective prompt injection), agent runtime state (AgentRuntimeState, lightweight per-agent execution status for dashboard queries and recove...

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) -- PEP 758 except syntax, enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.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/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed

Applied to files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/budget/enforcer.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/engine/agent_engine.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/engine/agent_engine.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/agent_engine.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).

Applied to files:

  • tests/unit/engine/test_agent_engine_project.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/engine/test_agent_engine_project.py
  • src/synthorg/budget/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.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/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.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/enforcer.py
  • src/synthorg/budget/_enforcer_helpers.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/enforcer.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/enforcer.py
📚 Learning: 2026-04-08T15:24:49.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:24:49.467Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors include: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry.

Applied to files:

  • src/synthorg/budget/enforcer.py
🔇 Additional comments (1)
tests/unit/engine/test_agent_engine_project.py (1)

58-77: Tests properly set task.project via the shared fixture.

The sample_task_with_criteria fixture in conftest.py:133 already defines project="proj-001" in its Task construction. All tests in this file receive this fixture, so project validation is properly exercised.

			> Likely an incorrect or invalid review comment.

@Aureliolo Aureliolo merged commit 74f1362 into main Apr 8, 2026
10 of 12 checks passed
@Aureliolo Aureliolo deleted the feat/multi-project-support branch April 8, 2026 19:04
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 19:04 — 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: implement multi-project support

2 participants