Skip to content

feat: structured ModelRequirement in template agent configs#795

Merged
Aureliolo merged 3 commits intomainfrom
feat/implement-722-modelreq
Mar 23, 2026
Merged

feat: structured ModelRequirement in template agent configs#795
Aureliolo merged 3 commits intomainfrom
feat/implement-722-modelreq

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Extend TemplateAgentConfig.model to accept either a string tier alias ("medium") or a structured dict ({tier: "medium", priority: "quality", min_context: 100000}) with full ModelRequirement fields
  • Thread parsed ModelRequirement through the setup wizard path to the matching engine, bypassing personality-based affinity defaults when structured format is used
  • Update all 9 builtin templates with role-appropriate structured models: leaders/architects get quality + 100k context, DevOps/SRE get speed, mid-level devs get cost, content writers get quality, PMs/scrum masters get speed
  • Extract _build_departments and _validate_as_root_config from renderer.py to _render_helpers.py (pre-existing 875-line file now at 770)

Test plan

  • Schema tests: string accepted, dict accepted, invalid tier/priority/extra fields rejected, empty dict defaults, invalid string tier rejected
  • Renderer tests: dict model extracts tier, string model still works, mixed formats in same template
  • Matcher tests: dict requirement used, min_context filtering, requirement overrides affinity, object used directly, fallback to tier+preset, invalid dict gracefully skipped
  • All 9 builtin templates render to valid RootConfig (existing integration test)
  • Setup wizard tests: all 66 setup controller tests pass (exercises expand_template_agents with structured models)
  • Full suite: 10269 unit tests pass, lint + mypy + pre-commit clean

Pre-reviewed by 8 agents, 10 findings addressed.

Closes #722

🤖 Generated with Claude Code

Aureliolo and others added 2 commits March 23, 2026 20:07
Extend TemplateAgentConfig.model to accept either a string tier alias
(backward-compatible) or a structured dict with tier, priority,
min_context, and capabilities fields. This lets template authors express
explicit model requirements per agent role instead of relying solely on
personality-based affinity defaults.

- Schema: model field type NotBlankStr -> NotBlankStr | dict, with
  field_validator for dict validation via parse_model_requirement()
- Renderer: _expand_single_agent() extracts tier from dict model values
- Setup agents: expand_template_agents() parses structured dicts into
  ModelRequirement and threads serialized requirement to matcher
- Model matcher: match_all_agents() uses model_requirement when present,
  falls back to tier + personality affinity when absent
- Builtin templates: leaders/architects get quality + 100k context,
  DevOps/SRE get speed, mid-level devs get cost, content writers get
  quality, PMs/scrum masters get speed
- Design spec: updated template example showing both formats

Closes #722

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- schema.py: validator now catches exceptions and re-raises as
  ValueError (Pydantic v2 contract); validates both string and dict
  model values at schema boundary; deferred import pattern
- model_matcher.py: dict branch uses parse_model_requirement() with
  try/except for graceful error handling; updated docstring to document
  model_requirement key resolution paths
- renderer.py: extract _build_departments and _validate_as_root_config
  to _render_helpers.py (file was 884 lines, now 770; limit is 800);
  extract _resolve_model_tier helper
- tests: add model_id assertion, invalid dict skip test, empty dict
  defaults test, invalid string tier rejection test

Pre-reviewed by 8 agents, 10 findings addressed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 23, 2026

Dependency Review

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

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

Walkthrough

Template agent model now accepts either a legacy tier string or a structured dict with tier, priority, min_context, and optional capabilities. Schema, renderer, and model-matching logic were updated to parse and consume structured requirements; expand_template_agents persists resolved tier and conditionally includes model_requirement. A new templates helper module (_render_helpers.py) centralizes department building and RootConfig validation. Builtin YAML templates were revised to use structured model specs for various roles. Unit tests and documentation were added/updated to cover the new input form and behavior.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: structured ModelRequirement in template agent configs' accurately captures the main feature: extending template agent model configuration to support structured requirements.
Description check ✅ Passed The description is related to the changeset, detailing the extension of TemplateAgentConfig.model to accept dicts, threading ModelRequirement through the setup wizard, and updating builtin templates.
Linked Issues check ✅ Passed The PR successfully implements all key objectives from issue #722: schema extension for dict models, loader/renderer changes to thread ModelRequirement, template YAML updates with role-appropriate priorities, refactoring of validation helpers, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: schema/loader/renderer modifications for structured ModelRequirement, builtin template updates, extraction of validation helpers to _render_helpers.py, and corresponding test additions.
Docstring Coverage ✅ Passed Docstring coverage is 65.31% 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.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the flexibility and precision of model configuration for agents within templates. By introducing a structured ModelRequirement format, users can now specify detailed criteria for model selection, moving beyond simple tier aliases. This change is propagated through the setup wizard and model matching engine, ensuring that agents are assigned models that align more closely with their specific roles and operational priorities. The update also includes a substantial refactoring of template rendering logic to improve code structure and maintainability.

Highlights

  • Structured Model Requirements: Extended the TemplateAgentConfig.model field to accept a structured dictionary representing a ModelRequirement, allowing for precise control over model selection criteria such as tier, priority, min_context, and capabilities, in addition to the existing string tier alias.
  • Enhanced Model Matching Logic: Modified the model matching engine to prioritize and directly utilize the new structured ModelRequirement when provided, bypassing personality-based affinity defaults and enabling more granular model selection.
  • Built-in Template Updates: Updated all nine built-in templates to leverage the structured model configuration, assigning role-appropriate model requirements (e.g., quality for leaders, speed for DevOps, cost for mid-level developers, quality for content writers).
  • Code Refactoring for Maintainability: Refactored the renderer.py module by extracting helper functions _build_departments and _validate_as_root_config into a new dedicated file _render_helpers.py, improving code organization and reducing file size.
  • Improved Schema Validation: Implemented robust validation for the model field in TemplateAgentConfig to ensure that both string tier aliases and structured ModelRequirement dictionaries adhere to the defined schema, rejecting invalid inputs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 23, 2026 19:35 — with GitHub Actions Inactive
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 introduces a valuable feature by allowing structured model requirements in templates, providing more fine-grained control over model selection. The implementation is well-structured, with changes spanning the schema, rendering, matching, and setup logic. The refactoring of renderer.py into _render_helpers.py is a good move for maintainability. The built-in templates have been updated to leverage this new feature, which is great. I've found a couple of issues: a critical syntax error in exception handling that will break the code, and a minor improvement for exception specificity. After addressing these, the PR should be in excellent shape.

