Skip to content

feat: add workflow configs to builtin templates#963

Merged
Aureliolo merged 5 commits intomainfrom
feat/template-workflow-configs
Apr 1, 2026
Merged

feat: add workflow configs to builtin templates#963
Aureliolo merged 5 commits intomainfrom
feat/template-workflow-configs

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add workflow_config field to CompanyTemplate schema so templates can specify real Kanban/Sprint sub-configurations instead of bare workflow type strings
  • Update the renderer to build a WorkflowConfig-compatible dict from template data, fixing a bug where the template workflow type was silently dropped during rendering
  • Update all 9 builtin templates with workflow configs: sprint durations, WIP limits, ceremony schedules, and enforce_wip settings tailored to each org archetype
  • Add sprint_length and wip_limit template variables for user customization
  • Update design spec docs (organization.md, engine.md) with workflow_config examples and merge semantics

Test plan

  • 41 new tests in test_workflow_config_integration.py covering schema acceptance, renderer integration, variable substitution, backward compatibility, edge cases (unknown keys, invalid config), and parametrized builtin validation
  • Updated test_renderer.py to assert WorkflowConfig type match on all builtins
  • Full test suite passes (11709 passed)
  • Lint (ruff), format, and type-check (mypy) all pass
  • Pre-reviewed by 5 agents (code-reviewer, conventions-enforcer, docs-consistency, issue-resolution-verifier, test-quality-reviewer), 7 findings addressed

Closes #724

Release-As: 0.5.5

Copilot AI review requested due to automatic review settings April 1, 2026 12:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Walkthrough

Adds optional workflow_config support to templates and documents Kanban/Sprint sub-configurations. Many built-in templates gain sprint_length and/or wip_limit variables and corresponding workflow_config blocks. CompanyTemplate now has a workflow_config: dict[str, Any] field; the loader normalizes it into template kwargs; the renderer maps validated kanban/sprint sub-configs into the rendered RootConfig.workflow and emits an observable TEMPLATE_WORKFLOW_CONFIG_UNKNOWN_KEY event for unknown keys. Tests were added/updated to cover schema, rendering, validation, and builtin-template expectations.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding workflow configurations to builtin templates.
Description check ✅ Passed The description is well-related to the changeset, detailing schema additions, renderer updates, builtin template modifications, new template variables, documentation updates, and comprehensive test coverage.
Linked Issues check ✅ Passed The PR fully implements the coding requirements from issue #724: adds workflow_config field to CompanyTemplate, updates all 9 builtin templates with appropriate sprint durations/WIP limits/ceremonies, introduces sprint_length/wip_limit template variables, and updates design docs with examples and merge semantics.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #724 objectives. Schema additions, template updates, renderer modifications, documentation updates, new tests, and observability events are all within scope for implementing workflow config support.
Docstring Coverage ✅ Passed Docstring coverage is 55.17% which is sufficient. The required threshold is 40.00%.

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


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

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Dependency Review

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

Snapshot Warnings

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

Scanned Files

None

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between f511e1d and 347f2cf.

📒 Files selected for processing (16)
  • docs/design/engine.md
  • docs/design/organization.md
  • src/synthorg/templates/builtins/agency.yaml
  • src/synthorg/templates/builtins/consultancy.yaml
  • src/synthorg/templates/builtins/data_team.yaml
  • src/synthorg/templates/builtins/dev_shop.yaml
  • src/synthorg/templates/builtins/full_company.yaml
  • src/synthorg/templates/builtins/product_team.yaml
  • src/synthorg/templates/builtins/research_lab.yaml
  • src/synthorg/templates/builtins/solo_founder.yaml
  • src/synthorg/templates/builtins/startup.yaml
  • src/synthorg/templates/loader.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/schema.py
  • tests/unit/templates/test_renderer.py
  • 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: 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: No from __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), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
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)
Config vs runtime state: 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
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time
Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)
Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_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.py
  • src/synthorg/templates/renderer.py
  • src/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/small as aliases

Files:

  • src/synthorg/templates/loader.py
  • tests/unit/templates/test_renderer.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/schema.py
  • tests/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.md
  • 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/engine.md
  • docs/design/organization.md
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit for unit tests, @pytest.mark.integration for integration tests, @pytest.mark.e2e for e2e tests, @pytest.mark.slow for slow tests
Coverage: 80% minimum (enforced in CI)
Async: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given + @settings). Profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/templates/test_renderer.py
  • tests/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.py
  • src/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_config is modeled safely with a backward-compatible default.

This cleanly extends CompanyTemplate without breaking templates that omit workflow sub-config.

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

491-491: Good normalization update.

Including workflow_config at 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 WorkflowConfig integration 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-WorkflowConfig mapping and variable override behavior well.

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

41-48: LGTM!

New sprint_length and wip_limit variables 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_config block 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: 5 provides good sprint velocity tracking
src/synthorg/templates/builtins/research_lab.yaml (2)

