Skip to content

feat: capability-aware prompt profiles for model tier adaptation#1047

Merged
Aureliolo merged 7 commits intomainfrom
feat/capability-aware-prompt-profiles
Apr 3, 2026
Merged

feat: capability-aware prompt profiles for model tier adaptation#1047
Aureliolo merged 7 commits intomainfrom
feat/capability-aware-prompt-profiles

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Adds capability-aware prompt profiles that adapt system prompt verbosity based on the resolved model tier. Smaller/cheaper models receive simpler prompts they can follow more reliably; larger models receive the full prompt.

Changes

  • PromptProfile model and registry (engine/prompt_profiles.py): Frozen Pydantic model with 3 built-in profiles (full/standard/basic) mapped via MappingProxyType registry. Module-level completeness guard ensures all ModelTier values are covered.
  • Jinja2 template conditionals (engine/prompt_template.py): Profile-driven rendering for personality section (full/condensed/minimal), org policy inclusion, acceptance criteria format (nested list vs flat semicolons). Three tiers of autonomy instructions (AUTONOMY_INSTRUCTIONS, AUTONOMY_SUMMARY, AUTONOMY_MINIMAL).
  • build_system_prompt() model-tier awareness (engine/prompt.py): Accepts model_tier parameter, resolves profile, threads through the full rendering pipeline.
  • Engine wiring (engine/agent_engine.py): Passes identity.model.model_tier to prompt builder.
  • Budget auto-downgrade integration (budget/_enforcer_helpers.py): Sets model_tier on downgraded ModelConfig when target alias matches a valid tier; preserves existing tier on non-tier aliases.
  • ModelTier consolidation (core/types.py): Canonical definition moved from templates/model_requirements.py to core/types.py with backward-compatible re-export.
  • ModelConfig.model_tier (core/agent.py): New optional field tracking capability tier.
  • SeniorityInfo.typical_model_tier (core/role.py): Retyped from NotBlankStr to ModelTier for stronger validation.
  • Section extraction (engine/_prompt_helpers.py): Section constants and compute_sections() moved from prompt.py to keep it under 800 lines. _AUTONOMY_LOOKUP wrapped in MappingProxyType.
  • Design spec (docs/design/engine.md): New "Prompt Profiles" section with built-in profile table, tier flow, and invariants.
  • Documentation updates: CLAUDE.md package structure + logging examples, agents.md YAML example, operations.md auto-downgrade section.

Invariants

  • Authority and identity sections are never stripped regardless of profile
  • model_tier=None defaults to the full (large) profile (safe default)
  • Profile selection logged via prompt.profile.selected event

Test plan

  • 33 new tests across 3 files:
    • test_prompt_profiles.py: Profile model validation, registry completeness, immutability, all tiers
    • test_prompt.py: Integration tests for all 3 tiers (personality modes, org policies, acceptance criteria, authority/identity invariance, metadata, token reduction, autonomy text variation, core context defaults)
    • test_prompt_template.py: Autonomy summary/minimal coverage, uniqueness, monotonic length
    • test_enforcer.py: model_tier assertions on downgrade, non-tier alias preservation
  • Full suite: 13385 passed, 9 skipped
  • Pre-reviewed by 10 agents, 27 findings addressed

Closes #805

Copilot AI review requested due to automatic review settings April 3, 2026 19:36
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 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: ad4e2b1e-5137-4c2b-a2fc-43a7b52256f1

📥 Commits

Reviewing files that changed from the base of the PR and between 6804406 and bc4a81d.

📒 Files selected for processing (1)
  • src/synthorg/templates/model_requirements.py

Walkthrough

Adds model-tier–aware prompt profiles and propagates tier metadata through matching, budgeting, identity, and prompt construction. New string Literal types (ModelTier, AutonomyDetailLevel, PersonalityMode) were introduced. A frozen PromptProfile Pydantic model, a built-in registry, and get_prompt_profile() were added. ModelConfig and agent assignment now carry model_tier; the budget enforcer conditionally preserves tier for non-canonical downgrade aliases. Prompt building gained a model_tier parameter, selects a profile, and uses profile-driven section computation, autonomy/personality choices, and template conditionals. New observability event constants and corresponding tests and docs were added. No public API removals.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: adding capability-aware prompt profiles that adapt system prompts based on model tier.
Description check ✅ Passed The description provides comprehensive context covering all major changes, design rationale, invariants, test coverage, and issue closure.
Linked Issues check ✅ Passed The PR successfully implements all core objectives from #805: PromptProfile registry with 3 built-in profiles, Jinja2 template conditionals, model-tier awareness in build_system_prompt, engine wiring, auto-downgrade integration, event logging, tests, and design documentation.
Out of Scope Changes check ✅ Passed All changes align with issue #805 scope. The PR focuses on prompt profiles, template adaptation, and integration without introducing unrelated features or optional components.
Docstring Coverage ✅ Passed Docstring coverage is 93.04% 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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

Dependency Review

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

Snapshot Warnings

⚠️: No snapshots were found for the head SHA bc4a81d.
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

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

Introduces capability-aware prompt rendering profiles that adapt system prompt verbosity and structure based on the resolved ModelTier, ensuring downgraded/smaller models receive simpler prompts while preserving critical identity/authority constraints.

Changes:

  • Add PromptProfile + immutable registry keyed by ModelTier, and thread the resolved profile through prompt context/section tracking and Jinja2 template conditionals.
  • Wire model tier end-to-end: ModelTier canonical type, ModelConfig.model_tier, engine prompt building, and budget auto-downgrade updates.
  • Add extensive unit/integration tests covering tier-specific rendering and downgrade behavior; update design/docs accordingly.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/engine/test_prompt.py Adds integration tests asserting tier-driven prompt differences and metadata.
tests/unit/engine/test_prompt_template.py Adds coverage tests for new autonomy summary/minimal instruction maps.
tests/unit/engine/test_prompt_profiles.py New tests for PromptProfile validation, immutability, and registry completeness.
tests/unit/core/test_role.py Updates validation expectations due to typical_model_tier typing change.
tests/unit/budget/test_enforcer.py Asserts model_tier is set/preserved across auto-downgrades.
src/synthorg/templates/model_requirements.py Re-exports ModelTier from core and removes local literal definition.
src/synthorg/providers/routing/strategies.py Minor adjustment to tier fallback initialization for no-available-model errors.
src/synthorg/observability/events/prompt.py Adds prompt profile event constants.
src/synthorg/engine/prompt.py Adds model_tier parameter, selects profile, threads it through render/trim/metadata.
src/synthorg/engine/prompt_template.py Adds profile-driven Jinja2 conditionals (personality/org policies/criteria).
src/synthorg/engine/prompt_profiles.py New prompt profile model + built-in profiles + completeness guard + lookup.
src/synthorg/engine/agent_engine.py Passes identity.model.model_tier into build_system_prompt().
src/synthorg/engine/_prompt_helpers.py Extracts section constants/compute_sections and adds profile-derived template flags.
src/synthorg/core/types.py Adds canonical ModelTier literal type.
src/synthorg/core/role.py Retypes SeniorityInfo.typical_model_tier to ModelTier.
src/synthorg/core/agent.py Adds ModelConfig.model_tier optional field used for prompt profile selection.
src/synthorg/budget/_enforcer_helpers.py Sets model_tier on downgrade when target alias matches a valid tier.
src/synthorg/api/controllers/setup_agents.py Includes model_tier in setup wizard model assignment output.
docs/design/operations.md Documents how auto-downgrade persists model_tier for prompt adaptation.
docs/design/engine.md Adds “Prompt Profiles” spec section describing flow/invariants.
docs/design/agents.md Updates YAML example to include model_tier.
CLAUDE.md Updates repository structure/logging examples to mention prompt profile events.

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