elif isinstance(model_req, dict):
try:
req = parse_model_requirement(model_req)
except ValidationError, ValueError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except syntax is for Python 2. In Python 3, multiple exceptions must be caught as a tuple. This will raise a SyntaxError.

            except (ValidationError, ValueError):

preset = agent.get("personality_preset")
try:
req = resolve_model_requirement(tier, preset)
except ValidationError, ValueError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except syntax is for Python 2. In Python 3, multiple exceptions must be caught as a tuple. This will raise a SyntaxError.

Suggested change
except ValidationError, ValueError:
except (ValidationError, ValueError):

Comment on lines +139 to +140
except Exception as exc:
raise ValueError(str(exc)) from exc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors. It's better to catch the specific exceptions that parse_model_requirement is expected to raise, which are ValueError and pydantic.ValidationError.

Suggested change
except Exception as exc:
raise ValueError(str(exc)) from exc
except (ValidationError, ValueError) as exc:
raise ValueError(str(exc)) from exc

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 23, 2026

Codecov Report

❌ Patch coverage is 90.07634% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.33%. Comparing base (3c76de0) to head (690d63d).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/templates/_render_helpers.py 92.00% 4 Missing and 2 partials ⚠️
src/synthorg/templates/renderer.py 73.33% 4 Missing ⚠️
src/synthorg/templates/model_matcher.py 86.36% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #795      +/-   ##
==========================================
+ Coverage   92.31%   92.33%   +0.02%     
==========================================
  Files         573      574       +1     
  Lines       29729    29798      +69     
  Branches     2881     2889       +8     
==========================================
+ Hits        27443    27513      +70     
+ Misses       1806     1803       -3     
- Partials      480      482       +2     

☔ 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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/synthorg/templates/builtins/consultancy.yaml (1)

86-91: 🧹 Nitpick | 🔵 Trivial

Technical Writer model may warrant structured format per PR objectives.

The PR objectives specify "content writers (quality)" as the intended priority. The Technical Writer here uses the legacy model: "small" string format. Consider updating to a structured format with priority: "quality" for consistency with the stated role-appropriate model assignments:

model:
  tier: "small"
  priority: "quality"

This would align with the full_company.yaml template where the Content Writer uses {tier: "small", priority: "quality"}.

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

