feat: template packs for post-setup additive team expansion#996
feat: template packs for post-setup additive team expansion#996
Conversation
- Add uses_packs field to CompanyTemplate schema - Create pack discovery service (pack_loader.py) with builtin + user paths - Add 5 built-in packs: security-team, data-team, qa-pipeline, creative-marketing, design-team - Integrate pack resolution into renderer (extends -> packs -> child) - Add GET /template-packs and POST /template-packs/apply endpoints - Add PackSelectionDialog with Add Team button on DepartmentsTab - Add pack event constants, MSW handlers, Storybook stories - Tests: pack loader, uses_packs composition, backward compatibility Closes #727
…idation - Add pack names to _chain in _resolve_packs to prevent circular pack-to-pack references - Change BUILTIN_PACKS to MappingProxyType for read-only enforcement - Add extra='forbid' to PackInfoResponse and ApplyTemplatePackResponse - Validate uses_packs is not a string before tuple() conversion - Handle empty departments explicitly instead of fragile or-fallback
- Extract _deduplicate_departments and _persist_merged_config from apply_template_pack (was 93 lines, now ~30) - Extract _validate_pack_name from load_pack (was 57 lines, now ~40) - Move Sequence and TemplateDepartmentConfig to TYPE_CHECKING block
WalkthroughAdds end-to-end support for template packs: new pack discovery/loading APIs ( Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Code Review
This pull request introduces 'template packs', which are reusable fragments of templates (agents and departments) that can be applied to an organization or composed into larger templates. The implementation includes a new TemplatePackController for listing and applying packs, a pack_loader for discovery of built-in and user-defined packs, and updates to the template rendering engine to support the uses_packs field. Feedback focuses on improving the robustness of the path traversal check in the pack loader and suggests more explicit error handling and type casting during pack resolution and data normalization.
src/synthorg/templates/loader.py
Outdated
| if "uses_packs" in data: | ||
| result["uses_packs"] = tuple(data["uses_packs"]) |
There was a problem hiding this comment.
The uses_packs field is being cast to a tuple here, but the schema defines it as a tuple of NotBlankStr. While this works, it is safer to ensure the elements are explicitly validated as strings before casting to a tuple to avoid potential type mismatches if the input data is malformed.
if "uses_packs" in data:
result["uses_packs"] = tuple(str(p) for p in data["uses_packs"])| logger.debug(TEMPLATE_PACK_LOAD_START, pack_name=name_clean) | ||
|
|
||
| # Sanitize to prevent path traversal (OS-independent). | ||
| if "/" in name_clean or "\\" in name_clean or ".." in name_clean: |
There was a problem hiding this comment.
The path traversal check is good, but it is better to use os.path.basename or pathlib.Path.name to ensure the name is treated as a filename, rather than manually checking for separators which might miss platform-specific edge cases.
| if "/" in name_clean or "\\" in name_clean or ".." in name_clean: | |
| if Path(name_clean).name != name_clean: |
| for pack_name in pack_names: | ||
| logger.info( | ||
| TEMPLATE_PACK_MERGE_START, | ||
| pack_name=pack_name, | ||
| ) | ||
| pack_loaded = load_pack(pack_name) | ||
| pack_config = _render_to_dict( | ||
| pack_loaded, | ||
| None, | ||
| locales=locales, | ||
| _chain=_chain, | ||
| custom_presets=custom_presets, | ||
| _as_parent=True, | ||
| ) | ||
| result = merge_template_configs(result, pack_config) | ||
| logger.info( | ||
| TEMPLATE_PACK_MERGE_SUCCESS, | ||
| pack_name=pack_name, | ||
| ) | ||
| return result |
There was a problem hiding this comment.
The loop iterates over pack_names and calls load_pack inside. If a pack fails to load, the entire rendering process will crash. Consider adding a try-except block inside the loop to log the error and potentially continue or raise a more descriptive error about which pack failed.
for pack_name in pack_names:
logger.info(TEMPLATE_PACK_MERGE_START, pack_name=pack_name)
try:
pack_loaded = load_pack(pack_name)
except TemplateNotFoundError as exc:
logger.error(TEMPLATE_PACK_APPLY_ERROR, pack_name=pack_name, error=str(exc))
raise
pack_config = _render_to_dict(
pack_loaded,
None,
locales=locales,
_chain=_chain,
custom_presets=custom_presets,
_as_parent=True,
)
result = merge_template_configs(result, pack_config)
logger.info(TEMPLATE_PACK_MERGE_SUCCESS, pack_name=pack_name)There was a problem hiding this comment.
Pull request overview
Adds “template packs” (small reusable YAML template fragments) that can be composed into templates via uses_packs and applied additively to an existing organization through new API endpoints and a new “Add Team” UI flow.
Changes:
- Backend: introduce pack discovery/loading, pack merge into template rendering (
extends→ packs → child), and new pack list/apply API endpoints. - Frontend: add pack API types/endpoints, pack selection dialog, and “Add Team” entry point in org edit Departments tab.
- Tests/mocks: add unit tests for pack loader +
uses_packs, plus Storybook/MSW handlers.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/pages/org-edit/PackSelectionDialog.tsx | New dialog to list packs and apply one to the current org. |
| web/src/pages/org-edit/PackSelectionDialog.stories.tsx | Storybook coverage for dialog states with MSW. |
| web/src/pages/org-edit/DepartmentsTab.tsx | Adds “Add Team” button + mounts the pack selection dialog. |
| web/src/mocks/handlers/template-packs.ts | MSW handlers for pack list/apply endpoints. |
| web/src/mocks/handlers/index.ts | Exports the new template pack MSW handlers. |
| web/src/api/types.ts | Adds TS DTOs for pack list/apply APIs. |
| web/src/api/endpoints/template-packs.ts | New client wrapper for /template-packs endpoints. |
| tests/unit/templates/test_uses_packs.py | Unit tests for pack composition ordering/behavior. |
| tests/unit/templates/test_pack_loader.py | Unit tests for pack discovery/loading and traversal rejection. |
| src/synthorg/templates/schema.py | Adds uses_packs field and loosens agent-count validation when packs are used. |
| src/synthorg/templates/renderer.py | Integrates pack merge step into rendering flow and adds pack merge logging. |
| src/synthorg/templates/packs/security-team.yaml | Built-in security team pack YAML. |
| src/synthorg/templates/packs/data-team.yaml | Built-in data team pack YAML. |
| src/synthorg/templates/packs/qa-pipeline.yaml | Built-in QA pipeline pack YAML. |
| src/synthorg/templates/packs/creative-marketing.yaml | Built-in creative/marketing pack YAML. |
| src/synthorg/templates/packs/design-team.yaml | Built-in design team pack YAML. |
| src/synthorg/templates/packs/init.py | Package marker for built-in packs. |
| src/synthorg/templates/pack_loader.py | New service to list/load packs from built-in + user directories. |
| src/synthorg/templates/loader.py | Normalizes uses_packs from YAML into the schema input. |
| src/synthorg/templates/_inheritance.py | Refactors to expose render_parent_config() for the new merge flow. |
| src/synthorg/templates/init.py | Exposes pack APIs (list/load/info) at package level. |
| src/synthorg/observability/events/template.py | Adds structured event constants for pack load/list/merge/apply. |
| src/synthorg/api/controllers/template_packs.py | New controller for listing packs and applying a pack to the running org. |
| src/synthorg/api/controllers/init.py | Registers/export TemplatePackController. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (open && !prevOpenRef.current) { | ||
| setLoading(true) | ||
| setError(null) | ||
| setApplying(null) | ||
| } | ||
| if (!open && prevOpenRef.current) { | ||
| setError(null) | ||
| setApplying(null) | ||
| setLoading(false) | ||
| } | ||
| prevOpenRef.current = open | ||
|
|
||
| useEffect(() => { | ||
| if (!open) return |
There was a problem hiding this comment.
This component updates state during render based on open transitions (setLoading, setError, setApplying). React discourages state updates in render and this can trigger warnings / extra renders (especially under StrictMode). Move this transition logic into a useEffect that watches open (or handle it in the same effect that fetches packs).
| if (open && !prevOpenRef.current) { | |
| setLoading(true) | |
| setError(null) | |
| setApplying(null) | |
| } | |
| if (!open && prevOpenRef.current) { | |
| setError(null) | |
| setApplying(null) | |
| setLoading(false) | |
| } | |
| prevOpenRef.current = open | |
| useEffect(() => { | |
| if (!open) return | |
| useEffect(() => { | |
| const wasOpen = prevOpenRef.current | |
| if (open && !wasOpen) { | |
| setLoading(true) | |
| setError(null) | |
| setApplying(null) | |
| } else if (!open && wasOpen) { | |
| setError(null) | |
| setApplying(null) | |
| setLoading(false) | |
| } | |
| prevOpenRef.current = open | |
| if (!open) return |
src/synthorg/templates/loader.py
Outdated
| if isinstance(raw_packs, str): | ||
| msg = "Template field 'uses_packs' must be a list, not a string" | ||
| logger.warning( | ||
| TEMPLATE_LOAD_STRUCTURE_ERROR, | ||
| source="template.uses_packs", | ||
| error=msg, | ||
| ) | ||
| raise TypeError(msg) | ||
| result["uses_packs"] = tuple(raw_packs) |
There was a problem hiding this comment.
uses_packs is normalized with tuple(data["uses_packs"]) without validating the input type. If the YAML mistakenly provides a string (e.g. uses_packs: "security-team"), this becomes a tuple of characters and will later try to load packs named "s", "e", etc. Add a structure/type check here (ensure list/tuple of strings) or rely on a Pydantic validator that rejects string input and raises a clear TemplateValidationError.
| if isinstance(raw_packs, str): | |
| msg = "Template field 'uses_packs' must be a list, not a string" | |
| logger.warning( | |
| TEMPLATE_LOAD_STRUCTURE_ERROR, | |
| source="template.uses_packs", | |
| error=msg, | |
| ) | |
| raise TypeError(msg) | |
| result["uses_packs"] = tuple(raw_packs) | |
| if raw_packs is None: | |
| # Treat explicit null as "no packs" for robustness. | |
| result["uses_packs"] = () | |
| else: | |
| # Expect a list/tuple of strings; reject wrong structures early. | |
| if ( | |
| isinstance(raw_packs, str) | |
| or not isinstance(raw_packs, (list, tuple)) | |
| or not all(isinstance(p, str) for p in raw_packs) | |
| ): | |
| msg = ( | |
| "Template field 'template.uses_packs' must be a list or tuple " | |
| "of strings" | |
| ) | |
| logger.warning( | |
| TEMPLATE_LOAD_STRUCTURE_ERROR, | |
| source="template.uses_packs", | |
| error=msg, | |
| ) | |
| raise TemplateValidationError(msg) | |
| result["uses_packs"] = tuple(raw_packs) |
| uses_packs: tuple[NotBlankStr, ...] = Field( | ||
| default=(), | ||
| description="Pack names to compose into this template", | ||
| ) | ||
|
|
There was a problem hiding this comment.
uses_packs currently has no normalization/validation beyond NotBlankStr. In particular, there’s no guard against a single string value being treated as an iterable, and no normalization (e.g. strip/lower) to align with load_pack()’s lookup behavior. Consider adding a @field_validator("uses_packs", mode="before") to reject str inputs and normalize each entry (strip/lower) so loader + schema are robust even if _normalize_template_data() changes.
| pack_loaded, | ||
| None, | ||
| locales=locales, | ||
| _chain=_chain | {pack_name}, |
There was a problem hiding this comment.
Pack resolution doesn’t participate in circular detection: _resolve_packs() calls _render_to_dict(..., _chain=_chain) without adding the current pack_name to the chain. Since packs share CompanyTemplate (and can themselves declare uses_packs), a pack-to-pack cycle (or self-reference) can recurse indefinitely. Track applied pack names in the chain (e.g. _chain | {f"<pack:{pack_name}>"}) and raise a clear error on repeats.
| _chain=_chain | {pack_name}, | |
| _chain=_chain | {f"<pack:{pack_name}>"}, |
| pack_loaded = load_pack(pack_name) | ||
| pack_config = _render_to_dict( | ||
| pack_loaded, | ||
| None, | ||
| locales=locales, | ||
| _chain=_chain | {pack_name}, | ||
| custom_presets=custom_presets, | ||
| _as_parent=True, | ||
| ) |
There was a problem hiding this comment.
When rendering packs, _render_to_dict() is called with variables=None, so packs can’t access the child template’s resolved variables (and required pack variables will fail even if provided by the child). If packs are intended to support Jinja2 variables / defaults like normal templates, pass through the child’s vars_dict (or a merged variable set similar to collect_parent_variables) when rendering each pack.
| meta = loaded.template.metadata | ||
| return PackInfo( | ||
| name=name, | ||
| display_name=meta.name, | ||
| description=meta.description, | ||
| source=source, | ||
| tags=meta.tags, | ||
| agent_count=len(loaded.template.agents), |
There was a problem hiding this comment.
User pack names are taken from path.stem without normalization, but load_pack() lowercases the requested name and looks for a lowercase <name>.yaml. On case-sensitive filesystems, a user file like Security-Team.yaml will be listed as Security-Team but can’t be loaded/applied. Normalize user pack filenames to a canonical form (e.g. lowercase stems) and/or make load_pack() search the directory case-insensitively.
| variables: dict[str, Any] = Field( | ||
| default_factory=dict, | ||
| description="Optional variable overrides", | ||
| ) |
There was a problem hiding this comment.
ApplyTemplatePackRequest accepts variables, but the controller never uses them. Additionally, the pack is never rendered through the Jinja2 (Pass 2) renderer, so user packs that rely on variables (or any Jinja2 fields besides agent name) won’t work as the API contract suggests. Either remove variables from the request, or render the pack via render_template(loaded, data.variables) and persist agents/departments from the rendered config.
| variables: dict[str, Any] = Field( | |
| default_factory=dict, | |
| description="Optional variable overrides", | |
| ) |
| return json.loads(entry.value) if entry.value else [] | ||
| except SettingNotFoundError: | ||
| return [] | ||
|
|
||
|
|
There was a problem hiding this comment.
_read_setting_list() calls json.loads() without handling JSONDecodeError. If the stored agents/departments settings are corrupted (something other controllers already guard against), this endpoint will 500 instead of returning a controlled API error. Consider catching JSONDecodeError and either returning an empty list (like SettingNotFoundError) or raising a validation error with a clear message.
| return json.loads(entry.value) if entry.value else [] | |
| except SettingNotFoundError: | |
| return [] | |
| except SettingNotFoundError: | |
| return [] | |
| if not entry.value: | |
| return [] | |
| try: | |
| return json.loads(entry.value) | |
| except json.JSONDecodeError: | |
| # Corrupted JSON in settings: treat as missing/empty for robustness. | |
| return [] |
There was a problem hiding this comment.
Actionable comments posted: 14
🤖 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/api/controllers/template_packs.py`:
- Around line 153-166: The handler currently expands the raw loaded.template
without applying caller-supplied variables, so pack fields that use Jinja
variables are never rendered; before calling
expand_template_agents(loaded.template) you must render the pack template with
the request variables (the same render step used by full template application) —
e.g., call the project’s template render function (the one used elsewhere for
full template application) passing loaded.template and data.variables, handle
any render errors consistently (raise the same error type or log as
TEMPLATE_PACK_APPLY_ERROR), then pass the rendered template into
expand_template_agents; keep load_pack(...) and the existing
TemplateNotFoundError handling unchanged.
- Around line 168-208: The current read-modify-write splits updates across two
settings keys via _read_setting_list and settings_svc.set which can cause races
and partial writes; change this to a single atomic update: read the current
company settings once (both agents and departments) using _read_setting_list or
an existing settings_svc.get_company helper, compute merged_agents and
merged_depts as you do now, then call a single atomic settings operation (e.g.
settings_svc.set_company or settings_svc.transaction/compare_and_swap using the
original combined value) to persist both agents and departments together; if
your settings service lacks transactional API add/use a compare-and-swap that
passes the expected current company payload and the new payload so concurrent
applies fail safely, and keep the existing TEMPLATE_PACK_APPLY_ERROR logging
when a compare-and-swap conflict or error occurs.
- Around line 175-179: The pack application is losing department fields because
departments are passed through departments_to_json (when building new_depts_raw
from loaded.template.departments), but that helper only emits name and
budget_percent; update the merge to preserve head_role and reporting_lines
instead of using departments_to_json. Specifically, replace the
departments_to_json round-trip or modify departments_to_json to include
head_role and reporting_lines so new_depts_raw contains full department dicts
(ensure keys "head_role" and "reporting_lines" from loaded.template.departments
are carried into new_depts_raw) and then merge against existing_dept_names as
before (symbols: departments_to_json, new_depts_raw, current_depts,
loaded.template.departments, head_role, reporting_lines).
In `@src/synthorg/templates/loader.py`:
- Around line 500-501: The code blindly converts data["uses_packs"] to a tuple
which splits strings into characters; change the logic around the "uses_packs"
key in loader.py to first validate the incoming type (e.g., ensure
isinstance(data["uses_packs"], (list, tuple)) and not
isinstance(data["uses_packs"], str)) and that each item is a str, then set
result["uses_packs"] = tuple(data["uses_packs"]); if validation fails, either
raise a clear ValueError or log a descriptive error and skip/ignore the field so
invalid string inputs like "design-team" are not converted into ("d","e",...).
Ensure the validation references the same data and result variables and the
"uses_packs" key so it’s easy to locate.
In `@src/synthorg/templates/pack_loader.py`:
- Around line 193-201: The pack listing uses path.stem verbatim which conflicts
with load_pack()'s lowercase lookup; update _collect_user_packs to normalize
stems to a canonical key (e.g., name_lower = path.stem.lower()), skip files
whose normalized key is already in seen (to dedupe case-variants), and use that
normalized key when assigning into seen and when calling _pack_info_from_loaded
(still pass the loaded data from _load_from_file and source "user"); reference
symbols: _collect_user_packs, _USER_PACKS_DIR, _load_from_file,
_pack_info_from_loaded.
- Around line 135-155: The user YAML override branch currently returns
immediately and can shadow a valid builtin when the user file is unreadable or
invalid; update the load path (the block using _USER_PACKS_DIR, user_path, and
_load_from_file) to attempt to load the user file but catch parsing/IO failures
from _load_from_file (or treat a falsy/None result as failure), log a clear
warning, and then fall back to loading the builtin via _load_builtin if
name_clean is in BUILTIN_PACKS; keep the existing TEMPLATE_PACK_LOAD_SUCCESS
debug logs but set source="builtin" for the fallback case so list_packs() and
load behavior remain consistent.
- Around line 36-42: BUILTIN_PACKS is a mutable module-global dict; make it
immutable by constructing a deep copy of the literal and wrapping it in
types.MappingProxyType before exporting. Import copy.deepcopy and
types.MappingProxyType, build a new dict (e.g., deepcopied =
copy.deepcopy({...})) and then set BUILTIN_PACKS = MappingProxyType(deepcopied)
(adjust the type annotation to Mapping[str, str] if desired) so the exported
BUILTIN_PACKS is read-only and cannot be mutated at runtime.
In `@src/synthorg/templates/packs/creative-marketing.yaml`:
- Around line 1-45: The pyproject build config under the
[tool.hatch.build.targets.wheel] target currently only declares SQL artifacts,
causing synthorg.templates.packs YAML files to be omitted from wheels; update
that target's artifacts list (the artifacts key) to include a glob for YAML
files (e.g., add "**/*.yaml" alongside the existing "**/*.sql") so
importlib.resources can find synthorg.templates.packs YAMLs at runtime when
installed from a wheel or sdist.
In `@src/synthorg/templates/renderer.py`:
- Around line 183-189: _resolve_packs is currently invoking pack rendering with
None for the variable set, which drops caller/template-supplied variables
(base_config) so parameterized packs render with defaults or fail; fix by
threading the resolved variable set (the updated base_config or a resolved_vars
dict returned from _resolve_packs) into the pack-rendering calls inside
_resolve_packs and any callers (locations around the template.uses_packs
invocations shown), i.e., pass the resolved_vars/base_config into the pack
render function(s) used by _resolve_packs (and corresponding calls at the other
noted spots) so packs receive the template's variables rather than None.
In `@web/src/api/types.ts`:
- Around line 1522-1535: PackInfoResponse does not expose variable metadata so
the UI cannot discover required inputs/defaults before calling
ApplyTemplatePackRequest; update the PackInfoResponse (or add a dedicated DTO
used by the pack listing) to include the same variables shape as
TemplateInfoResponse.variables (e.g., a variables field describing each
variable’s name, type, required flag, and default) so the client can render and
validate inputs prior to submitting ApplyTemplatePackRequest.variables; ensure
the new field is readonly and mirrors the TemplateInfoResponse.variables
contract used elsewhere in the codebase.
In `@web/src/mocks/handlers/template-packs.ts`:
- Around line 55-62: The 404 branch in the http.post handler returns an
ApiResponse with error_detail: null which doesn't match the client's required
ErrorDetail shape; update the error response in the handler used by mockPacks
(the http.post('*/template-packs/apply' callback) to return a full RFC
9457-style ErrorDetail object (or call the shared error helper used elsewhere)
instead of null so the mock envelope matches ApiResponse<...>
expectations—ensure the HttpResponse.json payload fills error and error_detail
with the same structured fields the client types expect.
In `@web/src/pages/org-edit/PackSelectionDialog.tsx`:
- Around line 63-75: The current try/catch treats failures of fetchCompanyData()
as if applyTemplatePack() failed; change the control flow so a successful
applyTemplatePack({ pack_name: packName }) immediately shows the success toast
(addToast) and closes the dialog (onOpenChange(false)), then perform the refresh
in a separate try/catch: call useCompanyStore.getState().fetchCompanyData()
inside its own try and, if that refresh fails, surface a non-blocking warning
(e.g., addToast with variant 'warning' or a dedicated warning handler) instead
of calling setError or leaving the dialog open; keep the outer finally to call
setApplying(null). Reference functions/variables: applyTemplatePack, addToast,
useCompanyStore.getState().fetchCompanyData, onOpenChange, setError,
setApplying.
- Around line 129-166: Extract the large JSX inside packs.map into a new
reusable PackListItem component (e.g., function PackListItem({pack, disabled,
busy, applying, onApply})), move the button, Loader2, StatPill and custom badge
rendering into that component, and update the map to render <PackListItem
key={pack.name} pack={pack} disabled={disabled} busy={busy} applying={applying}
onApply={handleApply} />; ensure PackListItem calls onApply(pack.name) for
clicks and preserves all classNames and props so behavior (disabled state, apply
spinner, stats) remains identical.
- Around line 28-57: The render-time prevOpenRef block sets loading only during
render, so when the component mounts with open=true the async fetch
(listTemplatePacks called in useEffect) starts while loading remains false; move
the "start request" state updates into the effect: in the useEffect that depends
on open, when open is true immediately call setLoading(true), setError(null),
setApplying(null) before invoking listTemplatePacks, and keep the cancelled
guard and finally handler to setLoading(false); remove or simplify the
prevOpenRef render-time state mutations (prevOpenRef.current = open can remain)
so state changes happen inside the effect, referencing prevOpenRef, useEffect,
listTemplatePacks, setLoading, setError, and setApplying to locate the changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3f92e4ed-9b6c-4600-a097-da0224d30d5e
📒 Files selected for processing (24)
src/synthorg/api/controllers/__init__.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/observability/events/template.pysrc/synthorg/templates/__init__.pysrc/synthorg/templates/_inheritance.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/templates/packs/__init__.pysrc/synthorg/templates/packs/creative-marketing.yamlsrc/synthorg/templates/packs/data-team.yamlsrc/synthorg/templates/packs/design-team.yamlsrc/synthorg/templates/packs/qa-pipeline.yamlsrc/synthorg/templates/packs/security-team.yamlsrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.pytests/unit/templates/test_pack_loader.pytests/unit/templates/test_uses_packs.pyweb/src/api/endpoints/template-packs.tsweb/src/api/types.tsweb/src/mocks/handlers/index.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/DepartmentsTab.tsxweb/src/pages/org-edit/PackSelectionDialog.stories.tsxweb/src/pages/org-edit/PackSelectionDialog.tsx
📜 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). (9)
- GitHub Check: Deploy Preview
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dashboard Test
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Agent
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations— Python 3.14 has PEP 649
Useexcept A, B:(no parentheses) per PEP 758 except syntax — ruff enforces this on Python 3.14
Type hints required on all public functions, enforced by mypy strict mode
Docstrings must use Google style and are required on public classes and functions, enforced by ruff D rules
Never mutate existing objects — create new objects; for non-Pydantic internal collections, usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Use frozen Pydantic models for config/identity; separate mutable-via-copy models usingmodel_copy(update=...)for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing and validating redundant fields
UseNotBlankStr(fromcore.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple variants — instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) for structured concurrency instead of barecreate_task
Line length must be 88 characters, enforced by ruff
Functions must be less than 50 lines; files must be less than 800 lines
Logging variable name must always belogger(not_logger, notlog)
Always use structured logging withlogger.info(EVENT, key=value)— neverlogger.info("msg %s", val)
All error paths must log at WAR...
Files:
src/synthorg/templates/packs/__init__.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/_inheritance.pysrc/synthorg/api/controllers/__init__.pytests/unit/templates/test_uses_packs.pysrc/synthorg/templates/__init__.pysrc/synthorg/templates/schema.pysrc/synthorg/observability/events/template.pytests/unit/templates/test_pack_loader.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/renderer.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code, except inobservability/setup.py,observability/sinks.py,observability/syslog_handler.py, andobservability/http_handler.py
Always use event name constants from the domain-specific module undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool); import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Files:
src/synthorg/templates/packs/__init__.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/_inheritance.pysrc/synthorg/api/controllers/__init__.pysrc/synthorg/templates/__init__.pysrc/synthorg/templates/schema.pysrc/synthorg/observability/events/template.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/renderer.py
web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (web/CLAUDE.md)
web/src/**/*.{tsx,ts}: ALWAYS reuse existing components fromweb/src/components/ui/before creating new ones (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup, Drawer, InputField, SelectField, SliderField, ToggleField, TaskStatusIndicator, PriorityBadge, ProviderHealthBadge, TokenUsageBar, CodeMirrorEditor, SegmentedControl, ThemeToggle, LiveRegion, MobileUnsupportedOverlay, LazyCodeMirrorEditor, TagInput, MetadataGrid, ProjectStatusBadge, ContentTypeBadge)
Use Tailwind semantic classes (text-foreground,bg-card,text-accent,text-success,bg-danger) or CSS variables (var(--so-*)) for colors. NEVER hardcode hex values or rgba() in.tsx/.tsfiles
Usefont-sansorfont-monofor typography (maps to Geist tokens). NEVER setfontFamilydirectly
Use density-aware tokens (p-card,gap-section-gap,gap-grid-gap) or standard Tailwind spacing. NEVER hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover),border-border,border-bright) for shadows and borders. NEVER hardcode shadow or border values
Importcnfrom@/lib/utilsfor conditional class merging
Do NOT recreate status dots inline -- use<StatusBadge>component
Do NOT build card-with-header layouts from scratch -- use<SectionCard>component
Do NOT create metric displays withtext-metric font-bold-- use<MetricCard>component
Do NOT render initials circles manually -- use<Avatar>component
Do NOT create complex (>8 line) JSX inside.map()-- extract to a shared component
Use Framer Motion presets from@/lib/motioninstead of hardcoded transition durations
Use ErrorBoundary withlevelprop set to'page','section', or'component'for appropriate error boundary scoping
Usesideprop (left or right, default right) for Drawer component, with ...
Files:
web/src/mocks/handlers/index.tsweb/src/api/types.tsweb/src/api/endpoints/template-packs.tsweb/src/pages/org-edit/DepartmentsTab.tsxweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsxweb/src/pages/org-edit/PackSelectionDialog.stories.tsx
web/src/**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (web/CLAUDE.md)
ESLint must enforce zero warnings on all web frontend code
Files:
web/src/mocks/handlers/index.tsweb/src/api/types.tsweb/src/api/endpoints/template-packs.tsweb/src/pages/org-edit/DepartmentsTab.tsxweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsxweb/src/pages/org-edit/PackSelectionDialog.stories.tsx
web/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (web/CLAUDE.md)
CSS side-effect imports need type declarations; use Vite's
/// <reference types="vite/client" />directive
web/src/**/*.{ts,tsx}: Eslint-web pre-commit hook enforced with zero warnings on web dashboard TypeScript/React files
Always reuse existing components fromweb/src/components/ui/before creating new ones
Files:
web/src/mocks/handlers/index.tsweb/src/api/types.tsweb/src/api/endpoints/template-packs.tsweb/src/pages/org-edit/DepartmentsTab.tsxweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsxweb/src/pages/org-edit/PackSelectionDialog.stories.tsx
web/src/**/*.{ts,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Never hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions in web code — use design tokens and
@/lib/motionpresets
Files:
web/src/mocks/handlers/index.tsweb/src/api/types.tsweb/src/api/endpoints/template-packs.tsweb/src/pages/org-edit/DepartmentsTab.tsxweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsxweb/src/pages/org-edit/PackSelectionDialog.stories.tsx
web/src/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
A PostToolUse hook (
scripts/check_web_design_system.py) enforces component reuse and design token rules on every Edit/Write toweb/src/
Files:
web/src/mocks/handlers/index.tsweb/src/api/types.tsweb/src/api/endpoints/template-packs.tsweb/src/pages/org-edit/DepartmentsTab.tsxweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsxweb/src/pages/org-edit/PackSelectionDialog.stories.tsx
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowto classify tests
Useasyncio_mode = "auto"for async tests — no manual@pytest.mark.asyncioneeded
Global timeout is 30 seconds per test inpyproject.toml— do not add per-filepytest.mark.timeout(30)markers; non-default overrides liketimeout(60)are allowed
Prefer@pytest.mark.parametrizefor testing similar cases
Use Hypothesis for property-based testing in Python with@given+@settingsdecorators
Use Hypothesis profiles:ci(50 examples, default) anddev(1000 examples), controlled viaHYPOTHESIS_PROFILEenv var
Never skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; for timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()instead of widening timing margins; for tasks that must block indefinitely, useasyncio.Event().wait()
No-redundant-timeout pre-commit hook enforced
Files:
tests/unit/templates/test_uses_packs.pytests/unit/templates/test_pack_loader.py
web/src/**/*.stories.tsx
📄 CodeRabbit inference engine (web/CLAUDE.md)
web/src/**/*.stories.tsx: Storybook 10: Import fromstorybook/testinstead of@storybook/test
Storybook 10: Import fromstorybook/actionsinstead of@storybook/addon-actions
Storybook 10: Useparameters.a11y.test: 'error' | 'todo' | 'off'for a11y testing configuration (replaces old.elementand.manual)
Files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
🧠 Learnings (37)
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.
Applied to files:
src/synthorg/templates/packs/__init__.pysrc/synthorg/api/controllers/__init__.pysrc/synthorg/templates/__init__.pysrc/synthorg/templates/schema.pysrc/synthorg/templates/packs/design-team.yamlsrc/synthorg/templates/pack_loader.pysrc/synthorg/templates/packs/data-team.yamlsrc/synthorg/templates/packs/creative-marketing.yamlsrc/synthorg/templates/renderer.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/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:
src/synthorg/api/controllers/__init__.pysrc/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/api/controllers/__init__.pysrc/synthorg/templates/__init__.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management
Applied to files:
src/synthorg/api/controllers/__init__.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/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).
Applied to files:
src/synthorg/api/controllers/__init__.py
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/tsconfig.json : TypeScript 6: Explicitly list needed types in `types` array (e.g., `"types": ["vitest/globals"]`) -- `types` no longer auto-discovers `types/*`
Applied to files:
web/src/api/types.ts
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.{tsx,ts} : ALWAYS reuse existing components from `web/src/components/ui/` before creating new ones (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup, Drawer, InputField, SelectField, SliderField, ToggleField, TaskStatusIndicator, PriorityBadge, ProviderHealthBadge, TokenUsageBar, CodeMirrorEditor, SegmentedControl, ThemeToggle, LiveRegion, MobileUnsupportedOverlay, LazyCodeMirrorEditor, TagInput, MetadataGrid, ProjectStatusBadge, ContentTypeBadge)
Applied to files:
web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.{tsx,ts} : Import `cn` from `@/lib/utils` for conditional class merging
Applied to files:
web/src/pages/org-edit/DepartmentsTab.tsx
📚 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 web/src/**/*.{ts,tsx} : Use React 19, TypeScript 6.0+, and design system tokens from shadcn/ui + Tailwind CSS 4 + Radix UI in web dashboard
Applied to files:
web/src/pages/org-edit/DepartmentsTab.tsxweb/src/pages/org-edit/PackSelectionDialog.tsx
📚 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 web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)
Applied to files:
web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/components/layout/**/*.tsx : Mount `ToastContainer` once in AppLayout for the global toast notification system
Applied to files:
web/src/pages/org-edit/DepartmentsTab.tsx
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)
Applied to files:
src/synthorg/observability/events/template.pysrc/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 src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging
Applied to files:
src/synthorg/observability/events/template.pysrc/synthorg/templates/renderer.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/templates/packs/security-team.yaml
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.
Applied to files:
src/synthorg/templates/packs/security-team.yaml
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
Applied to files:
src/synthorg/templates/packs/security-team.yaml
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
Applied to files:
src/synthorg/templates/packs/design-team.yaml
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations
Applied to files:
src/synthorg/templates/packs/design-team.yaml
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)
Applied to files:
src/synthorg/templates/packs/design-team.yaml
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/components/ui/**/*.tsx : Create a `.stories.tsx` Storybook file alongside each new shared component with all states (default, hover, loading, error, empty)
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.stories.tsx : Storybook 10: Import from `storybook/test` instead of `storybook/test`
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.stories.tsx : Storybook 10: Import from `storybook/actions` instead of `storybook/addon-actions`
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.stories.tsx : Storybook 10: Use `parameters.a11y.test: 'error' | 'todo' | 'off'` for a11y testing configuration (replaces old `.element` and `.manual`)
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/.storybook/{main,preview}.{ts,tsx} : Storybook 10: Use `defineMain` from `storybook/react-vite/node` and `definePreview` from `storybook/react-vite` in Storybook config files
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/.storybook/preview.tsx : Storybook 10: Use `parameters.backgrounds.options` (object keyed by name) and `initialGlobals.backgrounds.value` for backgrounds configuration
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.stories.tsx
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings
Applied to files:
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-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.
Applied to files:
src/synthorg/templates/renderer.py
🔇 Additional comments (6)
src/synthorg/templates/schema.py (1)
383-386:uses_packsschema + range-validation skip logic looks correct.The new
uses_packsfield typing and the conditional skip in agent-count validation align with pack-based composition flow.Also applies to: 406-407
src/synthorg/templates/_inheritance.py (1)
157-206: Good refactor split for parent rendering vs merge.Extracting
render_parent_config()cleanly supports pack layering while keeping merge behavior intact through_render_and_merge_parent().Also applies to: 208-229
src/synthorg/templates/packs/design-team.yaml (1)
1-45: Pack definition is coherent and complete.Metadata, department structure, and agent entries are consistent with the intended two-agent design team pack.
web/src/mocks/handlers/index.ts (1)
25-25: Handler index export update looks good.Re-exporting
templatePacksListkeeps the handler barrel aligned with the new API flow.src/synthorg/api/controllers/__init__.py (1)
32-32: Controller wiring is complete.Import, router registration (
ALL_CONTROLLERS), and__all__exposure forTemplatePackControllerare all in place.Also applies to: 60-60, 90-90
src/synthorg/templates/__init__.py (1)
49-54: Public templates API extension looks good.
PackInfo,list_builtin_packs,list_packs, andload_packare correctly re-exported in the package surface.Also applies to: 70-70, 81-81, 83-83, 85-85
| # Load and validate the pack. | ||
| try: | ||
| loaded = load_pack(data.pack_name) | ||
| except TemplateNotFoundError as exc: | ||
| logger.warning( | ||
| TEMPLATE_PACK_APPLY_ERROR, | ||
| pack_name=data.pack_name, | ||
| error=str(exc), | ||
| ) | ||
| msg = f"Template pack {data.pack_name!r} not found" | ||
| raise NotFoundError(msg) from exc | ||
|
|
||
| # Expand pack agents into persistable dicts. | ||
| pack_agents = expand_template_agents(loaded.template) |
There was a problem hiding this comment.
Render pack variables before expanding agents.
ApplyTemplatePackRequest accepts variables, but this handler only uses the pass-1 loaded.template. Any pack field that relies on Jinja/template variables is never rendered here, so live-apply ignores caller-supplied values. This needs the same render step used by full template application, or variables should be removed from the API until it works.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/api/controllers/template_packs.py` around lines 153 - 166, The
handler currently expands the raw loaded.template without applying
caller-supplied variables, so pack fields that use Jinja variables are never
rendered; before calling expand_template_agents(loaded.template) you must render
the pack template with the request variables (the same render step used by full
template application) — e.g., call the project’s template render function (the
one used elsewhere for full template application) passing loaded.template and
data.variables, handle any render errors consistently (raise the same error type
or log as TEMPLATE_PACK_APPLY_ERROR), then pass the rendered template into
expand_template_agents; keep load_pack(...) and the existing
TemplateNotFoundError handling unchanged.
| export interface PackInfoResponse { | ||
| readonly name: string | ||
| readonly display_name: string | ||
| readonly description: string | ||
| readonly source: 'builtin' | 'user' | ||
| readonly tags: readonly string[] | ||
| readonly agent_count: number | ||
| readonly department_count: number | ||
| } | ||
|
|
||
| export interface ApplyTemplatePackRequest { | ||
| readonly pack_name: string | ||
| readonly variables?: Record<string, unknown> | ||
| } |
There was a problem hiding this comment.
Expose pack variable metadata in the listing contract.
ApplyTemplatePackRequest already accepts variables, but PackInfoResponse only gives the UI a name/description/counts. That makes any pack with required variables impossible to apply from the web flow, because the client has no way to discover inputs/defaults before posting. Mirror TemplateInfoResponse.variables here, or add a dedicated details DTO, before shipping user-pack support.
🧩 Suggested contract change
export interface PackInfoResponse {
readonly name: string
readonly display_name: string
readonly description: string
readonly source: 'builtin' | 'user'
readonly tags: readonly string[]
+ readonly variables: readonly TemplateVariable[]
readonly agent_count: number
readonly department_count: number
}📝 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.
| export interface PackInfoResponse { | |
| readonly name: string | |
| readonly display_name: string | |
| readonly description: string | |
| readonly source: 'builtin' | 'user' | |
| readonly tags: readonly string[] | |
| readonly agent_count: number | |
| readonly department_count: number | |
| } | |
| export interface ApplyTemplatePackRequest { | |
| readonly pack_name: string | |
| readonly variables?: Record<string, unknown> | |
| } | |
| export interface PackInfoResponse { | |
| readonly name: string | |
| readonly display_name: string | |
| readonly description: string | |
| readonly source: 'builtin' | 'user' | |
| readonly tags: readonly string[] | |
| readonly variables: readonly TemplateVariable[] | |
| readonly agent_count: number | |
| readonly department_count: number | |
| } | |
| export interface ApplyTemplatePackRequest { | |
| readonly pack_name: string | |
| readonly variables?: Record<string, unknown> | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/api/types.ts` around lines 1522 - 1535, PackInfoResponse does not
expose variable metadata so the UI cannot discover required inputs/defaults
before calling ApplyTemplatePackRequest; update the PackInfoResponse (or add a
dedicated DTO used by the pack listing) to include the same variables shape as
TemplateInfoResponse.variables (e.g., a variables field describing each
variable’s name, type, required flag, and default) so the client can render and
validate inputs prior to submitting ApplyTemplatePackRequest.variables; ensure
the new field is readonly and mirrors the TemplateInfoResponse.variables
contract used elsewhere in the codebase.
| const prevOpenRef = useRef(open) | ||
| if (open && !prevOpenRef.current) { | ||
| setLoading(true) | ||
| setError(null) | ||
| setApplying(null) | ||
| } | ||
| if (!open && prevOpenRef.current) { | ||
| setError(null) | ||
| setApplying(null) | ||
| setLoading(false) | ||
| } | ||
| prevOpenRef.current = open | ||
|
|
||
| useEffect(() => { | ||
| if (!open) return | ||
| let cancelled = false | ||
| listTemplatePacks() | ||
| .then((data) => { | ||
| if (!cancelled) setPacks(data) | ||
| }) | ||
| .catch((err) => { | ||
| if (!cancelled) setError(getErrorMessage(err)) | ||
| }) | ||
| .finally(() => { | ||
| if (!cancelled) setLoading(false) | ||
| }) | ||
| return () => { | ||
| cancelled = true | ||
| } | ||
| }, [open]) |
There was a problem hiding this comment.
Set loading when the request starts, not during render.
loading only flips in the render-time prevOpenRef block, so any mount with open=true starts the request while loading is still false. In that path, the dialog briefly renders the empty state before the pack list arrives; web/src/pages/org-edit/PackSelectionDialog.stories.tsx already exercises exactly that mount shape with open: true.
🩹 Suggested shape
-import { useCallback, useEffect, useRef, useState } from 'react'
+import { useCallback, useEffect, useState } from 'react'
...
- const prevOpenRef = useRef(open)
- if (open && !prevOpenRef.current) {
- setLoading(true)
- setError(null)
- setApplying(null)
- }
- if (!open && prevOpenRef.current) {
- setError(null)
- setApplying(null)
- setLoading(false)
- }
- prevOpenRef.current = open
-
useEffect(() => {
- if (!open) return
+ if (!open) {
+ setError(null)
+ setApplying(null)
+ setLoading(false)
+ return
+ }
+
+ setLoading(true)
+ setError(null)
+ setApplying(null)
+
let cancelled = false
listTemplatePacks()
.then((data) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/pages/org-edit/PackSelectionDialog.tsx` around lines 28 - 57, The
render-time prevOpenRef block sets loading only during render, so when the
component mounts with open=true the async fetch (listTemplatePacks called in
useEffect) starts while loading remains false; move the "start request" state
updates into the effect: in the useEffect that depends on open, when open is
true immediately call setLoading(true), setError(null), setApplying(null) before
invoking listTemplatePacks, and keep the cancelled guard and finally handler to
setLoading(false); remove or simplify the prevOpenRef render-time state
mutations (prevOpenRef.current = open can remain) so state changes happen inside
the effect, referencing prevOpenRef, useEffect, listTemplatePacks, setLoading,
setError, and setApplying to locate the changes.
| {!loading && !error && packs.length > 0 && ( | ||
| <StaggerGroup className="space-y-2 max-h-80 overflow-y-auto pr-1"> | ||
| {packs.map((pack) => ( | ||
| <StaggerItem key={pack.name}> | ||
| <button | ||
| type="button" | ||
| disabled={disabled || busy} | ||
| onClick={() => handleApply(pack.name)} | ||
| className={cn( | ||
| 'w-full rounded-lg border border-border p-3 text-left transition-colors', | ||
| 'hover:border-accent hover:bg-card/50', | ||
| 'disabled:opacity-50 disabled:cursor-not-allowed', | ||
| )} | ||
| > | ||
| <div className="flex items-start justify-between gap-2"> | ||
| <div className="min-w-0 flex-1"> | ||
| <p className="text-sm font-medium text-foreground truncate"> | ||
| {pack.display_name} | ||
| </p> | ||
| <p className="text-xs text-text-secondary mt-0.5 line-clamp-2"> | ||
| {pack.description} | ||
| </p> | ||
| </div> | ||
| {applying === pack.name && ( | ||
| <Loader2 className="size-4 shrink-0 animate-spin text-accent" /> | ||
| )} | ||
| </div> | ||
| <div className="flex items-center gap-2 mt-2"> | ||
| <StatPill label="Agents" value={pack.agent_count} /> | ||
| <StatPill label="Depts" value={pack.department_count} /> | ||
| {pack.source === 'user' && ( | ||
| <span className="text-[10px] text-text-secondary bg-card rounded px-1.5 py-0.5"> | ||
| custom | ||
| </span> | ||
| )} | ||
| </div> | ||
| </button> | ||
| </StaggerItem> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract the pack row out of packs.map().
This mapped JSX block is already large enough that the busy/disabled/render states are hard to follow. Pulling it into a small PackListItem component will make the dialog logic much easier to maintain.
As per coding guidelines, "Do NOT create complex (>8 line) JSX inside .map() -- extract to a shared component".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/pages/org-edit/PackSelectionDialog.tsx` around lines 129 - 166,
Extract the large JSX inside packs.map into a new reusable PackListItem
component (e.g., function PackListItem({pack, disabled, busy, applying,
onApply})), move the button, Loader2, StatPill and custom badge rendering into
that component, and update the map to render <PackListItem key={pack.name}
pack={pack} disabled={disabled} busy={busy} applying={applying}
onApply={handleApply} />; ensure PackListItem calls onApply(pack.name) for
clicks and preserves all classNames and props so behavior (disabled state, apply
spinner, stats) remains identical.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #996 +/- ##
==========================================
- Coverage 91.93% 91.67% -0.26%
==========================================
Files 643 645 +2
Lines 34897 35141 +244
Branches 3423 3448 +25
==========================================
+ Hits 32082 32215 +133
- Misses 2211 2314 +103
- Partials 604 612 +8 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (7)
src/synthorg/templates/renderer.py (1)
183-189:⚠️ Potential issue | 🟠 MajorPass
vars_dictinto pack rendering.
_resolve_packs()still renders every pack withNone, so any pack that relies on caller/template variables will resolve with placeholders/defaults or fail required-variable validation.Also applies to: 200-206, 233-241
🤖 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 183 - 189, The pack resolution calls to _resolve_packs are missing the vars_dict argument, causing packs to render with None; update each call that passes (base_config, template.uses_packs, locales=locales, _chain=_chain, custom_presets=custom_presets) to also pass vars_dict=vars_dict (or the correct variable container) so pack rendering in _resolve_packs receives caller/template variables; ensure you change the occurrences around the base_config assignment and the other two places referenced (the similar calls) so all invocations include vars_dict.src/synthorg/templates/pack_loader.py (2)
149-169:⚠️ Potential issue | 🟠 MajorDon't let a broken user override shadow a valid builtin pack.
This branch immediately loads the user file and propagates any parse/read error.
list_packs()skips that invalid override and still exposes the builtin entry, so load/apply can fail for a pack the API just advertised.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/pack_loader.py` around lines 149 - 169, The current pack loader attempts to load a user pack via _load_from_file and immediately propagates any parse/read exception, which can shadow a valid builtin; modify the logic in the section that checks _USER_PACKS_DIR so that calls to _load_from_file(name_clean) are wrapped in a try/except (catch IO/parse errors), and on exception log a clear warning (via logger.warn/logger.warning) including pack_name and error details but do NOT return—allow execution to continue to the BUILTIN_PACKS branch and call _load_builtin(name_clean) if present; keep the existing TEMPLATE_PACK_LOAD_SUCCESS logger.debug calls for both successful user and builtin loads.
207-215:⚠️ Potential issue | 🟠 MajorNormalize user pack names in the listing.
_collect_user_packs()returnspath.stemverbatim, butload_pack()lowercases and probes<lowercase>.yaml. On a case-sensitive filesystem the listed name can be un-loadable, or it resolves to the builtin instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/pack_loader.py` around lines 207 - 215, The user pack listing uses path.stem verbatim which can mismatch load_pack's lowercase probing; update _collect_user_packs to normalize the pack name the same way load_pack expects (e.g., lowercase) before using it as the key and when calling _pack_info_from_loaded, so the seen dict stores the normalized name (and you still pass the original loaded content to _pack_info_from_loaded); reference functions: _collect_user_packs, _load_from_file, _pack_info_from_loaded, and load_pack to ensure consistent name normalization.src/synthorg/templates/loader.py (1)
500-510:⚠️ Potential issue | 🟠 MajorValidate
uses_packsas a list/tuple of strings before tuple conversion.The string guard fixes the common case, but mappings and other iterables still slip through
tuple(raw_packs). A YAML object likeuses_packs: {design-team: true}becomes("design-team",)instead of failing validation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/loader.py` around lines 500 - 510, The code treating "uses_packs" allows non-sequence iterables (e.g., mappings) to slip through; update the loader logic around raw_packs (the value read from data["uses_packs"]) to first check isinstance(raw_packs, (list, tuple)), then verify every item is a str (e.g., all(isinstance(p, str) for p in raw_packs)), and if either check fails log via logger.warning with TEMPLATE_LOAD_STRUCTURE_ERROR and source="template.uses_packs" and raise a TypeError with a clear message; only after those checks assign result["uses_packs"] = tuple(raw_packs).src/synthorg/api/controllers/template_packs.py (3)
94-103:⚠️ Potential issue | 🔴 CriticalPersist this as one atomic company update.
The endpoint does a read-modify-write across two independent settings keys, then writes them separately. Concurrent apply requests can overwrite each other, and a failure after the first
set()leaves the org partially updated.Also applies to: 129-145, 209-221
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/template_packs.py` around lines 94 - 103, The current flow does read-modify-write across two independent settings keys using settings_service.get and separate settings_service.set calls (see _read_setting_list and the apply/update logic), which can lead to race conditions and partial updates; change the implementation to perform the company update atomically by using an atomic batch/transaction API on settings_service (e.g., a single set_multiple/set_many or a transactional update method) that writes both keys in one call, or wrap the two key updates in a settings_service.transaction/with_transaction block so both are applied or none are, and apply the same change pattern to the other similar blocks referenced (around lines 129-145 and 209-221).
115-118:⚠️ Potential issue | 🟠 MajorPreserve the full department payload here.
departments_to_json()insrc/synthorg/api/controllers/setup_agents.pyonly serializesnameandbudget_percent, so pack-definedhead_roleandreporting_linesare dropped before persistence.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/template_packs.py` around lines 115 - 118, The code currently calls departments_to_json(pack_depts) which drops fields (head_role, reporting_lines) before persistence; instead preserve the full department payload by using the original pack_depts (or a deep-serialized copy) when building raw and new_depts. Replace the json.loads(departments_to_json(pack_depts)) expression with the full payload (e.g., raw = pack_depts or raw = json.loads(json.dumps(pack_depts)) / jsonable_encoder(pack_depts) if conversion is needed) and keep the existing filter into new_depts (using existing_names) so name-based deduplication remains but all fields (head_role, reporting_lines, budget_percent, etc.) are retained.
208-215:⚠️ Potential issue | 🟠 MajorRender the pack before consuming
data.variables.
expand_template_agents()only sees the pass-1CompanyTemplate, soApplyTemplatePackRequest.variablesnever influences Jinja-backed fields here. Any agent or department values derived from template variables will be wrong on live apply.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/template_packs.py` around lines 208 - 215, Render the template with ApplyTemplatePackRequest.variables before deriving agents/departments: instead of calling expand_template_agents(loaded.template) and reading departments from loaded.template, first apply data.variables to produce the rendered CompanyTemplate (the pass-2 template), then call expand_template_agents on that rendered template and use rendered_template.departments for _deduplicate_departments; ensure data.variables from ApplyTemplatePackRequest is passed into whatever render function you have so Jinja-backed fields are resolved before consuming pack_agents, current_depts, and related logic.
🤖 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/api/controllers/template_packs.py`:
- Around line 177-236: The apply_template_pack handler is still too long—extract
the core apply flow into a new helper/service (e.g., apply_template_pack_service
or apply_template_pack_impl) that accepts AppState and ApplyTemplatePackRequest
and performs load_pack, expand_template_agents, _read_setting_list,
_deduplicate_departments, and _persist_merged_config, returning a small result
object (pack_name, agents_added, departments_added); then reduce the existing
apply_template_pack method to call that helper, handle TemplateNotFoundError and
logging (TEMPLATE_PACK_APPLY_START / SUCCESS / APPLY_ERROR) and wrap the helper
result into ApiResponse, keeping all existing error handling and log calls in
the controller.
In `@src/synthorg/templates/renderer.py`:
- Around line 227-241: The loop that iterates pack_names forwards the _chain set
into recursive calls of _render_to_dict but never checks for cycles; update the
loop in the renderer (the code using pack_names, load_pack, _render_to_dict and
_chain) to detect if pack_name is already in _chain before calling
_render_to_dict, and handle it deterministically (e.g., log an error/warning
with TEMPLATE_PACK_MERGE_START context and either raise a clear
CycleDetectedError or skip the pack) so direct or indirect uses_packs cycles do
not recurse indefinitely.
---
Duplicate comments:
In `@src/synthorg/api/controllers/template_packs.py`:
- Around line 94-103: The current flow does read-modify-write across two
independent settings keys using settings_service.get and separate
settings_service.set calls (see _read_setting_list and the apply/update logic),
which can lead to race conditions and partial updates; change the implementation
to perform the company update atomically by using an atomic batch/transaction
API on settings_service (e.g., a single set_multiple/set_many or a transactional
update method) that writes both keys in one call, or wrap the two key updates in
a settings_service.transaction/with_transaction block so both are applied or
none are, and apply the same change pattern to the other similar blocks
referenced (around lines 129-145 and 209-221).
- Around line 115-118: The code currently calls departments_to_json(pack_depts)
which drops fields (head_role, reporting_lines) before persistence; instead
preserve the full department payload by using the original pack_depts (or a
deep-serialized copy) when building raw and new_depts. Replace the
json.loads(departments_to_json(pack_depts)) expression with the full payload
(e.g., raw = pack_depts or raw = json.loads(json.dumps(pack_depts)) /
jsonable_encoder(pack_depts) if conversion is needed) and keep the existing
filter into new_depts (using existing_names) so name-based deduplication remains
but all fields (head_role, reporting_lines, budget_percent, etc.) are retained.
- Around line 208-215: Render the template with
ApplyTemplatePackRequest.variables before deriving agents/departments: instead
of calling expand_template_agents(loaded.template) and reading departments from
loaded.template, first apply data.variables to produce the rendered
CompanyTemplate (the pass-2 template), then call expand_template_agents on that
rendered template and use rendered_template.departments for
_deduplicate_departments; ensure data.variables from ApplyTemplatePackRequest is
passed into whatever render function you have so Jinja-backed fields are
resolved before consuming pack_agents, current_depts, and related logic.
In `@src/synthorg/templates/loader.py`:
- Around line 500-510: The code treating "uses_packs" allows non-sequence
iterables (e.g., mappings) to slip through; update the loader logic around
raw_packs (the value read from data["uses_packs"]) to first check
isinstance(raw_packs, (list, tuple)), then verify every item is a str (e.g.,
all(isinstance(p, str) for p in raw_packs)), and if either check fails log via
logger.warning with TEMPLATE_LOAD_STRUCTURE_ERROR and
source="template.uses_packs" and raise a TypeError with a clear message; only
after those checks assign result["uses_packs"] = tuple(raw_packs).
In `@src/synthorg/templates/pack_loader.py`:
- Around line 149-169: The current pack loader attempts to load a user pack via
_load_from_file and immediately propagates any parse/read exception, which can
shadow a valid builtin; modify the logic in the section that checks
_USER_PACKS_DIR so that calls to _load_from_file(name_clean) are wrapped in a
try/except (catch IO/parse errors), and on exception log a clear warning (via
logger.warn/logger.warning) including pack_name and error details but do NOT
return—allow execution to continue to the BUILTIN_PACKS branch and call
_load_builtin(name_clean) if present; keep the existing
TEMPLATE_PACK_LOAD_SUCCESS logger.debug calls for both successful user and
builtin loads.
- Around line 207-215: The user pack listing uses path.stem verbatim which can
mismatch load_pack's lowercase probing; update _collect_user_packs to normalize
the pack name the same way load_pack expects (e.g., lowercase) before using it
as the key and when calling _pack_info_from_loaded, so the seen dict stores the
normalized name (and you still pass the original loaded content to
_pack_info_from_loaded); reference functions: _collect_user_packs,
_load_from_file, _pack_info_from_loaded, and load_pack to ensure consistent name
normalization.
In `@src/synthorg/templates/renderer.py`:
- Around line 183-189: The pack resolution calls to _resolve_packs are missing
the vars_dict argument, causing packs to render with None; update each call that
passes (base_config, template.uses_packs, locales=locales, _chain=_chain,
custom_presets=custom_presets) to also pass vars_dict=vars_dict (or the correct
variable container) so pack rendering in _resolve_packs receives caller/template
variables; ensure you change the occurrences around the base_config assignment
and the other two places referenced (the similar calls) so all invocations
include vars_dict.
🪄 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: 388e0ed1-6699-48a3-b5fd-039adb2b07e9
📒 Files selected for processing (4)
src/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/templates/renderer.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations— Python 3.14 has PEP 649
Useexcept A, B:(no parentheses) per PEP 758 except syntax — ruff enforces this on Python 3.14
Type hints required on all public functions, enforced by mypy strict mode
Docstrings must use Google style and are required on public classes and functions, enforced by ruff D rules
Never mutate existing objects — create new objects; for non-Pydantic internal collections, usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Use frozen Pydantic models for config/identity; separate mutable-via-copy models usingmodel_copy(update=...)for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing and validating redundant fields
UseNotBlankStr(fromcore.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple variants — instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) for structured concurrency instead of barecreate_task
Line length must be 88 characters, enforced by ruff
Functions must be less than 50 lines; files must be less than 800 lines
Logging variable name must always belogger(not_logger, notlog)
Always use structured logging withlogger.info(EVENT, key=value)— neverlogger.info("msg %s", val)
All error paths must log at WAR...
Files:
src/synthorg/templates/loader.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/renderer.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code, except inobservability/setup.py,observability/sinks.py,observability/syslog_handler.py, andobservability/http_handler.py
Always use event name constants from the domain-specific module undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool); import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Files:
src/synthorg/templates/loader.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/renderer.py
🧠 Learnings (23)
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/templates/**/*.py : Templates: pre-built company templates, personality presets, and builder.
Applied to files:
src/synthorg/templates/loader.pysrc/synthorg/templates/pack_loader.pysrc/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 : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/templates/loader.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 `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators
Applied to files:
src/synthorg/templates/loader.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/templates/pack_loader.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/pack_loader.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/pack_loader.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to **/*.py : Never mutate existing objects — create new objects; for non-Pydantic internal collections, use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/templates/pack_loader.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/pack_loader.pysrc/synthorg/api/controllers/template_packs.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/pack_loader.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to **/*.py : For `dict`/`list` fields in frozen Pydantic models, rely on `frozen=True` for field reassignment prevention and use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Applied to files:
src/synthorg/templates/pack_loader.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion
Applied to files:
src/synthorg/api/controllers/template_packs.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/api/controllers/template_packs.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/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)
Applied to files:
src/synthorg/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 src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging
Applied to files:
src/synthorg/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 : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings
Applied to files:
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-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.
Applied to files:
src/synthorg/templates/renderer.py
…t, and Gemini - Add AGENT_LOCK for read-modify-write safety in apply endpoint - Fix department serialization preserving head_role and reporting_lines - Add circular pack dependency detection in _resolve_packs - Remove unused variables field from ApplyTemplatePackRequest - Catch json.JSONDecodeError in _read_setting_list - Add YAML pack files to pyproject.toml wheel artifacts - Full uses_packs validation (null, dict, non-string items) - Thread vars_dict into pack rendering - Remove dead resolve_inheritance and _render_and_merge_parent - Rename deduplicate_merged_agent_names to public, return new dict - Add agent deduplication in apply endpoint - Use NotBlankStr for ApplyTemplatePackResponse.pack_name - Use asyncio.to_thread for sync I/O in async endpoints - Extract _apply_pack_to_settings helper (<50 line methods) - Replace path traversal denylist with allowlist regex - Add PackInfo.__post_init__ validation - Normalize user pack names in _collect_user_packs - Fallback to builtin when user pack file is broken - Make _collect_user_packs return dict instead of mutating argument - Add uses_packs uniqueness validator on CompanyTemplate - Add TEMPLATE_PACK_APPLY_DEPT_SKIPPED and TEMPLATE_PACK_CIRCULAR events - Extract PackListItem from dialog map block - Separate fetchCompanyData error from apply error - Replace p-3 with p-card on pack selection buttons - Fix MSW 404 handler to use apiError helper - Add multi-pack ordering and extends+uses_packs tests - Fix test fixture typing and remove unused tmp_path - Update CLAUDE.md, web/CLAUDE.md, and docs/design/organization.md
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
web/src/api/types.ts (1)
1522-1534:⚠️ Potential issue | 🟠 MajorPack API contract still cannot express pack variables.
PackInfoResponseandApplyTemplatePackRequestcurrently prevent clients from discovering/submitting variable inputs for packs that require them.🔧 Suggested contract update
export interface PackInfoResponse { readonly name: string readonly display_name: string readonly description: string readonly source: 'builtin' | 'user' readonly tags: readonly string[] + readonly variables: readonly TemplateVariable[] readonly agent_count: number readonly department_count: number } export interface ApplyTemplatePackRequest { readonly pack_name: string + readonly variables?: Readonly<Record<string, string | number | boolean>> }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/api/types.ts` around lines 1522 - 1534, Pack API types don't allow packs to declare or accept variables; update PackInfoResponse to include a description of pack variables (e.g., a readonly variables array or map where each variable has a unique name, type, required flag, description, and optional default) so clients can discover required inputs, and update ApplyTemplatePackRequest to accept those variables (e.g., a readonly variables object/map keyed by variable name with values typed as string | number | boolean | null) so clients can submit values; make sure to reference and update the PackInfoResponse and ApplyTemplatePackRequest interfaces accordingly and keep field names stable for backward compatibility.web/src/pages/org-edit/PackSelectionDialog.tsx (1)
74-104:⚠️ Potential issue | 🟠 MajorLoading state not set when component mounts with
open=true.The render-time
prevOpenRefblock only setsloading=trueon open transitions (open && !prevOpenRef.current). When the component mounts withopen=true, bothopenandprevOpenRef.currentstart astrue, so the condition is false andloadingstaysfalse. This causes a brief flash of the empty state before the pack list loads.Move the state initialization into the effect:
🔧 Suggested fix
- // Track open transitions via ref so the effect only fires on changes. - const prevOpenRef = useRef(open) - if (open && !prevOpenRef.current) { - setLoading(true) - setError(null) - setApplying(null) - } - if (!open && prevOpenRef.current) { - setError(null) - setApplying(null) - setLoading(false) - } - prevOpenRef.current = open - useEffect(() => { - if (!open) return + if (!open) { + setError(null) + setApplying(null) + setLoading(false) + return + } + + setLoading(true) + setError(null) + setApplying(null) + let cancelled = false listTemplatePacks()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/pages/org-edit/PackSelectionDialog.tsx` around lines 74 - 104, The loading state isn't set when the component mounts with open=true because the render-time prevOpenRef check only handles transitions; to fix, move the state initialization into the effect: inside the useEffect that runs when [open] is true, call setLoading(true), setError(null) and setApplying(null) before invoking listTemplatePacks(), then proceed with the existing promise handlers (setPacks, setError(getErrorMessage(err)), setLoading(false)) and cancellation logic; keep prevOpenRef updates as-is to preserve transition behavior.src/synthorg/api/controllers/template_packs.py (1)
203-224:⚠️ Potential issue | 🔴 CriticalPersist this as one atomic company update.
Line 215 and Line 220 still write
company/agentsandcompany/departmentsindependently. If the secondset()fails, the org is left half-applied._AGENT_LOCKalso does not serialize all writers:src/synthorg/api/controllers/setup.py:270-290clearscompany/agentswithout that lock, so a concurrent setup reset can clobber the new agents while this handler still persists the new departments. This needs a single settings-layer transaction / compare-and-swap over the combined company payload.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/template_packs.py` around lines 203 - 224, Current code writes "company/agents" and "company/departments" separately under _AGENT_LOCK which can leave the org half-applied and is racy with the setup reset that clears company/agents; instead construct the combined company payload (merge current_agents + new_agents and current_depts + new_depts) and persist it in a single atomic operation using the settings service transaction/compare-and-swap API (e.g., settings_svc.compare_and_swap or a single settings_svc.set on a unified "company" value), and/or expand the locking so the same _AGENT_LOCK (or a broader company-level lock) serializes the setup reset writer as well; update code around _deduplicate_agents/_deduplicate_departments and replace the two settings_svc.set calls with one atomic write of the combined company payload (or use compare-and-swap with the expected current value to detect conflicts).
🤖 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/api/controllers/template_packs.py`:
- Around line 113-124: The JSON decode block in template_packs.py must validate
that the decoded value is a list of dicts before returning so consumers like
_deduplicate_agents and _deduplicate_departments don't blow up on null, dict, or
wrong-element types; after json.loads(entry.value) check that result is a list
and each item is a dict (keys are strings) and otherwise log
TEMPLATE_PACK_APPLY_ERROR with the decoding/validation error and raise a
5xx-style API error (not NotFoundError) to signal corrupted internal data; keep
the existing logger.exception usage but include the validation error details and
replace the NotFoundError raise with the appropriate server/error class.
- Around line 279-285: The call to _apply_pack_to_settings currently only logs
success; wrap the call to _apply_pack_to_settings(app_state, data) in a
try/except that catches Exception, logs TEMPLATE_PACK_APPLY_ERROR via
logger.error (or logger.warning if desired) including pack_name=data.pack_name
and the exception details (and traceback/context) so failures in load_pack,
expand_template_agents, or settings writes are recorded, then re-raise the
exception so existing error handling still applies; keep
TEMPLATE_PACK_APPLY_START and the successful TEMPLATE_PACK_APPLY_* log intact.
In `@src/synthorg/templates/schema.py`:
- Around line 453-460: The _validate_unique_pack_names method currently checks
uniqueness of self.uses_packs in a case-sensitive way; normalize pack names
first (e.g., map each name through a canonicalizer such as str.strip().lower()
or a project-specific normalization function) then perform the length vs set
check and use collections.Counter on the normalized list to find duplicates;
update the warning message to reference the normalized duplicate names and keep
raising ValueError as before; touch symbols: _validate_unique_pack_names,
self.uses_packs, Counter, logger, TEMPLATE_SCHEMA_VALIDATION_ERROR.
---
Duplicate comments:
In `@src/synthorg/api/controllers/template_packs.py`:
- Around line 203-224: Current code writes "company/agents" and
"company/departments" separately under _AGENT_LOCK which can leave the org
half-applied and is racy with the setup reset that clears company/agents;
instead construct the combined company payload (merge current_agents +
new_agents and current_depts + new_depts) and persist it in a single atomic
operation using the settings service transaction/compare-and-swap API (e.g.,
settings_svc.compare_and_swap or a single settings_svc.set on a unified
"company" value), and/or expand the locking so the same _AGENT_LOCK (or a
broader company-level lock) serializes the setup reset writer as well; update
code around _deduplicate_agents/_deduplicate_departments and replace the two
settings_svc.set calls with one atomic write of the combined company payload (or
use compare-and-swap with the expected current value to detect conflicts).
In `@web/src/api/types.ts`:
- Around line 1522-1534: Pack API types don't allow packs to declare or accept
variables; update PackInfoResponse to include a description of pack variables
(e.g., a readonly variables array or map where each variable has a unique name,
type, required flag, description, and optional default) so clients can discover
required inputs, and update ApplyTemplatePackRequest to accept those variables
(e.g., a readonly variables object/map keyed by variable name with values typed
as string | number | boolean | null) so clients can submit values; make sure to
reference and update the PackInfoResponse and ApplyTemplatePackRequest
interfaces accordingly and keep field names stable for backward compatibility.
In `@web/src/pages/org-edit/PackSelectionDialog.tsx`:
- Around line 74-104: The loading state isn't set when the component mounts with
open=true because the render-time prevOpenRef check only handles transitions; to
fix, move the state initialization into the effect: inside the useEffect that
runs when [open] is true, call setLoading(true), setError(null) and
setApplying(null) before invoking listTemplatePacks(), then proceed with the
existing promise handlers (setPacks, setError(getErrorMessage(err)),
setLoading(false)) and cancellation logic; keep prevOpenRef updates as-is to
preserve transition behavior.
🪄 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: 15cdba5d-2e53-4f0a-8b56-cb6b46151350
📒 Files selected for processing (17)
CLAUDE.mddocs/design/organization.mdpyproject.tomlsrc/synthorg/api/controllers/template_packs.pysrc/synthorg/observability/events/template.pysrc/synthorg/templates/_inheritance.pysrc/synthorg/templates/loader.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/templates/renderer.pysrc/synthorg/templates/schema.pytests/unit/templates/test_inheritance.pytests/unit/templates/test_pack_loader.pytests/unit/templates/test_uses_packs.pyweb/CLAUDE.mdweb/src/api/types.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Dashboard Test
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
🧰 Additional context used
📓 Path-based instructions (11)
{pyproject.toml,src/synthorg/__init__.py}
📄 CodeRabbit inference engine (CLAUDE.md)
Version locations:
pyproject.toml([tool.commitizen].version),src/synthorg/__init__.py(__version__)
Files:
pyproject.toml
pyproject.toml
📄 CodeRabbit inference engine (CLAUDE.md)
pyproject.toml: All versions inpyproject.tomluse==(pinned); organized into groups:test,dev(includes test + tools);uv syncinstalls everything (dev group is default)
Required Python dependencies:mem0ai(Mem0 memory backend — default and currently only backend),cryptography(Fernet encryption),faker(multi-locale name generation)
Docs group inpyproject.tomlincludes:zensical,mkdocstrings[python],griffe-pydantic; install withuv sync --group docs
Files:
pyproject.toml
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations— Python 3.14 has PEP 649
Useexcept A, B:(no parentheses) per PEP 758 except syntax — ruff enforces this on Python 3.14
Type hints required on all public functions, enforced by mypy strict mode
Docstrings must use Google style and are required on public classes and functions, enforced by ruff D rules
Never mutate existing objects — create new objects; for non-Pydantic internal collections, usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Use frozen Pydantic models for config/identity; separate mutable-via-copy models usingmodel_copy(update=...)for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing and validating redundant fields
UseNotBlankStr(fromcore.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple variants — instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) for structured concurrency instead of barecreate_task
Line length must be 88 characters, enforced by ruff
Functions must be less than 50 lines; files must be less than 800 lines
Logging variable name must always belogger(not_logger, notlog)
Always use structured logging withlogger.info(EVENT, key=value)— neverlogger.info("msg %s", val)
All error paths must log at WAR...
Files:
src/synthorg/templates/loader.pytests/unit/templates/test_inheritance.pysrc/synthorg/templates/schema.pytests/unit/templates/test_uses_packs.pytests/unit/templates/test_pack_loader.pysrc/synthorg/observability/events/template.pysrc/synthorg/templates/_inheritance.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/renderer.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code, except inobservability/setup.py,observability/sinks.py,observability/syslog_handler.py, andobservability/http_handler.py
Always use event name constants from the domain-specific module undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool); import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Files:
src/synthorg/templates/loader.pysrc/synthorg/templates/schema.pysrc/synthorg/observability/events/template.pysrc/synthorg/templates/_inheritance.pysrc/synthorg/templates/pack_loader.pysrc/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/renderer.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowto classify tests
Useasyncio_mode = "auto"for async tests — no manual@pytest.mark.asyncioneeded
Global timeout is 30 seconds per test inpyproject.toml— do not add per-filepytest.mark.timeout(30)markers; non-default overrides liketimeout(60)are allowed
Prefer@pytest.mark.parametrizefor testing similar cases
Use Hypothesis for property-based testing in Python with@given+@settingsdecorators
Use Hypothesis profiles:ci(50 examples, default) anddev(1000 examples), controlled viaHYPOTHESIS_PROFILEenv var
Never skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; for timing-sensitive tests, mocktime.monotonic()andasyncio.sleep()instead of widening timing margins; for tasks that must block indefinitely, useasyncio.Event().wait()
No-redundant-timeout pre-commit hook enforced
Files:
tests/unit/templates/test_inheritance.pytests/unit/templates/test_uses_packs.pytests/unit/templates/test_pack_loader.py
web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (web/CLAUDE.md)
web/src/**/*.{tsx,ts}: ALWAYS reuse existing components fromweb/src/components/ui/before creating new ones (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup, Drawer, InputField, SelectField, SliderField, ToggleField, TaskStatusIndicator, PriorityBadge, ProviderHealthBadge, TokenUsageBar, CodeMirrorEditor, SegmentedControl, ThemeToggle, LiveRegion, MobileUnsupportedOverlay, LazyCodeMirrorEditor, TagInput, MetadataGrid, ProjectStatusBadge, ContentTypeBadge)
Use Tailwind semantic classes (text-foreground,bg-card,text-accent,text-success,bg-danger) or CSS variables (var(--so-*)) for colors. NEVER hardcode hex values or rgba() in.tsx/.tsfiles
Usefont-sansorfont-monofor typography (maps to Geist tokens). NEVER setfontFamilydirectly
Use density-aware tokens (p-card,gap-section-gap,gap-grid-gap) or standard Tailwind spacing. NEVER hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover),border-border,border-bright) for shadows and borders. NEVER hardcode shadow or border values
Importcnfrom@/lib/utilsfor conditional class merging
Do NOT recreate status dots inline -- use<StatusBadge>component
Do NOT build card-with-header layouts from scratch -- use<SectionCard>component
Do NOT create metric displays withtext-metric font-bold-- use<MetricCard>component
Do NOT render initials circles manually -- use<Avatar>component
Do NOT create complex (>8 line) JSX inside.map()-- extract to a shared component
Use Framer Motion presets from@/lib/motioninstead of hardcoded transition durations
Use ErrorBoundary withlevelprop set to'page','section', or'component'for appropriate error boundary scoping
Usesideprop (left or right, default right) for Drawer component, with ...
Files:
web/src/api/types.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsx
web/src/**/*.{tsx,ts,jsx,js}
📄 CodeRabbit inference engine (web/CLAUDE.md)
ESLint must enforce zero warnings on all web frontend code
Files:
web/src/api/types.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsx
web/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (web/CLAUDE.md)
CSS side-effect imports need type declarations; use Vite's
/// <reference types="vite/client" />directive
web/src/**/*.{ts,tsx}: Eslint-web pre-commit hook enforced with zero warnings on web dashboard TypeScript/React files
Always reuse existing components fromweb/src/components/ui/before creating new ones
Files:
web/src/api/types.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsx
web/src/**/*.{ts,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Never hardcode hex colors, font-family, pixel spacing, or Framer Motion transitions in web code — use design tokens and
@/lib/motionpresets
Files:
web/src/api/types.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsx
web/src/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
A PostToolUse hook (
scripts/check_web_design_system.py) enforces component reuse and design token rules on every Edit/Write toweb/src/
Files:
web/src/api/types.tsweb/src/mocks/handlers/template-packs.tsweb/src/pages/org-edit/PackSelectionDialog.tsx
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Docs in
docs/(Markdown, built with Zensical, config:mkdocs.yml); design spec:docs/design/(12 pages); architecture:docs/architecture/; roadmap:docs/roadmap/
Files:
docs/design/organization.md
🧠 Learnings (57)
📚 Learning: 2026-03-21T12:54:22.557Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Version bumping (pre-1.0): `fix:` = patch, `feat:` = patch, `feat!:` = minor, `BREAKING CHANGE` trailer = minor. Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)
Applied to files:
pyproject.toml
📚 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:
pyproject.tomlweb/CLAUDE.mddocs/design/organization.mdsrc/synthorg/templates/pack_loader.pyCLAUDE.mdsrc/synthorg/api/controllers/template_packs.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 pyproject.toml : Dev group in `pyproject.toml` includes: test group + ruff, mypy, pre-commit, commitizen, pip-audit
Applied to files:
pyproject.toml
📚 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:
pyproject.tomlsrc/synthorg/templates/loader.pytests/unit/templates/test_inheritance.pydocs/design/organization.mdsrc/synthorg/templates/pack_loader.pyCLAUDE.mdsrc/synthorg/api/controllers/template_packs.pysrc/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 src/synthorg/**/*.py : Maintain 80% minimum test coverage (enforced in CI)
Applied to files:
pyproject.toml
📚 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 {pyproject.toml,src/synthorg/__init__.py} : Version locations: `pyproject.toml` (`[tool.commitizen].version`), `src/synthorg/__init__.py` (`__version__`)
Applied to files:
pyproject.toml
📚 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:
web/CLAUDE.mdCLAUDE.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 web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)
Applied to files:
web/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: Applies to web/package.json : Web dashboard Node.js 22+, TypeScript 6.0+, dependencies in `web/package.json`
Applied to files:
web/CLAUDE.mdweb/src/api/types.ts
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).
Applied to files:
web/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:
web/CLAUDE.mdCLAUDE.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: Applies to web/src/**/*.{ts,tsx} : Use React 19, TypeScript 6.0+, and design system tokens from shadcn/ui + Tailwind CSS 4 + Radix UI in web dashboard
Applied to files:
web/CLAUDE.md
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to site/**/*.{astro,tsx,ts} : Landing page in `site/` (Astro + React islands via `astrojs/react`), includes `/get/` CLI install page, contact form, interactive dashboard preview, SEO
Applied to files:
web/CLAUDE.md
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.{tsx,ts} : ALWAYS reuse existing components from `web/src/components/ui/` before creating new ones (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup, Drawer, InputField, SelectField, SliderField, ToggleField, TaskStatusIndicator, PriorityBadge, ProviderHealthBadge, TokenUsageBar, CodeMirrorEditor, SegmentedControl, ThemeToggle, LiveRegion, MobileUnsupportedOverlay, LazyCodeMirrorEditor, TagInput, MetadataGrid, ProjectStatusBadge, ContentTypeBadge)
Applied to files:
web/CLAUDE.mdweb/src/pages/org-edit/PackSelectionDialog.tsx
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to site/package.json : Landing page dependencies in `site/package.json` (Astro 6, astrojs/react, React 19, Tailwind CSS 4)
Applied to files:
web/CLAUDE.md
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/components/ui/**/*.tsx : Place new shared components in `web/src/components/ui/` with descriptive kebab-case filenames
Applied to files:
web/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: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators
Applied to files:
src/synthorg/templates/loader.pysrc/synthorg/templates/schema.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/templates/loader.pysrc/synthorg/templates/schema.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.
Applied to files:
src/synthorg/templates/loader.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple variants — instead of manual whitespace validators
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 `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/tsconfig.json : TypeScript 6: Explicitly list needed types in `types` array (e.g., `"types": ["vitest/globals"]`) -- `types` no longer auto-discovers `types/*`
Applied to files:
web/src/api/types.ts
📚 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:
docs/design/organization.mdCLAUDE.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:
docs/design/organization.mdCLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations
Applied to files:
docs/design/organization.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)
Applied to files:
docs/design/organization.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)
Applied to files:
src/synthorg/observability/events/template.pysrc/synthorg/templates/renderer.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Applied to files:
src/synthorg/observability/events/template.pysrc/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 src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging
Applied to files:
src/synthorg/observability/events/template.pysrc/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 : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
Applied to files:
src/synthorg/observability/events/template.py
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do NOT create complex (>8 line) JSX inside `.map()` -- extract to a shared component
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.tsx
📚 Learning: 2026-04-01T06:54:44.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: web/CLAUDE.md:0-0
Timestamp: 2026-04-01T06:54:44.133Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do NOT recreate status dots inline -- use `<StatusBadge>` component
Applied to files:
web/src/pages/org-edit/PackSelectionDialog.tsx
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/templates/pack_loader.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/pack_loader.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/pack_loader.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to **/*.py : Never mutate existing objects — create new objects; for non-Pydantic internal collections, use `copy.deepcopy()` at construction + `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/templates/pack_loader.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/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.mdsrc/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/synthorg/api/**/*.py : Litestar API must include setup wizard, auth/, auto-wiring, and lifecycle management
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST API and WebSocket API with JWT + API key + WS ticket authentication, RFC 9457 structured errors, and content negotiation.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/backup/**/*.py : Backup package (backup/): scheduled/manual/lifecycle backups of persistence DB, agent memory, company config. BackupService orchestrator, BackupScheduler (periodic asyncio task), RetentionManager (count + age pruning), tar.gz compression, SHA-256 checksums, manifest tracking, validated restore with atomic rollback and safety backup. handlers/ subpackage: ComponentHandler protocol + concrete handlers (PersistenceComponentHandler, MemoryComponentHandler, ConfigComponentHandler)
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: 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-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion
Applied to files:
src/synthorg/api/controllers/template_packs.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/api/controllers/template_packs.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,web/src/**/*.{ts,tsx}} : Keep functions under 50 lines and files under 800 lines
Applied to files:
src/synthorg/api/controllers/template_packs.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 : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings
Applied to files:
src/synthorg/templates/renderer.py
🔇 Additional comments (19)
src/synthorg/observability/events/template.py (1)
57-68: Good addition of domain event constants for template-pack lifecycle.Centralizing these names in the template events module keeps logging contracts consistent and discoverable.
web/CLAUDE.md (1)
26-26: Docs update looks accurate for the new API surface.pyproject.toml (1)
42-42: Wheel artifact expansion is correct for shipping template-pack YAML assets.CLAUDE.md (1)
89-89: Package-structure docs are aligned with the template-pack implementation.Also applies to: 104-104
src/synthorg/templates/loader.py (1)
500-517:uses_packsnormalization/validation is solid here.This correctly blocks malformed inputs and preserves explicit
nullas “no packs”.tests/unit/templates/test_inheritance.py (1)
15-16: Nice test refactor for return-based semantics and immutability guarantees.The added non-mutation assertion is especially valuable for guarding merge-side effects.
Also applies to: 358-420
web/src/mocks/handlers/template-packs.ts (1)
1-69: LGTM!The mock handlers correctly use the
apiErrorhelper for the 404 case (addressing the previous review concern), andmockPacksis appropriately typed asreadonly. The handlers align well with the frontend API client expectations.src/synthorg/templates/renderer.py (2)
169-198: Pack resolution layer correctly positioned between extends and child.The merge order implementation is correct:
- Build
base_configfrom parent viarender_parent_config(ifextendsset)- Layer packs onto base via
_resolve_packs(ifuses_packsset)- Merge child on top (child wins)
- Deduplicate merged agent names
This properly addresses the documented resolution semantics.
201-266: Pack resolution with proper circular detection and variable threading.The implementation correctly:
- Detects circular pack dependencies before recursing (line 237)
- Threads caller variables into pack rendering (line 255)
- Updates
_chainwith each processed pack (line 257)- Logs lifecycle events at INFO level per guidelines
Previous review concerns about missing variable threading and cycle detection are fully addressed.
tests/unit/templates/test_uses_packs.py (1)
1-231: Comprehensive test coverage foruses_packscomposition.The tests effectively cover:
- Schema validation (defaults, acceptance, duplicate rejection, agent-count bypass)
- Rendering behavior (single pack, multi-pack ordering, child override semantics)
- Backward compatibility with existing templates
- Combined
extends+uses_packsscenariosTest structure follows guidelines with
@pytest.mark.unitmarkers and clear separation of concerns.tests/unit/templates/test_pack_loader.py (1)
1-128: Thorough test coverage for pack loader functionality.Excellent use of
@pytest.mark.parametrizefor testing all builtin packs uniformly. The tests cover:
- Listing operations (sorted, complete, type-correct)
- Load operations (success, not-found, invalid names, case-insensitive)
- User pack override behavior with proper mocking
- Built-in pack validity (schema, agents, departments)
web/src/pages/org-edit/PackSelectionDialog.tsx (2)
29-64: PackListItem extraction addresses the JSX complexity concern.The extracted
PackListItemcomponent properly encapsulates the pack row logic, improving maintainability. Uses semantic Tailwind classes (text-foreground,bg-card,border-border) correctly per design system guidelines.
106-133: Apply/refresh error handling is correct.The implementation properly separates apply success from refresh failure—showing a success toast and closing the dialog immediately on apply success, then issuing a warning toast if the subsequent refresh fails. This prevents users from retrying an already-successful mutation.
src/synthorg/templates/_inheritance.py (2)
78-126: Clean separation of parent rendering from child merging.The
render_parent_configfunction correctly:
- Validates inheritance chain for cycles and depth
- Loads and renders the parent with
_as_parent=True- Returns the raw config without merging, enabling the renderer to layer packs between parent and child
This refactor enables the new pack composition flow in
renderer.py.
129-164: Immutable return pattern for agent deduplication.The function now returns a new dict rather than mutating the input, addressing immutability guidelines. The rebuilt agents list and spread-dict return (
{**merged, "agents": new_agents}) correctly avoids input mutation.src/synthorg/templates/pack_loader.py (3)
40-48: Immutable builtin registry viaMappingProxyType.The
BUILTIN_PACKSregistry is correctly wrapped withMappingProxyType, preventing accidental mutation of the pack catalog at runtime. This aligns with the coding guidelines for non-Pydantic internal collections.
168-194: User pack fallback to builtin on failure.The load path correctly tries the user file first and falls back to the builtin when:
- The user file fails to parse/validate
- A builtin with the same name exists
This ensures consistent behavior between
list_packs()(which skips invalid user packs but shows builtins) andload_pack()(which now also falls back).
242-276: User pack names normalized for case-insensitive matching.Pack stems are lowercased at discovery time (line 252), and duplicates from case-variant filenames are skipped (line 260-261). This ensures consistency with
load_pack()which also normalizes to lowercase.docs/design/organization.md (1)
407-447: Documentation accurately reflects the implementation.The Template Packs section clearly documents:
- Resolution order (extends → packs → child)
- Circular dependency detection behavior
- Case-insensitive department deduplication and name-based agent deduplication
- The endpoint path
POST /api/v1/template-packs/applyis correct per the API controller configuration
…logging, case-insensitive pack uniqueness - Validate json.loads result is list[dict] before returning from _read_setting_list - Use ApiError (500) instead of NotFoundError (404) for corrupted settings - Wrap _apply_pack_to_settings call with try/except for pack-context error logging - Make uses_packs uniqueness check case-insensitive (matches load_pack normalization)
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/synthorg/templates/schema.py (1)
452-461: 🧹 Nitpick | 🔵 TrivialError message reports normalized names instead of originals.
The validation is now case-insensitive (addressing prior feedback), but the error message reports the lowercased duplicates rather than the user's original input. For consistency with
_validate_unique_department_names(lines 439-449), report the original pack names. Also consider usingcasefold()instead oflower()for better Unicode handling consistency.♻️ Suggested alignment with department validator pattern
`@model_validator`(mode="after") def _validate_unique_pack_names(self) -> Self: """Pack names in uses_packs must be unique (case-insensitive).""" - normalized = [p.strip().lower() for p in self.uses_packs] + normalized = [p.strip().casefold() for p in self.uses_packs] if len(normalized) != len(set(normalized)): - dupes = sorted(n for n, c in Counter(normalized).items() if c > 1) + dup_keys = {n for n, c in Counter(normalized).items() if c > 1} + dupes = sorted( + p for p in self.uses_packs if p.strip().casefold() in dup_keys + ) msg = f"Duplicate pack names in uses_packs: {dupes}" logger.warning(TEMPLATE_SCHEMA_VALIDATION_ERROR, error=msg) raise ValueError(msg) return self🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/templates/schema.py` around lines 452 - 461, The _validate_unique_pack_names model_validator currently normalizes pack names with .lower() and reports those normalized values in the error; change it to mirror _validate_unique_department_names by normalizing with .casefold() for comparison but when constructing dupes map back to the original self.uses_packs values (preserving original casing/spaces) so the error message shows the user’s input, e.g., build a mapping from normalized->list(originals), find keys with >1 entries and include the sorted original names in the ValueError and logger.warning; keep raising ValueError(msg) and returning self unchanged otherwise.
🤖 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/api/controllers/template_packs.py`:
- Around line 186-242: _extract the persistence and deduplication block inside
_apply_pack_to_settings into a new helper function (e.g.,
_persist_pack_settings) that is called from within the async with _AGENT_LOCK
block; the new helper should accept app_state, data.pack_name (or
loaded.template and pack_name), pack_agents, and loaded.template.departments,
perform the reads via _read_setting_list, call _deduplicate_agents and
_deduplicate_departments, write back with settings_svc.set for "agents" and
"departments", and return the new_agents and new_depts counts or lists so
_apply_pack_to_settings can build and return ApplyTemplatePackResponse—this
moves the reading/deduping/writing logic out of _apply_pack_to_settings and
reduces its length below 50 lines while still using the existing helpers
(_read_setting_list, _deduplicate_agents, _deduplicate_departments,
settings_svc).
- Around line 102-110: The debug log inside the except block for
SettingNotFoundError is using the misleading event constant
TEMPLATE_PACK_APPLY_START; change the log to use a proper settings-read event or
a plain string literal instead of TEMPLATE_PACK_APPLY_START so the message
reflects "setting_not_found_default_empty" context. Update the logger.debug call
in the except handling around app_state.settings_service.get(...) (the
SettingNotFoundError block) to reference an appropriate event constant (e.g.,
SETTINGS_READ or similar) or remove the constant and pass a descriptive string,
keeping the same key and action fields.
---
Duplicate comments:
In `@src/synthorg/templates/schema.py`:
- Around line 452-461: The _validate_unique_pack_names model_validator currently
normalizes pack names with .lower() and reports those normalized values in the
error; change it to mirror _validate_unique_department_names by normalizing with
.casefold() for comparison but when constructing dupes map back to the original
self.uses_packs values (preserving original casing/spaces) so the error message
shows the user’s input, e.g., build a mapping from normalized->list(originals),
find keys with >1 entries and include the sorted original names in the
ValueError and logger.warning; keep raising ValueError(msg) and returning self
unchanged otherwise.
🪄 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: 3f2767e2-f754-42cd-8915-67df05eb907a
📒 Files selected for processing (2)
src/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/schema.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dashboard Test
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations— Python 3.14 has PEP 649
Useexcept A, B:(no parentheses) per PEP 758 except syntax — ruff enforces this on Python 3.14
Type hints required on all public functions, enforced by mypy strict mode
Docstrings must use Google style and are required on public classes and functions, enforced by ruff D rules
Never mutate existing objects — create new objects; for non-Pydantic internal collections, usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Use frozen Pydantic models for config/identity; separate mutable-via-copy models usingmodel_copy(update=...)for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing and validating redundant fields
UseNotBlankStr(fromcore.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple variants — instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) for structured concurrency instead of barecreate_task
Line length must be 88 characters, enforced by ruff
Functions must be less than 50 lines; files must be less than 800 lines
Logging variable name must always belogger(not_logger, notlog)
Always use structured logging withlogger.info(EVENT, key=value)— neverlogger.info("msg %s", val)
All error paths must log at WAR...
Files:
src/synthorg/templates/schema.pysrc/synthorg/api/controllers/template_packs.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code, except inobservability/setup.py,observability/sinks.py,observability/syslog_handler.py, andobservability/http_handler.py
Always use event name constants from the domain-specific module undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool); import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Files:
src/synthorg/templates/schema.pysrc/synthorg/api/controllers/template_packs.py
🧠 Learnings (22)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-03-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/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 **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple variants — instead of manual whitespace validators
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 `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-03-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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion
Applied to files:
src/synthorg/api/controllers/template_packs.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/api/controllers/template_packs.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,web/src/**/*.{ts,tsx}} : Keep functions under 50 lines and files under 800 lines
Applied to files:
src/synthorg/api/controllers/template_packs.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/api/controllers/template_packs.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/api/controllers/template_packs.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:
src/synthorg/api/controllers/template_packs.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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.
Applied to files:
src/synthorg/api/controllers/template_packs.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 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:
src/synthorg/api/controllers/template_packs.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 : All error paths must log at WARNING or ERROR with context before raising
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/api/controllers/template_packs.py
🔇 Additional comments (5)
src/synthorg/templates/schema.py (2)
383-386: LGTM!The
uses_packsfield correctly usestuple[NotBlankStr, ...]per project conventions for identifier fields, with an appropriate default and description.
406-407: LGTM!The condition correctly skips agent count validation when
uses_packsis non-empty, mirroring theextendsbypass. The truthiness check on the tuple works as intended.src/synthorg/api/controllers/template_packs.py (3)
42-72: LGTM! DTOs follow all Pydantic v2 guidelines.Frozen models with proper validation constraints (
extra="forbid",allow_inf_nan=False),NotBlankStrfor identifiers, andField(ge=0)for counts.
139-183: LGTM! Deduplication helpers are well-structured.Proper case-insensitive matching, no mutation of input collections, and
_serialize_departmentsnow correctly preserveshead_roleandreporting_lines.
248-313: LGTM! Controller follows logging and error handling guidelines.
- State transitions logged at INFO (
TEMPLATE_PACK_APPLY_START,TEMPLATE_PACK_APPLY_SUCCESS)- Generic exceptions logged with context before re-raising (lines 300-306)
NotFoundErrorpassthrough is correct since it's already logged in_apply_pack_to_settings- Proper guards applied for authorization
| async def _apply_pack_to_settings( | ||
| app_state: AppState, | ||
| data: ApplyTemplatePackRequest, | ||
| ) -> ApplyTemplatePackResponse: | ||
| """Core pack application logic under the agent lock. | ||
|
|
||
| Args: | ||
| app_state: Application state. | ||
| data: Request with pack name. | ||
|
|
||
| Returns: | ||
| Summary of agents and departments added. | ||
|
|
||
| Raises: | ||
| NotFoundError: If the pack is not found. | ||
| """ | ||
| try: | ||
| loaded = await asyncio.to_thread(load_pack, data.pack_name) | ||
| except TemplateNotFoundError as exc: | ||
| logger.warning( | ||
| TEMPLATE_PACK_APPLY_ERROR, | ||
| pack_name=data.pack_name, | ||
| error=str(exc), | ||
| ) | ||
| msg = f"Template pack {data.pack_name!r} not found" | ||
| raise NotFoundError(msg) from exc | ||
|
|
||
| pack_agents = expand_template_agents(loaded.template) | ||
|
|
||
| async with _AGENT_LOCK: | ||
| current_agents = await _read_setting_list(app_state, "agents") | ||
| current_depts = await _read_setting_list(app_state, "departments") | ||
|
|
||
| new_agents = _deduplicate_agents(pack_agents, current_agents) | ||
| new_depts = _deduplicate_departments( | ||
| data.pack_name, | ||
| loaded.template.departments, | ||
| current_depts, | ||
| ) | ||
|
|
||
| settings_svc = app_state.settings_service | ||
| await settings_svc.set( | ||
| "company", | ||
| "agents", | ||
| json.dumps(current_agents + new_agents), | ||
| ) | ||
| await settings_svc.set( | ||
| "company", | ||
| "departments", | ||
| json.dumps(current_depts + new_depts), | ||
| ) | ||
|
|
||
| return ApplyTemplatePackResponse( | ||
| pack_name=data.pack_name, | ||
| agents_added=len(new_agents), | ||
| departments_added=len(new_depts), | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
_apply_pack_to_settings exceeds the 50-line function limit (57 lines).
The persistence logic (reading, deduplicating, writing back settings) can be extracted into a smaller helper to bring this function under the limit.
♻️ Suggested extraction
+async def _persist_merged_config(
+ app_state: AppState,
+ pack_name: str,
+ pack_agents: list[dict[str, Any]],
+ pack_depts: Sequence[TemplateDepartmentConfig],
+) -> tuple[int, int]:
+ """Merge and persist pack agents/departments under AGENT_LOCK.
+
+ Returns:
+ Tuple of (agents_added, departments_added).
+ """
+ async with _AGENT_LOCK:
+ current_agents = await _read_setting_list(app_state, "agents")
+ current_depts = await _read_setting_list(app_state, "departments")
+
+ new_agents = _deduplicate_agents(pack_agents, current_agents)
+ new_depts = _deduplicate_departments(pack_name, pack_depts, current_depts)
+
+ settings_svc = app_state.settings_service
+ await settings_svc.set(
+ "company",
+ "agents",
+ json.dumps(current_agents + new_agents),
+ )
+ await settings_svc.set(
+ "company",
+ "departments",
+ json.dumps(current_depts + new_depts),
+ )
+
+ return len(new_agents), len(new_depts)
+
+
async def _apply_pack_to_settings(
app_state: AppState,
data: ApplyTemplatePackRequest,
) -> ApplyTemplatePackResponse:
- """Core pack application logic under the agent lock.
-
- Args:
- app_state: Application state.
- data: Request with pack name.
-
- Returns:
- Summary of agents and departments added.
-
- Raises:
- NotFoundError: If the pack is not found.
- """
+ """Load pack and delegate merge/persist."""
try:
loaded = await asyncio.to_thread(load_pack, data.pack_name)
except TemplateNotFoundError as exc:
logger.warning(
TEMPLATE_PACK_APPLY_ERROR,
pack_name=data.pack_name,
error=str(exc),
)
msg = f"Template pack {data.pack_name!r} not found"
raise NotFoundError(msg) from exc
pack_agents = expand_template_agents(loaded.template)
-
- async with _AGENT_LOCK:
- current_agents = await _read_setting_list(app_state, "agents")
- current_depts = await _read_setting_list(app_state, "departments")
-
- new_agents = _deduplicate_agents(pack_agents, current_agents)
- new_depts = _deduplicate_departments(
- data.pack_name,
- loaded.template.departments,
- current_depts,
- )
-
- settings_svc = app_state.settings_service
- await settings_svc.set(
- "company",
- "agents",
- json.dumps(current_agents + new_agents),
- )
- await settings_svc.set(
- "company",
- "departments",
- json.dumps(current_depts + new_depts),
- )
+ agents_added, departments_added = await _persist_merged_config(
+ app_state,
+ data.pack_name,
+ pack_agents,
+ loaded.template.departments,
+ )
return ApplyTemplatePackResponse(
pack_name=data.pack_name,
- agents_added=len(new_agents),
- departments_added=len(new_depts),
+ agents_added=agents_added,
+ departments_added=departments_added,
)As per coding guidelines, "Functions must be less than 50 lines; files must be less than 800 lines".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/api/controllers/template_packs.py` around lines 186 - 242,
_extract the persistence and deduplication block inside _apply_pack_to_settings
into a new helper function (e.g., _persist_pack_settings) that is called from
within the async with _AGENT_LOCK block; the new helper should accept app_state,
data.pack_name (or loaded.template and pack_name), pack_agents, and
loaded.template.departments, perform the reads via _read_setting_list, call
_deduplicate_agents and _deduplicate_departments, write back with
settings_svc.set for "agents" and "departments", and return the new_agents and
new_depts counts or lists so _apply_pack_to_settings can build and return
ApplyTemplatePackResponse—this moves the reading/deduping/writing logic out of
_apply_pack_to_settings and reduces its length below 50 lines while still using
the existing helpers (_read_setting_list, _deduplicate_agents,
_deduplicate_departments, settings_svc).
…old for pack uniqueness - Remove no-value debug log in _read_setting_list SettingNotFoundError handler (TEMPLATE_PACK_APPLY_START was semantically wrong; missing setting is expected) - Use .casefold() instead of .lower() in _validate_unique_pack_names - Show original pack names in duplicate error (mirrors _validate_unique_department_names)
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/synthorg/api/controllers/template_packs.py (2)
198-218:⚠️ Potential issue | 🟠 MajorRender the pack before persisting it.
load_pack()gives you the first-passCompanyTemplate, but this flow storesloaded.templatedirectly viaexpand_template_agents()andloaded.template.departments. Any pack that relies on template rendering/Jinja will be live-applied with raw{{ ... }}values instead of the rendered result. Please run the same render step used by full template application here, or reject packs that contain unresolved template expressions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/template_packs.py` around lines 198 - 218, After loading the pack with load_pack, render the loaded.template (same render step the full template application uses) before calling expand_template_agents or reading loaded.template.departments so you persist the rendered CompanyTemplate instead of raw Jinja tokens; update the flow in this handler to call the shared render function (used by full template application) on loaded.template and use that rendered object in expand_template_agents, _deduplicate_agents and _deduplicate_departments, or alternatively validate the template for unresolved expressions and raise a rejection error if rendering would leave {{ ... }} tokens.
221-231:⚠️ Potential issue | 🔴 CriticalPersist the company update as one atomic operation.
These
set()calls can still leave the org half-applied: if theagentswrite succeeds and thedepartmentswrite fails, the request returns an error after mutating only one key._AGENT_LOCKprevents in-process interleaving, but it does not make the pair all-or-nothing; this needs a single transaction/CAS at the settings layer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/api/controllers/template_packs.py` around lines 221 - 231, The two separate calls to settings_svc.set("company","agents",...) and settings_svc.set("company","departments",...) can leave the org half-applied if one succeeds and the other fails; instead perform a single atomic update via the settings service (e.g. add or use an existing transactional/CAS method like settings_svc.set_many / settings_svc.transactional_update / settings_svc.compare_and_swap) that writes both keys in one operation; update the code in template_packs.py to call that atomic method (or implement it in the settings service) and remove the two independent set() calls, preserving _AGENT_LOCK only for in-process coordination while relying on the settings layer for all-or-nothing semantics.
🤖 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/schema.py`:
- Around line 406-407: Current early return in the render path (the if checking
self.extends and self.uses_packs) bypasses TemplateMetadata.max_agents
validation, allowing composed templates to exceed metadata.max_agents; change
the logic so that only the lower-bound check (min_agents) may be deferred for
pack-based templates while the upper-bound max_agents validation always runs
after merge. Concretely, update the branch around uses_packs/extends so it does
not skip the max_agents validation (or remove the early return and move the
metadata count check into the post-merge/render path that handles merged
templates), ensuring TemplateMetadata.min_agents and TemplateMetadata.max_agents
are enforced against the final agent count for functions/methods that assemble
templates.
---
Duplicate comments:
In `@src/synthorg/api/controllers/template_packs.py`:
- Around line 198-218: After loading the pack with load_pack, render the
loaded.template (same render step the full template application uses) before
calling expand_template_agents or reading loaded.template.departments so you
persist the rendered CompanyTemplate instead of raw Jinja tokens; update the
flow in this handler to call the shared render function (used by full template
application) on loaded.template and use that rendered object in
expand_template_agents, _deduplicate_agents and _deduplicate_departments, or
alternatively validate the template for unresolved expressions and raise a
rejection error if rendering would leave {{ ... }} tokens.
- Around line 221-231: The two separate calls to
settings_svc.set("company","agents",...) and
settings_svc.set("company","departments",...) can leave the org half-applied if
one succeeds and the other fails; instead perform a single atomic update via the
settings service (e.g. add or use an existing transactional/CAS method like
settings_svc.set_many / settings_svc.transactional_update /
settings_svc.compare_and_swap) that writes both keys in one operation; update
the code in template_packs.py to call that atomic method (or implement it in the
settings service) and remove the two independent set() calls, preserving
_AGENT_LOCK only for in-process coordination while relying on the settings layer
for all-or-nothing semantics.
🪄 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: 9b9559c0-1828-43b1-aa20-468010597dcb
📒 Files selected for processing (2)
src/synthorg/api/controllers/template_packs.pysrc/synthorg/templates/schema.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dashboard Test
- GitHub Check: Build Sandbox
- GitHub Check: Build Web
- GitHub Check: Build Backend
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations— Python 3.14 has PEP 649
Useexcept A, B:(no parentheses) per PEP 758 except syntax — ruff enforces this on Python 3.14
Type hints required on all public functions, enforced by mypy strict mode
Docstrings must use Google style and are required on public classes and functions, enforced by ruff D rules
Never mutate existing objects — create new objects; for non-Pydantic internal collections, usecopy.deepcopy()at construction +MappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, rely onfrozen=Truefor field reassignment prevention and usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Use frozen Pydantic models for config/identity; separate mutable-via-copy models usingmodel_copy(update=...)for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict); useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing and validating redundant fields
UseNotBlankStr(fromcore.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple variants — instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) for structured concurrency instead of barecreate_task
Line length must be 88 characters, enforced by ruff
Functions must be less than 50 lines; files must be less than 800 lines
Logging variable name must always belogger(not_logger, notlog)
Always use structured logging withlogger.info(EVENT, key=value)— neverlogger.info("msg %s", val)
All error paths must log at WAR...
Files:
src/synthorg/templates/schema.pysrc/synthorg/api/controllers/template_packs.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code, except inobservability/setup.py,observability/sinks.py,observability/syslog_handler.py, andobservability/http_handler.py
Always use event name constants from the domain-specific module undersynthorg.observability.events(e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool); import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Files:
src/synthorg/templates/schema.pysrc/synthorg/api/controllers/template_packs.py
🧠 Learnings (21)
📓 Common learnings
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.
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-03-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 `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-04-02T09:13:44.997Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:13:44.997Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple variants — instead of manual whitespace validators
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 `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
src/synthorg/templates/schema.py
📚 Learning: 2026-03-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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion
Applied to files:
src/synthorg/api/controllers/template_packs.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/api/controllers/template_packs.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,web/src/**/*.{ts,tsx}} : Keep functions under 50 lines and files under 800 lines
Applied to files:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/api/controllers/template_packs.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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/api/controllers/template_packs.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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.
Applied to files:
src/synthorg/api/controllers/template_packs.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 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:
src/synthorg/api/controllers/template_packs.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow them. All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/api/controllers/template_packs.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 : All error paths must log at WARNING or ERROR with context before raising
Applied to files:
src/synthorg/api/controllers/template_packs.py
| if self.extends is not None or self.uses_packs: | ||
| return self |
There was a problem hiding this comment.
uses_packs now disables the only max_agents check.
Returning here skips both bounds as soon as a template references a pack. The pack render path later validates only RootConfig, which does not know about TemplateMetadata.min_agents / max_agents, so a composed template can exceed metadata.max_agents silently. Please move the metadata count check to the post-merge render path, or only defer the lower-bound check here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/templates/schema.py` around lines 406 - 407, Current early
return in the render path (the if checking self.extends and self.uses_packs)
bypasses TemplateMetadata.max_agents validation, allowing composed templates to
exceed metadata.max_agents; change the logic so that only the lower-bound check
(min_agents) may be deferred for pack-based templates while the upper-bound
max_agents validation always runs after merge. Concretely, update the branch
around uses_packs/extends so it does not skip the max_agents validation (or
remove the early return and move the metadata count check into the
post-merge/render path that handles merged templates), ensuring
TemplateMetadata.min_agents and TemplateMetadata.max_agents are enforced against
the final agent count for functions/methods that assemble templates.
🤖 I have created a release *beep* *boop* --- ## [0.5.7](v0.5.6...v0.5.7) (2026-04-02) ### Features * comparison page -- SynthOrg vs agent orchestration frameworks ([#994](#994)) ([6f937ef](6f937ef)), closes [#981](#981) * event-driven and budget-driven ceremony scheduling strategies ([#995](#995)) ([f88e7b0](f88e7b0)), closes [#971](#971) [#972](#972) * template packs for post-setup additive team expansion ([#996](#996)) ([b45e14a](b45e14a)), closes [#727](#727) ### Performance * preload JetBrains Mono font, remove unused api.github.com preconnect ([#998](#998)) ([2a189c2](2a189c2)) * run only affected modules in pre-push hooks ([#992](#992)) ([7956e23](7956e23)) ### Maintenance * bump astro from 6.1.2 to 6.1.3 in /site in the all group ([#988](#988)) ([17b58db](17b58db)) * bump the all group across 1 directory with 2 updates ([#989](#989)) ([1ff462a](1ff462a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
Template packs -- small, focused YAML fragments that can be applied additively to a running org or composed into templates via a
uses_packsfield.What changed
Backend (Python)
uses_packsfield toCompanyTemplateschema (optional, backward-compatible)pack_loader.py) with built-in + user~/.synthorg/template-packs/paths_inheritance.pyto exposerender_parent_config()for the new merge flowGET /template-packsandPOST /template-packs/applyendpointsFrontend (React)
PackInfoResponse,ApplyTemplatePackRequest/Response)template-packs.ts)PackSelectionDialogcomponent with loading/error/empty statesTests
test_pack_loader.py: loader, discovery, path traversal, user override, schema validationtest_uses_packs.py: composition, merge ordering, child-wins, backward compatibilityReview coverage
Pre-reviewed by 3 agents (code-reviewer, frontend-reviewer, conventions-enforcer), 9 findings addressed:
BUILTIN_PACKSwrapped inMappingProxyTypeextra="forbid"uses_packsstring input validationload_packTest plan
Closes #727