Comment on lines +179 to +185
profile = get_prompt_profile(model_tier)
logger.info(
PROMPT_PROFILE_SELECTED,
tier=model_tier or "large",
personality_mode=profile.personality_mode,
autonomy_detail_level=profile.autonomy_detail_level,
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

When model_tier is None, this still emits PROMPT_PROFILE_SELECTED and hardcodes tier="large". That makes the new prompt.profile.default event (and the docs) inaccurate and prevents distinguishing explicit vs defaulted profile selection. Consider emitting PROMPT_PROFILE_DEFAULT when model_tier is None (and PROMPT_PROFILE_SELECTED only for explicit tiers), and log tier=profile.tier to reflect the resolved profile.

Copilot uses AI. Check for mistakes.
PROMPT_POLICY_VALIDATION_FAILED: Final[str] = "prompt.policy.validation_failed"
PROMPT_TOKEN_RATIO_HIGH: Final[str] = "prompt.token_ratio.high" # noqa: S105 -- event name, not a credential
PROMPT_PROFILE_SELECTED: Final[str] = "prompt.profile.selected"
PROMPT_PROFILE_DEFAULT: Final[str] = "prompt.profile.default"
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

PROMPT_PROFILE_DEFAULT is introduced here but appears unused in the codebase (search shows only this definition). Either start emitting this event when model_tier is unset (to match docs) or remove the constant to avoid dead event names.

Suggested change
PROMPT_PROFILE_DEFAULT: Final[str] = "prompt.profile.default"

Copilot uses AI. Check for mistakes.
Comment on lines +723 to +724
- Profile selection is logged via `prompt.profile.selected` /
`prompt.profile.default` events
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This section claims profile selection is logged via both prompt.profile.selected and prompt.profile.default, but current implementation only logs prompt.profile.selected even when model_tier is None. Either update the docs to match the actual logging or implement the missing prompt.profile.default event emission.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +16
from synthorg.core.types import (
ModelTier as ModelTier, # noqa: PLC0414 -- re-export for mypy
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The ModelTier as ModelTier import alias is redundant and requires a linter suppression (PLC0414). If the intent is a backward-compatible re-export, a plain from synthorg.core.types import ModelTier plus an explicit __all__ entry is clearer and avoids suppressing a real lint rule.

Copilot uses AI. Check for mistakes.
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 "Prompt Profiles" to adapt system prompt verbosity and detail based on model capability tiers (large, medium, small). It introduces a PromptProfile model and registry, updates the system prompt building logic to use these profiles, and modifies the Jinja2 template to support conditional rendering of personality, autonomy, and organizational policy sections. The model_tier is now integrated into ModelConfig and maintained through model matching and budget-driven auto-downgrades. Review feedback suggests refining the simplified acceptance criteria formatting to avoid parsing issues and evaluating the impact of import-time validation errors.

{% endfor %}
{% else %}

**Criteria**: {{ task.acceptance_criteria | map(attribute='description') | join('; ') }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The simplified acceptance criteria rendering uses a semicolon-separated line. While this is efficient for small models, it might be clearer to use a bulleted list if the model can handle it, or at least ensure that the description doesn't contain semicolons which could lead to parsing confusion for the model.

Comment on lines +114 to +117
_missing_profiles = set(get_args(ModelTier)) - set(PROMPT_PROFILE_REGISTRY)
if _missing_profiles:
_msg_p = f"Missing prompt profiles for tiers: {sorted(_missing_profiles)}"
raise ValueError(_msg_p)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The module-level completeness guard is excellent for maintainability. However, raising a ValueError at import time might be too aggressive for some deployment scenarios. Consider logging a critical error instead, or ensuring this is caught during CI/CD validation.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 85.43689% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.69%. Comparing base (55f5206) to head (bc4a81d).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/engine/prompt_template.py 42.85% 6 Missing and 2 partials ⚠️
src/synthorg/engine/_prompt_helpers.py 88.88% 2 Missing and 1 partial ⚠️
src/synthorg/engine/prompt_profiles.py 89.65% 2 Missing and 1 partial ⚠️
src/synthorg/budget/_enforcer_helpers.py 88.88% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1047      +/-   ##
==========================================
- Coverage   90.71%   90.69%   -0.02%     
==========================================
  Files         702      703       +1     
  Lines       39858    39939      +81     
  Branches     3975     3982       +7     
==========================================
+ Hits        36158    36224      +66     
- Misses       3009     3018       +9     
- Partials      691      697       +6     

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

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/prompt.py (1)

469-496: ⚠️ Potential issue | 🟡 Minor

trimmed_sections can claim org_policies was removed even when that section was never rendered.

On profile-gated renders, this loop still reaches the org_policies branch and records it as trimmed based on the raw inputs, not on the actual included sections. The prompt content does not change, but PROMPT_BUILD_TOKEN_TRIMMED becomes misleading and the extra trim pass buys nothing.

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

In `@src/synthorg/engine/prompt.py` around lines 469 - 496, The loop is marking
_SECTION_ORG_POLICIES (and others) as trimmed based on raw inputs even if that
section was never rendered (e.g., profile-gated), producing misleading
PROMPT_BUILD_TOKEN_TRIMMED entries; fix by having _render_and_estimate return
whether each trimmable section was actually included (e.g., add an
included_sections set or booleans) and only mutate org_policies and append
section to trimmed_sections when that section is present in the returned
included_sections from _render_and_estimate (update call sites that consume the
new return shape accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/prompt_template.py`:
- Around line 226-235: The template currently only checks
simplify_acceptance_criteria and collapses "large" and "medium" into one path;
update the branching in prompt_template.py to handle three distinct profiles
(full/standard/basic) instead of a boolean. Replace the single if/else around
simplify_acceptance_criteria with a conditional on an
acceptance_criteria_profile (or existing enum/flag) and implement: full (large)
renders the multi-line "### Acceptance Criteria" with a bullet loop over
task.acceptance_criteria, standard (medium) renders a clearer inline or
semi-colon joined string (e.g., join descriptions with '; '), and basic (small)
renders a concise single-line summary (e.g., comma-joined descriptions or
truncated text); ensure you reference simplify_acceptance_criteria removal or
mapping and update any code that sets that flag so the template uses the new
profile variable.
- Around line 88-125: The exported AUTONOMY_SUMMARY and AUTONOMY_MINIMAL dicts
are mutable; wrap each in an immutable MappingProxyType of a deep copy at
construction to prevent accidental mutation: import copy and MappingProxyType,
build the tables as normal dict literals, then replace their public bindings
with MappingProxyType(copy.deepcopy(<dict>)) (apply the same change to the
sibling full table AUTONOMY_SUMMARY and any other exported autonomy mappings),
keeping the same names so callers are unaffected; also remove any code that
computes _missing_summary against the mutable dicts or compute it against the
immutable proxy via copy if needed.

In `@src/synthorg/engine/prompt.py`:
- Around line 179-185: The telemetry currently logs only the effective tier from
get_prompt_profile(model_tier) which hides when model_tier was None; update the
PROMPT_PROFILE_SELECTED logger.info call to include the original requested value
and/or a defaulted boolean (e.g., include requested_tier=model_tier and
defaulted=(model_tier is None)) alongside tier=... and the existing
personality_mode/autonomy_detail_level so callers (functions using
get_prompt_profile, logger.info, PROMPT_PROFILE_SELECTED, model_tier,
profile.personality_mode, profile.autonomy_detail_level) can distinguish
explicit "large" vs fallback-to-full in events.

---

Outside diff comments:
In `@src/synthorg/engine/prompt.py`:
- Around line 469-496: The loop is marking _SECTION_ORG_POLICIES (and others) as
trimmed based on raw inputs even if that section was never rendered (e.g.,
profile-gated), producing misleading PROMPT_BUILD_TOKEN_TRIMMED entries; fix by
having _render_and_estimate return whether each trimmable section was actually
included (e.g., add an included_sections set or booleans) and only mutate
org_policies and append section to trimmed_sections when that section is present
in the returned included_sections from _render_and_estimate (update call sites
that consume the new return shape accordingly).
🪄 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: 3b24f2fc-9d3b-4d72-a5c1-ce74af643e86

📥 Commits

Reviewing files that changed from the base of the PR and between 1111602 and 876e596.

📒 Files selected for processing (22)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/engine.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/core/types.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/observability/events/prompt.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/templates/model_requirements.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/core/test_role.py
  • tests/unit/engine/test_prompt.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/engine/test_prompt_template.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Agent
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant docs/design/ page before implementing any feature. Design spec is the starting point for architecture, data models, and behavior. If implementation deviates from spec, alert the user and explain why -- user decides whether to proceed or update the spec. When approved deviations occur, update the relevant docs/design/ page

Files:

  • docs/design/agents.md
  • docs/design/operations.md
  • docs/design/engine.md
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation in docs/ is built with Zensical, configured via mkdocs.yml. Design spec docs are in docs/design/ (12 pages), architecture in docs/architecture/, roadmap in docs/roadmap/. REST API reference auto-generated via scripts/export_openapi.py

Files:

  • docs/design/agents.md
  • docs/design/operations.md
  • docs/design/engine.md
  • CLAUDE.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
Line length: 88 characters, enforced by ruff
Global test timeout: 30 seconds per test (configured in pyproject.toml). Non-default overrides like timeout(60) are allowed on specific tests

Files:

  • src/synthorg/observability/events/prompt.py
  • src/synthorg/providers/routing/strategies.py
  • tests/unit/core/test_role.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/api/controllers/setup_agents.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/engine/test_prompt_template.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/agent_engine.py
  • tests/unit/engine/test_prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
  • tests/unit/engine/test_prompt.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: All public functions and classes must have type hints and Google-style docstrings, enforced by ruff D rules and mypy strict mode
Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging / logging.getLogger() / print() in application code for logging -- use synthorg.observability.get_logger() instead
Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls
Functions must be < 50 lines, files < 800 lines
Immutability: create new objects, never mutate existing ones. Use copy.deepcopy() at construction and MappingProxyType wrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (with frozen=True in ConfigDict) for config/identity; use separate mutable-via-copy models (via model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 with conventions: allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields including optional and tuple variants
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over bare create_task
Handle errors explicitly, never silently swallow exceptions. All error paths must log at WARNING or ERROR with context before raising
Validate at system boundaries (user input, external APIs, config files)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, or comments. Vendor names m...

Files:

  • src/synthorg/observability/events/prompt.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_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/observability/events/prompt.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
src/synthorg/providers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/synthorg/providers/routing/strategies.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow for test categorization
Maintain 80% minimum code coverage, enforced in CI with --cov-fail-under=80
Always run tests with -n 8 for parallelism via pytest-xdist; never run tests sequentially locally. CI uses -n auto
Use @pytest.mark.parametrize for testing similar cases instead of repeating test code
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in tests. Use generic names: test-provider, test-small-001, test-medium-001, test-large-001, or small/medium/large aliases
Use Hypothesis with @given + @settings for property-based testing. Never skip, dismiss, or ignore flaky tests -- fix them fundamentally. Use mocking for time-sensitive tests to make them deterministic instead of widening timing margins

Files:

  • tests/unit/core/test_role.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/engine/test_prompt_template.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/engine/test_prompt.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/core/test_role.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/engine/test_prompt_template.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/engine/test_prompt.py
🧠 Learnings (49)
📚 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/prompt.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/prompt.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/prompt.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/prompt.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/prompt.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/prompt.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/prompt.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/prompt.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls

Applied to files:

  • src/synthorg/observability/events/prompt.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/prompt.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/engine/prompt.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/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/engine/prompt.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/core/types.py
  • src/synthorg/core/agent.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/core/types.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/core/role.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/api/controllers/setup_agents.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)

Applied to files:

  • src/synthorg/core/role.py
  • src/synthorg/core/agent.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/core/role.py
  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/core/role.py
  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models (with `frozen=True` in ConfigDict) for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model

Applied to files:

  • src/synthorg/core/agent.py
  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Immutability: create new objects, never mutate existing ones. Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)

Applied to files:

  • src/synthorg/core/agent.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 {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `allow_inf_nan=False` in all `ConfigDict` declarations to reject `NaN`/`Inf` in numeric fields at validation time

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with conventions: `allow_inf_nan=False` in all ConfigDict declarations to reject NaN/Inf; use computed_field for derived values instead of storing redundant fields; use `NotBlankStr` (from core.types) for all identifier/name fields including optional and tuple variants

Applied to files:

  • src/synthorg/templates/model_requirements.py
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.

Applied to files:

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

Applied to files:

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

Applied to files:

  • docs/design/engine.md
  • 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 : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: CLI (Go binary): see `cli/CLAUDE.md` for commands, flags, and reference; use `go -C cli` (never `cd cli`)

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

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

  • CLAUDE.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

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

Applied to files:

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • CLAUDE.md
🔇 Additional comments (15)
src/synthorg/core/types.py (1)

4-10: Canonical ModelTier definition is clean and consistent.

Line 8 establishes a single source of truth for tier literals and is aligned with downstream typing.

src/synthorg/core/role.py (1)

80-82: SeniorityInfo.typical_model_tier tightening is correct.

Using ModelTier here improves validation guarantees and keeps tier semantics consistent.

docs/design/agents.md (1)

114-114: Design doc update matches the new tier-aware model config.

The example now documents model_tier in the right place and keeps config docs in sync.

src/synthorg/providers/routing/strategies.py (1)

249-257: Good hardening of _raise_no_available error path.

Initializing tier upfront eliminates the unbound-variable edge case while preserving context-rich logs.

tests/unit/core/test_role.py (1)

145-145: Test adjustments are appropriate for ModelTier Literal validation.

These updates keep the negative cases valid while avoiding brittle error-message coupling.

Also applies to: 168-173

src/synthorg/core/agent.py (1)

160-163: ModelConfig.model_tier addition is well-typed and backward-safe.

Optional ModelTier cleanly supports tier-aware prompt selection without breaking configs that omit tier.

Also applies to: 184-187

src/synthorg/templates/model_requirements.py (1)

14-16: Shared ModelTier import here is the right consolidation.

This keeps template parsing aligned with the canonical tier vocabulary in core types.

src/synthorg/api/controllers/setup_agents.py (1)

158-162: Tier propagation in model assignment is correctly wired.

Including model_tier in match_map closes the data flow from matching to runtime prompt selection.

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

34-35: Tier-aware downgrade propagation is implemented cleanly.

The valid-tier guard and conditional model_tier update are correct, and passing target_alias through the helper keeps downgrade behavior consistent and explicit.

Also applies to: 96-97, 124-143

docs/design/operations.md (1)

305-307: Good design-doc alignment with runtime behavior.

This addition clearly documents how auto-downgrade now propagates tier context into prompt profile selection.

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

783-790: Correct engine wiring for prompt profile selection.

Passing identity.model.model_tier into build_system_prompt() is the right integration point and preserves None fallback behavior safely.

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

16-17: Prompt-profile event constants look good.

These additions fit the existing event naming scheme and make prompt profile selection/default paths observable.

CLAUDE.md (1)

99-99: Guidance updates are consistent with the feature rollout.

The engine inventory and logging convention examples now correctly include prompt-profile adaptation and its event constant.

Also applies to: 148-148

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

465-466: Great coverage for tier propagation and non-tier preservation.

These assertions and the new non-tier alias test close the key regression risks in downgrade-to-prompt-profile behavior.

Also applies to: 595-596, 726-787

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

8-10: Strong invariant tests for autonomy detail tiers.

The new suite validates both enum coverage and verbosity ordering, which is exactly what profile-driven prompt rendering depends on.

Also applies to: 40-86

Comment on lines +226 to +235
{% if not simplify_acceptance_criteria %}

### Acceptance Criteria
{% for criterion in task.acceptance_criteria %}
- {{ criterion.description }}
{% endfor %}
{% else %}

**Criteria**: {{ task.acceptance_criteria | map(attribute='description') | join('; ') }}
{% endif %}
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

This renderer still only has two acceptance-criteria modes.

The PR objective calls for distinct full / standard / basic criteria formatting, but this template only branches on simplify_acceptance_criteria. That leaves large and medium on the same render path, so the standard profile never gets its own criteria format.

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

In `@src/synthorg/engine/prompt_template.py` around lines 226 - 235, The template
currently only checks simplify_acceptance_criteria and collapses "large" and
"medium" into one path; update the branching in prompt_template.py to handle
three distinct profiles (full/standard/basic) instead of a boolean. Replace the
single if/else around simplify_acceptance_criteria with a conditional on an
acceptance_criteria_profile (or existing enum/flag) and implement: full (large)
renders the multi-line "### Acceptance Criteria" with a bullet loop over
task.acceptance_criteria, standard (medium) renders a clearer inline or
semi-colon joined string (e.g., join descriptions with '; '), and basic (small)
renders a concise single-line summary (e.g., comma-joined descriptions or
truncated text); ensure you reference simplify_acceptance_criteria removal or
mapping and update any code that sets that flag so the template uses the new
profile variable.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/synthorg/core/role.py (1)

40-61: ⚠️ Potential issue | 🟡 Minor

Authority.budget_limit documentation is internally inconsistent.

Line 40 switches to “base currency units”, but the field description still says “USD (base currency)”. Update the field description to the same base-currency wording to avoid ambiguity.

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

In `@src/synthorg/core/role.py` around lines 40 - 61, The Field description for
the budget_limit attribute is inconsistent with the docstring—update the
budget_limit Field's description to use "base currency units" (e.g., "Maximum
spend per task in base currency units") so it matches the surrounding
documentation; modify the description string on the budget_limit Field (in the
class that declares budget_limit) to remove "USD" and use the base-currency
wording.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/engine.md`:
- Around line 702-706: The "Acceptance Criteria" cell for the "**standard**"
profile row in the table is incorrect ("Nested list"); update that cell to
reflect the intended format by replacing "Nested list" with "Bullet list" (or
"Bullet-point criteria") so the "**standard**" row under the "Acceptance
Criteria" column matches the documented target behavior.

In `@src/synthorg/budget/_enforcer_helpers.py`:
- Around line 144-149: The debug log is reusing the BUDGET_DOWNGRADE_APPLIED
event constant (causing duplicate applied events); change the debug call in
_enforcer_helpers.py so it does not emit that applied-event constant—either
remove the event constant from logger.debug or use a distinct non-applied
constant (e.g., BUDGET_DOWNGRADE_DEBUG or BUDGET_DOWNGRADE_SKIPPED) and keep the
same context fields (current.model_tier and target_alias) so counters/alerts are
not skewed when calling logger.debug near the downgrade-path that also logs
BUDGET_DOWNGRADE_APPLIED at info level.

In `@tests/unit/api/controllers/test_setup_agents.py`:
- Around line 203-245: Combine the two duplicate tests into a single
parametrized test using pytest.mark.parametrize to cover both "large" and
"small" tiers; keep the patch on
synthorg.templates.model_matcher.match_all_agents and reuse
match_and_assign_models, parameterizing match.provider_name, match.model_id,
match.tier, agent dict's "tier", and the expected model_tier; create a single
test (e.g., test_model_tier_propagation) that builds the MagicMock match from
the parameters, sets mock_match.return_value = [match], calls
match_and_assign_models(agents, {}), and asserts the provider, model_id and that
result[0]["model"]["model_tier"] equals the expected tier.

In `@tests/unit/core/test_role.py`:
- Around line 167-190: Replace the two nearly identical tests
test_whitespace_model_tier_rejected and test_invalid_model_tier_rejected with a
single parametrized pytest test that iterates over the invalid
typical_model_tier values (e.g., "   " and "extra-large") and asserts a
ValidationError with the same message when constructing SeniorityInfo; reference
the SeniorityInfo constructor and the typical_model_tier field in the
parametrize decorator and reuse the existing assertion logic inside the single
test function.

In `@tests/unit/engine/test_prompt.py`:
- Around line 1190-1204: The test parameter autonomy_map in
test_autonomy_text_varies_by_tier is typed as dict[SeniorityLevel, str] but
actual constants (AUTONOMY_INSTRUCTIONS, AUTONOMY_SUMMARY, AUTONOMY_MINIMAL) are
MappingProxyType; change the type hint to a generic mapping type (e.g.,
Mapping[SeniorityLevel, str]) and add the appropriate import (from typing import
Mapping) so the signature of test_autonomy_text_varies_by_tier accurately
reflects MappingProxyType inputs and keeps type-checking correct.

---

Outside diff comments:
In `@src/synthorg/core/role.py`:
- Around line 40-61: The Field description for the budget_limit attribute is
inconsistent with the docstring—update the budget_limit Field's description to
use "base currency units" (e.g., "Maximum spend per task in base currency
units") so it matches the surrounding documentation; modify the description
string on the budget_limit Field (in the class that declares budget_limit) to
remove "USD" and use the base-currency wording.
🪄 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: 9755f57c-5bc3-459b-aa26-2993462a1d10

📥 Commits

Reviewing files that changed from the base of the PR and between 876e596 and 762ae60.

📒 Files selected for processing (14)
  • docs/design/engine.md
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/core/types.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/templates/model_requirements.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/core/test_role.py
  • tests/unit/engine/test_prompt.py
  • tests/unit/engine/test_prompt_template.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
Line length: 88 characters, enforced by ruff
Global test timeout: 30 seconds per test (configured in pyproject.toml). Non-default overrides like timeout(60) are allowed on specific tests

Files:

  • tests/unit/api/controllers/test_setup_agents.py
  • src/synthorg/core/types.py
  • tests/unit/core/test_role.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • tests/unit/engine/test_prompt_template.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt_profiles.py
  • tests/unit/engine/test_prompt.py
  • src/synthorg/engine/_prompt_helpers.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow for test categorization
Maintain 80% minimum code coverage, enforced in CI with --cov-fail-under=80
Always run tests with -n 8 for parallelism via pytest-xdist; never run tests sequentially locally. CI uses -n auto
Use @pytest.mark.parametrize for testing similar cases instead of repeating test code
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in tests. Use generic names: test-provider, test-small-001, test-medium-001, test-large-001, or small/medium/large aliases
Use Hypothesis with @given + @settings for property-based testing. Never skip, dismiss, or ignore flaky tests -- fix them fundamentally. Use mocking for time-sensitive tests to make them deterministic instead of widening timing margins

Files:

  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/core/test_role.py
  • tests/unit/engine/test_prompt_template.py
  • tests/unit/engine/test_prompt.py

⚙️ CodeRabbit configuration file

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

Files:

  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/core/test_role.py
  • tests/unit/engine/test_prompt_template.py
  • tests/unit/engine/test_prompt.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: All public functions and classes must have type hints and Google-style docstrings, enforced by ruff D rules and mypy strict mode
Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)
Never use import logging / logging.getLogger() / print() in application code for logging -- use synthorg.observability.get_logger() instead
Use event name constants from synthorg.observability.events domain-specific modules instead of string literals for logger.info() and other logging calls
Functions must be < 50 lines, files < 800 lines
Immutability: create new objects, never mutate existing ones. Use copy.deepcopy() at construction and MappingProxyType wrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (with frozen=True in ConfigDict) for config/identity; use separate mutable-via-copy models (via model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 with conventions: allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr (from core.types) for all identifier/name fields including optional and tuple variants
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls) over bare create_task
Handle errors explicitly, never silently swallow exceptions. All error paths must log at WARNING or ERROR with context before raising
Validate at system boundaries (user input, external APIs, config files)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, or comments. Vendor names m...

Files:

  • src/synthorg/core/types.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_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/core/types.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant docs/design/ page before implementing any feature. Design spec is the starting point for architecture, data models, and behavior. If implementation deviates from spec, alert the user and explain why -- user decides whether to proceed or update the spec. When approved deviations occur, update the relevant docs/design/ page

Files:

  • docs/design/engine.md
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation in docs/ is built with Zensical, configured via mkdocs.yml. Design spec docs are in docs/design/ (12 pages), architecture in docs/architecture/, roadmap in docs/roadmap/. REST API reference auto-generated via scripts/export_openapi.py

Files:

  • docs/design/engine.md
🧠 Learnings (33)
📚 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/core/types.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/engine/prompt.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/core/types.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with conventions: `allow_inf_nan=False` in all ConfigDict declarations to reject NaN/Inf; use computed_field for derived values instead of storing redundant fields; use `NotBlankStr` (from core.types) for all identifier/name fields including optional and tuple variants

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_template.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/core/types.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/core/types.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_helpers.py
  • docs/design/engine.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/engine/prompt.py
  • docs/design/engine.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/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models (with `frozen=True` in ConfigDict) for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model

Applied to files:

  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Immutability: create new objects, never mutate existing ones. Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for non-Pydantic collections. For frozen Pydantic models with dict/list fields, use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)

Applied to files:

  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_template.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 {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/core/agent.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.

Applied to files:

  • src/synthorg/engine/prompt_template.py
  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.

Applied to files:

  • src/synthorg/engine/prompt_template.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/prompt_profiles.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/engine/prompt_profiles.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-04-03T19:34:11.483Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T19:34:11.483Z
Learning: Applies to src/synthorg/**/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/engine/prompt_profiles.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 : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

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

Applied to files:

  • docs/design/engine.md
🔇 Additional comments (28)
src/synthorg/core/types.py (1)

8-15: Centralized tier/profile literals look correct.

This is a clean canonicalization point for cross-module typing.

src/synthorg/core/role.py (1)

80-82: typical_model_tier tightening to ModelTier is a good constraint upgrade.

This removes free-form values and keeps seniority defaults aligned with supported tiers.

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

34-35: Tier validation via get_args(ModelTier) is solid.

Using the canonical ModelTier source avoids drift between downgrade logic and core types.

src/synthorg/templates/model_requirements.py (2)

14-15: Good move to reuse canonical ModelTier from core types.

This prevents tier-literal drift between templates and the rest of the system.


17-17: No breaking change risk: no wildcard imports from this module exist in the codebase.

The original concern about from synthorg.templates.model_requirements import * is unfounded—explicit searches show zero wildcard imports. All existing code uses explicit imports (ModelRequirement, parse_model_requirement, resolve_model_requirement, MODEL_AFFINITY), which are unaffected by __all__.

Separately: __all__ = ["ModelTier"] is incorrect—it lists a re-exported symbol from synthorg.core.types while omitting the actual public API symbols. However, this has no practical impact since __all__ is not consulted for explicit imports.

			> Likely an incorrect or invalid review comment.
src/synthorg/core/agent.py (1)

184-187: ModelConfig.model_tier addition is well-scoped and correctly typed.

This cleanly supports post-resolution tier propagation without weakening model validation.

tests/unit/engine/test_prompt_template.py (2)

14-61: Well-structured parametrized tests for autonomy maps.

The test organization is clean: @pytest.mark.unit at class level, parametrized tests for the three autonomy tiers, and explicit length-ordering assertions to enforce the invariant that summary ≤ full and minimal ≤ summary. This aligns with the coding guidelines for using @pytest.mark.parametrize for similar cases.


64-81: Guard detection test correctly validates the set-subtraction pattern.

The docstring update clearly explains that import-time guards cannot be re-exercised after import, and the test verifies that the detection logic (set subtraction) would catch missing levels. This is a reasonable approach to validate the guard's correctness indirectly.

tests/unit/engine/test_prompt.py (4)

996-1046: Comprehensive profile integration tests.

The tests thoroughly validate the tier-driven behavior: None defaults to full profile, small tier omits org policies while large includes them. The assertions are specific and verify both content presence/absence and section tracking.


1047-1113: Good coverage of acceptance criteria and personality mode variations.

Tests verify that small tier uses simplified **Criteria**: format without ### Acceptance Criteria heading, large tier uses full nested format, and personality rendering varies by tier (minimal omits enums, condensed shows description/style/traits without enums).


1114-1146: Authority and identity invariant tests across all tiers.

The parametrized tests confirm that ## Authority and ## Identity sections are never stripped regardless of profile tier, satisfying the PR objective that "authority and identity sections are never removed regardless of profile."


1209-1224: Direct test of build_core_context defaults.

Testing build_core_context(..., role=None) with implicit profile=None to verify full-profile defaults (personality_mode == "full", include_org_policies is True, simplify_acceptance_criteria is False) is a good practice for verifying the helper function's fallback behavior.

src/synthorg/engine/prompt.py (4)

180-188: Profile selection logging addresses observability concerns.

The logging now includes requested_tier, selected_tier, and defaulted flag, making it easy to distinguish explicit "large" from fallback-to-full in telemetry. This properly addresses the previous review feedback.


251-253: Good use of sanitize_message for exception details.

Sanitizing the exception string before embedding it in PromptBuildError prevents potential information leakage from unexpected exceptions.


493-498: Org-policy trimming correctly respects profile settings.

The condition profile is None or profile.include_org_policies ensures that org policies are only trimmed when they would actually be rendered. When include_org_policies=False (basic profile), trimming org_policies is a no-op since they wouldn't appear in the prompt anyway.


637-639: Conditional metadata addition is clean.

Adding profile_tier to metadata only when a profile is provided keeps backward compatibility for cases where profile=None propagates through the system.

src/synthorg/engine/prompt_profiles.py (3)

32-76: Well-designed frozen PromptProfile model.

The model follows project conventions: frozen=True, extra="forbid", allow_inf_nan=False. Field descriptions are clear, and the max_personality_tokens field is explicitly documented as reserved for future token-based trimming.


108-120: Immutable registry with completeness guard.

Using MappingProxyType for the registry ensures immutability, and the module-level guard validates that all ModelTier values have corresponding profiles at import time. This prevents runtime KeyError surprises. As per coding guidelines, "Immutability: create new objects, never mutate existing ones. Use ... MappingProxyType wrapping for non-Pydantic collections."


123-138: get_prompt_profile provides safe defaults.

The function correctly returns the full profile for None tier (unknown capability assumes full capability) and logs the default selection at debug level. Direct return of _FULL_PROFILE for the None case is efficient.

src/synthorg/engine/prompt_template.py (5)

29-86: Autonomy instructions properly frozen with MappingProxyType.

The AUTONOMY_INSTRUCTIONS mapping is now immutable, preventing accidental mutation. The module-level guard ensures all SeniorityLevel values are covered at import time. This addresses the previous review feedback about mutable exported dicts.


88-138: New AUTONOMY_SUMMARY and AUTONOMY_MINIMAL maps with completeness guards.

Both new maps are properly frozen with MappingProxyType and have their own completeness guards. The text content follows the expected hierarchy: summary is one sentence, minimal is one phrase.


152-176: Template personality rendering correctly branches on personality_mode.

The Jinja2 template now handles three modes:

  • full: all personality fields (description, style, verbosity, risk, creativity, decision-making, collaboration, conflict, traits)
  • condensed: description, style, and traits only
  • fallback (minimal): style only (plus optional traits)

This aligns with the PR objectives for personality presets.


200-211: Org policies section correctly gated by both include_org_policies and org_policies.

The condition {% if include_org_policies and org_policies %} ensures org policies are only rendered when the profile allows it AND policies are provided. This prevents empty sections and respects profile settings.


234-243: Acceptance criteria has two rendering modes as designed.

The past review comment suggested three distinct modes (full/standard/basic), but the current implementation uses a boolean simplify_acceptance_criteria:

  • False (large/medium): nested ### Acceptance Criteria with bullet list
  • True (small): flat **Criteria**: with semicolon-joined descriptions

Per the PR objectives, the "standard" profile has simplify_acceptance_criteria=False, so large and medium share the same criteria format. This matches the _STANDARD_PROFILE definition in prompt_profiles.py (line 94: simplify_acceptance_criteria=False). The design appears intentional.

src/synthorg/engine/_prompt_helpers.py (4)

28-43: _AUTONOMY_LOOKUP correctly maps detail levels to autonomy maps.

The lookup is properly frozen with MappingProxyType, and the guard validates that all AutonomyDetailLevel variants ("full", "summary", "minimal") are covered at import time.


68-88: _resolve_profile_flags uses deferred import to avoid circular dependency.

The deferred import of get_prompt_profile inside the function body is necessary because prompt_profiles.py imports from this module. The # noqa: PLC0415 comment correctly suppresses the linter warning for import-inside-function.


111-142: Core context now includes profile-driven template flags.

The context dict includes personality_mode, include_org_policies, and simplify_acceptance_criteria from the resolved profile, enabling the Jinja2 template to branch on these values. The autonomy_instructions value is correctly selected from the appropriate autonomy map based on autonomy_detail.


175-224: compute_sections correctly determines section presence.

The function builds the sections tuple based on inputs and profile settings. Key behaviors:

  • Core sections (identity, personality, skills, authority, autonomy) are always present
  • org_policies is included only when policies exist AND profile allows it
  • task, company, context_budget are included when their inputs are provided
  • tools is only included for custom templates (per D22 non-inferable principle)

This centralizes section tracking logic that was previously scattered.

@Aureliolo Aureliolo force-pushed the feat/capability-aware-prompt-profiles branch from 762ae60 to 1c20fbf Compare April 3, 2026 20:36
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 3, 2026 20:37 — with GitHub Actions Inactive
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: 8

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

144-149: ⚠️ Potential issue | 🟡 Minor

Use a non-SKIPPED event for tier-preservation debug logs.

This branch executes during a successful downgrade, so emitting BUDGET_DOWNGRADE_SKIPPED can inflate “skipped downgrade” telemetry. Please switch to a dedicated tier-preserved debug event (or remove this debug event).

🤖 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 144 - 149, Replace the
incorrect BUDGET_DOWNGRADE_SKIPPED telemetry event emitted in the logger.debug
call with a dedicated tier-preserved event (e.g., BUDGET_TIER_PRESERVED) so
successful downgrades that preserve model_tier are not recorded as "skipped";
update the logger.debug invocation that currently references
BUDGET_DOWNGRADE_SKIPPED to use the new event symbol and keep the same context
fields (note, current_tier=current.model_tier, target_alias=target_alias), and
ensure the new telemetry constant is defined where other budget event constants
live.
src/synthorg/engine/prompt_template.py (1)

234-243: ⚠️ Potential issue | 🟠 Major

medium still collapses onto the large acceptance-criteria path.

This branch still only has two states, so "medium" and "large" render identically here. The standard profile never gets its own acceptance-criteria formatting, which leaves the original #805 gap unresolved.

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

In `@src/synthorg/engine/prompt_template.py` around lines 234 - 243, The template
currently treats simplify_acceptance_criteria as a boolean and only renders two
paths so the "medium" profile falls through to the "large" output; update the
Jinja conditional around simplify_acceptance_criteria in prompt_template.py to
explicitly handle three cases (e.g., simplify_acceptance_criteria == 'large',
simplify_acceptance_criteria == 'medium', else/false) and add a distinct
rendering for the medium branch (using task.acceptance_criteria and
attribute='description' like the others) so the standard/medium profile gets its
own acceptance-criteria formatting; reference the simplify_acceptance_criteria
variable and task.acceptance_criteria in your changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/engine.md`:
- Around line 719-720: The doc wording implies budget auto-downgrade always
updates identity.model.model_tier, but the implementation only updates
model_tier when the target alias is a canonical tier; if the alias is
non-canonical (e.g., "local-small") the existing identity.model.model_tier is
preserved and passed unchanged to build_system_prompt(); update the text to
state this conditional behavior explicitly — say that step 3 updates model_tier
only when the downgrade target maps to a canonical tier name and otherwise
leaves identity.model.model_tier untouched, and that step 4 reads the preserved
or updated identity.model.model_tier and passes it to build_system_prompt().

In `@src/synthorg/api/controllers/setup_agents.py`:
- Around line 158-162: The code assigns "model_tier" from the requested
requirement m.tier, but when a fallback model is matched the resolved model_id
can have a different capability; add an effective_tier field to ModelMatch and
populate it from the matched model's alias/capability mapping (i.e., resolve
capability for m.model_id via your model registry/alias->capability lookup
inside the matcher), then replace uses of m.tier in the output dict (the block
that currently sets "model_tier": m.tier alongside m.provider_name and
m.model_id) to use m.effective_tier; also ensure any downstream prompt-profile
selection code consumes effective_tier instead of the original requested tier.

In `@src/synthorg/engine/_prompt_helpers.py`:
- Around line 222-223: compute_sections() currently appends
SECTION_CONTEXT_BUDGET when context_budget is not None, which differs from
DEFAULT_TEMPLATE's truthy check and causes a mismatch for empty-string values;
update the predicate in compute_sections() to use a truthiness check (e.g., if
context_budget) so SECTION_CONTEXT_BUDGET is only added when context_budget
would actually render in the DEFAULT_TEMPLATE, ensuring result.sections matches
the produced prompt.
- Around line 8-43: This module raises ValueError on import when _missing_detail
is non-empty but doesn't log context; import get_logger from
synthorg.observability, add logger = get_logger(__name__) near the top of the
file, and before raising in the block that computes _missing_detail log an ERROR
(or WARNING) with the missing levels and context (referencing _missing_detail
and _AUTONOMY_LOOKUP) and then raise the ValueError as before so startup
failures are logged.

In `@src/synthorg/engine/prompt_profiles.py`:
- Around line 123-138: The function get_prompt_profile currently can raise a raw
KeyError for unknown tiers; change it to validate the input against
PROMPT_PROFILE_REGISTRY before indexing: if tier is None or tier not in
PROMPT_PROFILE_REGISTRY, log the fallback (use logger.debug with
PROMPT_PROFILE_DEFAULT and include the invalid tier value) and return
_FULL_PROFILE as the safe default; otherwise return
PROMPT_PROFILE_REGISTRY[tier]. Ensure you update get_prompt_profile to perform
the membership check rather than directly indexing PROMPT_PROFILE_REGISTRY.
- Around line 117-120: Before raising the ValueError when registry completeness
fails, log the missing tiers at ERROR using the module logger: capture the same
content as _msg_p (the sorted _missing_profiles for ModelTier vs
PROMPT_PROFILE_REGISTRY) and call logger.error with clear context (e.g.,
"Missing prompt profiles for tiers: ...") immediately before the raise in the
block that defines _missing_profiles; leave the ValueError raise unchanged
otherwise.

In `@src/synthorg/engine/prompt_template.py`:
- Around line 18-25: Add structured logging to this module by importing
get_logger from synthorg.observability and defining logger =
get_logger(__name__) at the top; then, in every import-time completeness guard
and error path (including the checks that raise for missing template keys around
the later block referenced), call logger.warning or logger.error with clear
context (e.g., which key(s) or template name are missing and
PROMPT_TEMPLATE_VERSION) immediately before raising the exception so failures
are emitted to the observability stream.

In `@src/synthorg/engine/prompt.py`:
- Around line 493-497: The branch condition that checks
profile.include_org_policies prevents _trim_sections() from ever dropping
org_policies even when trimming is needed; update the conditional in prompt.py
(the elif that tests section == _SECTION_ORG_POLICIES) to remove the
profile.include_org_policies guard so the branch triggers solely on section ==
_SECTION_ORG_POLICIES and org_policies presence, allowing _trim_sections() to
trim this section while still passing org_policies into the render context for
custom templates.

---

Duplicate comments:
In `@src/synthorg/budget/_enforcer_helpers.py`:
- Around line 144-149: Replace the incorrect BUDGET_DOWNGRADE_SKIPPED telemetry
event emitted in the logger.debug call with a dedicated tier-preserved event
(e.g., BUDGET_TIER_PRESERVED) so successful downgrades that preserve model_tier
are not recorded as "skipped"; update the logger.debug invocation that currently
references BUDGET_DOWNGRADE_SKIPPED to use the new event symbol and keep the
same context fields (note, current_tier=current.model_tier,
target_alias=target_alias), and ensure the new telemetry constant is defined
where other budget event constants live.

In `@src/synthorg/engine/prompt_template.py`:
- Around line 234-243: The template currently treats
simplify_acceptance_criteria as a boolean and only renders two paths so the
"medium" profile falls through to the "large" output; update the Jinja
conditional around simplify_acceptance_criteria in prompt_template.py to
explicitly handle three cases (e.g., simplify_acceptance_criteria == 'large',
simplify_acceptance_criteria == 'medium', else/false) and add a distinct
rendering for the medium branch (using task.acceptance_criteria and
attribute='description' like the others) so the standard/medium profile gets its
own acceptance-criteria formatting; reference the simplify_acceptance_criteria
variable and task.acceptance_criteria in your changes.
🪄 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: dd2566df-6e42-44ae-ae02-ae087973311a

📥 Commits

Reviewing files that changed from the base of the PR and between 762ae60 and 1c20fbf.

📒 Files selected for processing (23)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/engine.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/core/types.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/observability/events/prompt.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/templates/model_requirements.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/core/test_role.py
  • tests/unit/engine/test_prompt.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/engine/test_prompt_template.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax: except A, B: (no parentheses) in Python 3.14 code. ruff enforces this.
Type hints required on all public functions in Python. mypy strict mode is enforced.
Google-style docstrings required on all public classes and functions in Python. ruff D rules enforce this.
Use immutability principles: create new objects, never mutate existing ones. For non-Pydantic collections, use copy.deepcopy() at construction and MappingProxyType for read-only enforcement.
Distinguish config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations. Use @computed_field for derived values. Use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new Python code. Prefer structured concurrency over bare create_task.
Python line length: 88 characters (enforced by ruff).
Python functions must be < 50 lines, files < 800 lines.
Handle errors explicitly in Python. Never silently swallow exceptions.
Validate at system boundaries in Python: user input, external APIs, config files. Use pydantic models or explicit validation.

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/prompt.py
  • src/synthorg/api/controllers/setup_agents.py
  • tests/unit/core/test_role.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/providers/routing/strategies.py
  • tests/unit/budget/test_enforcer.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • tests/unit/engine/test_prompt_template.py
  • src/synthorg/engine/prompt.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/engine/test_prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/prompt_template.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every Python module with business logic MUST import from synthorg.observability import get_logger and define logger = get_logger(__name__).
Never use import logging, logging.getLogger(), or print() in synthorg application code. Exception: observability setup files may use stdlib logging and stderr print for bootstrap.
Always use logger as the variable name, not _logger or log, in Python code.
Always use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging in Python: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
All Python error paths must log at WARNING or ERROR with context before raising.
All Python state transitions must log at INFO level.
Use DEBUG logging for object creation, internal flow, and entry/exit of key functions in Python.

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/prompt.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/prompt_template.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned Python code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, etc. Exceptions: Operations design page, .claude/ files, third-party imports, and provider presets.

Files:

  • src/synthorg/engine/agent_engine.py
  • src/synthorg/observability/events/prompt.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/prompt_template.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/observability/events/prompt.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/prompt_template.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Python test files must use markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow.
Prefer @pytest.mark.parametrize for testing similar cases in Python tests.
Use test-provider, test-small-001, etc. in Python tests instead of real vendor names.
Use Hypothesis for property-based testing in Python. Use @given + @settings. Configure via HYPOTHESIS_PROFILE env var: ci (10 examples, deterministic), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, overnight).
NEVER skip, dismiss, or ignore flaky tests. Always fix them fully and fundamentally. Mock time.monotonic() and asyncio.sleep() to make timing-sensitive tests deterministic. Use asyncio.Event().wait() for indefinite blocking.

Files:

  • tests/unit/core/test_role.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/engine/test_prompt_template.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/engine/test_prompt.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/core/test_role.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/engine/test_prompt_template.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/engine/test_prompt.py
src/synthorg/providers/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/providers/**/*.py: All provider calls in Python must go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.
Configure RetryConfig and RateLimiterConfig per-provider in ProviderConfig in Python. Do not implement retry logic elsewhere.

Files:

  • src/synthorg/providers/routing/strategies.py
🧠 Learnings (68)
📚 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/prompt.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/prompt.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/prompt.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/prompt.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/prompt.py
📚 Learning: 2026-04-03T20:24:03.685Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T20:24:03.685Z
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`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/prompt.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/prompt.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/prompt.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/prompt.py
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: CLI (Go binary): see `cli/CLAUDE.md` for commands, flags, and reference; use `go -C cli` (never `cd cli`)

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 : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
📚 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:

  • CLAUDE.md
  • 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:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

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

Applied to files:

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

  • CLAUDE.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-03T20:24:03.685Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T20:24:03.685Z
Learning: Applies to src/synthorg/**/*.py : Every Python module with business logic MUST import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.

Applied to files:

  • tests/unit/core/test_role.py
  • tests/unit/api/controllers/test_setup_agents.py
📚 Learning: 2026-04-03T20:24:03.685Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T20:24:03.685Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases in Python tests.

Applied to files:

  • tests/unit/core/test_role.py
  • tests/unit/api/controllers/test_setup_agents.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/core/test_role.py
  • tests/unit/api/controllers/test_setup_agents.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 : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/core/test_role.py
  • tests/unit/api/controllers/test_setup_agents.py
📚 Learning: 2026-04-03T20:24:03.685Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T20:24:03.685Z
Learning: Applies to tests/**/*.py : Use `test-provider`, `test-small-001`, etc. in Python tests instead of real vendor names.

Applied to files:

  • tests/unit/core/test_role.py
  • tests/unit/api/controllers/test_setup_agents.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/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/core/role.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt.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/core/types.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/templates/model_requirements.py
  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.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/core/types.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/core/types.py
📚 Learning: 2026-04-03T20:24:03.684Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T20:24:03.684Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `allow_inf_nan=False` in all `ConfigDict` declarations. Use `computed_field` for derived values. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • src/synthorg/core/types.py
  • src/synthorg/engine/prompt.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/core/role.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_helpers.py
  • docs/design/engine.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/budget/_enforcer_helpers.py
  • docs/design/engine.md
  • src/synthorg/engine/prompt.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations

Applied to files:

  • src/synthorg/templates/model_requirements.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/core/agent.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/engine.md
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • 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: Property-based testing: Python uses Hypothesis (given + settings). Hypothesis profiles: ci (200 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties.

Applied to files:

  • tests/unit/engine/test_prompt_profiles.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Tests must use `test-provider`, `test-small-001`, etc. instead of real vendor names

Applied to files:

  • tests/unit/api/controllers/test_setup_agents.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 : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/engine/test_prompt.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)

Applied to files:

  • tests/unit/engine/test_prompt.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/engine/prompt_profiles.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 **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/engine/prompt_profiles.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-04-03T20:24:03.684Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T20:24:03.684Z
Learning: Applies to **/*.py : Use immutability principles: create new objects, never mutate existing ones. For non-Pydantic collections, use `copy.deepcopy()` at construction and `MappingProxyType` for read-only enforcement.

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.

Applied to files:

  • src/synthorg/engine/prompt_template.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/engine/prompt_template.py
🔇 Additional comments (12)
docs/design/agents.md (1)

114-114: Nice documentation update for tier-aware identity fields.

Adding model_tier to the identity YAML makes the setup flow clearer and matches the prompt-profile wiring introduced in this PR.

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

865-865: Correct wiring of resolved model tier into prompt construction.

Line 865 threads the runtime-resolved tier into prompt building, which is the right place to make profile selection deterministic.

docs/design/operations.md (1)

305-307: Good clarification on downgrade-to-tier propagation.

This note clearly explains when model_tier is updated and why prompt profiles will adapt afterward.

src/synthorg/providers/routing/strategies.py (1)

249-249: Good defensive fix for the no-available error path.

Initializing tier upfront removes the possibility of referencing an undefined local when get_seniority_info(...) fails.

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

16-17: Event constants are added in the right place and naming scheme.

These additions cleanly support prompt-profile observability without introducing string literals in call sites.

Based on learnings: Use event name constants from domain-specific modules under synthorg.observability.events and import directly.

tests/unit/core/test_role.py (1)

167-177: Good consolidation to a single parametrized invalid-tier test.

This removes duplication while preserving coverage of both whitespace and non-allowed tier values.

tests/unit/api/controllers/test_setup_agents.py (1)

203-234: Nice parameterized coverage for tier propagation behavior.

The test now validates both representative tiers with one body and keeps assertions focused on the model payload contract.

src/synthorg/core/role.py (1)

80-82: Good type-safety improvement on typical_model_tier.

Switching this field to ModelTier removes free-form tier drift and aligns it with the canonical tier model.

src/synthorg/core/types.py (1)

8-15: Nice centralization of prompt/tier literals.

These shared aliases reduce cross-module drift and keep tier/profile typing consistent.

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

728-786: Great edge-case coverage for non-tier downgrade aliases.

This test closes the gap around preserving model_tier when alias downgrades are not canonical tiers.

src/synthorg/core/agent.py (1)

184-187: Model tier field addition looks solid.

The optional ModelTier on ModelConfig cleanly supports downstream tier-aware behavior without breaking existing identities.

src/synthorg/templates/model_requirements.py (1)

14-15: Good move to a single ModelTier source of truth.

This keeps requirement parsing aligned with other tier-aware subsystems.

Also applies to: 29-29

Comment on lines +158 to +162
m.agent_index: {
"provider": m.provider_name,
"model_id": m.model_id,
"model_tier": m.tier,
}
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

model_tier is being populated from requested tier, not the resolved model capability

At Line 161, model_tier is set from m.tier. In the matcher flow, that tier comes from the agent requirement, and fallback matches can return a different model_id. This can mislabel the assigned model’s capability tier and select the wrong prompt profile downstream.

♻️ Suggested direction
 match_map = {
     m.agent_index: {
         "provider": m.provider_name,
         "model_id": m.model_id,
-        "model_tier": m.tier,
+        "model_tier": m.effective_tier,
     }
     for m in matches
 }

Then populate effective_tier in ModelMatch from the matched model’s alias/capability mapping (not requirement input).

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

In `@src/synthorg/api/controllers/setup_agents.py` around lines 158 - 162, The
code assigns "model_tier" from the requested requirement m.tier, but when a
fallback model is matched the resolved model_id can have a different capability;
add an effective_tier field to ModelMatch and populate it from the matched
model's alias/capability mapping (i.e., resolve capability for m.model_id via
your model registry/alias->capability lookup inside the matcher), then replace
uses of m.tier in the output dict (the block that currently sets "model_tier":
m.tier alongside m.provider_name and m.model_id) to use m.effective_tier; also
ensure any downstream prompt-profile selection code consumes effective_tier
instead of the original requested tier.

Comment on lines +8 to +43
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, get_args

from synthorg.engine.prompt_template import AUTONOMY_INSTRUCTIONS
from synthorg.core.enums import SeniorityLevel # noqa: TC001 -- used in type annotation
from synthorg.core.types import AutonomyDetailLevel, PersonalityMode
from synthorg.engine.prompt_template import (
AUTONOMY_INSTRUCTIONS,
AUTONOMY_MINIMAL,
AUTONOMY_SUMMARY,
)

if TYPE_CHECKING:
from synthorg.core.agent import AgentIdentity
from synthorg.core.company import Company
from synthorg.core.role import Role
from synthorg.core.task import Task
from synthorg.engine.prompt_profiles import PromptProfile
from synthorg.providers.models import ToolDefinition
from synthorg.security.autonomy.models import EffectiveAutonomy

_AUTONOMY_LOOKUP: MappingProxyType[
AutonomyDetailLevel,
MappingProxyType[SeniorityLevel, str],
] = MappingProxyType(
{
"full": AUTONOMY_INSTRUCTIONS,
"summary": AUTONOMY_SUMMARY,
"minimal": AUTONOMY_MINIMAL,
},
)

_expected_detail_levels = set(get_args(AutonomyDetailLevel))
_missing_detail = _expected_detail_levels - set(_AUTONOMY_LOOKUP)
if _missing_detail:
_msg_d = f"Missing autonomy lookup for detail levels: {sorted(_missing_detail)}"
raise ValueError(_msg_d)
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

Import-time lookup guards need structured logging too.

_missing_detail currently raises during module import without any module logger in place. Add logger = get_logger(__name__) and log the missing detail levels before raising so startup failures are diagnosable.

As per coding guidelines, "Every Python module with business logic MUST import from synthorg.observability import get_logger and define logger = get_logger(__name__)." and "All Python error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/engine/_prompt_helpers.py` around lines 8 - 43, This module
raises ValueError on import when _missing_detail is non-empty but doesn't log
context; import get_logger from synthorg.observability, add logger =
get_logger(__name__) near the top of the file, and before raising in the block
that computes _missing_detail log an ERROR (or WARNING) with the missing levels
and context (referencing _missing_detail and _AUTONOMY_LOOKUP) and then raise
the ValueError as before so startup failures are logged.

Comment on lines +117 to +120
_missing_profiles = set(get_args(ModelTier)) - set(PROMPT_PROFILE_REGISTRY)
if _missing_profiles:
_msg_p = f"Missing prompt profiles for tiers: {sorted(_missing_profiles)}"
raise ValueError(_msg_p)
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

Log registry completeness failures before raising.

If ModelTier grows and this registry lags behind, module import aborts with a bare ValueError even though this module already has a structured logger. Emit the missing tiers at ERROR before raising.

As per coding guidelines, "All Python error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/engine/prompt_profiles.py` around lines 117 - 120, Before
raising the ValueError when registry completeness fails, log the missing tiers
at ERROR using the module logger: capture the same content as _msg_p (the sorted
_missing_profiles for ModelTier vs PROMPT_PROFILE_REGISTRY) and call
logger.error with clear context (e.g., "Missing prompt profiles for tiers: ...")
immediately before the raise in the block that defines _missing_profiles; leave
the ValueError raise unchanged otherwise.

Comment on lines +123 to +138
def get_prompt_profile(tier: ModelTier | None) -> PromptProfile:
"""Return the built-in prompt profile for a model tier.

When *tier* is ``None``, returns the full (large) profile as a
safe default -- if the tier is unknown, assume full capability.

Args:
tier: Model tier, or ``None`` for the default (full) profile.

Returns:
The matching ``PromptProfile``.
"""
if tier is None:
logger.debug(PROMPT_PROFILE_DEFAULT, default_tier="large")
return _FULL_PROFILE
return PROMPT_PROFILE_REGISTRY[tier]
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

get_prompt_profile() should not leak a raw KeyError.

The docstring describes this as a safe default, but any unexpected runtime string still falls through to PROMPT_PROFILE_REGISTRY[tier]. Because build_system_prompt() resolves the profile before its PromptBuildError wrapper, a config typo now bypasses the normal prompt-build error path.

As per coding guidelines, "Validate at system boundaries in Python: user input, external APIs, config files. Use pydantic models or explicit validation."

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

In `@src/synthorg/engine/prompt_profiles.py` around lines 123 - 138, The function
get_prompt_profile currently can raise a raw KeyError for unknown tiers; change
it to validate the input against PROMPT_PROFILE_REGISTRY before indexing: if
tier is None or tier not in PROMPT_PROFILE_REGISTRY, log the fallback (use
logger.debug with PROMPT_PROFILE_DEFAULT and include the invalid tier value) and
return _FULL_PROFILE as the safe default; otherwise return
PROMPT_PROFILE_REGISTRY[tier]. Ensure you update get_prompt_profile to perform
the membership check rather than directly indexing PROMPT_PROFILE_REGISTRY.

Comment on lines +18 to 25
from types import MappingProxyType
from typing import Final

from synthorg.core.enums import SeniorityLevel

# Frozen at "1.0.0" until the app has users -- no caching, snapshots,
# or migrations depend on this yet. Bump to a meaningful version when
# the first production deployment ships.
# Version tracks incompatible template changes. Bump when the template
# structure changes in ways that affect caching, snapshots, or migrations.
PROMPT_TEMPLATE_VERSION: Final[str] = "1.0.0"
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

Add structured logging to these import-time guards.

These completeness checks now fail fast during import, but this module still has no logger, so missing-level failures won't show up in the project's normal observability stream. Please add logger = get_logger(__name__) and log the missing keys before raising.

As per coding guidelines, "Every Python module with business logic MUST import from synthorg.observability import get_logger and define logger = get_logger(__name__)." and "All Python error paths must log at WARNING or ERROR with context before raising."

Also applies to: 82-138

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

In `@src/synthorg/engine/prompt_template.py` around lines 18 - 25, Add structured
logging to this module by importing get_logger from synthorg.observability and
defining logger = get_logger(__name__) at the top; then, in every import-time
completeness guard and error path (including the checks that raise for missing
template keys around the later block referenced), call logger.warning or
logger.error with clear context (e.g., which key(s) or template name are missing
and PROMPT_TEMPLATE_VERSION) immediately before raising the exception so
failures are emitted to the observability stream.

Comment on lines +493 to +497
elif (
section == _SECTION_ORG_POLICIES
and org_policies
and (profile is None or profile.include_org_policies)
):
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

Always allow org policies to be trimmed once this branch is reached.

Custom templates still receive org_policies in the render context, even when the selected profile sets include_org_policies=False. With the extra profile guard here, a small-tier custom prompt can stay over budget because _trim_sections() refuses to drop content the template may still be rendering.

Suggested fix
-        elif (
-            section == _SECTION_ORG_POLICIES
-            and org_policies
-            and (profile is None or profile.include_org_policies)
-        ):
+        elif section == _SECTION_ORG_POLICIES and org_policies:
             org_policies = ()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/prompt.py` around lines 493 - 497, The branch condition
that checks profile.include_org_policies prevents _trim_sections() from ever
dropping org_policies even when trimming is needed; update the conditional in
prompt.py (the elif that tests section == _SECTION_ORG_POLICIES) to remove the
profile.include_org_policies guard so the branch triggers solely on section ==
_SECTION_ORG_POLICIES and org_policies presence, allowing _trim_sections() to
trim this section while still passing org_policies into the render context for
custom templates.

Add PromptProfile model and registry that maps ModelTier to rendering
profiles (full/standard/basic). Smaller models receive simpler prompts:

- full (large): all sections, full personality enums, full autonomy
- standard (medium): condensed personality, summary autonomy
- basic (small): minimal personality, no org policies, flat criteria

Key changes:
- PromptProfile frozen Pydantic model + 3 built-in profiles
- ModelTier type alias moved to core/types.py (avoid circular imports)
- model_tier field on ModelConfig (set at template matching, carried
  through auto-downgrade)
- build_system_prompt() gains model_tier parameter
- Jinja2 template conditionals for personality_mode,
  include_org_policies, simplify_acceptance_criteria
- AUTONOMY_SUMMARY and AUTONOMY_MINIMAL dicts for tier-adapted
  autonomy instructions
- Engine wiring: identity.model.model_tier flows to prompt builder
- Authority and Identity sections never stripped regardless of profile
- Profile selection logged via prompt.profile.selected/default events

Closes #805
Pre-reviewed by 10 agents, 27 findings addressed:
- Remove duplicate profile selection logging (keep single INFO in prompt.py)
- Fix _build_downgraded_model_config to preserve existing model_tier on non-tier aliases
- Move section constants and _compute_sections to _prompt_helpers.py (prompt.py under 800 lines)
- Wrap _AUTONOMY_LOOKUP in MappingProxyType for immutability
- Add module-level completeness guard for PROMPT_PROFILE_REGISTRY
- Fix docstrings: 'security' -> 'identity', 'comma' -> 'semicolon', ModelTier reference
- Update CLAUDE.md package structure and logging examples
- Update design spec: agents.md model_tier YAML, operations.md auto-downgrade section
- Retype SeniorityInfo.typical_model_tier from NotBlankStr to ModelTier
- Fix strategies.py type annotation for tier fallback
- Convert test loops to @pytest.mark.parametrize, use get_args(ModelTier)
- Add tests: autonomy text variation, build_core_context defaults, enforcer model_tier
@Aureliolo Aureliolo force-pushed the feat/capability-aware-prompt-profiles branch from 1c20fbf to 6804406 Compare April 3, 2026 20:59
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 3, 2026 21:00 — with GitHub Actions Inactive
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: 3

♻️ Duplicate comments (6)
src/synthorg/engine/_prompt_helpers.py (1)

8-43: 🛠️ Refactor suggestion | 🟠 Major

Log autonomy-lookup guard failures before raising.

This helper module still has no logger, so _missing_detail kills import with only a ValueError. That makes lookup-coverage regressions invisible in the normal observability path.

As per coding guidelines, "Every Python module with business logic MUST import from synthorg.observability import get_logger and define logger = get_logger(__name__)." and "All Python error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/engine/_prompt_helpers.py` around lines 8 - 43, The module fails
to log missing autonomy lookup coverage before raising, so add "from
synthorg.observability import get_logger" and define "logger =
get_logger(__name__)" near the top, then replace the current raise-only path for
_missing_detail with a logger.error (or warning) that includes the
_missing_detail and a snapshot of keys in _AUTONOMY_LOOKUP for context, and only
after logging raise the ValueError with the same message; ensure the referenced
symbols are _AUTONOMY_LOOKUP, _missing_detail, and get_logger/logger.
src/synthorg/engine/prompt_profiles.py (2)

123-138: ⚠️ Potential issue | 🟠 Major

Reject or default unknown tiers instead of leaking KeyError.

build_system_prompt() resolves the profile before its PromptBuildError wrapper, so any unexpected runtime string here escapes as a raw KeyError. Guard membership before indexing and fall back to _FULL_PROFILE with a default log.

🩹 Proposed fix
 def get_prompt_profile(tier: ModelTier | None) -> PromptProfile:
@@
-    if tier is None:
-        logger.debug(PROMPT_PROFILE_DEFAULT, default_tier="large")
+    if tier is None or tier not in PROMPT_PROFILE_REGISTRY:
+        logger.debug(
+            PROMPT_PROFILE_DEFAULT,
+            default_tier="large",
+            requested_tier=tier,
+        )
         return _FULL_PROFILE
     return PROMPT_PROFILE_REGISTRY[tier]

As per coding guidelines, "Validate at system boundaries in Python: user input, external APIs, config files."

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

In `@src/synthorg/engine/prompt_profiles.py` around lines 123 - 138,
get_prompt_profile currently indexes PROMPT_PROFILE_REGISTRY[tier] directly
which raises KeyError for unknown tiers; update get_prompt_profile to validate
membership (e.g., if tier not in PROMPT_PROFILE_REGISTRY) and fall back to
returning _FULL_PROFILE while logging a clear debug/warn message including the
unknown tier; keep the existing behavior for tier is None (logging
default_tier="large") and only use the membership guard for non-None tiers to
avoid leaking raw KeyError from PROMPT_PROFILE_REGISTRY.

117-120: 🛠️ Refactor suggestion | 🟠 Major

Log missing-profile guard failures before raising.

This module already has logger, but the registry completeness guard still raises a bare ValueError. If ModelTier grows and this table lags, startup will fail without the missing tiers in structured logs.

As per coding guidelines, "All Python error paths must log at WARNING or ERROR with context before raising."

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

In `@src/synthorg/engine/prompt_profiles.py` around lines 117 - 120, The guard
that computes _missing_profiles (using get_args(ModelTier) and
PROMPT_PROFILE_REGISTRY) raises a ValueError without logging; before raising,
log the same contextual message via the module logger (e.g., logger.warning or
logger.error) including the sorted list of _missing_profiles and any relevant
context (ModelTier name/registry size) so structured logs capture the failure,
then raise the ValueError with the existing message.
src/synthorg/engine/prompt.py (1)

493-497: 🛠️ Refactor suggestion | 🟠 Major

Always allow policy trimming once policies are in the render context.

Custom templates still receive org_policies even when a profile suppresses the default policies section. With this extra guard, small-tier custom prompts can keep policy text that _trim_sections() will never remove, so they can stay over budget. Please apply the same conservative rule to compute_sections() so section metadata stays honest for custom templates.

🩹 Proposed fix
-        elif (
-            section == _SECTION_ORG_POLICIES
-            and org_policies
-            and (profile is None or profile.include_org_policies)
-        ):
+        elif section == _SECTION_ORG_POLICIES and org_policies:
             org_policies = ()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/prompt.py` around lines 493 - 497, In compute_sections(),
the current branch for _SECTION_ORG_POLICIES gates inclusion on
profile.include_org_policies which prevents _trim_sections() from ever trimming
org_policies when they are present in the render context; change the condition
so that when org_policies is present the section is treated as trimmable
regardless of profile.include_org_policies (i.e., use the symbols
compute_sections, _SECTION_ORG_POLICIES, org_policies, and
profile.include_org_policies and remove the profile.include_org_policies guard
so org_policies are always considered for trimming like _trim_sections expects).
src/synthorg/engine/prompt_template.py (2)

18-25: 🛠️ Refactor suggestion | 🟠 Major

Log autonomy-map guard failures before raising.

This module still has no logger, so missing entries in _missing_levels, _missing_summary, or _missing_minimal abort import with only a bare ValueError. Please emit those failures through the observability stream first.

As per coding guidelines, "Every Python module with business logic MUST import from synthorg.observability import get_logger and define logger = get_logger(__name__)." and "All Python error paths must log at WARNING or ERROR with context before raising."

Also applies to: 82-138

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

In `@src/synthorg/engine/prompt_template.py` around lines 18 - 25, The module
raises ValueError when autonomy-map guards (_missing_levels, _missing_summary,
_missing_minimal) are non-empty but does not log those failures; import
synthorg.observability.get_logger and define logger = get_logger(__name__), then
before any raise ValueError that originates from checks of _missing_levels,
_missing_summary, or _missing_minimal (the guard logic in prompt_template.py)
call logger.error or logger.warning with the collection contents and context
(e.g., template name/version and which guard failed) so failures are emitted to
observability prior to raising.

234-243: ⚠️ Potential issue | 🟠 Major

Medium tier still has no distinct acceptance-criteria render path.

This branch only distinguishes simplify_acceptance_criteria true/false. Because both the large and medium built-ins set that flag to False, standard-tier prompts render identically to full-tier prompts here, so the documented bullet-list variant never exists.

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

In `@src/synthorg/engine/prompt_template.py` around lines 234 - 243, The template
currently only toggles on simplify_acceptance_criteria, causing both large and
medium builtin tiers to render the same; update the Jinja conditional to
explicitly branch for the medium tier so it gets a distinct render path.
Specifically, change the block around simplify_acceptance_criteria to a
three-way decision that checks for model_tier == 'medium' (or a provided tier
variable) and render the medium-specific format (e.g., its own bullet-list or
concise format using task.acceptance_criteria), keep the existing full/large
branch (when simplify_acceptance_criteria is False and not medium), and keep the
simplified single-line join when simplify_acceptance_criteria is True. Ensure
you reference simplify_acceptance_criteria, model_tier (or the tier variable
your code uses), and task.acceptance_criteria in the new branches so medium is
handled separately from large.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/engine/prompt.py`:
- Around line 637-645: The metadata dict returned by _build_metadata(agent) is
currently mutable and can be mutated after SystemPrompt construction; import
MappingProxyType from types and wrap the built metadata with MappingProxyType
when constructing the SystemPrompt (i.e., pass
metadata=MappingProxyType(_build_metadata(agent))) so SystemPrompt's metadata
becomes a read-only mapping; also update the SystemPrompt metadata annotation to
MappingProxyType[str, str] where declared to reflect the read-only type.

In `@src/synthorg/templates/model_requirements.py`:
- Around line 14-17: The __all__ export is placed between imports which breaks
conventional ordering; move the __all__ declaration so it appears after all
import statements (i.e., after both ModelTier and get_logger are imported).
Update the module so imports (from synthorg.core.types import ModelTier and from
synthorg.observability import get_logger) appear together followed by __all__ =
["ModelTier"] to restore conventional import/export ordering.

In `@tests/unit/api/controllers/test_setup_agents.py`:
- Line 210: The patch currently targets the function definition site; change the
patch decorator in the test to mock where match_all_agents is looked up by the
code under test: replace the target
"synthorg.templates.model_matcher.match_all_agents" with
"synthorg.api.controllers.setup_agents.match_all_agents" so the mock intercepts
the local import used by match_and_assign_models.

---

Duplicate comments:
In `@src/synthorg/engine/_prompt_helpers.py`:
- Around line 8-43: The module fails to log missing autonomy lookup coverage
before raising, so add "from synthorg.observability import get_logger" and
define "logger = get_logger(__name__)" near the top, then replace the current
raise-only path for _missing_detail with a logger.error (or warning) that
includes the _missing_detail and a snapshot of keys in _AUTONOMY_LOOKUP for
context, and only after logging raise the ValueError with the same message;
ensure the referenced symbols are _AUTONOMY_LOOKUP, _missing_detail, and
get_logger/logger.

In `@src/synthorg/engine/prompt_profiles.py`:
- Around line 123-138: get_prompt_profile currently indexes
PROMPT_PROFILE_REGISTRY[tier] directly which raises KeyError for unknown tiers;
update get_prompt_profile to validate membership (e.g., if tier not in
PROMPT_PROFILE_REGISTRY) and fall back to returning _FULL_PROFILE while logging
a clear debug/warn message including the unknown tier; keep the existing
behavior for tier is None (logging default_tier="large") and only use the
membership guard for non-None tiers to avoid leaking raw KeyError from
PROMPT_PROFILE_REGISTRY.
- Around line 117-120: The guard that computes _missing_profiles (using
get_args(ModelTier) and PROMPT_PROFILE_REGISTRY) raises a ValueError without
logging; before raising, log the same contextual message via the module logger
(e.g., logger.warning or logger.error) including the sorted list of
_missing_profiles and any relevant context (ModelTier name/registry size) so
structured logs capture the failure, then raise the ValueError with the existing
message.

In `@src/synthorg/engine/prompt_template.py`:
- Around line 18-25: The module raises ValueError when autonomy-map guards
(_missing_levels, _missing_summary, _missing_minimal) are non-empty but does not
log those failures; import synthorg.observability.get_logger and define logger =
get_logger(__name__), then before any raise ValueError that originates from
checks of _missing_levels, _missing_summary, or _missing_minimal (the guard
logic in prompt_template.py) call logger.error or logger.warning with the
collection contents and context (e.g., template name/version and which guard
failed) so failures are emitted to observability prior to raising.
- Around line 234-243: The template currently only toggles on
simplify_acceptance_criteria, causing both large and medium builtin tiers to
render the same; update the Jinja conditional to explicitly branch for the
medium tier so it gets a distinct render path. Specifically, change the block
around simplify_acceptance_criteria to a three-way decision that checks for
model_tier == 'medium' (or a provided tier variable) and render the
medium-specific format (e.g., its own bullet-list or concise format using
task.acceptance_criteria), keep the existing full/large branch (when
simplify_acceptance_criteria is False and not medium), and keep the simplified
single-line join when simplify_acceptance_criteria is True. Ensure you reference
simplify_acceptance_criteria, model_tier (or the tier variable your code uses),
and task.acceptance_criteria in the new branches so medium is handled separately
from large.

In `@src/synthorg/engine/prompt.py`:
- Around line 493-497: In compute_sections(), the current branch for
_SECTION_ORG_POLICIES gates inclusion on profile.include_org_policies which
prevents _trim_sections() from ever trimming org_policies when they are present
in the render context; change the condition so that when org_policies is present
the section is treated as trimmable regardless of profile.include_org_policies
(i.e., use the symbols compute_sections, _SECTION_ORG_POLICIES, org_policies,
and profile.include_org_policies and remove the profile.include_org_policies
guard so org_policies are always considered for trimming like _trim_sections
expects).
🪄 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: 7f92836b-374c-41c8-89af-4b19be86560c

📥 Commits

Reviewing files that changed from the base of the PR and between 1c20fbf and 6804406.

📒 Files selected for processing (24)
  • CLAUDE.md
  • docs/design/agents.md
  • docs/design/engine.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/budget/_enforcer_helpers.py
  • src/synthorg/core/agent.py
  • src/synthorg/core/role.py
  • src/synthorg/core/types.py
  • src/synthorg/engine/_prompt_helpers.py
  • src/synthorg/engine/agent_engine.py
  • src/synthorg/engine/prompt.py
  • src/synthorg/engine/prompt_profiles.py
  • src/synthorg/engine/prompt_template.py
  • src/synthorg/observability/events/budget.py
  • src/synthorg/observability/events/prompt.py
  • src/synthorg/providers/routing/strategies.py
  • src/synthorg/templates/model_requirements.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/budget/test_enforcer.py
  • tests/unit/core/test_role.py
  • tests/unit/engine/test_prompt.py
  • tests/unit/engine/test_prompt_profiles.py
  • tests/unit/engine/test_prompt_template.py

Comment on lines +637 to +645
metadata = _build_metadata(agent)
if profile is not None:
metadata["profile_tier"] = profile.tier
return SystemPrompt(
content=content,
template_version=PROMPT_TEMPLATE_VERSION,
estimated_tokens=estimated,
sections=sections,
metadata=_build_metadata(agent),
metadata=metadata,
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

🧩 Analysis chain

🌐 Web query:

In Pydantic v2, does ConfigDict(frozen=True) prevent mutation of nested dict fields, or only assignment to the top-level model attribute?

💡 Result:

In Pydantic v2, ConfigDict(frozen=True) prevents assignment to the top-level model attribute (e.g., model.field = new_value raises ValidationError), but does not prevent mutation of nested dict fields (e.g., model.field['key'] = value succeeds because the dict is a regular mutable Python dict, and frozen only hooks into setattr).

Citations:


🏁 Script executed:

rg "class SystemPrompt" src/synthorg/ -A 20

Repository: Aureliolo/synthorg

Length of output: 1524


🏁 Script executed:

rg "metadata:" src/synthorg/engine/prompt.py -B 5 -A 2

Repository: Aureliolo/synthorg

Length of output: 765


🏁 Script executed:

rg "ConfigDict" src/synthorg/engine/prompt.py -B 2 -A 5

Repository: Aureliolo/synthorg

Length of output: 626


🏁 Script executed:

rg "MappingProxyType" src/synthorg/engine/prompt.py

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

head -20 src/synthorg/engine/prompt.py | grep -E "^(import|from)"

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

sed -n '1,30p' src/synthorg/engine/prompt.py

Repository: Aureliolo/synthorg

Length of output: 1339


Wrap metadata in MappingProxyType to enforce immutability.

The model's frozen=True prevents reassignment (result.metadata = ...) but not nested mutations (result.metadata["profile_tier"] = ...). Since SystemPrompt is declared immutable and the docstring acknowledges "shallow-frozen," the dict must be wrapped in MappingProxyType per the coding guidelines: "For non-Pydantic collections, use MappingProxyType for read-only enforcement."

Change line 645:

metadata: MappingProxyType[str, str]  # Read-only view of agent identity metadata

Import from types import MappingProxyType at the top of the file, and wrap the dict at construction (line 642):

metadata=MappingProxyType(_build_metadata(agent))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/engine/prompt.py` around lines 637 - 645, The metadata dict
returned by _build_metadata(agent) is currently mutable and can be mutated after
SystemPrompt construction; import MappingProxyType from types and wrap the built
metadata with MappingProxyType when constructing the SystemPrompt (i.e., pass
metadata=MappingProxyType(_build_metadata(agent))) so SystemPrompt's metadata
becomes a read-only mapping; also update the SystemPrompt metadata annotation to
MappingProxyType[str, str] where declared to reflect the read-only type.

Comment on lines +14 to +17
from synthorg.core.types import ModelTier
from synthorg.observability import get_logger

__all__ = ["ModelTier"]
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.

🧹 Nitpick | 🔵 Trivial

Consider moving __all__ after all imports for conventional ordering.

The __all__ export is placed between imports, which is unconventional. Python allows this, but most style guides recommend placing __all__ after all imports for readability.

♻️ Suggested reordering
 from synthorg.core.types import ModelTier
 from synthorg.observability import get_logger
-
-__all__ = ["ModelTier"]
 from synthorg.observability.events.template import (
     TEMPLATE_MODEL_REQUIREMENT_INVALID,
     TEMPLATE_MODEL_REQUIREMENT_PARSED,
     TEMPLATE_MODEL_REQUIREMENT_RESOLVED,
 )
+
+__all__ = ["ModelTier"]
 
 logger = get_logger(__name__)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/templates/model_requirements.py` around lines 14 - 17, The
__all__ export is placed between imports which breaks conventional ordering;
move the __all__ declaration so it appears after all import statements (i.e.,
after both ModelTier and get_logger are imported). Update the module so imports
(from synthorg.core.types import ModelTier and from synthorg.observability
import get_logger) appear together followed by __all__ = ["ModelTier"] to
restore conventional import/export ordering.

("small", "test-small-001"),
],
)
@patch("synthorg.templates.model_matcher.match_all_agents")
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check where match_all_agents is imported in setup_agents.py
rg -n "from synthorg.templates.model_matcher import" src/synthorg/api/controllers/setup_agents.py
# Check the actual import path used
ast-grep --pattern 'from synthorg.templates.model_matcher import $$$'

Repository: Aureliolo/synthorg

Length of output: 871


🏁 Script executed:

sed -n '200,220p' tests/unit/api/controllers/test_setup_agents.py

Repository: Aureliolo/synthorg

Length of output: 709


🏁 Script executed:

sed -n '210,250p' tests/unit/api/controllers/test_setup_agents.py

Repository: Aureliolo/synthorg

Length of output: 906


🏁 Script executed:

# Check import at top of test file
head -30 tests/unit/api/controllers/test_setup_agents.py | grep -A 5 -B 5 "import"

# Check the match_and_assign_models function in setup_agents.py
rg -n "def match_and_assign_models" src/synthorg/api/controllers/setup_agents.py -A 30

Repository: Aureliolo/synthorg

Length of output: 1730


Fix the patch target to where the function is imported and used.

The patch should target synthorg.api.controllers.setup_agents.match_all_agents, not synthorg.templates.model_matcher.match_all_agents. Since match_all_agents is imported locally inside match_and_assign_models (line 154), the mock must intercept the lookup in that module's namespace, not at the definition site.

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

In `@tests/unit/api/controllers/test_setup_agents.py` at line 210, The patch
currently targets the function definition site; change the patch decorator in
the test to mock where match_all_agents is looked up by the code under test:
replace the target "synthorg.templates.model_matcher.match_all_agents" with
"synthorg.api.controllers.setup_agents.match_all_agents" so the mock intercepts
the local import used by match_and_assign_models.

@Aureliolo Aureliolo merged commit 67650c5 into main Apr 3, 2026
27 of 29 checks passed
@Aureliolo Aureliolo deleted the feat/capability-aware-prompt-profiles branch April 3, 2026 21:17
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 3, 2026 21:17 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 4, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.6.1](v0.6.0...v0.6.1)
(2026-04-04)


### Features

* capability-aware prompt profiles for model tier adaptation
([#1047](#1047))
([67650c5](67650c5)),
closes [#805](#805)
* implement procedural memory auto-generation from agent failures
([#1048](#1048))
([55f5206](55f5206)),
closes [#420](#420)
* implement quality scoring Layers 2+3 -- LLM judge and human override
([#1057](#1057))
([4a8adfe](4a8adfe)),
closes [#230](#230)
* token-based personality trimming via
PromptProfile.max_personality_tokens
([#1059](#1059))
([75afd52](75afd52)),
closes [#1045](#1045)
* workflow execution lifecycle + editor improvements
([#1058](#1058))
([7b54262](7b54262)),
closes [#1029](#1029)
[#1042](#1042)


### Refactoring

* **web:** address complexity and logging issues in dashboard
([#1056](#1056))
([ada997b](ada997b)),
closes [#1055](#1055)


### Documentation

* comprehensive documentation refresh
([#1050](#1050))
([c7a4259](c7a4259))


### Tests

* fix Hypothesis fuzzing infra and speed up slow unit tests
([#1044](#1044))
([1111602](1111602))


### Maintenance

* add text=auto catch-all to .gitattributes
([#1051](#1051))
([fc65d72](fc65d72))
* bump defu from 6.1.4 to 6.1.6 in /site
([#1062](#1062))
([f0cc439](f0cc439))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: capability-aware prompt profiles for model tier adaptation

2 participants