In `@src/synthorg/templates/builtins/consultancy.yaml` around lines 86 - 91,
Replace the legacy string model entry for the "Technical Writer" role in the
consultancy template with the structured model object used elsewhere: change
model: "small" to a mapping model: { tier: "small", priority: "quality" } (or
YAML block form), ensuring the role entry for "Technical Writer" still contains
name, level, personality_preset, and department fields; update the existing role
block that begins with role: "Technical Writer" to use model.tier and
model.priority for consistency with full_company.yaml.
🤖 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/templates/_render_helpers.py`:
- Around line 25-107: build_departments is too large; extract logical parts into
small helper functions to stay under 50 lines: create parse_budget_pct(raw_dept)
that uses to_float and raises TemplateRenderError while logging
TEMPLATE_RENDER_TYPE_ERROR (preserve the same error message/fields), create
resolve_head(raw_dept) that returns (head_role, head_merge_id, optional head_id)
and emits TEMPLATE_RENDER_VARIABLE_ERROR warnings as currently done, and create
validate_optional_fields(raw_dept) to check/report/return reporting_lines and
policies (raising TemplateRenderError with TEMPLATE_RENDER_TYPE_ERROR on bad
types). Update build_departments to call these helpers and assemble dept_dict,
keeping existing symbols (build_departments, to_float, logger,
TEMPLATE_RENDER_TYPE_ERROR, TEMPLATE_RENDER_VARIABLE_ERROR, TemplateRenderError)
and preserve all current log messages and exception behavior.
- Around line 37-53: The loop over raw_depts must guard non-mapping items:
before calling dept.get(...) in the for raw_depts loop, check isinstance(dept,
Mapping) (or dict) and if not, log the same TEMPLATE_RENDER_TYPE_ERROR via
logger.warning with department=str(dept) or empty name, field="departments", and
an appropriate error message, then raise TemplateRenderError with a clear
message; keep the existing to_float/ValueError handling for mapping items
unchanged and reference the dept variable, to_float, TEMPLATE_RENDER_TYPE_ERROR,
logger.warning, and TemplateRenderError when implementing the guard.
- Around line 92-105: The code attaches mutable objects by reference into
dept_dict (see dept_dict["reporting_lines"] assignment and dept_dict["policies"]
= policies), which can allow later mutations to leak; fix by importing copy and
using copy.deepcopy(...) when assigning these fields (i.e., deep-copy
reporting_lines and policies before storing them), leaving the existing type
checks and TemplateRenderError behavior intact so the stored values are
independent copies.

In `@src/synthorg/templates/schema.py`:
- Around line 137-141: The current broad except Exception in the validation
wrapper around parse_model_requirement should be narrowed to only the expected
errors: catch ValueError and ValidationError (the latter from the module that
defines it) instead of Exception; update the except block to except (ValueError,
ValidationError) as exc and re-raise ValueError(str(exc)) from exc so unexpected
errors (ImportError/AttributeError) bubble up and the original exception is
preserved; ensure ValidationError is imported or referenced correctly where
parse_model_requirement is used.

---

Outside diff comments:
In `@src/synthorg/templates/builtins/consultancy.yaml`:
- Around line 86-91: Replace the legacy string model entry for the "Technical
Writer" role in the consultancy template with the structured model object used
elsewhere: change model: "small" to a mapping model: { tier: "small", priority:
"quality" } (or YAML block form), ensuring the role entry for "Technical Writer"
still contains name, level, personality_preset, and department fields; update
the existing role block that begins with role: "Technical Writer" to use
model.tier and model.priority for consistency with full_company.yaml.
🪄 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: 767a4a96-301b-4940-881d-636bdd191fa2

📥 Commits

Reviewing files that changed from the base of the PR and between 3c76de0 and e6e242d.

📒 Files selected for processing (18)
  • docs/design/organization.md
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/templates/builtins/agency.yaml
  • src/synthorg/templates/builtins/consultancy.yaml
  • src/synthorg/templates/builtins/data_team.yaml
  • src/synthorg/templates/builtins/dev_shop.yaml
  • src/synthorg/templates/builtins/full_company.yaml
  • src/synthorg/templates/builtins/product_team.yaml
  • src/synthorg/templates/builtins/research_lab.yaml
  • src/synthorg/templates/builtins/solo_founder.yaml
  • src/synthorg/templates/builtins/startup.yaml
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/schema.py
  • tests/unit/templates/test_model_matcher.py
  • tests/unit/templates/test_renderer.py
  • tests/unit/templates/test_schema.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers
All public functions and methods must have type hints; enforce strict mypy mode
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules

Files:

  • tests/unit/templates/test_schema.py
  • tests/unit/templates/test_renderer.py
  • src/synthorg/api/controllers/setup_agents.py
  • tests/unit/templates/test_model_matcher.py
  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/_render_helpers.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers on all tests
Maintain 80% minimum code coverage (enforced in CI)
Use asyncio_mode = 'auto' -- no manual @pytest.mark.asyncio needed on async tests
Global test timeout is 30 seconds per test; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed
ALWAYS run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially
Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use Hypothesis for Python property-based testing with @given + @settings decorators; use profiles: ci (50 examples, default) and dev (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Run Hypothesis dev profile with: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally

Files:

  • tests/unit/templates/test_schema.py
  • tests/unit/templates/test_renderer.py
  • tests/unit/templates/test_model_matcher.py
docs/**

📄 CodeRabbit inference engine (CLAUDE.md)

Run uv run zensical build to build docs (output: _site/docs/); uv run zensical serve for local preview

Files:

  • docs/design/organization.md
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use Markdown for documentation in docs/ directory, built with Zensical (config: mkdocs.yml)

Files:

  • docs/design/organization.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Design spec in docs/design/ (7 pages); use for all architecture, data models, and behavior decisions. Reference with format: docs/design/<page>

Files:

  • docs/design/organization.md
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use from synthorg.observability import get_logger with variable name logger
Never use import logging, logging.getLogger(), or print() in application code except in observability/setup.py and observability/sinks.py
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 with kwargs: logger.info(EVENT, key=value) -- never use old-style string formatting like logger.info('msg %s', val)
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
Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence) for dict/list fields in frozen Pydantic models
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves (e.g., agent execution state, task progress)
Never mix static config fields with mutable runtime fields in one Pydantic model
Use @computed_field for derived values instead of storing + validating redundant fields (e.g., TokenUsage.total_tokens)
Use NotBlankStr from core.types for all identifier/name fields, including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) instead of bare create_task
Keep functions under 50 lines and files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, exter...

Files:

  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/_render_helpers.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Run uv run ruff check src/ tests/ for linting; uv run ruff check src/ tests/ --fix for auto-fix; uv run ruff format src/ tests/ for formatting
Run uv run mypy src/ tests/ for strict type-checking

Files:

  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/_render_helpers.py
🧠 Learnings (17)
📚 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:

  • docs/design/organization.md
  • tests/unit/templates/test_renderer.py
  • src/synthorg/templates/builtins/full_company.yaml
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/templates/builtins/startup.yaml
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to pyproject.toml : Required dependencies: `mem0ai` (default memory backend), `cryptography` (Fernet encryption for sensitive settings), `faker` (multi-locale agent name generation)

Applied to files:

  • docs/design/organization.md
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves (e.g., agent execution state, task progress)

Applied to files:

  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/schema.py
  • src/synthorg/templates/builtins/product_team.yaml
📚 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/templates/schema.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/templates/schema.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/templates/schema.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/templates/schema.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/templates/schema.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

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

Applied to files:

  • src/synthorg/templates/builtins/full_company.yaml
  • src/synthorg/templates/builtins/product_team.yaml
  • src/synthorg/templates/builtins/agency.yaml
  • src/synthorg/templates/builtins/startup.yaml
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: `example-provider`, `example-large-001`, `example-medium-001`, `example-small-001`, `large`/`medium`/`small` as aliases. Vendor names may only appear in: (1) Operations design page (`docs/design/operations.md`), (2) `.claude/` skill/agent files, (3) third-party import paths/module names

Applied to files:

  • src/synthorg/templates/builtins/full_company.yaml
📚 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:

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

Applied to files:

  • src/synthorg/templates/builtins/startup.yaml
🧬 Code graph analysis (7)
tests/unit/templates/test_schema.py (1)
src/synthorg/templates/schema.py (1)
  • TemplateAgentConfig (86-175)
src/synthorg/api/controllers/setup_agents.py (1)
src/synthorg/templates/model_requirements.py (1)
  • parse_model_requirement (62-107)
tests/unit/templates/test_model_matcher.py (2)
src/synthorg/templates/model_matcher.py (1)
  • match_all_agents (95-232)
src/synthorg/templates/model_requirements.py (1)
  • ModelRequirement (30-59)
src/synthorg/templates/schema.py (1)
src/synthorg/templates/model_requirements.py (1)
  • parse_model_requirement (62-107)
src/synthorg/templates/model_matcher.py (1)
src/synthorg/templates/model_requirements.py (3)
  • ModelRequirement (30-59)
  • parse_model_requirement (62-107)
  • resolve_model_requirement (159-194)
src/synthorg/templates/renderer.py (3)
src/synthorg/templates/_render_helpers.py (2)
  • build_departments (25-107)
  • validate_as_root_config (110-151)
src/synthorg/config/schema.py (1)
  • RootConfig (470-739)
src/synthorg/templates/model_requirements.py (1)
  • parse_model_requirement (62-107)
src/synthorg/templates/_render_helpers.py (1)
src/synthorg/config/schema.py (1)
  • RootConfig (470-739)
🔇 Additional comments (30)
src/synthorg/templates/builtins/data_team.yaml (1)

87-89: LGTM!

The structured model requirement for the mid-level Backend Developer correctly uses priority: "cost", aligning with the PR objective to assign cost priority to mid-level developers.

src/synthorg/templates/builtins/solo_founder.yaml (1)

47-50: LGTM!

The CEO agent correctly receives priority: "quality" and min_context: 100000, consistent with the PR objective for leader roles requiring high-quality responses and large context windows.

src/synthorg/templates/builtins/research_lab.yaml (1)

67-70: LGTM!

The Software Architect correctly receives priority: "quality" and min_context: 100000, aligning with the PR objective for architects who need comprehensive context for system-level decisions.

src/synthorg/templates/builtins/product_team.yaml (2)

77-79: LGTM!

The Product Manager correctly receives priority: "speed", aligning with the PR objective for PMs who need fast iteration cycles.


105-107: LGTM!

Both mid-level developers (Backend and Frontend) correctly receive priority: "cost", consistent with the PR objective for cost-optimized assignments to mid-level roles.

Also applies to: 113-115

tests/unit/templates/test_renderer.py (4)

152-250: LGTM!

The new TestRenderTemplateStructuredModel class provides comprehensive coverage for the structured model requirement feature:

  • test_dict_model_extracts_tier: Verifies tier extraction from structured dict
  • test_string_model_still_works: Ensures backward compatibility
  • test_mixed_string_and_dict_models: Validates mixed-format agent lists

All tests use proper @pytest.mark.unit markers and the tmp_template_file fixture pattern.


341-367: LGTM!

Import path updates correctly reflect the refactor moving build_departments and validate_as_root_config to _render_helpers.py.


420-453: LGTM!

Import path updates for build_departments in the passthrough tests are consistent with the helper extraction refactor.


521-538: LGTM!

Import path updates for build_departments in the type validation tests are consistent with the refactor.

docs/design/organization.md (1)

196-237: LGTM!

The documentation update clearly explains the new structured model requirement feature with:

  • Inline comment explaining both formats (lines 196-198)
  • Examples of structured models for CEO/CTO (quality + 100k context)
  • Example of legacy string format for backward compatibility (line 220)
  • Role-appropriate examples: mid-level dev with cost priority, PM with speed priority

This provides clear guidance for template authors.

src/synthorg/templates/builtins/dev_shop.yaml (3)

67-70: LGTM!

The Software Architect correctly receives priority: "quality" and min_context: 100000, consistent with the PR objective for architects.


84-86: LGTM!

Both mid-level developers correctly receive priority: "cost", aligned with the PR objective for cost-optimized mid-level roles.

Also applies to: 92-94


112-114: LGTM!

The DevOps/SRE Engineer correctly receives priority: "speed", matching the PR objective for SRE roles that prioritize fast response times.

tests/unit/templates/test_schema.py (2)

117-154: LGTM!

Comprehensive test coverage for the new dict-form model validation:

  • Validates acceptance of full and minimal dict formats
  • Tests rejection of invalid tiers, priorities, and extra fields
  • Covers both dict and string validation paths

148-150: This behavior is intentional—empty dict is accepted by design.

The test suite explicitly validates this pattern. An empty dict {} is accepted and stored as-is in TemplateAgentConfig, and ModelRequirement applies defaults (tier="medium", priority="balanced", min_context=0, capabilities=()) only when the dict is parsed during rendering. This lazy-evaluation approach allows flexible configuration loading without requiring validation at the schema level.

src/synthorg/templates/builtins/consultancy.yaml (1)

58-61: LGTM!

The structured model configurations for Software Architect and Project Manager are well-aligned with their roles:

  • Principal-level architect gets quality priority with 100k context for complex architectural decisions
  • Project Manager gets speed priority for coordination efficiency

Also applies to: 81-83

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

93-118: LGTM!

The structured model requirement handling is well-implemented:

  • Correctly distinguishes between dict and string formats
  • Parses dict format via parse_model_requirement to extract the tier
  • Conditionally persists model_requirement only when a structured dict was provided
  • Maintains backward compatibility with legacy string tiers

278-284: LGTM!

The exception handling correctly uses PEP 758 syntax and properly re-raises critical errors (MemoryError, RecursionError) while handling the expected SettingNotFoundError case gracefully.

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

143-174: LGTM!

The three-path model requirement resolution is well-structured:

  1. Direct ModelRequirement passthrough for pre-parsed objects
  2. Dict parsing via parse_model_requirement with graceful skip on invalid input
  3. Fallback to legacy tier + personality_preset path via resolve_model_requirement

Error handling correctly logs warnings and skips invalid agents rather than failing the entire batch.


110-118: LGTM!

The docstring clearly documents the three-path resolution order and is consistent with the implementation.

src/synthorg/templates/builtins/full_company.yaml (1)

129-168: LGTM!

The structured model configurations align well with the PR objectives:

  • C-suite and Software Architect: quality priority with 100k context for strategic decisions
  • DevOps/SRE, Project Manager, Scrum Master: speed priority for operational efficiency
  • Mid-level developers: cost priority for routine implementation work
  • Content Writer: quality priority despite small tier for better output quality

Also applies to: 175-177, 203-214, 234-236, 252-254, 295-305, 325-327

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

97-99: LGTM!

The field type and docstring updates correctly document the dual-format support while maintaining backward compatibility with the default "medium" tier.

Also applies to: 121-124

tests/unit/templates/test_model_matcher.py (2)

273-338: LGTM!

Comprehensive test coverage for the new model_requirement resolution paths:

  • Dict parsing and usage
  • Invalid dict graceful skip
  • min_context filtering with fallback
  • Structured requirement overriding personality-based affinity
  • Direct ModelRequirement object acceptance

340-367: LGTM!

The tests correctly verify both the new ModelRequirement object path and backward compatibility with the legacy tier + personality_preset path.

src/synthorg/templates/renderer.py (3)

41-44: LGTM!

Clean refactoring that extracts build_departments and validate_as_root_config to the helper module while maintaining the same behavior. The TYPE_CHECKING import for RootConfig is appropriate since it's only used in type annotations.

Also applies to: 108-108, 471-471


652-653: LGTM!

The change to use _resolve_model_tier(agent) enables extraction of the tier from both legacy string formats and new structured dict formats while maintaining backward compatibility.


679-699: The current implementation is correct as-is. Invalid model values are already caught and rejected during TemplateAgentConfig schema instantiation by the _validate_model field validator, which explicitly calls parse_model_requirement() and converts all exceptions to ValueError. By the time _resolve_model_tier executes in the renderer, the model field has already been validated, so no additional error handling is needed.

			> Likely an incorrect or invalid review comment.
src/synthorg/templates/builtins/startup.yaml (1)

63-66: LGTM!

The structured model configurations for the startup template align with the PR objectives:

  • C-suite (CEO, CTO): quality priority with 100k context for strategic leadership
  • Mid-level Full-Stack Developer: cost priority for routine work
  • Product Manager: speed priority for coordination efficiency

The senior Full-Stack Developer retaining the legacy string format demonstrates backward compatibility.

Also applies to: 72-75, 89-91, 97-99

src/synthorg/templates/builtins/agency.yaml (1)

82-84: Structured model mappings are coherent and role-appropriate.

These updates align with the new ModelRequirement shape and keep priority intent explicit per role without breaking mixed-format template compatibility.

Also applies to: 90-92, 98-100, 118-120, 145-147, 160-162

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

110-151: Validation error translation and re-raise path looks solid.

The conversion from Pydantic errors to ConfigLocation/field errors is clear and preserves debuggability.

…, 19 items)

- Narrow except Exception to (ValueError, ValidationError) in schema validator
- Decompose build_departments into sub-50-line helpers with non-dict guard
- Extract _resolve_agent_requirement from match_all_agents (137->80 lines)
- Wrap _resolve_model_tier error as TemplateRenderError (exception contract)
- Add copy.deepcopy for reporting_lines/policies at system boundary
- Add error=str(exc) to model_matcher warning logs for debuggability
- Fix docstrings: "legacy"->"plain" in renderer, add model_requirement to
  setup_agents Returns, clarify capabilities as optional in org design spec
- Update consultancy.yaml Technical Writer to structured model (quality)
- Strengthen test assertions: add model_id checks, use YAML-realistic lists
- Add 4 tests for expand_template_agents dict-model branch (was untested)
- Add model_matcher + model_requirements to docs/api/templates.md
- Fix "cost-based tier classification" -> priority-aware in organization.md
- Add model_requirement to agents.md Identity Card example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

🤖 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/templates/model_matcher.py`:
- Around line 95-139: The two exception handlers inside
_resolve_agent_requirement currently use parentheses around the exception tuple
(e.g., except (ValidationError, ValueError) as exc:); update both to the PEP 758
style by removing the parentheses so they read except ValidationError,
ValueError as exc:, leaving the rest of the handler bodies unchanged and
preserving the logger.warning calls and return None behavior.