30-33: LGTM!

New wip_limit variable with sensible default of 3 for a research-focused team.


119-124: LGTM!

The kanban-only workflow_config is 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_limit variable 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_length and wip_limit variables with appropriate defaults (14-day sprints, WIP limit of 3) for a product-focused team.


162-182: LGTM!

Well-structured workflow_config for a product team:

  • Kanban: Enforced WIP limits on in_progress and review columns
  • Sprint: Planning, review, and retrospective ceremonies (no daily standup aligns with PR objectives)
  • duration_tokens: 3000 for retrospective is within the valid range (100-50,000) per SprintCeremonyConfig
src/synthorg/templates/builtins/dev_shop.yaml (2)

30-37: LGTM!

New sprint_length and wip_limit variables 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_config for 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_length and wip_limit template variable examples, demonstrating the new workflow configuration parameters.


281-298: LGTM!

Clear example showing the new workflow_config structure 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 kanban will inherit the parent's sprint sub-config (and vice versa), since deep_merge recursively 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) and wip_limit (default 3) variables align with PR objectives for a fast-moving startup template.


114-130: LGTM!

Well-designed workflow_config for 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_CHECKING guard 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_builtins ensures no builtin is accidentally untested
  • Parametrized tests verify workflow types, WIP limit presence, ceremony definitions, and enforcement modes
  • test_advisory_wip_templates and test_strict_kanban_templates validate autonomy-aligned enforcement settings

The _EXPECTED_TYPES class variable with explicit workflow type expectations is a robust approach.

kanban:
wip_limits:
- column: "in_progress"
limit: {{ wip_limit | default(3) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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 "}}".

Comment on lines +470 to +472
for key in ("kanban", "sprint"):
if key in wf_config:
workflow_dict[key] = wf_config[key]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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])
As per coding guidelines: "Create new objects, never mutate existing ones." and "For `dict`/`list` fields in frozen Pydantic models ... use `copy.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 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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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 CompanyTemplate schema + loader normalization to accept workflow_config.
  • Update the renderer to build a WorkflowConfig dict (workflow type + kanban/sprint sub-configs) instead of passing a bare workflow string.
  • Update builtin templates and docs to include concrete workflow configurations and expose sprint_length / wip_limit variables.

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.

Comment on lines 520 to 526
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": {
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

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

Copilot uses AI. Check for mistakes.
Comment on lines +371 to +372
: Deep-merged (child keys override parent). A child that sets only ``kanban``
inherits the parent's ``sprint`` sub-config (and vice versa).
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
: 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.

Copilot uses AI. Check for mistakes.
Comment on lines +455 to +456
# All templates should have at least one WIP limit set.
assert len(config.workflow.kanban.wip_limits) >= 1
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

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

Suggested change
# 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()

Copilot uses AI. Check for mistakes.
"""Agile kanban templates must define at least one ceremony."""
loaded = load_template(name)
config = render_template(loaded)
assert len(config.workflow.sprint.ceremonies) >= 1
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

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

Suggested change
assert len(config.workflow.sprint.ceremonies) >= 1
default_sprint = SprintConfig()
assert config.workflow.sprint.ceremonies != default_sprint.ceremonies

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

codecov bot commented Apr 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.01%. Comparing base (43dfab3) to head (93cd268).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

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.
📢 Have feedback on the report? Share it here.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/synthorg/templates/renderer.py (1)

473-475: 🛠️ Refactor suggestion | 🟠 Major

Deep-copy kanban/sprint before storing them in workflow_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/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.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

📥 Commits

Reviewing files that changed from the base of the PR and between 347f2cf and 505ebf9.

📒 Files selected for processing (6)
  • CLAUDE.md
  • docs/design/organization.md
  • src/synthorg/observability/events/template.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/schema.py
  • 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). (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: No from __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), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
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)
Config vs runtime state: 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
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time
Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)
Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_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.py
  • src/synthorg/templates/schema.py
  • src/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/small as aliases

Files:

  • src/synthorg/observability/events/template.py
  • src/synthorg/templates/schema.py
  • tests/unit/templates/test_workflow_config_integration.py
  • src/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.unit for unit tests, @pytest.mark.integration for integration tests, @pytest.mark.e2e for e2e tests, @pytest.mark.slow for slow tests
Coverage: 80% minimum (enforced in CI)
Async: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given + @settings). Profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), use asyncio.Event().wait() instead of asyncio.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.md
  • src/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)

Comment on lines +470 to +483
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Aureliolo and others added 4 commits April 1, 2026 16:36
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>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 505ebf9 and 368e12c.

📒 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.unit for unit tests, @pytest.mark.integration for integration tests, @pytest.mark.e2e for e2e tests, @pytest.mark.slow for slow tests
Coverage: 80% minimum (enforced in CI)
Async: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given + @settings). Profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), use asyncio.Event().wait() instead of asyncio.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/small as aliases

