feat: add workflow configs to builtin templates#963
Conversation
WalkthroughAdds optional Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a workflow_config section to organization templates, allowing for the definition of Kanban and Sprint sub-configurations such as WIP limits, sprint durations, and ceremonies. The changes include updates to the design documentation, the CompanyTemplate schema, and all builtin templates to support these new settings via template variables. Additionally, the template renderer was updated with a new _build_workflow_dict helper to map these configurations into the root WorkflowConfig, supported by a comprehensive new integration test suite. I have no feedback to provide.
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/builtins/agency.yaml`:
- Line 185: The Jinja expression for the limit variable has extraneous spaces
inside the curly braces causing YAMLlint failures; edit the template line
containing "limit: {{ wip_limit | default(3) }}" and remove the extra inner
spaces so the expression uses standard Jinja spacing (e.g., "{{wip_limit |
default(3)}}" or follow your project's Jinja spacing convention) ensuring the
surrounding YAML indentation remains unchanged.
In `@src/synthorg/templates/builtins/consultancy.yaml`:
- Line 106: The Jinja expression in the "limit" value has spacing that triggers
YAMLlint; update the template expression used in the limit key (the "{{
wip_limit | default(2) }}" value) to the project's normalized brace spacing
(remove interior spaces and around the filter) so it becomes
"{{wip_limit|default(2)}}" (or whatever project-wide no-space Jinja style is
used) to satisfy the linter.
In `@src/synthorg/templates/builtins/solo_founder.yaml`:
- Line 71: In solo_founder.yaml, adjust the Jinja expression for wip_limit to
remove the extra spaces directly inside the braces so it satisfies YAMLlint;
replace "limit: {{ wip_limit | default(3) }}" with a compact form like "limit:
{{wip_limit | default(3)}}" (keeping the filter/pipeline spacing as needed) so
the template variable wip_limit | default(3) is preserved but no spaces appear
immediately after "{{" or before "}}".
In `@src/synthorg/templates/renderer.py`:
- Around line 470-472: The loop that assigns sub-configs by reference (for key
in ("kanban", "sprint"): if key in wf_config: workflow_dict[key] =
wf_config[key]) must deep-copy the payloads to avoid mutable aliasing across
stages; update the assignment to use copy.deepcopy(wf_config[key]) and add an
import for the copy module at the top of renderer.py so that kanban and sprint
inserted into workflow_dict are new objects rather than references to wf_config
entries.
🪄 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: 26338eb7-9278-4730-bab6-e0e7cf0527ef
📒 Files selected for processing (16)
docs/design/engine.mddocs/design/organization.mdsrc/synthorg/templates/builtins/agency.yamlsrc/synthorg/templates/builtins/consultancy.yamlsrc/synthorg/templates/builtins/data_team.yamlsrc/synthorg/templates/builtins/dev_shop.yamlsrc/synthorg/templates/builtins/full_company.yamlsrc/synthorg/templates/builtins/product_team.yamlsrc/synthorg/templates/builtins/research_lab.yamlsrc/synthorg/templates/builtins/solo_founder.yamlsrc/synthorg/templates/builtins/startup.yamlsrc/synthorg/templates/loader.pysrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.pytests/unit/templates/test_renderer.pytests/unit/templates/test_workflow_config_integration.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Agent
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) -- ruff enforces this on Python 3.14
Type hints: all public functions must have type hints, mypy strict mode
Google-style docstrings required on public classes and functions (enforced by ruff D rules)
Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention andcopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing + validating redundant fields (e.g.TokenUsage.total_tokens)
UseNotBlankStr(fromcore.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff enforced)
Functions must be < 50 lines, files must be < 800 lines
Handle errors explicitly, never silently swallow them
Validate at system boundaries (user input, external APIs, config files)
Every module wit...
Files:
src/synthorg/templates/loader.pysrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.py
{src,tests}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Vendor-agnostic everywhere: 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/smallas aliases
Files:
src/synthorg/templates/loader.pytests/unit/templates/test_renderer.pysrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.pytests/unit/templates/test_workflow_config_integration.py
docs/design/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
When approved deviations from the design spec occur, update the relevant
docs/design/page to reflect the new reality
Files:
docs/design/engine.mddocs/design/organization.md
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Docs:
docs/(Markdown, built with Zensical, config:mkdocs.yml). Design spec:docs/design/(11 pages), Architecture:docs/architecture/, Roadmap:docs/roadmap/
Files:
docs/design/engine.mddocs/design/organization.md
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unitfor unit tests,@pytest.mark.integrationfor integration tests,@pytest.mark.e2efor e2e tests,@pytest.mark.slowfor slow tests
Coverage: 80% minimum (enforced in CI)
Async:asyncio_mode = "auto"-- no manual@pytest.mark.asyncioneeded
Timeout: 30 seconds per test (global inpyproject.toml-- do not add per-filepytest.mark.timeout(30)markers; non-default overrides liketimeout(60)are allowed)
Parametrize: Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given+@settings). Profiles:ci(50 examples, default) anddev(1000 examples), controlled viaHYPOTHESIS_PROFILEenv var. Run dev profile:HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), useasyncio.Event().wait()instead ofasyncio.sleep(large_number)-- it is cancellation-safe
Files:
tests/unit/templates/test_renderer.pytests/unit/templates/test_workflow_config_integration.py
🧠 Learnings (2)
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.
Applied to files:
src/synthorg/templates/loader.pysrc/synthorg/templates/schema.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality
Applied to files:
docs/design/engine.md
🪛 LanguageTool
docs/design/organization.md
[style] ~371-~371: Consider using “who” when you are referring to a person instead of an object.
Context: ... (child keys override parent). A child that sets only kanban inherits the p...
(THAT_WHO)
🪛 YAMLlint (1.38.0)
src/synthorg/templates/builtins/solo_founder.yaml
[error] 71-71: too many spaces inside braces
(braces)
[error] 71-71: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/consultancy.yaml
[error] 106-106: too many spaces inside braces
(braces)
[error] 106-106: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/product_team.yaml
[error] 166-166: too many spaces inside braces
(braces)
[error] 166-166: too many spaces inside braces
(braces)
[error] 171-171: too many spaces inside braces
(braces)
[error] 171-171: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/agency.yaml
[error] 185-185: too many spaces inside braces
(braces)
[error] 185-185: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/dev_shop.yaml
[error] 148-148: too many spaces inside braces
(braces)
[error] 148-148: too many spaces inside braces
(braces)
[error] 153-153: too many spaces inside braces
(braces)
[error] 153-153: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/research_lab.yaml
[error] 123-123: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/data_team.yaml
[error] 110-110: too many spaces inside braces
(braces)
[error] 110-110: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/startup.yaml
[error] 118-118: too many spaces inside braces
(braces)
[error] 118-118: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
🔇 Additional comments (24)
src/synthorg/templates/schema.py (1)
342-348: Looks good —workflow_configis modeled safely with a backward-compatible default.This cleanly extends
CompanyTemplatewithout breaking templates that omit workflow sub-config.src/synthorg/templates/loader.py (1)
491-491: Good normalization update.Including
workflow_configat normalization time keeps Pass 1/Pass 2 data shape consistent.tests/unit/templates/test_renderer.py (1)
47-59: Great coverage improvement for workflow rendering.These assertions directly protect the
WorkflowConfigintegration path and the workflow type mapping.docs/design/engine.md (1)
172-176: Doc update is aligned and clear.This addition documents the new template-to-
WorkflowConfigmapping and variable override behavior well.src/synthorg/templates/builtins/full_company.yaml (2)
41-48: LGTM!New
sprint_lengthandwip_limitvariables are well-documented with appropriate types and sensible defaults for an enterprise template (14-day sprints, WIP limit of 5).
367-397: LGTM!The
workflow_configblock is comprehensive and well-suited for an enterprise template:
- Kanban: WIP limits with enforcement enabled
- Sprint: Full ceremony suite (planning, standup, review, retrospective) with appropriate protocols, frequencies, and token budgets
velocity_window: 5provides good sprint velocity trackingsrc/synthorg/templates/builtins/research_lab.yaml (2)
30-33: LGTM!New
wip_limitvariable with sensible default of 3 for a research-focused team.
119-124: LGTM!The kanban-only
workflow_configis appropriate for a research lab:
- Advisory WIP enforcement (
enforce_wip: false) matches the template's "full" autonomy level- Simple configuration without sprint ceremonies aligns with the research workflow
The YAMLlint warnings about "too many spaces inside braces" are false positives — the
{{ }}syntax is Jinja2 templating, not YAML flow mapping braces.src/synthorg/templates/builtins/data_team.yaml (2)
30-33: LGTM!New
wip_limitvariable with appropriate default of 3 for a data-focused team.
106-111: LGTM!Kanban-only configuration with advisory WIP enforcement (
enforce_wip: false) is consistent with the template's "full" autonomy level and aligns with other research/data-focused templates.src/synthorg/templates/builtins/product_team.yaml (2)
31-38: LGTM!New
sprint_lengthandwip_limitvariables with appropriate defaults (14-day sprints, WIP limit of 3) for a product-focused team.
162-182: LGTM!Well-structured
workflow_configfor a product team:
- Kanban: Enforced WIP limits on
in_progressandreviewcolumns- Sprint: Planning, review, and retrospective ceremonies (no daily standup aligns with PR objectives)
duration_tokens: 3000for retrospective is within the valid range (100-50,000) perSprintCeremonyConfigsrc/synthorg/templates/builtins/dev_shop.yaml (2)
30-37: LGTM!New
sprint_lengthandwip_limitvariables with appropriate defaults for an engineering squad (14-day sprints, WIP limit of 2 per agent as specified in PR objectives).
144-168: LGTM!Comprehensive
workflow_configfor an engineering squad:
- Kanban: Strict WIP enforcement with limit of 2 (matching PR objectives)
- Sprint: Full ceremony suite including daily standup, appropriate for a shipping-focused team
- Token budgets (2000 for standup, 3000 for retrospective) are within valid ranges
docs/design/organization.md (3)
193-201: LGTM!Good addition of
sprint_lengthandwip_limittemplate variable examples, demonstrating the new workflow configuration parameters.
281-298: LGTM!Clear example showing the new
workflow_configstructure with both kanban (WIP limits, enforcement) and sprint (duration, ceremonies) sub-configurations.
370-372: LGTM!The merge semantics documentation correctly describes the deep-merge behavior: a child template setting only
kanbanwill inherit the parent'ssprintsub-config (and vice versa), sincedeep_mergerecursively merges nested dicts while preserving keys only present in the base.src/synthorg/templates/builtins/startup.yaml (2)
31-38: LGTM!New
sprint_length(default 7 for 1-week sprints) andwip_limit(default 3) variables align with PR objectives for a fast-moving startup template.
114-130: LGTM!Well-designed
workflow_configfor a startup:
- 1-week sprints with minimal ceremonies (planning and review only) matches PR objectives
- Weekly ceremony frequency aligns with the 7-day sprint duration
- Enforced WIP limits appropriate for the semi-autonomous team structure
tests/unit/templates/test_workflow_config_integration.py (5)
1-28: LGTM!Clear module docstring and appropriate imports. The
TYPE_CHECKINGguard for type hints is a good practice.
35-119: LGTM!Comprehensive schema-level tests for
CompanyTemplate.workflow_config:
- Default empty dict behavior
- Kanban-only, sprint-only, and combined configurations
- Jinja2 placeholder survival through pass 1 rendering
All tests properly marked with
@pytest.mark.unit.
234-373: LGTM!Thorough renderer integration tests covering:
- Kanban-only and agile_kanban template rendering
- Backward compatibility for templates without
workflow_config- Workflow type passthrough without config
- Unknown key handling (silently ignored)
- Invalid config validation errors
The inline YAML templates provide clear, self-contained test fixtures.
379-407: LGTM!Good coverage of Jinja2 variable substitution in
workflow_config:
- Default values applied correctly
- User overrides take precedence
413-489: LGTM!Excellent builtin template validation:
test_matrix_covers_all_builtinsensures no builtin is accidentally untested- Parametrized tests verify workflow types, WIP limit presence, ceremony definitions, and enforcement modes
test_advisory_wip_templatesandtest_strict_kanban_templatesvalidate autonomy-aligned enforcement settingsThe
_EXPECTED_TYPESclass variable with explicit workflow type expectations is a robust approach.
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(3) }} |
There was a problem hiding this comment.
Adjust Jinja expression spacing to satisfy YAMLlint.
Line 185 has extra spaces inside {{ ... }}.
✏️ Proposed fix
- limit: {{ wip_limit | default(3) }}
+ limit: {{wip_limit | default(3)}}📝 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.
| limit: {{ wip_limit | default(3) }} | |
| limit: {{wip_limit | default(3)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 185-185: too many spaces inside braces
(braces)
[error] 185-185: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/agency.yaml` at line 185, The Jinja
expression for the limit variable has extraneous spaces inside the curly braces
causing YAMLlint failures; edit the template line containing "limit: {{
wip_limit | default(3) }}" and remove the extra inner spaces so the expression
uses standard Jinja spacing (e.g., "{{wip_limit | default(3)}}" or follow your
project's Jinja spacing convention) ensuring the surrounding YAML indentation
remains unchanged.
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(2) }} |
There was a problem hiding this comment.
Normalize Jinja brace spacing for lint compliance.
Line 106 uses extra spaces inside braces and triggers YAMLlint.
✏️ Proposed fix
- limit: {{ wip_limit | default(2) }}
+ limit: {{wip_limit | default(2)}}📝 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.
| limit: {{ wip_limit | default(2) }} | |
| limit: {{wip_limit | default(2)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 106-106: too many spaces inside braces
(braces)
[error] 106-106: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/consultancy.yaml` at line 106, The Jinja
expression in the "limit" value has spacing that triggers YAMLlint; update the
template expression used in the limit key (the "{{ wip_limit | default(2) }}"
value) to the project's normalized brace spacing (remove interior spaces and
around the filter) so it becomes "{{wip_limit|default(2)}}" (or whatever
project-wide no-space Jinja style is used) to satisfy the linter.
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(3) }} |
There was a problem hiding this comment.
Fix Jinja brace spacing to satisfy YAMLlint.
Line 71 has extra spaces inside braces under current lint rules.
✏️ Proposed fix
- limit: {{ wip_limit | default(3) }}
+ limit: {{wip_limit | default(3)}}📝 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.
| limit: {{ wip_limit | default(3) }} | |
| limit: {{wip_limit | default(3)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 71-71: too many spaces inside braces
(braces)
[error] 71-71: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/solo_founder.yaml` at line 71, In
solo_founder.yaml, adjust the Jinja expression for wip_limit to remove the extra
spaces directly inside the braces so it satisfies YAMLlint; replace "limit: {{
wip_limit | default(3) }}" with a compact form like "limit: {{wip_limit |
default(3)}}" (keeping the filter/pipeline spacing as needed) so the template
variable wip_limit | default(3) is preserved but no spaces appear immediately
after "{{" or before "}}".
src/synthorg/templates/renderer.py
Outdated
| for key in ("kanban", "sprint"): | ||
| if key in wf_config: | ||
| workflow_dict[key] = wf_config[key] |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Deep-copy kanban/sprint payloads before attaching to workflow_dict.
At Line 472, sub-configs are attached by reference; use defensive copying to avoid mutable aliasing across config stages.
♻️ Proposed fix
+import copy
from typing import TYPE_CHECKING, Any
@@
if isinstance(wf_config, dict):
for key in ("kanban", "sprint"):
if key in wf_config:
- workflow_dict[key] = wf_config[key]
+ workflow_dict[key] = copy.deepcopy(wf_config[key])🤖 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 470 - 472, The loop that
assigns sub-configs by reference (for key in ("kanban", "sprint"): if key in
wf_config: workflow_dict[key] = wf_config[key]) must deep-copy the payloads to
avoid mutable aliasing across stages; update the assignment to use
copy.deepcopy(wf_config[key]) and add an import for the copy module at the top
of renderer.py so that kanban and sprint inserted into workflow_dict are new
objects rather than references to wf_config entries.
There was a problem hiding this comment.
Pull request overview
Adds support for structured workflow configuration in company templates by introducing a workflow_config section (Kanban/Sprint sub-configs) and ensuring the template renderer produces a WorkflowConfig-compatible object on RootConfig.workflow.
Changes:
- Extend
CompanyTemplateschema + loader normalization to acceptworkflow_config. - Update the renderer to build a
WorkflowConfigdict (workflow type +kanban/sprintsub-configs) instead of passing a bare workflow string. - Update builtin templates and docs to include concrete workflow configurations and expose
sprint_length/wip_limitvariables.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/templates/test_workflow_config_integration.py | Adds integration tests for template workflow_config + variable substitution + builtins validation. |
| tests/unit/templates/test_renderer.py | Strengthens builtin rendering assertions to ensure workflow is a WorkflowConfig and type matches the template. |
| src/synthorg/templates/schema.py | Adds workflow_config: dict[str, Any] to CompanyTemplate. |
| src/synthorg/templates/renderer.py | Introduces _build_workflow_dict and routes rendered workflow data into RootConfig.workflow. |
| src/synthorg/templates/loader.py | Normalizes workflow_config during Pass 1 template loading. |
| src/synthorg/templates/builtins/startup.yaml | Adds workflow config + sprint_length/wip_limit variables. |
| src/synthorg/templates/builtins/solo_founder.yaml | Adds workflow config + wip_limit variable. |
| src/synthorg/templates/builtins/research_lab.yaml | Adds workflow config + wip_limit variable. |
| src/synthorg/templates/builtins/product_team.yaml | Adds workflow config + sprint_length/wip_limit variables. |
| src/synthorg/templates/builtins/full_company.yaml | Adds workflow config + sprint_length/wip_limit variables. |
| src/synthorg/templates/builtins/dev_shop.yaml | Adds workflow config + sprint_length/wip_limit variables. |
| src/synthorg/templates/builtins/data_team.yaml | Adds workflow config + wip_limit variable. |
| src/synthorg/templates/builtins/consultancy.yaml | Adds workflow config + wip_limit variable. |
| src/synthorg/templates/builtins/agency.yaml | Adds workflow config + wip_limit variable. |
| docs/design/organization.md | Documents workflow_config usage and inheritance merge semantics. |
| docs/design/engine.md | Notes template-driven workflow configuration and variable customization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| result: dict[str, Any] = { | ||
| "company_name": company_name, | ||
| "company_type": company.get("type", template.metadata.company_type.value), | ||
| "agents": agents, | ||
| "departments": departments, | ||
| "workflow": _build_workflow_dict(rendered_data, template), | ||
| "config": { |
There was a problem hiding this comment.
_build_config_dict() now emits a top-level workflow dict, but template inheritance merging (merge_template_configs) does not currently merge/carry forward the workflow key at all. As a result, any template that uses extends will silently drop the parent's/child's workflow type and workflow_config during inheritance resolution and fall back to RootConfig defaults. Update the inheritance merge logic to include workflow (and deep-merge its kanban/sprint sub-dicts) so workflow settings survive extends chains and match the documented merge semantics.
docs/design/organization.md
Outdated
| : Deep-merged (child keys override parent). A child that sets only ``kanban`` | ||
| inherits the parent's ``sprint`` sub-config (and vice versa). |
There was a problem hiding this comment.
The merge semantics section states that workflow_config is deep-merged across inheritance, but the current inheritance implementation does not merge workflow/workflow_config at all (so these settings are dropped when using extends). Either implement this deep-merge behavior in the inheritance merge code, or adjust the documentation to reflect the actual behavior.
| : Deep-merged (child keys override parent). A child that sets only ``kanban`` | |
| inherits the parent's ``sprint`` sub-config (and vice versa). | |
| : Not merged or inherited. When using `extends`, any `workflow` / `workflow_config` | |
| defined in the parent is not applied to the child; the child must define its full | |
| workflow configuration explicitly. |
| # All templates should have at least one WIP limit set. | ||
| assert len(config.workflow.kanban.wip_limits) >= 1 |
There was a problem hiding this comment.
test_workflow_config_valid doesn’t actually verify that builtin templates provide non-default Kanban settings: KanbanConfig() already has default WIP limits, so len(config.workflow.kanban.wip_limits) >= 1 will pass even if workflow_config is omitted or dropped during rendering. To make this test meaningful, assert against the defaults (e.g., config.workflow.kanban != KanbanConfig() / check specific expected limits), or assert that the template declares workflow_config in Pass 1 (e.g., loaded.template.workflow_config is non-empty).
| # All templates should have at least one WIP limit set. | |
| assert len(config.workflow.kanban.wip_limits) >= 1 | |
| # Builtins must override the default KanbanConfig, not rely on bare defaults. | |
| assert config.workflow.kanban != KanbanConfig() |
| """Agile kanban templates must define at least one ceremony.""" | ||
| loaded = load_template(name) | ||
| config = render_template(loaded) | ||
| assert len(config.workflow.sprint.ceremonies) >= 1 |
There was a problem hiding this comment.
test_agile_templates_have_ceremonies is currently vacuous because SprintConfig() has non-empty default ceremonies, so len(config.workflow.sprint.ceremonies) >= 1 will always pass even if templates don’t declare ceremonies (or if workflow_config.sprint is dropped). Consider asserting that the rendered sprint config differs from SprintConfig() defaults, or explicitly asserting the template declares a sprint.ceremonies section (e.g., via loaded.template.workflow_config).
| assert len(config.workflow.sprint.ceremonies) >= 1 | |
| default_sprint = SprintConfig() | |
| assert config.workflow.sprint.ceremonies != default_sprint.ceremonies |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #963 +/- ##
=======================================
Coverage 92.00% 92.01%
=======================================
Files 622 622
Lines 33527 33544 +17
Branches 3235 3239 +4
=======================================
+ Hits 30848 30865 +17
Misses 2123 2123
Partials 556 556 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/synthorg/templates/renderer.py (1)
473-475: 🛠️ Refactor suggestion | 🟠 MajorDeep-copy
kanban/sprintbefore storing them inworkflow_dict.Line 475 still reuses the parsed YAML sub-configs by reference. Please
copy.deepcopy()these payloads at the boundary so later merge/validation work cannot alias mutable rendered data.Based on learnings: For
dict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention andcopy.deepcopy()at system boundaries.🤖 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 473 - 475, The loop copies references from wf_config into workflow_dict causing shared mutable state for nested structures like kanban/sprint; modify the assignment in the for-loop that iterates known_keys so that when key is one of the nested payloads (e.g., "kanban" or "sprint") you store a deep copy of wf_config[key] (use copy.deepcopy) into workflow_dict[key] instead of the direct reference, keeping direct assignment for other keys; ensure you import copy where the renderer (the function containing known_keys, wf_config, workflow_dict) is defined.
🤖 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/renderer.py`:
- Around line 470-483: The code currently ignores non-mapping rendered
workflow_config values (wf_config) and silently falls back to defaults; instead,
validate wf_config at the top of this block: if wf_config is None keep returning
workflow_dict, if isinstance(wf_config, dict) continue with the existing
known_keys handling (workflow_dict, TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY,
logger.warning), but if wf_config is present and not a dict then log an error
via logger.error (include TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY or a new
TEMPLATE_WORKFLOW_CONFIG_INVALID_TYPE message, template.metadata.name, and the
actual wf_config/type) and raise a validation exception (ValueError or the
module’s validation exception) so malformed templates fail fast rather than
being silently dropped.
In `@tests/unit/templates/test_workflow_config_integration.py`:
- Around line 308-340: Update the
test_unknown_keys_in_workflow_config_silently_ignored test to assert the new
warning: rename the test to reflect it is no longer silent, use the caplog
fixture when calling load_template_file()/render_template() and assert that a
log record with TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY was emitted, while still
asserting that the scrumban key is dropped by checking
config.workflow.kanban.enforce_wip is False; reference the test function name
and the renderer warning constant TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY and the
helper functions load_template_file and render_template to locate the code to
change.
---
Duplicate comments:
In `@src/synthorg/templates/renderer.py`:
- Around line 473-475: The loop copies references from wf_config into
workflow_dict causing shared mutable state for nested structures like
kanban/sprint; modify the assignment in the for-loop that iterates known_keys so
that when key is one of the nested payloads (e.g., "kanban" or "sprint") you
store a deep copy of wf_config[key] (use copy.deepcopy) into workflow_dict[key]
instead of the direct reference, keeping direct assignment for other keys;
ensure you import copy where the renderer (the function containing known_keys,
wf_config, workflow_dict) is defined.
🪄 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: e2157eb0-5637-4767-8eec-08202e0392c8
📒 Files selected for processing (6)
CLAUDE.mddocs/design/organization.mdsrc/synthorg/observability/events/template.pysrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.pytests/unit/templates/test_workflow_config_integration.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) -- ruff enforces this on Python 3.14
Type hints: all public functions must have type hints, mypy strict mode
Google-style docstrings required on public classes and functions (enforced by ruff D rules)
Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention andcopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing + validating redundant fields (e.g.TokenUsage.total_tokens)
UseNotBlankStr(fromcore.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff enforced)
Functions must be < 50 lines, files must be < 800 lines
Handle errors explicitly, never silently swallow them
Validate at system boundaries (user input, external APIs, config files)
Every module wit...
Files:
src/synthorg/observability/events/template.pysrc/synthorg/templates/schema.pysrc/synthorg/templates/renderer.py
{src,tests}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Vendor-agnostic everywhere: 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/smallas aliases
Files:
src/synthorg/observability/events/template.pysrc/synthorg/templates/schema.pytests/unit/templates/test_workflow_config_integration.pysrc/synthorg/templates/renderer.py
docs/design/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
When approved deviations from the design spec occur, update the relevant
docs/design/page to reflect the new reality
Files:
docs/design/organization.md
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Docs:
docs/(Markdown, built with Zensical, config:mkdocs.yml). Design spec:docs/design/(11 pages), Architecture:docs/architecture/, Roadmap:docs/roadmap/
Files:
docs/design/organization.md
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unitfor unit tests,@pytest.mark.integrationfor integration tests,@pytest.mark.e2efor e2e tests,@pytest.mark.slowfor slow tests
Coverage: 80% minimum (enforced in CI)
Async:asyncio_mode = "auto"-- no manual@pytest.mark.asyncioneeded
Timeout: 30 seconds per test (global inpyproject.toml-- do not add per-filepytest.mark.timeout(30)markers; non-default overrides liketimeout(60)are allowed)
Parametrize: Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given+@settings). Profiles:ci(50 examples, default) anddev(1000 examples), controlled viaHYPOTHESIS_PROFILEenv var. Run dev profile:HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), useasyncio.Event().wait()instead ofasyncio.sleep(large_number)-- it is cancellation-safe
Files:
tests/unit/templates/test_workflow_config_integration.py
🧠 Learnings (22)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-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:
CLAUDE.mdsrc/synthorg/templates/schema.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to {docs/design/operations.md,src/synthorg/providers/presets.py,.claude/**/*.{md,yml,yaml}} : Vendor names may appear only in: (1) Operations design page, (2) `.claude/` skill/agent files, (3) third-party import paths, (4) provider presets (`src/synthorg/providers/presets.py`), (5) tests using `test-provider`, `test-small-001`, etc.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-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: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Web dashboard: see `web/CLAUDE.md` for commands, design system, and component inventory
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/templates/renderer.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/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 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/renderer.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/templates/renderer.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/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 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/renderer.py
📚 Learning: 2026-04-01T10:42:14.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T10:42:14.650Z
Learning: Applies to src/**/*.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/renderer.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/templates/renderer.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/templates/renderer.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/renderer.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG
Applied to files:
src/synthorg/templates/renderer.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
🪛 markdownlint-cli2 (0.22.0)
CLAUDE.md
[warning] 104-104: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
| wf_config = rendered_data.get("workflow_config") | ||
| if isinstance(wf_config, dict): | ||
| known_keys = {"kanban", "sprint"} | ||
| for key in known_keys: | ||
| if key in wf_config: | ||
| workflow_dict[key] = wf_config[key] | ||
| unknown = set(wf_config) - known_keys | ||
| if unknown: | ||
| logger.warning( | ||
| TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY, | ||
| unknown_keys=sorted(unknown), | ||
| source_name=template.metadata.name, | ||
| ) | ||
| return workflow_dict |
There was a problem hiding this comment.
Reject non-mapping workflow_config values instead of dropping them.
At Line 470, a rendered workflow_config of [], "bad", etc. is ignored because only mappings enter this branch. That lets malformed templates render successfully with default workflow settings instead of failing fast.
💡 Proposed fix
wf_config = rendered_data.get("workflow_config")
- if isinstance(wf_config, dict):
- known_keys = {"kanban", "sprint"}
- for key in known_keys:
- if key in wf_config:
- workflow_dict[key] = wf_config[key]
- unknown = set(wf_config) - known_keys
- if unknown:
- logger.warning(
- TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY,
- unknown_keys=sorted(unknown),
- source_name=template.metadata.name,
- )
+ if wf_config is None:
+ return workflow_dict
+ if not isinstance(wf_config, dict):
+ msg = "Rendered template 'workflow_config' must be a mapping"
+ logger.warning(
+ TEMPLATE_RENDER_TYPE_ERROR,
+ field="workflow_config",
+ expected="mapping",
+ got=type(wf_config).__name__,
+ source=template.metadata.name,
+ )
+ raise TemplateRenderError(msg)
+ known_keys = {"kanban", "sprint"}
+ for key in known_keys:
+ if key in wf_config:
+ workflow_dict[key] = wf_config[key]
+ unknown = set(wf_config) - known_keys
+ if unknown:
+ logger.warning(
+ TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY,
+ unknown_keys=sorted(unknown),
+ source_name=template.metadata.name,
+ )As per coding guidelines: "Handle errors explicitly, never silently swallow them" and "Validate at system boundaries (user input, external APIs, config files)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/renderer.py` around lines 470 - 483, The code
currently ignores non-mapping rendered workflow_config values (wf_config) and
silently falls back to defaults; instead, validate wf_config at the top of this
block: if wf_config is None keep returning workflow_dict, if
isinstance(wf_config, dict) continue with the existing known_keys handling
(workflow_dict, TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY, logger.warning), but if
wf_config is present and not a dict then log an error via logger.error (include
TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY or a new
TEMPLATE_WORKFLOW_CONFIG_INVALID_TYPE message, template.metadata.name, and the
actual wf_config/type) and raise a validation exception (ValueError or the
module’s validation exception) so malformed templates fail fast rather than
being silently dropped.
Add workflow_config support to the template system so builtin templates
can specify real Kanban WIP limits, sprint durations, and ceremony
schedules instead of bare workflow type strings. Also fixes a bug where
the template workflow type was silently dropped during rendering.
- Add workflow_config field to CompanyTemplate schema (dict for Jinja2 compat)
- Extract workflow_config in loader's _normalize_template_data
- Build WorkflowConfig-compatible dict in renderer's _build_config_dict
- Update all 9 builtin templates with workflow configs:
- Agile templates (startup, dev_shop, product_team, full_company):
sprint durations, ceremony schedules, strict WIP
- Kanban templates (agency, consultancy): strict WIP enforcement
- Autonomous templates (solo_founder, research_lab, data_team):
advisory WIP enforcement
- Add sprint_length and wip_limit template variables where appropriate
- Add 39 new tests for workflow config integration
Closes #724
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 5 agents, 7 findings addressed: - Extract _build_workflow_dict helper from _build_config_dict (function length) - Update docs/design/organization.md: add workflow_config to template example, variables section, and merge semantics - Update docs/design/engine.md: note about template-level workflow config - Add tests for unknown keys and invalid workflow_config validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pilot - Fix design doc claiming deep-merge for workflow_config during inheritance when merge.py does not implement it (updated docs to match reality) - Add workflow_config to CompanyTemplate docstring Attributes section - Remove quoted Jinja2 in design doc YAML example to match actual template convention - Replace hasattr duck-typing with isinstance(WorkflowType) in _build_workflow_dict - Add warning log for unknown workflow_config keys with new TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY event constant - Fix double-space in schema Field description - Update CLAUDE.md templates/ package description - Strengthen test_workflow_config_valid to check explicit declaration - Strengthen test_agile_templates_have_ceremonies to verify explicit ceremony declarations - Add enforce_wip assertion for agile_kanban templates - Document schema limitation gaps on issue #724 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update test_unknown_keys_in_workflow_config to verify the new TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY warning is emitted when unrecognized keys are present in workflow_config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/unit/templates/test_workflow_config_integration.py`:
- Around line 463-469: The test test_workflow_config_valid currently only checks
truthiness and type but won't detect dropped/mistyped workflow_config keys;
modify the test to capture logs emitted by render_template (use Python's caplog
or logging.capture_logs) for the render_template(loaded) call and assert that no
log record contains the TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY warning from
src/synthorg/templates/renderer.py, failing the test if that token appears; keep
existing assertions (load_template, render_template, isinstance config.workflow,
and loaded.template.workflow_config) and add the log-capture + assert to ensure
unknown keys are not silently dropped.
- Around line 432-442: The test asserts the wrong workflow for the "dev_shop"
template: update any occurrences where "dev_shop" is listed as "agile_kanban" or
otherwise categorized into sprint/ceremony-based buckets (notably the
_EXPECTED_TYPES list and the other two occurrences referenced near the same
block) so that "dev_shop" is instead set to "kanban" and ensure the related test
expectations enforce a WIP limit of 2 for the dev_shop template; search for the
symbol _EXPECTED_TYPES and the other nearby assertions that mention "dev_shop"
and change their values to reflect "kanban" and WIP=2.
- Around line 476-516: Add explicit value-level assertions for the named
template defaults: after loading/rendering the templates with load_template and
render_template in the existing tests (e.g., inside
test_agile_templates_enforce_wip and the strict/advisory kanban tests), assert
concrete settings such as config.workflow.sprint.length_days == 7 for "startup"
and config.workflow.sprint.length_days == 14 and config.workflow.kanban.wip == 3
for "product_team" (use the same config object already referenced in those
tests), and similarly assert expected config.workflow.kanban.wip integers for
other named templates to ensure the builtin YAML pins the promised defaults.
- Around line 444-446: The test test_matrix_covers_all_builtins currently
compares tested = {row[0] for row in self._EXPECTED_TYPES} to BUILTIN_TEMPLATES,
but BUILTIN_TEMPLATES is a hand-maintained registry so new YAML files can be
missed; update the test to either (A) enumerate the builtin template directory
(list all template YAML files, strip extensions and normalise names) and assert
that set(tested) == set(discovered_filenames), or (B) add an explicit
registration test that walks the builtin template directory and asserts each
discovered template name is present in BUILTIN_TEMPLATES; reference
test_matrix_covers_all_builtins and BUILTIN_TEMPLATES when implementing the
check and use the same filename-to-template-name mapping logic as the loader to
avoid false mismatches.
🪄 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: e3a308cb-5e2e-4efa-a8af-e688f7fd2cc5
📒 Files selected for processing (1)
tests/unit/templates/test_workflow_config_integration.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Dependency Review
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unitfor unit tests,@pytest.mark.integrationfor integration tests,@pytest.mark.e2efor e2e tests,@pytest.mark.slowfor slow tests
Coverage: 80% minimum (enforced in CI)
Async:asyncio_mode = "auto"-- no manual@pytest.mark.asyncioneeded
Timeout: 30 seconds per test (global inpyproject.toml-- do not add per-filepytest.mark.timeout(30)markers; non-default overrides liketimeout(60)are allowed)
Parametrize: Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given+@settings). Profiles:ci(50 examples, default) anddev(1000 examples), controlled viaHYPOTHESIS_PROFILEenv var. Run dev profile:HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), useasyncio.Event().wait()instead ofasyncio.sleep(large_number)-- it is cancellation-safe
Files:
tests/unit/templates/test_workflow_config_integration.py
{src,tests}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Vendor-agnostic everywhere: 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/smallas aliases
Files:
tests/unit/templates/test_workflow_config_integration.py
| _EXPECTED_TYPES: ClassVar[list[tuple[str, str]]] = [ | ||
| ("solo_founder", "kanban"), | ||
| ("startup", "agile_kanban"), | ||
| ("dev_shop", "agile_kanban"), | ||
| ("product_team", "agile_kanban"), | ||
| ("agency", "kanban"), | ||
| ("research_lab", "kanban"), | ||
| ("full_company", "agile_kanban"), | ||
| ("consultancy", "kanban"), | ||
| ("data_team", "kanban"), | ||
| ] |
There was a problem hiding this comment.
dev_shop is asserted as sprint-based, not kanban.
Line 435, Line 473, and Line 509 contradict the linked requirement, which calls for dev_shop to remain a kanban template with a WIP limit of 2. Keeping it in the agile_kanban and ceremony-based buckets bakes the wrong contract into CI.
Also applies to: 471-475, 507-510
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/templates/test_workflow_config_integration.py` around lines 432 -
442, The test asserts the wrong workflow for the "dev_shop" template: update any
occurrences where "dev_shop" is listed as "agile_kanban" or otherwise
categorized into sprint/ceremony-based buckets (notably the _EXPECTED_TYPES list
and the other two occurrences referenced near the same block) so that "dev_shop"
is instead set to "kanban" and ensure the related test expectations enforce a
WIP limit of 2 for the dev_shop template; search for the symbol _EXPECTED_TYPES
and the other nearby assertions that mention "dev_shop" and change their values
to reflect "kanban" and WIP=2.
- Add log capture to test_workflow_config_valid to catch unknown keys - Add filesystem discovery test ensuring BUILTIN_TEMPLATES matches actual .yaml files on disk - Add test_builtin_default_values pinning WIP limits and sprint duration for all 9 builtins Rejected finding: dev_shop is correctly agile_kanban (confirmed by dev_shop.yaml:141 and rendered output), not kanban as reviewer claimed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
368e12c to
93cd268
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (6)
src/synthorg/templates/builtins/solo_founder.yaml (1)
71-71:⚠️ Potential issue | 🟡 MinorFix Jinja brace spacing on Line 71 to satisfy YAMLlint.
There are extra spaces immediately inside the braces.Proposed fix
- limit: {{ wip_limit | default(3) }} + limit: {{wip_limit | default(3)}}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/builtins/solo_founder.yaml` at line 71, The Jinja expression on the line with "limit:" has extra spaces immediately inside the braces which fails YAMLlint; edit the template solo_founder.yaml and change the Jinja interpolation for the wip_limit default so there are no spaces inside the double braces around the expression (i.e., tighten the braces for the "limit: {{ wip_limit | default(3) }}" line), ensuring the expression remains the same but follows the no-inner-space brace style.src/synthorg/templates/builtins/agency.yaml (1)
185-185:⚠️ Potential issue | 🟡 MinorFix Jinja brace spacing on Line 185 to pass YAMLlint.
The expression has extra spaces immediately inside{{and}}.Proposed fix
- limit: {{ wip_limit | default(3) }} + limit: {{wip_limit | default(3)}}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/builtins/agency.yaml` at line 185, Remove the extra spaces immediately inside the Jinja braces on the limit expression so YAMLlint passes: change the current "limit: {{ wip_limit | default(3) }}" to use no space after "{{" and no space before "}}", i.e. "{{wip_limit | default(3)}}", updating the template expression that references wip_limit in the agency.yaml builtin template.src/synthorg/templates/renderer.py (2)
473-476:⚠️ Potential issue | 🟠 MajorDeep-copy
kanban/sprintsub-configs before attaching toworkflow_dict.The current assignment passes mutable references from
wf_config, which can create aliasing across config stages.Based on learnings: "Use `copy.deepcopy()` at system boundaries ... for dict/list fields in frozen Pydantic models."♻️ Proposed fix
+import copy from typing import TYPE_CHECKING, Any @@ known_keys = {"kanban", "sprint"} for key in known_keys: if key in wf_config: - workflow_dict[key] = wf_config[key] + workflow_dict[key] = copy.deepcopy(wf_config[key])🤖 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 473 - 476, The loop that copies allowed keys from wf_config into workflow_dict is currently assigning mutable references (wf_config[key]) directly; detect when key is "kanban" or "sprint" (or any dict/list-valued sub-config) and attach a deep copy instead: import copy and use copy.deepcopy(wf_config[key]) when assigning into workflow_dict (leave other keys unchanged), ensuring workflow_dict and wf_config do not share mutable state; update the block that iterates known_keys and the assignment to workflow_dict accordingly.
470-483:⚠️ Potential issue | 🟠 MajorFail fast on invalid
workflow_configtypes instead of silently ignoring them.If
workflow_configrenders to a non-mapping (e.g., list/string), it is currently dropped and defaults are used. That hides template errors at a config boundary.As per coding guidelines: "Handle errors explicitly, never silently swallow them" and "Validate at system boundaries (user input, external APIs, config files)".🛡️ Proposed fix
wf_config = rendered_data.get("workflow_config") - if isinstance(wf_config, dict): - known_keys = {"kanban", "sprint"} - for key in known_keys: - if key in wf_config: - workflow_dict[key] = wf_config[key] - unknown = set(wf_config) - known_keys - if unknown: - logger.warning( - TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY, - unknown_keys=sorted(unknown), - source_name=template.metadata.name, - ) + if wf_config is None: + return workflow_dict + if not isinstance(wf_config, dict): + msg = "Rendered template 'workflow_config' must be a mapping" + logger.warning( + TEMPLATE_RENDER_TYPE_ERROR, + field="workflow_config", + expected="mapping", + got=type(wf_config).__name__, + source_name=template.metadata.name, + ) + raise TemplateRenderError(msg) + known_keys = {"kanban", "sprint"} + for key in known_keys: + if key in wf_config: + workflow_dict[key] = wf_config[key] + unknown = set(wf_config) - known_keys + if unknown: + logger.warning( + TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY, + unknown_keys=sorted(unknown), + source_name=template.metadata.name, + ) return workflow_dict🤖 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 470 - 483, The code currently silently ignores non-mapping workflow_config values; update the validation after wf_config = rendered_data.get("workflow_config") to fail fast: if wf_config is not None and not isinstance(wf_config, dict) raise a clear exception (e.g., TypeError or a custom TemplateRenderError) that includes the template identity (template.metadata.name) and the actual type/value, rather than dropping it into workflow_dict; keep the existing handling for dicts (known_keys, unknown warning using TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY) but remove the silent path so invalid types surface immediately.src/synthorg/templates/builtins/consultancy.yaml (1)
106-106:⚠️ Potential issue | 🟡 MinorNormalize Jinja brace spacing to satisfy YAMLlint.
Line 106 still uses spacing that triggers the
bracesrule.✏️ Proposed fix
- limit: {{ wip_limit | default(2) }} + limit: {{wip_limit|default(2)}}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/builtins/consultancy.yaml` at line 106, The Jinja brace spacing on the template line using wip_limit violates the YAMLlint "braces" rule; update the expression in consultancy.yaml (the line containing limit: {{ wip_limit | default(2) }}) to normalize spacing by removing inner spaces so it reads limit: {{wip_limit|default(2)}} (or use your project's canonical spacing for Jinja braces consistently across the file).tests/unit/templates/test_workflow_config_integration.py (1)
432-442:⚠️ Potential issue | 🟠 Major
dev_shopis classified asagile_kanbanbut should bekanban.According to the PR objectives and linked issue
#724,dev_shopshould be "kanban with WIP limit 2 per agent, explicit done criteria." The test matrix and ceremony/enforce_wip test groups incorrectly include it as an agile kanban template.Locations to update:
- Line 435: Change
("dev_shop", "agile_kanban")to("dev_shop", "kanban")- Line 498: Remove
"dev_shop"from the agile ceremonies test- Line 534: Remove
"dev_shop"from the agile enforce_wip test🔧 Proposed fix
_EXPECTED_TYPES: ClassVar[list[tuple[str, str]]] = [ ("solo_founder", "kanban"), ("startup", "agile_kanban"), - ("dev_shop", "agile_kanban"), + ("dev_shop", "kanban"), ("product_team", "agile_kanban"), ("agency", "kanban"),`@pytest.mark.parametrize`( "name", - ["startup", "dev_shop", "product_team", "full_company"], - ids=["startup", "dev_shop", "product_team", "full_company"], + ["startup", "product_team", "full_company"], + ids=["startup", "product_team", "full_company"], ) def test_agile_templates_have_ceremonies(self, name: str) -> None:`@pytest.mark.parametrize`( "name", - ["startup", "dev_shop", "product_team", "full_company"], - ids=["startup", "dev_shop", "product_team", "full_company"], + ["startup", "product_team", "full_company"], + ids=["startup", "product_team", "full_company"], ) def test_agile_templates_enforce_wip(self, name: str) -> None:Also applies to: 496-499, 532-535
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/templates/test_workflow_config_integration.py` around lines 432 - 442, Update the test expectations and test groups to classify "dev_shop" as kanban: in the _EXPECTED_TYPES list change the tuple ("dev_shop", "agile_kanban") to ("dev_shop", "kanban") and then remove "dev_shop" from the agile-related test matrices referenced in the ceremony and enforce_wip tests (the lists used by the test functions that build the "agile ceremonies" and "agile enforce_wip" parameterizations) so that "dev_shop" is no longer included in those two agile test groups.
🤖 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/builtins/data_team.yaml`:
- Line 110: Remove the extra spaces inside the Jinja expression braces on the
wip_limit line in the template: locate the expression using wip_limit and
default ({{ wip_limit | default(3) }}) and change it to have no leading/trailing
space inside the braces (e.g., {{wip_limit | default(3)}} or
{{wip_limit|default(3)}}) so it passes YAMLlint; update the single occurrence in
the templates/builtins/data_team.yaml.
In `@src/synthorg/templates/builtins/dev_shop.yaml`:
- Line 148: Normalize Jinja brace spacing by removing the extra spaces inside
the template delimiters: replace occurrences like "limit: {{ wip_limit |
default(2) }}" with "limit: {{wip_limit | default(2)}}" and do the same for the
other templated value on the same template (the second occurrence at the other
reported location) so there are no spaces immediately inside "{{" and "}}".
In `@src/synthorg/templates/builtins/product_team.yaml`:
- Line 166: Two Jinja expressions for the workflow WIP limit are inconsistently
spaced and trigger YAMLlint; update both occurrences (the template value using
the wip_limit variable and the other templated workflow value at the second
occurrence) to use a normalized spacing style like "{{ wip_limit | default(3)
}}" (i.e., single spaces after opening braces and before closing braces, and
around the pipe) so both templates match and pass YAML linting.
In `@src/synthorg/templates/builtins/research_lab.yaml`:
- Line 123: The Jinja expression currently uses spaced braces which triggers
yamllint's braces rule; update the template token in the research_lab template
so there are no spaces immediately inside the braces — replace "{{ wip_limit |
default(3) }}" with "{{wip_limit|default(3)}}" (locate the occurrence in the
file where the limit key is set and update the token in that line).
---
Duplicate comments:
In `@src/synthorg/templates/builtins/agency.yaml`:
- Line 185: Remove the extra spaces immediately inside the Jinja braces on the
limit expression so YAMLlint passes: change the current "limit: {{ wip_limit |
default(3) }}" to use no space after "{{" and no space before "}}", i.e.
"{{wip_limit | default(3)}}", updating the template expression that references
wip_limit in the agency.yaml builtin template.
In `@src/synthorg/templates/builtins/consultancy.yaml`:
- Line 106: The Jinja brace spacing on the template line using wip_limit
violates the YAMLlint "braces" rule; update the expression in consultancy.yaml
(the line containing limit: {{ wip_limit | default(2) }}) to normalize spacing
by removing inner spaces so it reads limit: {{wip_limit|default(2)}} (or use
your project's canonical spacing for Jinja braces consistently across the file).
In `@src/synthorg/templates/builtins/solo_founder.yaml`:
- Line 71: The Jinja expression on the line with "limit:" has extra spaces
immediately inside the braces which fails YAMLlint; edit the template
solo_founder.yaml and change the Jinja interpolation for the wip_limit default
so there are no spaces inside the double braces around the expression (i.e.,
tighten the braces for the "limit: {{ wip_limit | default(3) }}" line), ensuring
the expression remains the same but follows the no-inner-space brace style.
In `@src/synthorg/templates/renderer.py`:
- Around line 473-476: The loop that copies allowed keys from wf_config into
workflow_dict is currently assigning mutable references (wf_config[key])
directly; detect when key is "kanban" or "sprint" (or any dict/list-valued
sub-config) and attach a deep copy instead: import copy and use
copy.deepcopy(wf_config[key]) when assigning into workflow_dict (leave other
keys unchanged), ensuring workflow_dict and wf_config do not share mutable
state; update the block that iterates known_keys and the assignment to
workflow_dict accordingly.
- Around line 470-483: The code currently silently ignores non-mapping
workflow_config values; update the validation after wf_config =
rendered_data.get("workflow_config") to fail fast: if wf_config is not None and
not isinstance(wf_config, dict) raise a clear exception (e.g., TypeError or a
custom TemplateRenderError) that includes the template identity
(template.metadata.name) and the actual type/value, rather than dropping it into
workflow_dict; keep the existing handling for dicts (known_keys, unknown warning
using TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY) but remove the silent path so
invalid types surface immediately.
In `@tests/unit/templates/test_workflow_config_integration.py`:
- Around line 432-442: Update the test expectations and test groups to classify
"dev_shop" as kanban: in the _EXPECTED_TYPES list change the tuple ("dev_shop",
"agile_kanban") to ("dev_shop", "kanban") and then remove "dev_shop" from the
agile-related test matrices referenced in the ceremony and enforce_wip tests
(the lists used by the test functions that build the "agile ceremonies" and
"agile enforce_wip" parameterizations) so that "dev_shop" is no longer included
in those two agile test groups.
🪄 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: 0f9aff5a-2b12-47e8-942b-95af3c77bad3
📒 Files selected for processing (18)
CLAUDE.mddocs/design/engine.mddocs/design/organization.mdsrc/synthorg/observability/events/template.pysrc/synthorg/templates/builtins/agency.yamlsrc/synthorg/templates/builtins/consultancy.yamlsrc/synthorg/templates/builtins/data_team.yamlsrc/synthorg/templates/builtins/dev_shop.yamlsrc/synthorg/templates/builtins/full_company.yamlsrc/synthorg/templates/builtins/product_team.yamlsrc/synthorg/templates/builtins/research_lab.yamlsrc/synthorg/templates/builtins/solo_founder.yamlsrc/synthorg/templates/builtins/startup.yamlsrc/synthorg/templates/loader.pysrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.pytests/unit/templates/test_renderer.pytests/unit/templates/test_workflow_config_integration.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (python)
- GitHub Check: Dependency Review
🧰 Additional context used
📓 Path-based instructions (5)
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unitfor unit tests,@pytest.mark.integrationfor integration tests,@pytest.mark.e2efor e2e tests,@pytest.mark.slowfor slow tests
Coverage: 80% minimum (enforced in CI)
Async:asyncio_mode = "auto"-- no manual@pytest.mark.asyncioneeded
Timeout: 30 seconds per test (global inpyproject.toml-- do not add per-filepytest.mark.timeout(30)markers; non-default overrides liketimeout(60)are allowed)
Parametrize: Prefer@pytest.mark.parametrizefor testing similar cases
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given+@settings). Profiles:ci(50 examples, default) anddev(1000 examples), controlled viaHYPOTHESIS_PROFILEenv var. Run dev profile:HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), useasyncio.Event().wait()instead ofasyncio.sleep(large_number)-- it is cancellation-safe
Files:
tests/unit/templates/test_renderer.pytests/unit/templates/test_workflow_config_integration.py
{src,tests}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Vendor-agnostic everywhere: 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/smallas aliases
Files:
tests/unit/templates/test_renderer.pysrc/synthorg/observability/events/template.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/schema.pysrc/synthorg/templates/renderer.pytests/unit/templates/test_workflow_config_integration.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649
Use PEP 758 except syntax:except A, B:(no parentheses) -- ruff enforces this on Python 3.14
Type hints: all public functions must have type hints, mypy strict mode
Google-style docstrings required on public classes and functions (enforced by ruff D rules)
Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries,BaseTool), usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention andcopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (usingmodel_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing + validating redundant fields (e.g.TokenUsage.total_tokens)
UseNotBlankStr(fromcore.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over barecreate_task
Line length: 88 characters (ruff enforced)
Functions must be < 50 lines, files must be < 800 lines
Handle errors explicitly, never silently swallow them
Validate at system boundaries (user input, external APIs, config files)
Every module wit...
Files:
src/synthorg/observability/events/template.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/schema.pysrc/synthorg/templates/renderer.py
docs/design/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
When approved deviations from the design spec occur, update the relevant
docs/design/page to reflect the new reality
Files:
docs/design/engine.mddocs/design/organization.md
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Docs:
docs/(Markdown, built with Zensical, config:mkdocs.yml). Design spec:docs/design/(11 pages), Architecture:docs/architecture/, Roadmap:docs/roadmap/
Files:
docs/design/engine.mddocs/design/organization.md
🧠 Learnings (25)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-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: Settings: Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge, ConfigResolver (typed composed reads for controllers), validation, registry, change notifications via message bus. Per-namespace setting definitions in definitions/ submodule (api, company, providers, memory, budget, security, coordination, observability, backup).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Web dashboard: see `web/CLAUDE.md` for commands, design system, and component inventory
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.
Applied to files:
src/synthorg/templates/loader.pysrc/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-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/templates/renderer.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/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 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/renderer.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/templates/renderer.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/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 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/renderer.py
📚 Learning: 2026-04-01T10:42:14.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T10:42:14.650Z
Learning: Applies to src/**/*.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/renderer.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/templates/renderer.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/templates/renderer.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/renderer.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/renderer.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG
Applied to files:
src/synthorg/templates/renderer.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
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit
Applied to files:
tests/unit/templates/test_workflow_config_integration.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 : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.
Applied to files:
tests/unit/templates/test_workflow_config_integration.py
🪛 YAMLlint (1.38.0)
src/synthorg/templates/builtins/agency.yaml
[error] 185-185: too many spaces inside braces
(braces)
[error] 185-185: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/research_lab.yaml
[error] 123-123: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/data_team.yaml
[error] 110-110: too many spaces inside braces
(braces)
[error] 110-110: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/product_team.yaml
[error] 166-166: too many spaces inside braces
(braces)
[error] 166-166: too many spaces inside braces
(braces)
[error] 171-171: too many spaces inside braces
(braces)
[error] 171-171: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/consultancy.yaml
[error] 106-106: too many spaces inside braces
(braces)
[error] 106-106: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/dev_shop.yaml
[error] 148-148: too many spaces inside braces
(braces)
[error] 148-148: too many spaces inside braces
(braces)
[error] 153-153: too many spaces inside braces
(braces)
[error] 153-153: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/solo_founder.yaml
[error] 71-71: too many spaces inside braces
(braces)
[error] 71-71: too many spaces inside braces
(braces)
src/synthorg/templates/builtins/startup.yaml
[error] 118-118: too many spaces inside braces
(braces)
[error] 118-118: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
🔇 Additional comments (18)
CLAUDE.md (1)
104-104: Looks accurate and aligned with the new template responsibilities.
This documentation update is clear and matches the workflow-config rendering additions.src/synthorg/templates/loader.py (1)
491-491: Good normalization for backward compatibility.
Includingworkflow_configin normalized kwargs with a safe default is the right loader behavior.src/synthorg/observability/events/template.py (1)
47-50: Event constant addition is clean and consistent.
This fits the template event taxonomy and supports observable unknown-key handling.tests/unit/templates/test_renderer.py (1)
47-59: Great regression coverage for rendered workflow typing.
These assertions directly validate the workflow object type and workflow type mapping across all builtins.docs/design/engine.md (1)
172-176: Design spec update is clear and appropriately scoped.
This addition accurately documents howworkflow_configand template variables affect rendered workflow settings.src/synthorg/templates/schema.py (1)
344-350:workflow_configfield integration is implemented correctly.
The typed field withdefault_factory=dictcleanly supports optional template workflow sub-configs in pass-1 schema validation.docs/design/organization.md (1)
281-298: Great doc alignment forworkflow_configbehavior.The example and inheritance semantics clearly document how
workflow_configis defined and how it behaves withextends.Also applies to: 370-373
src/synthorg/templates/builtins/full_company.yaml (1)
367-398:workflow_configstructure is coherent and matches the template intent.The Kanban and Sprint sub-configs are complete and consistent with the declared
agile_kanbanworkflow.src/synthorg/templates/builtins/startup.yaml (2)
31-38: LGTM — well-documented template variables.Clear variable definitions with appropriate types and sensible defaults for a startup template. The 7-day sprint aligns with the PR objective for fast iteration.
114-130: LGTM — workflow_config structure is correct.The Kanban WIP limits, sprint duration, and ceremony definitions are well-structured. The static analysis warnings about "too many spaces inside braces" are false positives—
{{ variable | default(value) }}is standard Jinja2 syntax and the spaces are idiomatic.tests/unit/templates/test_workflow_config_integration.py (8)
1-30: LGTM — clean module structure and imports.Well-organized imports with proper TYPE_CHECKING guard for type hints that aren't needed at runtime.
35-119: LGTM — comprehensive schema acceptance tests.Good coverage of the
CompanyTemplate.workflow_configfield including kanban-only, sprint-only, combined configs, and the important edge case of Jinja2 placeholder strings surviving Pass 1 parsing.
125-231: LGTM — well-designed test fixtures.The YAML templates cover the key workflow configurations (kanban-only, agile kanban, and variable-driven) with appropriate test data.
234-386: LGTM — thorough renderer integration tests.Good coverage of workflow config rendering including kanban-only, agile kanban, backward compatibility, pass-through defaults, unknown key handling with log capture, and validation errors. The
structlog.testing.capture_logs()pattern correctly verifies the warning emission.
392-420: LGTM — variable substitution tests.Correctly verifies that template variables (
sprint_length,wip_limit) are substituted during rendering, both with defaults and with user-provided overrides.
444-458: LGTM — filesystem enumeration test addresses the registry gap.The
test_builtin_registry_matches_filesystemtest ensures thatBUILTIN_TEMPLATESstays synchronized with the actual YAML files in the builtins directory, preventing undiscoverable templates from being missed.
475-494: LGTM — log capture prevents silent key drops.The test now captures logs and asserts no
TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEYwarnings are emitted for builtin templates, ensuring mistyped workflow_config keys won't silently pass CI.
544-571: LGTM — concrete default value assertions.The
_EXPECTED_DEFAULTSmatrix withtest_builtin_default_valuespins the documented per-template settings (WIP limits and sprint durations), ensuring builtin YAMLs match their promised configurations.
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(3) }} |
There was a problem hiding this comment.
Fix Jinja brace spacing for lint compliance.
Line 110 has extra spaces inside braces and fails YAMLlint.
✏️ Proposed fix
- limit: {{ wip_limit | default(3) }}
+ limit: {{wip_limit|default(3)}}📝 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.
| limit: {{ wip_limit | default(3) }} | |
| limit: {{wip_limit|default(3)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 110-110: too many spaces inside braces
(braces)
[error] 110-110: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/data_team.yaml` at line 110, Remove the extra
spaces inside the Jinja expression braces on the wip_limit line in the template:
locate the expression using wip_limit and default ({{ wip_limit | default(3) }})
and change it to have no leading/trailing space inside the braces (e.g.,
{{wip_limit | default(3)}} or {{wip_limit|default(3)}}) so it passes YAMLlint;
update the single occurrence in the templates/builtins/data_team.yaml.
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(2) }} |
There was a problem hiding this comment.
Normalize Jinja brace spacing in workflow templated values.
Lines 148 and 153 have extra spaces inside braces and trip YAMLlint.
✏️ Proposed fix
- limit: {{ wip_limit | default(2) }}
+ limit: {{wip_limit|default(2)}}
@@
- duration_days: {{ sprint_length | default(14) }}
+ duration_days: {{sprint_length|default(14)}}Also applies to: 153-153
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 148-148: too many spaces inside braces
(braces)
[error] 148-148: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/dev_shop.yaml` at line 148, Normalize Jinja
brace spacing by removing the extra spaces inside the template delimiters:
replace occurrences like "limit: {{ wip_limit | default(2) }}" with "limit:
{{wip_limit | default(2)}}" and do the same for the other templated value on the
same template (the second occurrence at the other reported location) so there
are no spaces immediately inside "{{" and "}}".
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(3) }} |
There was a problem hiding this comment.
Normalize Jinja expressions on both templated workflow values.
Lines 166 and 171 use brace spacing that triggers YAMLlint.
✏️ Proposed fix
- limit: {{ wip_limit | default(3) }}
+ limit: {{wip_limit|default(3)}}
@@
- duration_days: {{ sprint_length | default(14) }}
+ duration_days: {{sprint_length|default(14)}}Also applies to: 171-171
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 166-166: too many spaces inside braces
(braces)
[error] 166-166: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/product_team.yaml` at line 166, Two Jinja
expressions for the workflow WIP limit are inconsistently spaced and trigger
YAMLlint; update both occurrences (the template value using the wip_limit
variable and the other templated workflow value at the second occurrence) to use
a normalized spacing style like "{{ wip_limit | default(3) }}" (i.e., single
spaces after opening braces and before closing braces, and around the pipe) so
both templates match and pass YAML linting.
| kanban: | ||
| wip_limits: | ||
| - column: "in_progress" | ||
| limit: {{ wip_limit | default(3) }} |
There was a problem hiding this comment.
Fix Jinja brace spacing for lint compliance.
Line 123 uses spacing that triggers YAMLlint’s braces rule.
✏️ Proposed fix
- limit: {{ wip_limit | default(3) }}
+ limit: {{wip_limit|default(3)}}📝 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.
| limit: {{ wip_limit | default(3) }} | |
| limit: {{wip_limit|default(3)}} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 123-123: too many spaces inside braces
(braces)
[error] 123-123: too many spaces inside braces
(braces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/builtins/research_lab.yaml` at line 123, The Jinja
expression currently uses spaced braces which triggers yamllint's braces rule;
update the template token in the research_lab template so there are no spaces
immediately inside the braces — replace "{{ wip_limit | default(3) }}" with
"{{wip_limit|default(3)}}" (locate the occurrence in the file where the limit
key is set and update the token in that line).
🤖 I have created a release *beep* *boop* --- ## [0.5.5](v0.5.4...v0.5.5) (2026-04-01) ### Features * add workflow configs to builtin templates ([#963](#963)) ([b7fe6e3](b7fe6e3)) * implement Kanban board and Agile sprints workflow types ([#960](#960)) ([f511e1d](f511e1d)) * personality preset support in template YAML schema ([#959](#959)) ([97ca81e](97ca81e)) ### Documentation * LMEB embedding evaluation + CSP accepted risk ([#695](#695), [#925](#925)) ([#962](#962)) ([43dfab3](43dfab3)) ### CI/CD * bump wrangler from 4.78.0 to 4.79.0 in /.github in the all group across 1 directory ([#955](#955)) ([18b4cb1](18b4cb1)) ### Maintenance * bump mypy from 1.19.1 to 1.20.0 in the all group across 1 directory ([#956](#956)) ([29cc419](29cc419)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
workflow_configfield toCompanyTemplateschema so templates can specify real Kanban/Sprint sub-configurations instead of bare workflow type stringsWorkflowConfig-compatible dict from template data, fixing a bug where the template workflow type was silently dropped during renderingenforce_wipsettings tailored to each org archetypesprint_lengthandwip_limittemplate variables for user customizationTest plan
test_workflow_config_integration.pycovering schema acceptance, renderer integration, variable substitution, backward compatibility, edge cases (unknown keys, invalid config), and parametrized builtin validationtest_renderer.pyto assertWorkflowConfigtype match on all builtinsCloses #724
Release-As: 0.5.5