In `@src/synthorg/templates/renderer.py`:
- Around line 705-714: Update the exception handler in the block that calls
parse_model_requirement so it uses the PEP 758-style except syntax without
parentheses (i.e., except ValidationError, ValueError:) instead of except
(ValidationError, ValueError) as exc:, keeping the same logging via
logger.warning(TEMPLATE_RENDER_TYPE_ERROR, field="model", error=str(exc)) and
re-raising the TemplateRenderError(msg) from exc; leave parse_model_requirement,
TEMPLATE_RENDER_TYPE_ERROR, logger, msg, and TemplateRenderError unchanged.

In `@src/synthorg/templates/schema.py`:
- Around line 144-148: The except clause around parse_model_requirement
currently uses a parenthesized tuple of exceptions; update the handler to use
PEP 758 style by listing the exception types without parentheses and binding the
caught exception to the same name (exc) so that ValueError and ValidationError
are caught using the new syntax and then re-raised as before; adjust the except
line that references ValueError and ValidationError in the block containing
parse_model_requirement(value) to the PEP 758 form and leave the raise
ValueError(str(exc)) from exc unchanged.
🪄 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: 3fca7c1a-8ea1-4708-8ac1-01a3809ba41f

📥 Commits

Reviewing files that changed from the base of the PR and between e6e242d and 690d63d.