Files:

  • tests/unit/templates/test_workflow_config_integration.py

Comment on lines +432 to +442
_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"),
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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>
@Aureliolo Aureliolo force-pushed the feat/template-workflow-configs branch from 368e12c to 93cd268 Compare April 1, 2026 15:16
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 1, 2026 15:17 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (6)
src/synthorg/templates/builtins/solo_founder.yaml (1)

71-71: ⚠️ Potential issue | 🟡 Minor

Fix 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 | 🟡 Minor

Fix 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 | 🟠 Major

Deep-copy kanban/sprint sub-configs before attaching to workflow_dict.

The current assignment passes mutable references from wf_config, which can create aliasing across config stages.

♻️ 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])
Based on learnings: "Use `copy.deepcopy()` at system boundaries ... for dict/list fields in frozen Pydantic models."
🤖 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 | 🟠 Major

Fail fast on invalid workflow_config types instead of silently ignoring them.

If workflow_config renders to a non-mapping (e.g., list/string), it is currently dropped and defaults are used. That hides template errors at a config boundary.

🛡️ 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
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 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 | 🟡 Minor

Normalize Jinja brace spacing to satisfy YAMLlint.

Line 106 still uses spacing that triggers the braces rule.

✏️ 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_shop is classified as agile_kanban but should be kanban.

According to the PR objectives and linked issue #724, dev_shop should 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

📥 Commits

Reviewing files that changed from the base of the PR and between 368e12c and 93cd268.

📒 Files selected for processing (18)
  • CLAUDE.md
  • docs/design/engine.md
  • docs/design/organization.md
  • src/synthorg/observability/events/template.py
  • src/synthorg/templates/builtins/agency.yaml
  • src/synthorg/templates/builtins/consultancy.yaml
  • src/synthorg/templates/builtins/data_team.yaml
  • src/synthorg/templates/builtins/dev_shop.yaml
  • src/synthorg/templates/builtins/full_company.yaml
  • src/synthorg/templates/builtins/product_team.yaml
  • src/synthorg/templates/builtins/research_lab.yaml
  • src/synthorg/templates/builtins/solo_founder.yaml
  • src/synthorg/templates/builtins/startup.yaml
  • src/synthorg/templates/loader.py
  • src/synthorg/templates/renderer.py
  • src/synthorg/templates/schema.py
  • tests/unit/templates/test_renderer.py
  • 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 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.unit for unit tests, @pytest.mark.integration for integration tests, @pytest.mark.e2e for e2e tests, @pytest.mark.slow for slow tests
Coverage: 80% minimum (enforced in CI)
Async: asyncio_mode = "auto" -- no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test (global in pyproject.toml -- do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed)
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing (Python): use Hypothesis (@given + @settings). Profiles: ci (50 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins
For tasks that must block indefinitely until cancelled (e.g. simulating a slow provider or stubborn coroutine), use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe

Files:

  • tests/unit/templates/test_renderer.py
  • 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/small as aliases

Files:

  • tests/unit/templates/test_renderer.py
  • src/synthorg/observability/events/template.py
  • src/synthorg/templates/loader.py
  • src/synthorg/templates/schema.py
  • src/synthorg/templates/renderer.py
  • tests/unit/templates/test_workflow_config_integration.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: No from __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), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
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)
Config vs runtime state: 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
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time
Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)
Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_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.py
  • src/synthorg/templates/loader.py
  • src/synthorg/templates/schema.py
  • src/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.md
  • 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/engine.md
  • docs/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.py
  • src/synthorg/templates/schema.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/templates/schema.py
📚 Learning: 2026-03-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.
Including workflow_config in 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 how workflow_config and template variables affect rendered workflow settings.

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

344-350: workflow_config field integration is implemented correctly.
The typed field with default_factory=dict cleanly supports optional template workflow sub-configs in pass-1 schema validation.

docs/design/organization.md (1)

281-298: Great doc alignment for workflow_config behavior.

The example and inheritance semantics clearly document how workflow_config is defined and how it behaves with extends.

Also applies to: 370-373

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

367-398: workflow_config structure is coherent and matches the template intent.

The Kanban and Sprint sub-configs are complete and consistent with the declared agile_kanban workflow.

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_config field 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_filesystem test ensures that BUILTIN_TEMPLATES stays 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_KEY warnings are emitted for builtin templates, ensuring mistyped workflow_config keys won't silently pass CI.


544-571: LGTM — concrete default value assertions.

The _EXPECTED_DEFAULTS matrix with test_builtin_default_values pins 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) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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).

@Aureliolo Aureliolo merged commit b7fe6e3 into main Apr 1, 2026
32 checks passed
@Aureliolo Aureliolo deleted the feat/template-workflow-configs branch April 1, 2026 15:25
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 1, 2026 15:25 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 1, 2026
🤖 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: update templates with workflow configs after Kanban/Sprints ship

2 participants