📒 Files selected for processing (11)
  • docs/api/templates.md
  • docs/design/agents.md
  • docs/design/organization.md
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/templates/builtins/consultancy.yaml
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/schema.py
  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/templates/test_model_matcher.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
docs/**

📄 CodeRabbit inference engine (CLAUDE.md)

Run uv run zensical build to build docs (output: _site/docs/); uv run zensical serve for local preview

Files:

  • docs/api/templates.md
  • docs/design/agents.md
  • docs/design/organization.md
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use Markdown for documentation in docs/ directory, built with Zensical (config: mkdocs.yml)

Files:

  • docs/api/templates.md
  • docs/design/agents.md
  • docs/design/organization.md
docs/api/**

📄 CodeRabbit inference engine (CLAUDE.md)

Library reference auto-generated via mkdocstrings + Griffe (AST-based) in docs/api/

Files:

  • docs/api/templates.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Design spec in docs/design/ (7 pages); use for all architecture, data models, and behavior decisions. Reference with format: docs/design/<page>

Files:

  • docs/design/agents.md
  • docs/design/organization.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers
All public functions and methods must have type hints; enforce strict mypy mode
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules

Files:

  • tests/unit/api/controllers/test_setup_agents.py
  • src/synthorg/templates/schema.py
  • tests/unit/templates/test_model_matcher.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/renderer.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers on all tests
Maintain 80% minimum code coverage (enforced in CI)
Use asyncio_mode = 'auto' -- no manual @pytest.mark.asyncio needed on async tests
Global test timeout is 30 seconds per test; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed
ALWAYS run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially
Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use Hypothesis for Python property-based testing with @given + @settings decorators; use profiles: ci (50 examples, default) and dev (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Run Hypothesis dev profile with: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally

Files:

  • tests/unit/api/controllers/test_setup_agents.py
  • tests/unit/templates/test_model_matcher.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use from synthorg.observability import get_logger with variable name logger
Never use import logging, logging.getLogger(), or print() in application code except in observability/setup.py and observability/sinks.py
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 with kwargs: logger.info(EVENT, key=value) -- never use old-style string formatting like logger.info('msg %s', val)
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
Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence) for dict/list fields in frozen Pydantic models
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves (e.g., agent execution state, task progress)
Never mix static config fields with mutable runtime fields in one Pydantic model
Use @computed_field for derived values instead of storing + validating redundant fields (e.g., TokenUsage.total_tokens)
Use NotBlankStr from core.types for all identifier/name fields, including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) instead of bare create_task
Keep functions under 50 lines and files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, exter...

Files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/renderer.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Run uv run ruff check src/ tests/ for linting; uv run ruff check src/ tests/ --fix for auto-fix; uv run ruff format src/ tests/ for formatting
Run uv run mypy src/ tests/ for strict type-checking

Files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/api/controllers/setup_agents.py
  • src/synthorg/templates/renderer.py
🧠 Learnings (37)
📚 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:

  • docs/api/templates.md
  • docs/design/organization.md
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/templates/renderer.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:

  • docs/api/templates.md
  • src/synthorg/templates/schema.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:

  • docs/api/templates.md
  • src/synthorg/templates/schema.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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/organization.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 docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/organization.md
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to pyproject.toml : Required dependencies: `mem0ai` (default memory backend), `cryptography` (Fernet encryption for sensitive settings), `faker` (multi-locale agent name generation)

Applied to files:

  • docs/design/organization.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : 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/templates/schema.py
  • src/synthorg/templates/renderer.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/templates/schema.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves (e.g., agent execution state, task progress)

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/api/controllers/setup_agents.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/templates/schema.py
  • src/synthorg/templates/_render_helpers.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/templates/schema.py
  • src/synthorg/templates/_render_helpers.py
  • src/synthorg/templates/renderer.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • src/synthorg/templates/schema.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/templates/schema.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/templates/schema.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/templates/schema.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/templates/schema.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to **/*.py : Use `except A, B:` syntax without parentheses (PEP 758 except syntax) in exception handlers

Applied to files:

  • src/synthorg/templates/schema.py
  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

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

Applied to files:

  • src/synthorg/templates/builtins/consultancy.yaml
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/templates/model_matcher.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Keep functions under 50 lines and files under 800 lines

Applied to files:

  • src/synthorg/templates/_render_helpers.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Functions must be less than 50 lines; files must be less than 800 lines

Applied to files:

  • src/synthorg/templates/_render_helpers.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/templates/_render_helpers.py
📚 Learning: 2026-03-17T13:48:18.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T13:48:18.114Z
Learning: Applies to **/*.py : Functions: < 50 lines, files < 800 lines.

Applied to files:

  • src/synthorg/templates/_render_helpers.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence) for `dict`/`list` fields in frozen Pydantic models

Applied to files:

  • src/synthorg/templates/_render_helpers.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/templates/_render_helpers.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/templates/_render_helpers.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.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/templates/_render_helpers.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 **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/templates/_render_helpers.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 **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; 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/templates/_render_helpers.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
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/templates/_render_helpers.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 : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/templates/renderer.py
🧬 Code graph analysis (7)
tests/unit/api/controllers/test_setup_agents.py (3)
src/synthorg/api/controllers/setup_agents.py (1)
  • expand_template_agents (40-122)
src/synthorg/core/enums.py (1)
  • CompanyType (148-160)
src/synthorg/templates/schema.py (3)
  • CompanyTemplate (298-430)
  • TemplateAgentConfig (93-182)
  • TemplateMetadata (241-295)
src/synthorg/templates/schema.py (1)
src/synthorg/templates/model_requirements.py (1)
  • parse_model_requirement (62-107)
tests/unit/templates/test_model_matcher.py (2)
src/synthorg/templates/model_matcher.py (1)
  • match_all_agents (142-258)
src/synthorg/templates/model_requirements.py (1)
  • ModelRequirement (30-59)
src/synthorg/templates/model_matcher.py (1)
src/synthorg/templates/model_requirements.py (3)
  • ModelRequirement (30-59)
  • parse_model_requirement (62-107)
  • resolve_model_requirement (159-194)
src/synthorg/templates/_render_helpers.py (1)
src/synthorg/config/schema.py (1)
  • RootConfig (470-739)
src/synthorg/api/controllers/setup_agents.py (1)
src/synthorg/templates/model_requirements.py (1)
  • parse_model_requirement (62-107)
src/synthorg/templates/renderer.py (2)
src/synthorg/templates/_render_helpers.py (2)
  • build_departments (128-167)
  • validate_as_root_config (173-214)
src/synthorg/templates/model_requirements.py (1)
  • parse_model_requirement (62-107)
🪛 markdownlint-cli2 (0.21.0)
docs/design/organization.md

[warning] 236-236: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🔇 Additional comments (25)
src/synthorg/templates/schema.py (1)

128-131: LGTM!

The field type union NotBlankStr | dict[str, Any] with default "medium" correctly supports both legacy string tiers and structured model requirements while maintaining backward compatibility.

src/synthorg/templates/_render_helpers.py (3)

143-152: LGTM!

Good defensive validation: the non-mapping guard at the loop boundary properly validates each department entry before accessing dict methods, preventing AttributeError and ensuring clear error messages via TemplateRenderError.


111-125: LGTM!

Proper use of copy.deepcopy() at system boundaries for mutable reporting_lines and policies fields, preventing mutation leakage into config construction paths.


189-214: LGTM!

The validate_as_root_config function correctly handles ValidationError, converts errors to ConfigLocation entries with proper key paths, and raises TemplateValidationError with full context for downstream handling.

docs/design/agents.md (1)

114-118: LGTM!

Good documentation addition showing the new model_requirement structure with all fields (tier, priority, min_context, capabilities). The placement alongside the existing model section clearly illustrates how templates can express structured requirements.

docs/api/templates.md (1)

30-36: LGTM!

Properly exposes the new model_requirements and model_matcher modules in the API reference, following the existing documentation pattern.

docs/design/organization.md (2)

196-238: LGTM!

Excellent documentation update showing both model formats side-by-side with clear comments. The examples demonstrate structured dicts for various priority profiles (quality for leaders, speed for PM, cost for mid-level) and the legacy string alias for backward compatibility.


334-335: LGTM!

Company Builder description correctly updated to reflect that tier classification now respects the agent's priority axis (quality, speed, cost, or balanced).

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

95-119: LGTM!

Clean implementation of the dual-format handling: dict models are parsed via parse_model_requirement to extract tier and persist model_requirement, while string models are used directly as tier without the extra field. The conditional model_requirement inclusion correctly distinguishes the two code paths.


56-58: LGTM!

Docstring accurately reflects the new behavior: model_requirement dict is conditionally included only when templates use structured model requirements.

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

28-102: LGTM!

Comprehensive test coverage for the new dict-model handling in expand_template_agents. Tests properly verify:

  • Dict model → model_requirement populated with correct tier/priority/min_context
  • String model → no model_requirement key present
  • Mixed templates → only dict-mode agents receive model_requirement
  • Empty dict → defaults to tier="medium", priority="balanced"
src/synthorg/templates/builtins/consultancy.yaml (1)

58-92: LGTM!

Role-appropriate structured model assignments:

  • Software Architect (principal): quality priority + 100k context for architectural decisions
  • Project Manager (senior): speed priority for responsive client communication
  • Technical Writer (mid): quality priority for accurate documentation

The Backend Developers correctly retain string "medium" as senior-level roles not requiring explicit priority overrides.

tests/unit/templates/test_model_matcher.py (6)

273-290: LGTM!

The test correctly validates that a serialized ModelRequirement dict bypasses the resolve_model_requirement path and uses the structured requirement directly. The assertion verifies both the tier and the model selection based on priority: "quality".


292-301: LGTM!

The test correctly validates that malformed model_requirement dicts (with invalid tier values) cause the agent to be skipped, resulting in an empty results list. This aligns with the _resolve_agent_requirement behavior that returns None on validation errors.


303-318: LGTM!

The test verifies that min_context filtering works correctly through the structured ModelRequirement path. When no model meets the context requirement, the fallback behavior correctly assigns a model with score=0.0.


320-339: LGTM!

This test is critical for verifying that explicit structured requirements override personality-preset affinity defaults. The test correctly shows that priority: "cost" in the dict takes precedence over visionary_leader's quality affinity.


341-353: LGTM!

The test validates that ModelRequirement objects (not just dicts) are accepted directly, covering the first branch of _resolve_agent_requirement. The assertion on tier and model_id confirms correct handling.


355-369: LGTM!

This test confirms backward compatibility by verifying that the legacy tier + personality_preset path still works when model_requirement is absent. The visionary_leader affinity correctly drives selection of the expensive model.

src/synthorg/templates/renderer.py (5)

41-44: LGTM!

Good refactoring to extract build_departments and validate_as_root_config into _render_helpers.py. This improves modularity and reduces file size as stated in the PR objectives.


64-65: LGTM!

Moving RootConfig import under TYPE_CHECKING is appropriate since runtime validation now occurs via validate_as_root_config helper that handles the import internally.


108-108: LGTM!

Delegating to validate_as_root_config from the helper module centralizes validation logic and keeps the renderer focused on its core responsibility.


471-471: LGTM!

Using the extracted build_departments helper maintains the same functionality while improving code organization.


652-653: LGTM!

The call to _resolve_model_tier properly extracts the tier from both string and structured dict formats, maintaining backward compatibility while supporting the new structured model requirements.

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

157-165: LGTM!

The updated docstring clearly documents the three-path resolution order for model requirements, which is essential for understanding the priority: ModelRequirement object → dict → tier+preset fallback.


177-200: LGTM!

The refactored resolution flow cleanly delegates to _resolve_agent_requirement, improving readability and testability. The unpacking of (req, tier) from the resolved result is straightforward.

Comment on lines +95 to +139
def _resolve_agent_requirement(
agent: dict[str, Any],
idx: int,
model_requirement_cls: type,
parse_fn: Any,
resolve_fn: Any,
) -> tuple[Any, ModelTier] | None:
"""Resolve a single agent's model requirement.

Returns:
``(requirement, tier)`` on success, or ``None`` if the agent
should be skipped (invalid requirement logged as warning).
"""
model_req = agent.get("model_requirement")
if isinstance(model_req, model_requirement_cls):
return model_req, model_req.tier # type: ignore[attr-defined]

if isinstance(model_req, dict):
try:
req = parse_fn(model_req)
except (ValidationError, ValueError) as exc:
logger.warning(
TEMPLATE_MODEL_MATCH_SKIPPED,
agent_index=idx,
reason="invalid_model_requirement_dict",
error=str(exc),
)
return None
return req, req.tier

tier: ModelTier = agent.get("tier", "medium")
preset = agent.get("personality_preset")
try:
req = resolve_fn(tier, preset)
except (ValidationError, ValueError) as exc:
logger.warning(
TEMPLATE_MODEL_MATCH_SKIPPED,
agent_index=idx,
tier=tier,
preset=preset,
reason="invalid_requirement",
error=str(exc),
)
return None
return req, tier
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use PEP 758 except syntax without parentheses.

Both exception handlers at lines 115 and 129 use parentheses around ValidationError, ValueError, which violates PEP 758 syntax requirements.

Proposed fix
     if isinstance(model_req, dict):
         try:
             req = parse_fn(model_req)
-        except (ValidationError, ValueError) as exc:
+        except ValidationError, ValueError as exc:
             logger.warning(
                 TEMPLATE_MODEL_MATCH_SKIPPED,
                 agent_index=idx,
                 reason="invalid_model_requirement_dict",
                 error=str(exc),
             )
             return None
         return req, req.tier

     tier: ModelTier = agent.get("tier", "medium")
     preset = agent.get("personality_preset")
     try:
         req = resolve_fn(tier, preset)
-    except (ValidationError, ValueError) as exc:
+    except ValidationError, ValueError as exc:
         logger.warning(
             TEMPLATE_MODEL_MATCH_SKIPPED,
             agent_index=idx,
             tier=tier,
             preset=preset,
             reason="invalid_requirement",
             error=str(exc),
         )
         return None

As per coding guidelines: "Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers".

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

In `@src/synthorg/templates/model_matcher.py` around lines 95 - 139, The two
exception handlers inside _resolve_agent_requirement currently use parentheses
around the exception tuple (e.g., except (ValidationError, ValueError) as exc:);
update both to the PEP 758 style by removing the parentheses so they read except
ValidationError, ValueError as exc:, leaving the rest of the handler bodies
unchanged and preserving the logger.warning calls and return None behavior.

Comment on lines +705 to +714
try:
return parse_model_requirement(model_raw).tier
except (ValidationError, ValueError) as exc:
msg = f"Invalid structured model requirement: {exc}"
logger.warning(
TEMPLATE_RENDER_TYPE_ERROR,
field="model",
error=str(exc),
)
raise TemplateRenderError(msg) from exc
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use PEP 758 except syntax without parentheses.

The exception handler uses parentheses around ValidationError, ValueError, but coding guidelines require the PEP 758 syntax without parentheses.

Proposed fix
         try:
             return parse_model_requirement(model_raw).tier
-        except (ValidationError, ValueError) as exc:
+        except ValidationError, ValueError as exc:
             msg = f"Invalid structured model requirement: {exc}"
             logger.warning(
                 TEMPLATE_RENDER_TYPE_ERROR,
                 field="model",
                 error=str(exc),
             )
             raise TemplateRenderError(msg) from exc

As per coding guidelines: "Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
return parse_model_requirement(model_raw).tier
except (ValidationError, ValueError) as exc:
msg = f"Invalid structured model requirement: {exc}"
logger.warning(
TEMPLATE_RENDER_TYPE_ERROR,
field="model",
error=str(exc),
)
raise TemplateRenderError(msg) from exc
try:
return parse_model_requirement(model_raw).tier
except ValidationError, ValueError as exc:
msg = f"Invalid structured model requirement: {exc}"
logger.warning(
TEMPLATE_RENDER_TYPE_ERROR,
field="model",
error=str(exc),
)
raise TemplateRenderError(msg) from exc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/templates/renderer.py` around lines 705 - 714, Update the
exception handler in the block that calls parse_model_requirement so it uses the
PEP 758-style except syntax without parentheses (i.e., except ValidationError,
ValueError:) instead of except (ValidationError, ValueError) as exc:, keeping
the same logging via logger.warning(TEMPLATE_RENDER_TYPE_ERROR, field="model",
error=str(exc)) and re-raising the TemplateRenderError(msg) from exc; leave
parse_model_requirement, TEMPLATE_RENDER_TYPE_ERROR, logger, msg, and
TemplateRenderError unchanged.

Comment on lines +144 to +148
try:
parse_model_requirement(value)
except (ValueError, ValidationError) as exc:
raise ValueError(str(exc)) from exc
return value
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use PEP 758 exception syntax without parentheses.

The coding guidelines require except A, B: syntax (no parentheses) for Python 3.14 with PEP 758. The current parenthesized form will fail ruff enforcement.

🔧 Proposed fix
         try:
             parse_model_requirement(value)
-        except (ValueError, ValidationError) as exc:
+        except ValueError, ValidationError as exc:
             raise ValueError(str(exc)) from exc

As per coding guidelines: "Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
parse_model_requirement(value)
except (ValueError, ValidationError) as exc:
raise ValueError(str(exc)) from exc
return value
try:
parse_model_requirement(value)
except ValueError, ValidationError as exc:
raise ValueError(str(exc)) from exc
return value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/templates/schema.py` around lines 144 - 148, The except clause
around parse_model_requirement currently uses a parenthesized tuple of
exceptions; update the handler to use PEP 758 style by listing the exception
types without parentheses and binding the caught exception to the same name
(exc) so that ValueError and ValidationError are caught using the new syntax and
then re-raised as before; adjust the except line that references ValueError and
ValidationError in the block containing parse_model_requirement(value) to the
PEP 758 form and leave the raise ValueError(str(exc)) from exc unchanged.

@Aureliolo Aureliolo merged commit 7433548 into main Mar 23, 2026
32 of 33 checks passed
@Aureliolo Aureliolo deleted the feat/implement-722-modelreq branch March 23, 2026 20:54
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 23, 2026 20:54 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

---
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: structured ModelRequirement in template agent configs

1 participant