Skip to content

feat: sprint ceremony runtime scheduler with pluggable strategies#983

Merged
Aureliolo merged 6 commits intomainfrom
feat/sprint-ceremony-scheduler
Apr 1, 2026
Merged

feat: sprint ceremony runtime scheduler with pluggable strategies#983
Aureliolo merged 6 commits intomainfrom
feat/sprint-ceremony-scheduler

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Introduce a pluggable ceremony scheduling system that bridges sprint lifecycle events to the meeting protocol system
  • Ship CeremonySchedulingStrategy protocol with 8 strategy types enumerated (task-driven reference implementation included)
  • Ship VelocityCalculator protocol with pluggable velocity computation (task-driven reference: pts/task)
  • Add CeremonyPolicyConfig with 3-level resolution (project / department / per-ceremony override)
  • Add CeremonyScheduler service with per-sprint strategy locking, completion counters, one-shot safety, and auto-transition
  • Add ceremony-to-meeting bridge functions converting SprintCeremonyConfig to MeetingTypeConfig
  • Extend VelocityRecord with optional task_completion_count, wall_clock_seconds, budget_consumed fields
  • Extend SprintCeremonyConfig (frequency now optional, policy_override added) and SprintConfig (ceremony_policy field)
  • Add comprehensive design page: docs/design/ceremony-scheduling.md documenting all 8 strategies, protocols, config, velocity, template defaults, and implementation roadmap
  • File 10 follow-up issues (feat: calendar ceremony scheduling strategy #969-feat: per-department ceremony policy override in template schema #980) for remaining strategy implementations, template defaults, migration UX, and dashboard UI across v0.5.7-v0.6.2

Test plan

  • 281 unit tests pass (uv run python -m pytest tests/unit/engine/workflow/ -n auto)
  • All existing 202 workflow tests still pass (no regressions)
  • 79 new tests covering: policy config/resolution, eval context, protocol conformance, task-driven strategy, velocity calculator, ceremony bridge, ceremony scheduler (activation, deactivation, one-shot safety, auto-transition, error propagation)
  • uv run ruff check src/ tests/ -- all clean
  • uv run mypy src/ tests/ -- all clean
  • Pre-reviewed by 6 agents (code-reviewer, type-design-analyzer, test-analyzer, silent-failure-hunter, async-reviewer, issue-verifier), 19 findings addressed

Closes #961

Aureliolo and others added 4 commits April 1, 2026 21:18
Introduce a pluggable ceremony scheduling system that bridges sprint
lifecycle events to the meeting protocol system. The architecture
supports 8 scheduling strategies (task-driven, calendar, hybrid,
event-driven, budget-driven, throughput-adaptive, external-trigger,
milestone-driven) behind a CeremonySchedulingStrategy protocol.

This PR ships the foundation: protocol interfaces, config models,
task-driven reference implementation, CeremonyScheduler service,
ceremony-to-meeting bridge, and comprehensive design docs. Follow-up
issues (#969-#980) track the remaining 7 strategy implementations,
template defaults, migration UX, and dashboard UI across v0.5.7-v0.6.2.

Key components:
- CeremonySchedulingStrategy protocol (stateless eval + lifecycle hooks)
- VelocityCalculator protocol (pluggable velocity computation)
- CeremonyPolicyConfig with 3-level resolution (project/dept/ceremony)
- CeremonyScheduler service (per-sprint strategy locking, counters)
- TaskDrivenStrategy + TaskDrivenVelocityCalculator (reference impls)
- Design page: docs/design/ceremony-scheduling.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix one-shot ceremony safety: _trigger_ceremony returns bool, callers
  only mark fired on success (prevents permanent ceremony loss on failure)
- Fix activate_sprint partial state: cleanup on async hook failure
- Fix _build_context: budget comments, logging on inactive scheduler
- Extract on_task_completed into sub-methods (< 50 lines per function)
- Add __post_init__ validation to CeremonyEvalContext (11 numeric fields)
- Fix TaskDrivenStrategy: use named constant for transition threshold
- Add logger to TaskDrivenVelocityCalculator, extract "pts/task" constant
- Use asyncio.TaskGroup for parallel ceremony firing
- Add 14 new tests: validation, new fields, error propagation, edge cases
- Add design decisions section to ceremony-scheduling.md

Pre-reviewed by 6 agents, 19 findings addressed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 1, 2026 19:52
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3261a5f7-448b-4966-bdb4-f0db9168c7be

📥 Commits

Reviewing files that changed from the base of the PR and between 067cbf8 and 411ed0c.

📒 Files selected for processing (2)
  • docs/design/ceremony-scheduling.md
  • src/synthorg/engine/workflow/ceremony_context.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python 3.14+ with PEP 649 native lazy annotations -- do NOT use from __future__ import annotations.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Maximum line length is 88 characters (ruff enforced).

Files:

  • src/synthorg/engine/workflow/ceremony_context.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints; mypy strict mode enforced.
Docstrings must follow Google style and are required on public classes/functions (enforced by ruff D rules).
Use immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Adopt Pydantic v2 conventions: use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Functions must be less than 50 lines, files must be less than 800 lines.
Handle errors explicitly, never silently swallow exceptions.
Validate at system boundaries (user input, external APIs, config files).
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001. Vendor names only in: (1) docs/design/operations.md, (2) .claude/ files, (3) third-party imports, (4) provider presets.

Files:

  • src/synthorg/engine/workflow/ceremony_context.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have from synthorg.observability import get_logger then logger = get_logger(__name__).
Never use import logging, logging.getLogger(), or print() in application code. Exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py may use stdlib logging and stderr prints.
Always use variable name logger (not _logger, not log).
Use event name constants from synthorg.observability.events domain-specific modules, not hardcoded strings. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Always use structured logging: logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
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 entry/exit of key functions.

Files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
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 entry/exit of key functions.

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.py
📚 Learning: 2026-04-01T18:29:59.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.467Z
Learning: Applies to src/**/*.py : Use immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.py
📚 Learning: 2026-04-01T18:29:59.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.467Z
Learning: Applies to src/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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 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/ceremony-scheduling.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: 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:

  • docs/design/ceremony-scheduling.md
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. Do not silently diverge from the design spec -- every deviation needs explicit user approval and must be updated in the relevant design page.

Applied to files:

  • docs/design/ceremony-scheduling.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/ceremony-scheduling.md
🪛 LanguageTool
docs/design/ceremony-scheduling.md

[style] ~514-~514: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... standup: task_driven (from project). Resolution for any department's retrospective: `ev...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🔇 Additional comments (2)
src/synthorg/engine/workflow/ceremony_context.py (1)

1-165: LGTM! Well-structured frozen dataclass with comprehensive validation.

The implementation correctly addresses all previous review feedback:

  • Module logger and structured logging with event constants in all error paths
  • Collection normalization with copy.deepcopy() for velocity_history (strings in external_events are immutable, so tuple() suffices)
  • __post_init__ split into _normalize_collections and _validate_fields helpers

The strict [0.0, 1.0] validation for fraction fields (budget_consumed_fraction, sprint_percentage_complete) is appropriate defensive behavior—upstream callers in CeremonyScheduler are responsible for ensuring computed fractions don't exceed bounds due to edge cases.

docs/design/ceremony-scheduling.md (1)

1-692: LGTM! Comprehensive design specification for the ceremony scheduling subsystem.

The documentation thoroughly covers:

  • Motivation and design decisions for pluggable scheduling strategies
  • Architecture overview with clear component relationships
  • All eight strategy types with YAML configuration examples
  • Protocol interfaces (CeremonySchedulingStrategy, VelocityCalculator) with detailed method signatures
  • 3-level config resolution semantics with field-by-field override logic
  • Velocity tracking extensions and strategy-specific defaults
  • Template defaults and implementation roadmap with linked issues

Previous review feedback has been addressed: union types in tables are properly escaped (int \| None), compound adjectives use consistent hyphenation ("built-in", "open-source").


Walkthrough

Adds a pluggable ceremony scheduling subsystem and task-driven sprint cadence. Introduces a runtime-safe CeremonyScheduler, a CeremonySchedulingStrategy protocol with eight strategy identifiers, a three-level CeremonyPolicyConfig resolver, and a VelocityCalculator protocol with a task-driven implementation. Extends sprint models (SprintConfig, SprintCeremonyConfig, VelocityRecord) with policy overrides and extra velocity fields. Provides a ceremony→meeting bridge, observability event constants, docs, and comprehensive unit tests for strategies, scheduler, policy, context, and velocity components.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.23% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: sprint ceremony runtime scheduler with pluggable strategies' accurately summarizes the main change—adding a pluggable ceremony scheduling system with a runtime scheduler.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, detailing the ceremony scheduling system, protocols, implementations, model extensions, and test coverage.
Linked Issues check ✅ Passed The PR successfully addresses all coding objectives from issue #961: implements CeremonySchedulingStrategy protocol with 8 types and task-driven reference, VelocityCalculator protocol, CeremonyPolicyConfig with 3-level resolution, CeremonyScheduler service with per-sprint locking/auto-transition, ceremony-to-meeting bridge, VelocityRecord extensions, SprintCeremonyConfig/SprintConfig model updates.
Out of Scope Changes check ✅ Passed All changes are in-scope: documentation pages, protocols and implementations for strategies/velocity, scheduler service, config models, bridge utilities, observability events, and comprehensive test coverage—all directly addressing the sprint ceremony scheduling objectives.

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


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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Dependency Review

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

Snapshot Warnings

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

Scanned Files

None

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a pluggable ceremony scheduling architecture for sprint workflows, allowing for flexible triggers based on tasks, time, or events. It includes the core CeremonyScheduler service, a reference task-driven strategy, and extended velocity tracking metrics. Feedback focuses on correcting invalid Python syntax in exception handling blocks within the new scheduler service.

try:
await strategy.on_sprint_activated(sprint, config)
await self._fire_sprint_start_ceremonies(sprint, config)
except MemoryError, RecursionError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

Invalid Python syntax for catching multiple exceptions. In Python 3, multiple exceptions must be grouped in a tuple.

        except (MemoryError, RecursionError):

event_name,
context=context,
)
except MemoryError, RecursionError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

Invalid Python syntax for catching multiple exceptions. In Python 3, multiple exceptions must be grouped in a tuple.

        except (MemoryError, RecursionError):

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new runtime “ceremony scheduling” subsystem in engine/workflow that can trigger sprint ceremonies via the meeting system using a pluggable strategy interface, plus pluggable velocity calculation and hierarchical policy configuration.

Changes:

  • Add core ceremony scheduling abstractions (CeremonySchedulingStrategy, CeremonyScheduler, CeremonyPolicyConfig resolution, CeremonyEvalContext) and a task-driven reference strategy.
  • Add velocity calculator protocol/types plus task-driven reference calculator; extend VelocityRecord with additional optional dimensions.
  • Wire the scheduler into API auto-wiring/app state and add design documentation + unit tests.

Reviewed changes

Copilot reviewed 32 out of 34 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/unit/engine/workflow/velocity_calculators/test_task_driven.py Unit tests for TaskDrivenVelocityCalculator.
tests/unit/engine/workflow/test_velocity_types.py Tests for VelocityCalcType and VelocityMetrics.
tests/unit/engine/workflow/test_velocity_calculator.py Protocol conformance tests for VelocityCalculator.
tests/unit/engine/workflow/test_sprint_velocity.py Tests for new optional fields on VelocityRecord + record_velocity params.
tests/unit/engine/workflow/test_sprint_config.py Tests for SprintCeremonyConfig validation + default/custom ceremony policy.
tests/unit/engine/workflow/test_ceremony_strategy.py Protocol conformance tests for CeremonySchedulingStrategy.
tests/unit/engine/workflow/test_ceremony_scheduler.py Tests for CeremonyScheduler activation, triggering, one-shot behavior, transitions, error handling.
tests/unit/engine/workflow/test_ceremony_policy.py Tests for policy models and 3-level resolution behavior.
tests/unit/engine/workflow/test_ceremony_context.py Tests for CeremonyEvalContext validation/immutability.
tests/unit/engine/workflow/test_ceremony_bridge.py Tests for ceremony-to-meeting bridge functions and trigger event naming.
tests/unit/engine/workflow/strategies/test_task_driven.py Tests for the TaskDrivenStrategy reference implementation.
src/synthorg/observability/events/workflow.py Add observability event constants for ceremony scheduling lifecycle.
src/synthorg/engine/workflow/velocity_types.py Add VelocityCalcType enum and VelocityMetrics model.
src/synthorg/engine/workflow/velocity_calculators/task_driven.py Add task-driven velocity calculator reference implementation.
src/synthorg/engine/workflow/velocity_calculators/init.py Export velocity calculator(s).
src/synthorg/engine/workflow/velocity_calculator.py Add runtime-checkable VelocityCalculator protocol.
src/synthorg/engine/workflow/strategies/task_driven.py Add task-driven scheduling strategy reference implementation.
src/synthorg/engine/workflow/strategies/init.py Export strategy implementation(s).
src/synthorg/engine/workflow/sprint_velocity.py Extend VelocityRecord + extend record_velocity API to accept new optional dimensions.
src/synthorg/engine/workflow/sprint_config.py Extend sprint config with ceremony_policy; allow ceremonies to be frequency-less via policy_override.
src/synthorg/engine/workflow/ceremony_strategy.py Add runtime-checkable CeremonySchedulingStrategy protocol.
src/synthorg/engine/workflow/ceremony_scheduler.py Add CeremonyScheduler service coordinating triggers and sprint transitions.
src/synthorg/engine/workflow/ceremony_policy.py Add policy models, strategy enum, and 3-level resolution function.
src/synthorg/engine/workflow/ceremony_context.py Add CeremonyEvalContext used during strategy evaluation.
src/synthorg/engine/workflow/ceremony_bridge.py Add pure bridge functions from sprint ceremonies to meeting type configs / trigger event names.
src/synthorg/engine/workflow/init.py Export new ceremony/velocity APIs from the workflow package.
src/synthorg/api/state.py Add ceremony_scheduler to application state.
src/synthorg/api/auto_wire.py Auto-wire CeremonyScheduler alongside meeting services.
src/synthorg/api/app.py Thread the auto-wired ceremony_scheduler into AppState.
docs/design/engine.md Link to the new ceremony scheduling design page.
docs/design/ceremony-scheduling.md New design document describing strategies, protocols, config resolution, and roadmap.
docs/DESIGN_SPEC.md Add the ceremony scheduling page to the design spec index.

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

Comment on lines +227 to +228
await self._evaluate_ceremonies(sprint)
await self._check_one_shot_triggers(sprint, context)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

on_task_completed() currently calls _evaluate_ceremonies() and then _check_one_shot_triggers(). For ceremonies with triggers like sprint_midpoint / sprint_end, this can cause the same ceremony to be triggered twice on the task completion where the threshold is first met (once via should_fire_ceremony, then again via the one-shot path). Consider skipping one-shot triggers inside _evaluate_ceremonies() (or moving the one-shot check earlier / marking one-shots as fired when triggered) so midpoint/end ceremonies fire at most once per threshold crossing.

Suggested change
await self._evaluate_ceremonies(sprint)
await self._check_one_shot_triggers(sprint, context)
await self._check_one_shot_triggers(sprint, context)
await self._evaluate_ceremonies(sprint)

Copilot uses AI. Check for mistakes.
Comment on lines +257 to +271
def _check_auto_transition(
self,
sprint: Sprint,
context: CeremonyEvalContext,
) -> Sprint:
"""Check and apply auto-transition if strategy says so."""
assert self._active_strategy is not None # noqa: S101
assert self._sprint_config is not None # noqa: S101

target = self._active_strategy.should_transition_sprint(
sprint,
self._sprint_config,
context,
)
if target is not None and sprint.status is SprintStatus.ACTIVE:
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

CeremonyPolicyConfig.auto_transition is not respected: _check_auto_transition() always calls strategy.should_transition_sprint(...) and applies the transition when it returns a target. This means setting auto_transition=False in the sprint’s ceremony_policy currently has no effect. Gate the transition logic on self._sprint_config.ceremony_policy.auto_transition (treat None as default) so the flag reliably disables auto-transitions.

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +147
except Exception:
logger.exception(
SPRINT_CEREMONY_SCHEDULER_STARTED,
sprint_id=sprint.id,
note="activation failed, deactivating",
)
await self.deactivate_sprint()
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

In the activation failure handler, logger.exception(...) uses the SPRINT_CEREMONY_SCHEDULER_STARTED event even though activation failed. This will make observability streams ambiguous (a failure will look like a successful start unless consumers inspect note). Consider using a dedicated failure event (or SPRINT_CEREMONY_SCHEDULER_STOPPED / a new ..._START_FAILED) so dashboards/alerts can distinguish start success vs failure reliably.

Copilot uses AI. Check for mistakes.
pct = completed / total_tasks if total_tasks > 0 else 0.0

return CeremonyEvalContext(
completions_since_last_trigger=self._total_completions,
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

_build_context() sets completions_since_last_trigger to self._total_completions, which is the total completions in the sprint and doesn’t match the field’s documented meaning (“since this ceremony’s trigger last fired”). Since this context is passed into strategy.on_task_completed(...), strategies that rely on completions_since_last_trigger may behave incorrectly. Consider either (a) tracking a separate global “completions since any ceremony fired” counter and using that here, or (b) setting this field to 0 in the global context and reserving the per-ceremony meaning for _build_ceremony_context().

Suggested change
completions_since_last_trigger=self._total_completions,
# In the global context, this field is reserved for per-ceremony usage
# and is therefore set to 0; the per-ceremony context builder is
# responsible for providing the actual "since last trigger" count.
completions_since_last_trigger=0,

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +57
- **Templates can mix strategies.** The 3-level config resolution (project /
department / per-ceremony) allows different ceremonies to use different
strategies within a single template.
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The doc states “Templates can mix strategies … different ceremonies to use different strategies within a single template”, but the shipped CeremonyScheduler.activate_sprint(...) accepts a single CeremonySchedulingStrategy locked for the sprint and evaluates all ceremonies through that one strategy. Either update the documentation to reflect the current “one strategy per sprint” behavior, or extend the runtime design (e.g., strategy registry + per-ceremony resolved policy) so mixed-strategy ceremonies are actually supported.

Suggested change
- **Templates can mix strategies.** The 3-level config resolution (project /
department / per-ceremony) allows different ceremonies to use different
strategies within a single template.
- **Templates can configure strategies at multiple levels.** The 3-level config
resolution (project / department / per-ceremony) feeds into strategy
selection, but the effective `CeremonySchedulingStrategy` is resolved and
locked per sprint.

Copilot uses AI. Check for mistakes.
Comment on lines +434 to +442
**Shipped implementations:**

| Calculator | Primary unit | Strategy default for |
|------------|-------------|---------------------|
| `TaskDrivenVelocityCalculator` | `pts/task` | task_driven, throughput_adaptive |
| `CalendarVelocityCalculator` | `pts/day` | calendar |
| `MultiDimensionalVelocityCalculator` | `pts/sprint` (+ secondary) | hybrid, event_driven, milestone_driven |
| `BudgetVelocityCalculator` | `pts/EUR` | budget_driven |
| `PointsPerSprintVelocityCalculator` | `pts/sprint` | external_trigger |
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The “Shipped implementations” table lists CalendarVelocityCalculator, MultiDimensionalVelocityCalculator, BudgetVelocityCalculator, and PointsPerSprintVelocityCalculator, but the code in this PR only adds TaskDrivenVelocityCalculator under src/synthorg/engine/workflow/velocity_calculators/. To avoid misleading readers, either mark the additional calculators as “planned / follow-up issues” or move this table into the roadmap section until those implementations land.

Suggested change
**Shipped implementations:**
| Calculator | Primary unit | Strategy default for |
|------------|-------------|---------------------|
| `TaskDrivenVelocityCalculator` | `pts/task` | task_driven, throughput_adaptive |
| `CalendarVelocityCalculator` | `pts/day` | calendar |
| `MultiDimensionalVelocityCalculator` | `pts/sprint` (+ secondary) | hybrid, event_driven, milestone_driven |
| `BudgetVelocityCalculator` | `pts/EUR` | budget_driven |
| `PointsPerSprintVelocityCalculator` | `pts/sprint` | external_trigger |
**Shipped and planned implementations:**
| Calculator | Primary unit | Strategy default for |
|------------|-------------|---------------------|
| `TaskDrivenVelocityCalculator` | `pts/task` | task_driven, throughput_adaptive |
| `CalendarVelocityCalculator` (planned) | `pts/day` | calendar |
| `MultiDimensionalVelocityCalculator` (planned) | `pts/sprint` (+ secondary) | hybrid, event_driven, milestone_driven |
| `BudgetVelocityCalculator` (planned) | `pts/EUR` | budget_driven |
| `PointsPerSprintVelocityCalculator` (planned) | `pts/sprint` | external_trigger |

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +119
strategy_config:
standup_every_n: 5
ceremonies:
- name: sprint_planning
# fires on sprint_start (one-shot)
- name: standup
# fires every 5 task completions
- name: retrospective
# fires on sprint_end (all tasks done)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The Task-Driven YAML example doesn’t match the shipped config schema/implementation:

  • TaskDrivenStrategy reads trigger config from each ceremony’s policy_override.strategy_config (keys like trigger, every_n_completions, sprint_percentage), not from sprint.ceremony_policy.strategy_config (e.g. standup_every_n).
  • Each ceremony must set at least one of frequency or policy_override (per SprintCeremonyConfig validator), but the example ceremonies omit both.
    Consider updating the example to use policy_override: { strategy: task_driven, strategy_config: ... } on the relevant ceremonies (or show frequency where appropriate).
Suggested change
strategy_config:
standup_every_n: 5
ceremonies:
- name: sprint_planning
# fires on sprint_start (one-shot)
- name: standup
# fires every 5 task completions
- name: retrospective
# fires on sprint_end (all tasks done)
ceremonies:
- name: sprint_planning
# fires on sprint_start (one-shot)
policy_override:
strategy: task_driven
strategy_config:
trigger: sprint_start
- name: standup
# fires every 5 task completions
policy_override:
strategy: task_driven
strategy_config:
trigger: task_completion
every_n_completions: 5
- name: retrospective
# fires on sprint_end (all tasks done)
policy_override:
strategy: task_driven
strategy_config:
trigger: sprint_end

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

codecov bot commented Apr 1, 2026

Codecov Report

❌ Patch coverage is 95.45455% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.91%. Comparing base (84be9f8) to head (411ed0c).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/engine/workflow/ceremony_scheduler.py 89.69% 12 Missing and 5 partials ⚠️
...synthorg/engine/workflow/strategies/task_driven.py 93.33% 4 Missing and 2 partials ⚠️
src/synthorg/api/state.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #983      +/-   ##
==========================================
+ Coverage   91.86%   91.91%   +0.05%     
==========================================
  Files         626      637      +11     
  Lines       33996    34521     +525     
  Branches     3315     3367      +52     
==========================================
+ Hits        31230    31731     +501     
- Misses       2179     2196      +17     
- Partials      587      594       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/ceremony-scheduling.md`:
- Line 322: Update the compound adjectives in the documentation to use
hyphenated technical style: replace the phrase "open source" in the sentence
containing "Best for: phased delivery, release-oriented workflows, open source"
with "open-source", and similarly replace "built in" or "built-in" occurrences
(notably the instance near the sentence that mentions "built in" around line
613) with "built-in" so both compound terms use consistent hyphenation.
- Around line 576-578: The table cells for the three fields
(`task_completion_count`, `wall_clock_seconds`, `budget_consumed`) contain
unescaped pipe characters in the type strings (e.g., `int | None`) which breaks
the Markdown table; edit those three rows to escape the pipe characters (either
replace "|" with "\|" or use the HTML entity "&#124;") inside the cell contents
so the types become e.g. `int \| None` or `int &#124; None`, keeping the
backticks and labels unchanged.

In `@src/synthorg/api/auto_wire.py`:
- Around line 291-299: The auto-creation of CeremonyScheduler (constructed as
ceremony_scheduler and returned in MeetingWireResult) is missing the structured
observability call; add a logger.info(API_SERVICE_AUTO_WIRED,
service="CeremonyScheduler", instance=ceremony_scheduler) (or matching
structured log keys used elsewhere) immediately after the CeremonyScheduler is
constructed so this auto-wired path emits the same API_SERVICE_AUTO_WIRED event
as other services.

In `@src/synthorg/engine/workflow/ceremony_bridge.py`:
- Around line 84-90: The log currently sets has_trigger based on
ceremony.policy_override which misreports hybrid ceremonies; instead derive the
trigger presence from the actual MeetingTypeConfig used (i.e., compute or fetch
the MeetingTypeConfig instance for this ceremony and set has_trigger =
meeting_type_config.trigger is not None) and use that in the
SPRINT_CEREMONY_BRIDGE_CREATED logger call so the logged has_trigger reflects
the real trigger value.

In `@src/synthorg/engine/workflow/ceremony_context.py`:
- Around line 8-10: Add the module logger by importing get_logger from
synthorg.observability and instantiate logger = get_logger(__name__) at the top
of ceremony_context.py (next to the dataclass/TYPE_CHECKING imports), then
before every error path that raises ValueError in this module (the validation
branches referenced in the review around the ValueError raises) log the failure
with context using logger.warning or logger.error as appropriate (include
variable/argument values and a short message) immediately prior to raising so
callers and logs capture why the ValueError is raised.
- Around line 50-52: In CeremonyContext.__post_init__, defensively normalize
incoming collection fields by deepcopying and converting any mutable sequences
to immutable tuples: deepcopy the incoming velocity_history and external_events
and assign tuple(...) versions to self.velocity_history and
self.external_events; for any non-Pydantic mapping fields (the other collection
fields referenced in the review), deepcopy them and wrap with
types.MappingProxyType to enforce read-only access. Ensure you import
copy.deepcopy and types.MappingProxyType and perform these assignments in
__post_init__ so the instance respects the tuple/map immutability contract.

In `@src/synthorg/engine/workflow/ceremony_scheduler.py`:
- Around line 347-394: _build_context and _build_ceremony_context duplicate the
same computation of total_tasks, completed, and pct; extract that logic into a
small helper (e.g., _compute_task_progress or _task_progress_for_sprint) that
takes a Sprint and returns total_tasks, completed, and
sprint_percentage_complete (or a dict/NamedTuple), then call this helper from
both _build_context and _build_ceremony_context and use the returned values when
building CeremonyEvalContext to preserve existing field assignments
(completions, total_completions, elapsed_seconds, budget fields,
velocity_history, external_events, story_points_*).

In `@src/synthorg/engine/workflow/sprint_velocity.py`:
- Around line 89-95: The record_velocity function accepts optional external
metrics (task_completion_count, wall_clock_seconds, budget_consumed) but does
not validate or log them before building the VelocityRecord; add explicit
validation checks in record_velocity before the VelocityRecord construction:
verify types and sensible ranges (e.g., non-negative numbers, integers where
expected for task_completion_count), log a WARNING with context (which
parameter, sprint id/name, provided value) for recoverable/coercible issues or
log ERROR and raise a ValueError for clearly invalid inputs, and normalize or
coerce values if appropriate; reference the parameter names
task_completion_count, wall_clock_seconds, budget_consumed and the
VelocityRecord construction site in record_velocity when adding checks and logs.

In `@src/synthorg/engine/workflow/velocity_calculators/task_driven.py`:
- Around line 47-50: Replace the hardcoded event string in the logger.debug call
inside the velocity task-driven logic with the canonical event constant: import
VELOCITY_TASK_DRIVEN_NO_TASK_COUNT from synthorg.observability.events.workflow
and use that constant as the first argument to logger.debug in the
function/method where the current
logger.debug("velocity.task_driven.no_task_count", sprint_id=record.sprint_id)
appears; ensure the import is added at the top of the module and nothing else in
the call is changed.

In `@tests/unit/engine/workflow/test_sprint_velocity.py`:
- Around line 99-134: Refactor the three nearly identical tests
(test_negative_task_completion_count_rejected,
test_negative_wall_clock_rejected, test_negative_budget_consumed_rejected) into
a single parametrized test that calls VelocityRecord with different invalid
fields; use pytest.mark.parametrize to iterate over tuples like
("task_completion_count", -1), ("wall_clock_seconds", -1.0), ("budget_consumed",
-0.01) and in the test construct kwargs merging the common args (sprint_id,
sprint_number, story_points_committed, story_points_completed, duration_days)
with the varying field, then assert pytest.raises(ValueError, match="greater
than or equal") around VelocityRecord(...) to preserve existing assertions and
names.
🪄 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: e8e39f90-b52b-462b-96b7-fd58432314c7

📥 Commits

Reviewing files that changed from the base of the PR and between a854dcc and 07261d6.

📒 Files selected for processing (34)
  • docs/DESIGN_SPEC.md
  • docs/design/ceremony-scheduling.md
  • docs/design/engine.md
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/ceremony_policy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/sprint_config.py
  • src/synthorg/engine/workflow/sprint_velocity.py
  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
  • src/synthorg/engine/workflow/velocity_calculator.py
  • src/synthorg/engine/workflow/velocity_calculators/__init__.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/velocity_types.py
  • src/synthorg/observability/events/workflow.py
  • tests/unit/engine/workflow/strategies/__init__.py
  • tests/unit/engine/workflow/strategies/test_task_driven.py
  • tests/unit/engine/workflow/test_ceremony_bridge.py
  • tests/unit/engine/workflow/test_ceremony_context.py
  • tests/unit/engine/workflow/test_ceremony_policy.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.py
  • tests/unit/engine/workflow/test_ceremony_strategy.py
  • tests/unit/engine/workflow/test_sprint_config.py
  • tests/unit/engine/workflow/test_sprint_velocity.py
  • tests/unit/engine/workflow/test_velocity_calculator.py
  • tests/unit/engine/workflow/test_velocity_types.py
  • tests/unit/engine/workflow/velocity_calculators/__init__.py
  • tests/unit/engine/workflow/velocity_calculators/test_task_driven.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Agent
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python 3.14+ with PEP 649 native lazy annotations -- do NOT use from __future__ import annotations.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Maximum line length is 88 characters (ruff enforced).

Files:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • tests/unit/engine/workflow/test_sprint_velocity.py
  • src/synthorg/engine/workflow/velocity_calculators/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
  • tests/unit/engine/workflow/test_sprint_config.py
  • tests/unit/engine/workflow/test_ceremony_context.py
  • src/synthorg/api/auto_wire.py
  • tests/unit/engine/workflow/test_velocity_calculator.py
  • src/synthorg/engine/workflow/velocity_calculator.py
  • tests/unit/engine/workflow/test_velocity_types.py
  • tests/unit/engine/workflow/test_ceremony_strategy.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/sprint_config.py
  • tests/unit/engine/workflow/test_ceremony_bridge.py
  • src/synthorg/engine/workflow/velocity_types.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/sprint_velocity.py
  • src/synthorg/observability/events/workflow.py
  • tests/unit/engine/workflow/velocity_calculators/test_task_driven.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • tests/unit/engine/workflow/test_ceremony_policy.py
  • tests/unit/engine/workflow/strategies/test_task_driven.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/ceremony_policy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints; mypy strict mode enforced.
Docstrings must follow Google style and are required on public classes/functions (enforced by ruff D rules).
Use immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Adopt Pydantic v2 conventions: use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Functions must be less than 50 lines, files must be less than 800 lines.
Handle errors explicitly, never silently swallow exceptions.
Validate at system boundaries (user input, external APIs, config files).
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001. Vendor names only in: (1) docs/design/operations.md, (2) .claude/ files, (3) third-party imports, (4) provider presets.

Files:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/engine/workflow/velocity_calculators/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/velocity_calculator.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/sprint_config.py
  • src/synthorg/engine/workflow/velocity_types.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/sprint_velocity.py
  • src/synthorg/observability/events/workflow.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/ceremony_policy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have from synthorg.observability import get_logger then logger = get_logger(__name__).
Never use import logging, logging.getLogger(), or print() in application code. Exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py may use stdlib logging and stderr prints.
Always use variable name logger (not _logger, not log).
Use event name constants from synthorg.observability.events domain-specific modules, not hardcoded strings. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Always use structured logging: logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
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 entry/exit of key functions.

Files:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/engine/workflow/velocity_calculators/__init__.py
  • src/synthorg/api/app.py
  • src/synthorg/api/state.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/velocity_calculator.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/sprint_config.py
  • src/synthorg/engine/workflow/velocity_types.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/sprint_velocity.py
  • src/synthorg/observability/events/workflow.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/ceremony_policy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use vendor-agnostic test names: test-provider, test-small-001, etc. in all Python tests.
Tests must use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow.
Minimum coverage is 80% (enforced in CI). Use pytest -m <marker> --cov=synthorg --cov-fail-under=80.
Use asyncio_mode = "auto" for async tests -- no manual @pytest.mark.asyncio needed.
Global test timeout is 30 seconds per test (do not add per-file markers; non-default overrides like timeout(60) are allowed).
Prefer @pytest.mark.parametrize for testing similar cases.
Use Hypothesis for property-based testing in Python with @given + @settings. Profiles: ci (50 examples, default) and dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties.
Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep(). For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/engine/workflow/test_sprint_velocity.py
  • tests/unit/engine/workflow/test_sprint_config.py
  • tests/unit/engine/workflow/test_ceremony_context.py
  • tests/unit/engine/workflow/test_velocity_calculator.py
  • tests/unit/engine/workflow/test_velocity_types.py
  • tests/unit/engine/workflow/test_ceremony_strategy.py
  • tests/unit/engine/workflow/test_ceremony_bridge.py
  • tests/unit/engine/workflow/velocity_calculators/test_task_driven.py
  • tests/unit/engine/workflow/test_ceremony_policy.py
  • tests/unit/engine/workflow/strategies/test_task_driven.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.py
🧠 Learnings (36)
📚 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_SPEC.md
  • docs/design/engine.md
  • docs/design/ceremony-scheduling.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_SPEC.md
  • docs/design/engine.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 docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/DESIGN_SPEC.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/DESIGN_SPEC.md
  • docs/design/ceremony-scheduling.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: 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:

  • docs/DESIGN_SPEC.md
  • docs/design/engine.md
  • docs/design/ceremony-scheduling.md
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. Do not silently diverge from the design spec -- every deviation needs explicit user approval and must be updated in the relevant design page.

Applied to files:

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

Applied to files:

  • docs/DESIGN_SPEC.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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue

Applied to files:

  • docs/DESIGN_SPEC.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_SPEC.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 docs/**/*.md : Documentation files in `docs/` (Markdown, built with Zensical, config: `mkdocs.yml`); design spec in `docs/design/` (11 pages)

Applied to files:

  • docs/DESIGN_SPEC.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Web dashboard: see `web/CLAUDE.md` for commands, design system, and component inventory

Applied to files:

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

Applied to files:

  • docs/DESIGN_SPEC.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

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

Applied to files:

  • docs/design/engine.md
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/engine/workflow/velocity_calculators/__init__.py
  • src/synthorg/api/state.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.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/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:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/engine/workflow/velocity_calculators/__init__.py
  • src/synthorg/api/state.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/strategies/task_driven.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • src/synthorg/api/app.py
  • tests/unit/engine/workflow/test_sprint_config.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/ceremony_bridge.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/state.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/strategies/task_driven.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/state.py
  • src/synthorg/engine/workflow/__init__.py
  • src/synthorg/engine/workflow/ceremony_strategy.py
  • src/synthorg/engine/workflow/ceremony_policy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/strategies/task_driven.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 : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/api/auto_wire.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/api/auto_wire.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 : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • src/synthorg/api/auto_wire.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`.

Applied to files:

  • src/synthorg/api/auto_wire.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 : Every module with business logic MUST have `from synthorg.observability import get_logger` followed by `logger = get_logger(__name__)`.

Applied to files:

  • src/synthorg/api/auto_wire.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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/workflow/test_ceremony_strategy.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events` domain-specific modules, not hardcoded strings. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/workflow.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/workflow.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/workflow.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/workflow.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/workflow.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/workflow.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/workflow.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/observability/events/workflow.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/workflow.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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/workflow.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 : Prefer `asyncio.TaskGroup` for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare `create_task`. Existing code is being migrated incrementally.

Applied to files:

  • src/synthorg/engine/workflow/strategies/task_driven.py
🪛 LanguageTool
docs/design/ceremony-scheduling.md

[grammar] ~322-~322: Use a hyphen to join words.
Context: ...livery, release-oriented workflows, open source projects with async contributors....

(QB_NEW_EN_HYPHEN)


[style] ~504-~504: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... standup: task_driven (from project). Resolution for any department's retrospective: `ev...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~613-~613: Ensure spelling is correct
Context: ...esets. --- ## Template Defaults Each builtin template ships with a default ceremony ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.22.0)
docs/design/ceremony-scheduling.md

[warning] 576-576: Table column count
Expected: 3; Actual: 4; Too many cells, extra data will be missing

(MD056, table-column-count)


[warning] 577-577: Table column count
Expected: 3; Actual: 4; Too many cells, extra data will be missing

(MD056, table-column-count)


[warning] 578-578: Table column count
Expected: 3; Actual: 4; Too many cells, extra data will be missing

(MD056, table-column-count)

🔇 Additional comments (42)
docs/DESIGN_SPEC.md (1)

24-24: Good index update.

The new design page is correctly linked from the spec index and keeps navigation consistent.

docs/design/engine.md (1)

178-182: Nice cross-reference placement.

This keeps engine docs concise while directing readers to the authoritative ceremony scheduling design page.

src/synthorg/engine/workflow/strategies/__init__.py (1)

1-13: Package export looks clean.

Docstring and explicit __all__ keep the public API of strategies predictable.

src/synthorg/api/app.py (1)

615-615: Wiring path is correct.

CeremonyScheduler is properly threaded from auto-wire output into AppState alongside other runtime services.

Also applies to: 641-641

src/synthorg/engine/workflow/velocity_calculators/__init__.py (1)

1-13: Looks good.

The module-level export is clear and matches the currently shipped calculator set.

src/synthorg/api/state.py (1)

27-27: LGTM!

The CeremonyScheduler integration follows the established pattern for optional services in AppState:

  • Import added correctly with noqa: TC001 for type-only import compliance.
  • Private slot _ceremony_scheduler added alphabetically.
  • Constructor parameter accepts None for dev/test mode.
  • Property returns CeremonyScheduler | None without _require_service() guard, consistent with similar optional services like approval_gate.

This aligns with the auto-wiring flow where MeetingWireResult guarantees non-None values post-initialization.

Also applies to: 75-75, 115-115, 140-140, 272-275

tests/unit/engine/workflow/test_ceremony_context.py (1)

1-119: LGTM!

Comprehensive test coverage for CeremonyEvalContext:

  • All tests properly marked with @pytest.mark.unit.
  • Tests cover construction, frozen behavior, velocity history integration, external events, and equality.
  • Generic test identifiers used (sprint-1), compliant with vendor-agnostic naming guidelines.
tests/unit/engine/workflow/test_sprint_config.py (1)

7-10: LGTM!

Well-structured tests for the new ceremony policy features:

  • Validation tests correctly verify the frequency/policy_override mutual requirement.
  • Default policy test aligns with the hardcoded defaults in SprintConfig.ceremony_policy field factory (TASK_DRIVEN, auto_transition=True, transition_threshold=1.0).
  • Custom policy override test confirms field propagation.

Also applies to: 59-91, 188-204

tests/unit/engine/workflow/test_velocity_types.py (1)

1-101: LGTM!

Thorough test coverage for velocity types:

  • VelocityCalcType enum membership and StrEnum inheritance verified.
  • VelocityMetrics tests cover construction, secondary metrics, immutability, and validation rules (ge=0.0, non-blank unit, allow_inf_nan=False).
  • Using Exception with match="frozen" is appropriate for catching Pydantic's frozen model mutation error.
tests/unit/engine/workflow/test_velocity_calculator.py (1)

1-96: LGTM!

Solid protocol conformance tests for VelocityCalculator:

  • _StubCalculator correctly implements all required methods (compute, rolling_average) and properties (calculator_type, primary_unit).
  • Tests verify isinstance check works (confirming @runtime_checkable), return types, and expected values.
  • Rolling average calculation verified: (41+42+43)/3 = 42.0.
tests/unit/engine/workflow/test_ceremony_strategy.py (1)

1-142: LGTM!

Comprehensive protocol conformance tests for CeremonySchedulingStrategy:

  • _StubStrategy implements all required methods and properties including async lifecycle hooks.
  • Using None with type: ignore[arg-type] for stub tests is acceptable since only return types are being verified.
  • Tests cover isinstance check, __protocol_attrs__ presence, core method return values, and property accessors.
src/synthorg/engine/workflow/velocity_calculator.py (1)

1-75: LGTM!

Well-designed VelocityCalculator protocol:

  • @runtime_checkable decorator enables isinstance checks at runtime.
  • Sequence import correctly guarded under TYPE_CHECKING (only used in type annotations).
  • Google-style docstrings with Args/Returns on all methods.
  • Protocol defines clear contract: compute, rolling_average, calculator_type, and primary_unit.
src/synthorg/engine/workflow/velocity_types.py (1)

1-61: LGTM!

Clean implementation of velocity types:

  • VelocityCalcType correctly extends StrEnum with all five calculator types.
  • VelocityMetrics follows all coding guidelines:
    • ConfigDict(frozen=True, allow_inf_nan=False) for immutability and NaN/Inf rejection.
    • NotBlankStr for primary_unit as required for identifier fields.
    • ge=0.0 constraint on primary_value.
  • Google-style docstrings with Attributes and Members sections.
src/synthorg/engine/workflow/__init__.py (1)

8-23: Public workflow API expansion looks consistent.

The new ceremony and velocity symbols are imported and exported coherently in __all__, and the package surface is clear.

Also applies to: 58-66, 68-106

tests/unit/engine/workflow/test_ceremony_bridge.py (1)

19-32: Coverage is solid for bridge behavior.

The tests exercise deterministic naming plus all conversion branches (frequency, trigger-only, hybrid) and payload carry-through.

Also applies to: 34-97

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

66-88: New workflow event constants are consistent and well-scoped.

The added ceremony/scheduler/policy event names follow the existing workflow event naming scheme cleanly.

src/synthorg/engine/workflow/sprint_config.py (1)

54-83: Config model evolution is clean and constrained.

The optional frequency/policy_override model plus post-validation guard provides the right flexibility while preserving valid scheduling input.

Also applies to: 117-124

tests/unit/engine/workflow/velocity_calculators/test_task_driven.py (1)

30-122: Good behavioral coverage for task-driven velocity metrics.

The suite validates normal and edge paths for both compute() and rolling_average() with clear expected values.

src/synthorg/engine/workflow/velocity_calculators/task_driven.py (2)

36-74: LGTM — compute() correctly handles None and zero task counts.

The fallback logic returns 0.0 with a pts_per_sprint secondary metric when task_completion_count is unavailable or zero, aligning with the optional int | None field in VelocityRecord (context snippet 2). The division is guarded against zero.


76-118: LGTM — rolling_average() handles edge cases correctly.

Empty records, window < 1, and all-zero/None task counts correctly return a zeroed VelocityMetrics. The weighted average formula (total_pts / total_tasks) is mathematically correct for points-per-task.

tests/unit/engine/workflow/test_ceremony_policy.py (2)

70-76: Consider using more specific exception type for frozen model validation.

Using Exception is broad; Pydantic raises ValidationError for frozen field assignment attempts. However, this test's purpose is to verify immutability, not exception type, so this is acceptable.


120-232: LGTM — Resolution precedence tests are comprehensive.

Tests cover project-only, department overrides, ceremony overrides, field-by-field inheritance, None department skipping, framework defaults, and strategy_config replacement (not merge) semantics. Good coverage of the 3-level resolution logic.

tests/unit/engine/workflow/test_ceremony_scheduler.py (3)

90-145: LGTM — Activation/deactivation lifecycle tests.

Tests verify state transitions, sprint_start ceremony firing, and double-activate behavior (deactivates first). The mock setup is clean and assertions are appropriate.


268-308: LGTM — Error handling tests correctly verify exception propagation behavior.

RuntimeError is logged and swallowed (line 269-287), while MemoryError propagates (line 290-307). This matches the intended resilience pattern in the production code.


318-338: LGTM — One-shot safety test verifies correct behavior on trigger failure.

This test confirms that one-shot ceremonies are not marked as fired when trigger_event fails, allowing retry on subsequent task completions. Accessing the private _fired_once_triggers attribute is acceptable for verifying internal state in tests.

tests/unit/engine/workflow/strategies/test_task_driven.py (3)

97-114: LGTM — Protocol conformance tests.

Tests verify TaskDrivenStrategy is an instance of CeremonySchedulingStrategy, returns correct strategy_type, and provides the expected default velocity calculator.


117-219: LGTM — Comprehensive should_fire_ceremony() tests.

All trigger types are tested: every_n_completions (at/below/above threshold), sprint_end (100%/below), sprint_midpoint (50%/below), sprint_start (always false in per-task eval), and sprint_percentage. Edge cases for no policy override and empty sprint are covered.


272-306: LGTM — Config validation tests cover error cases.

Tests verify valid configs pass, and invalid triggers, non-positive every_n_completions, and out-of-range sprint_percentage raise ValueError with appropriate messages. Empty config is validated as acceptable.

src/synthorg/engine/workflow/ceremony_strategy.py (2)

33-58: LGTM — Well-designed pluggable strategy protocol.

The @runtime_checkable decorator enables isinstance checks. Clear separation between core evaluation methods (must implement) and optional lifecycle hooks (no-ops for stateless strategies). Comprehensive docstrings document the contract.


100-199: LGTM — Async lifecycle hooks provide extensibility.

The hooks (on_sprint_activated, on_task_completed, etc.) enable stateful strategies to manage subscriptions and internal tracking. Making them async allows for I/O operations in advanced strategy implementations.

src/synthorg/engine/workflow/ceremony_policy.py (3)

55-95: LGTM — CeremonyPolicyConfig follows Pydantic v2 conventions.

Model uses ConfigDict(frozen=True, allow_inf_nan=False) per guidelines. Nullable fields with None defaults enable inheritance semantics. transition_threshold has appropriate bounds (gt=0.0, le=1.0).


131-140: LGTM — Validator provides useful configuration warning.

Warning when transition_threshold is set but auto_transition is disabled helps catch misconfiguration early. Using structured logging with the event constant is correct.


143-203: LGTM — Resolution function implements correct precedence.

Field-by-field resolution with "most specific wins" semantics. Framework defaults are applied when all layers have None. Structured logging captures resolution details for observability.

src/synthorg/engine/workflow/strategies/task_driven.py (4)

78-119: LGTM — should_fire_ceremony() correctly delegates trigger evaluation.

Extracts config from policy_override.strategy_config, evaluates the trigger, and logs appropriately. Returns False when no trigger is configured (graceful handling of frequency-only ceremonies).


260-291: LGTM — _evaluate_trigger() correctly implements all trigger types.

Handles sprint_start (always false, handled by scheduler), sprint_end (100%), sprint_midpoint (50%), every_n_completions, and sprint_percentage with proper threshold comparisons.


213-247: LGTM — Config validation is thorough.

Validates trigger values against _VALID_TRIGGERS, every_n_completions as positive integer, and sprint_percentage in (0, 100] range. Error messages are descriptive.


144-148: No action needed. SprintConfig.ceremony_policy is defined with a non-optional type CeremonyPolicyConfig and includes a default_factory, ensuring it is never None. Accessing config.ceremony_policy.transition_threshold is safe.

			> Likely an incorrect or invalid review comment.
src/synthorg/engine/workflow/ceremony_scheduler.py (5)

325-343: LGTM — Parallel ceremony firing uses asyncio.TaskGroup.

Correctly uses asyncio.TaskGroup for structured concurrency per guidelines. One-shot triggers are marked only after successful completion (line 342-343), ensuring retry on failure.


257-281: LGTM — Auto-transition uses correct Sprint.with_transition() method.

The transition logic correctly calls sprint.with_transition(target) (line 279), which matches the method signature in Sprint class (context snippet 2).


428-443: Same PEP 758 syntax used — consistent with line 139.

This except MemoryError, RecursionError: block follows the same pattern, correctly re-raising critical errors while logging and swallowing others.


45-72: LGTM — Scheduler class design follows guidelines.

Uses __slots__ for memory efficiency, documents the ownership model clearly, and separates strategy state from scheduler mechanics.


136-148: No changes needed. The except MemoryError, RecursionError: syntax correctly follows PEP 758 and aligns with the project's Python 3.14+ coding guidelines.

Comment on lines +291 to 299
ceremony_scheduler = CeremonyScheduler(
meeting_scheduler=meeting_scheduler,
)

return MeetingWireResult(
meeting_orchestrator=meeting_orchestrator,
meeting_scheduler=meeting_scheduler,
ceremony_scheduler=ceremony_scheduler,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add auto-wire observability for CeremonyScheduler.

ceremony_scheduler is now auto-created, but unlike other auto-wired services this path does not emit API_SERVICE_AUTO_WIRED.

Suggested addition
     ceremony_scheduler = CeremonyScheduler(
         meeting_scheduler=meeting_scheduler,
     )
+    logger.info(API_SERVICE_AUTO_WIRED, service="ceremony_scheduler")

As per coding guidelines, “Always use structured logging: logger.info(EVENT, key=value).”

📝 Committable suggestion

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

Suggested change
ceremony_scheduler = CeremonyScheduler(
meeting_scheduler=meeting_scheduler,
)
return MeetingWireResult(
meeting_orchestrator=meeting_orchestrator,
meeting_scheduler=meeting_scheduler,
ceremony_scheduler=ceremony_scheduler,
)
ceremony_scheduler = CeremonyScheduler(
meeting_scheduler=meeting_scheduler,
)
logger.info(API_SERVICE_AUTO_WIRED, service="ceremony_scheduler")
return MeetingWireResult(
meeting_orchestrator=meeting_orchestrator,
meeting_scheduler=meeting_scheduler,
ceremony_scheduler=ceremony_scheduler,
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/auto_wire.py` around lines 291 - 299, The auto-creation of
CeremonyScheduler (constructed as ceremony_scheduler and returned in
MeetingWireResult) is missing the structured observability call; add a
logger.info(API_SERVICE_AUTO_WIRED, service="CeremonyScheduler",
instance=ceremony_scheduler) (or matching structured log keys used elsewhere)
immediately after the CeremonyScheduler is constructed so this auto-wired path
emits the same API_SERVICE_AUTO_WIRED event as other services.

Comment on lines +50 to +52
velocity_history: tuple[VelocityRecord, ...]
external_events: tuple[str, ...]
sprint_percentage_complete: float
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Normalize incoming collections to immutable tuples at construction.

velocity_history and external_events are tuple-typed, but runtime callers can still pass mutable sequences. Normalize defensively in __post_init__ so context remains immutable by contract.

Suggested fix
+import copy
@@
     def __post_init__(self) -> None:
         """Validate field constraints."""
+        object.__setattr__(
+            self,
+            "velocity_history",
+            tuple(copy.deepcopy(self.velocity_history)),
+        )
+        object.__setattr__(
+            self,
+            "external_events",
+            tuple(copy.deepcopy(self.external_events)),
+        )
         if self.completions_since_last_trigger < 0:

As per coding guidelines, "For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement."

Also applies to: 56-57

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

In `@src/synthorg/engine/workflow/ceremony_context.py` around lines 50 - 52, In
CeremonyContext.__post_init__, defensively normalize incoming collection fields
by deepcopying and converting any mutable sequences to immutable tuples:
deepcopy the incoming velocity_history and external_events and assign tuple(...)
versions to self.velocity_history and self.external_events; for any non-Pydantic
mapping fields (the other collection fields referenced in the review), deepcopy
them and wrap with types.MappingProxyType to enforce read-only access. Ensure
you import copy.deepcopy and types.MappingProxyType and perform these
assignments in __post_init__ so the instance respects the tuple/map immutability
contract.

Comment on lines +89 to +95
def record_velocity(
sprint: Sprint,
*,
task_completion_count: int | None = None,
wall_clock_seconds: float | None = None,
budget_consumed: float | None = None,
) -> VelocityRecord:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate/log new optional metrics before constructing VelocityRecord.

record_velocity() now accepts external metric inputs, but invalid values are not explicitly validated/logged before Line 123 model construction. Add explicit checks so failures are deterministic and observable.

Suggested fix
 def record_velocity(
     sprint: Sprint,
     *,
     task_completion_count: int | None = None,
     wall_clock_seconds: float | None = None,
     budget_consumed: float | None = None,
 ) -> VelocityRecord:
@@
     if sprint.status is not SprintStatus.COMPLETED:
@@
         raise ValueError(msg)
+
+    if task_completion_count is not None and task_completion_count < 0:
+        msg = "task_completion_count must be >= 0"
+        logger.warning(
+            SPRINT_VELOCITY_INVALID,
+            sprint_id=sprint.id,
+            reason="invalid_task_completion_count",
+            task_completion_count=task_completion_count,
+        )
+        raise ValueError(msg)
+    if wall_clock_seconds is not None and wall_clock_seconds < 0.0:
+        msg = "wall_clock_seconds must be >= 0.0"
+        logger.warning(
+            SPRINT_VELOCITY_INVALID,
+            sprint_id=sprint.id,
+            reason="invalid_wall_clock_seconds",
+            wall_clock_seconds=wall_clock_seconds,
+        )
+        raise ValueError(msg)
+    if budget_consumed is not None and budget_consumed < 0.0:
+        msg = "budget_consumed must be >= 0.0"
+        logger.warning(
+            SPRINT_VELOCITY_INVALID,
+            sprint_id=sprint.id,
+            reason="invalid_budget_consumed",
+            budget_consumed=budget_consumed,
+        )
+        raise ValueError(msg)
+
     record = VelocityRecord(

As per coding guidelines, "Validate at system boundaries (user input, external APIs, config files)." and "All error paths must log at WARNING or ERROR with context before raising."

Also applies to: 123-132

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

In `@src/synthorg/engine/workflow/sprint_velocity.py` around lines 89 - 95, The
record_velocity function accepts optional external metrics
(task_completion_count, wall_clock_seconds, budget_consumed) but does not
validate or log them before building the VelocityRecord; add explicit validation
checks in record_velocity before the VelocityRecord construction: verify types
and sensible ranges (e.g., non-negative numbers, integers where expected for
task_completion_count), log a WARNING with context (which parameter, sprint
id/name, provided value) for recoverable/coercible issues or log ERROR and raise
a ValueError for clearly invalid inputs, and normalize or coerce values if
appropriate; reference the parameter names task_completion_count,
wall_clock_seconds, budget_consumed and the VelocityRecord construction site in
record_velocity when adding checks and logs.

…abbit

Critical fixes:
- auto_transition=False flag now respected in _check_auto_transition
- One-shot ceremonies no longer double-fire (skip in _evaluate_ceremonies)
- Design page missing from mkdocs.yml nav
- CLAUDE.md page count and package structure updated

Major fixes:
- Add asyncio.Lock to CeremonyScheduler for concurrent task completion safety
- Wrap deactivate_sprint strategy hook in try/except for cleanup guarantee
- Wrap on_task_completed strategy hook to isolate failures
- Add dedicated failure event constants (START_FAILED, TRIGGER_FAILED)
- Replace bare string event name with VELOCITY_TASK_DRIVEN_NO_TASK_COUNT
- Extract _compute_sprint_progress to DRY context builders
- TypeVar _resolve_field for type safety
- Call validate_strategy_config at activate_sprint time

Medium fixes:
- Use shared trigger constants from ceremony_policy (no more string duplication)
- Fix _build_context completions_since_last_trigger to 0 for global context
- Add warning log for unknown triggers in TaskDrivenStrategy
- Fix ceremony_context.py: add logger, NaN/Inf guards, deepcopy collections
- Fix ceremony_bridge has_trigger log to use meeting_type.trigger
- Add auto-wire observability log for CeremonyScheduler
- Restrict ceremony name to [a-z0-9_-]+ pattern
- Reject unknown config keys in validate_strategy_config
- Fix design page: threshold range, activate_sprint signature, YAML example,
  velocity calc table marks planned calcs, mixed-strategy wording, pipe escaping

Test improvements:
- Add 22 new validation tests for CeremonyEvalContext __post_init__
- Add activation failure cleanup test
- Fix weak >= 1 assertion to exact count
- Fix relative count assertions to exact delta
- Fix fragile string-based call assertion
- Replace private _fired_once_triggers access with behavioral test
- Parametrize negative-value rejection tests

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/ceremony-scheduling.md`:
- Line 624: Update the phrase "Each builtin template ships with a default
ceremony strategy that matches" to use the hyphenated compound adjective
"built-in" (i.e., change "builtin" to "built-in") in the
docs/design/ceremony-scheduling.md content so the sentence reads "Each built-in
template ships with a default ceremony strategy that matches"; ensure other
occurrences of "builtin" in this document follow the same hyphenation.

In `@src/synthorg/engine/workflow/ceremony_context.py`:
- Around line 69-119: The __post_init__ method is too large; extract the
collection normalization and field validation into two private helpers (e.g.,
_normalize_collections and _validate_fields) and have __post_init__ simply call
those helpers. Move the tuple/copy logic that sets velocity_history and
external_events into _normalize_collections, and move all _check_* calls
(completions_since_last_trigger, total_completions_this_sprint,
total_tasks_in_sprint, elapsed_seconds, budget_consumed_fraction,
budget_remaining, sprint_percentage_complete, story_points_completed,
story_points_committed) into _validate_fields, keeping the same argument names
and using object.__setattr__ where needed so behavior is unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4c521f1c-1645-4a0a-ab7a-2cecbfba7acc

📥 Commits

Reviewing files that changed from the base of the PR and between 07261d6 and 067cbf8.

📒 Files selected for processing (16)
  • CLAUDE.md
  • docs/design/ceremony-scheduling.md
  • mkdocs.yml
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/ceremony_policy.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/sprint_config.py
  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.py
  • tests/unit/engine/workflow/test_ceremony_context.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.py
  • tests/unit/engine/workflow/test_sprint_velocity.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Python 3.14+ with PEP 649 native lazy annotations -- do NOT use from __future__ import annotations.
Use PEP 758 except syntax: except A, B: (no parentheses) -- ruff enforces this on Python 3.14.
Maximum line length is 88 characters (ruff enforced).

Files:

  • tests/unit/engine/workflow/test_sprint_velocity.py
  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/api/auto_wire.py
  • tests/unit/engine/workflow/test_ceremony_context.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/sprint_config.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
  • src/synthorg/observability/events/workflow.py
  • src/synthorg/engine/workflow/ceremony_policy.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use vendor-agnostic test names: test-provider, test-small-001, etc. in all Python tests.
Tests must use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow.
Minimum coverage is 80% (enforced in CI). Use pytest -m <marker> --cov=synthorg --cov-fail-under=80.
Use asyncio_mode = "auto" for async tests -- no manual @pytest.mark.asyncio needed.
Global test timeout is 30 seconds per test (do not add per-file markers; non-default overrides like timeout(60) are allowed).
Prefer @pytest.mark.parametrize for testing similar cases.
Use Hypothesis for property-based testing in Python with @given + @settings. Profiles: ci (50 examples, default) and dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties.
Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep(). For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/engine/workflow/test_sprint_velocity.py
  • tests/unit/engine/workflow/test_ceremony_context.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: All public functions must have type hints; mypy strict mode enforced.
Docstrings must follow Google style and are required on public classes/functions (enforced by ruff D rules).
Use immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement.
Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Adopt Pydantic v2 conventions: use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields; use @computed_field for derived values instead of storing redundant fields; use NotBlankStr for all identifier/name fields.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Functions must be less than 50 lines, files must be less than 800 lines.
Handle errors explicitly, never silently swallow exceptions.
Validate at system boundaries (user input, external APIs, config files).
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001. Vendor names only in: (1) docs/design/operations.md, (2) .claude/ files, (3) third-party imports, (4) provider presets.

Files:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/sprint_config.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
  • src/synthorg/observability/events/workflow.py
  • src/synthorg/engine/workflow/ceremony_policy.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic MUST have from synthorg.observability import get_logger then logger = get_logger(__name__).
Never use import logging, logging.getLogger(), or print() in application code. Exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py may use stdlib logging and stderr prints.
Always use variable name logger (not _logger, not log).
Use event name constants from synthorg.observability.events domain-specific modules, not hardcoded strings. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Always use structured logging: logger.info(EVENT, key=value) -- never logger.info("msg %s", val).
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 entry/exit of key functions.

Files:

  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/ceremony_bridge.py
  • src/synthorg/engine/workflow/ceremony_context.py
  • src/synthorg/engine/workflow/sprint_config.py
  • src/synthorg/engine/workflow/ceremony_scheduler.py
  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/engine/workflow/strategies/task_driven.py
  • src/synthorg/observability/events/workflow.py
  • src/synthorg/engine/workflow/ceremony_policy.py
🧠 Learnings (69)
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to docs/**/*.md : Documentation files in `docs/` (Markdown, built with Zensical, config: `mkdocs.yml`); design spec in `docs/design/` (11 pages)

Applied to files:

  • mkdocs.yml
  • 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 docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • mkdocs.yml
  • CLAUDE.md
  • docs/design/ceremony-scheduling.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:

  • mkdocs.yml
  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to 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:

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

Applied to files:

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

Applied to files:

  • mkdocs.yml
  • 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: 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:

  • mkdocs.yml
  • CLAUDE.md
  • docs/design/ceremony-scheduling.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • mkdocs.yml
  • CLAUDE.md
  • docs/design/ceremony-scheduling.md
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. Do not silently diverge from the design spec -- every deviation needs explicit user approval and must be updated in the relevant design page.

Applied to files:

  • mkdocs.yml
  • CLAUDE.md
  • docs/design/ceremony-scheduling.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: Always read the relevant `docs/design/` page before implementing any feature or planning any issue

Applied to files:

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

Applied to files:

  • 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 {docs/rest-api.md,docs/_generated/api-reference.html} : REST API reference in `docs/rest-api.md` + `docs/_generated/api-reference.html` (generated by `scripts/export_openapi.py`)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Applies to docs/api/**/*.md : Library reference in `docs/api/` (auto-generated via mkdocstrings + Griffe, AST-based)

Applied to files:

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

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
  • src/synthorg/engine/workflow/strategies/__init__.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/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
  • src/synthorg/engine/workflow/strategies/__init__.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/engine/workflow/ceremony_scheduler.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/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/auto_wire.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:

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

Applied to files:

  • CLAUDE.md
  • src/synthorg/engine/workflow/ceremony_policy.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/engine/coordination/**/*.py : Task coordination uses multi-agent pipeline with 4 dispatchers (SAS/centralized/decentralized/context-dependent), wave execution, and workspace lifecycle integration.

Applied to files:

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

Applied to files:

  • tests/unit/engine/workflow/test_sprint_velocity.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Parametrize: Prefer pytest.mark.parametrize for testing similar cases.

Applied to files:

  • tests/unit/engine/workflow/test_sprint_velocity.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 tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases

Applied to files:

  • tests/unit/engine/workflow/test_sprint_velocity.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 tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/workflow/test_sprint_velocity.py
  • tests/unit/engine/workflow/test_ceremony_scheduler.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 tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/engine/workflow/test_sprint_velocity.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.

Applied to files:

  • tests/unit/engine/workflow/test_sprint_velocity.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 : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/api/auto_wire.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/api/auto_wire.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 : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • src/synthorg/api/auto_wire.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`.

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
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 entry/exit of key functions.

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.py
📚 Learning: 2026-04-01T18:29:59.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.467Z
Learning: Applies to src/**/*.py : Use immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.py
📚 Learning: 2026-04-01T18:29:59.467Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.467Z
Learning: Applies to src/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_context.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/engine/workflow/ceremony_context.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 `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/engine/workflow/ceremony_scheduler.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 : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_scheduler.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Applies to src/**/*.py : Handle errors explicitly, never silently swallow exceptions.

Applied to files:

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

Applied to files:

  • src/synthorg/engine/workflow/ceremony_scheduler.py
📚 Learning: 2026-04-01T18:29:59.468Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T18:29:59.468Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events` domain-specific modules, not hardcoded strings. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/engine/workflow/velocity_calculators/task_driven.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/engine/workflow/velocity_calculators/task_driven.py
  • src/synthorg/observability/events/workflow.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/observability/events/workflow.py
🪛 LanguageTool
docs/design/ceremony-scheduling.md

[style] ~514-~514: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... standup: task_driven (from project). Resolution for any department's retrospective: `ev...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~624-~624: Ensure spelling is correct
Context: ...esets. --- ## Template Defaults Each builtin template ships with a default ceremony ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (40)
tests/unit/engine/workflow/test_sprint_velocity.py (2)

101-124: Good consolidation with parametrized validation.

This is cleaner and easier to extend than separate negative-field tests, while preserving assertion strength.


143-154: Good coverage for optional velocity passthrough.

This closes a useful regression surface by asserting record_velocity(...) forwards the new fields.

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

66-104: Event constant additions look consistent and well-scoped.

The new ceremony/velocity constants follow the existing naming pattern and keep observability vocabulary centralized.

tests/unit/engine/workflow/test_ceremony_context.py (1)

140-211: Validation matrix is solid and well-parameterized.

Nice job covering negatives, bounds, NaN, and Inf with concise parametrized tests.

src/synthorg/engine/workflow/velocity_calculators/task_driven.py (1)

49-53: Implementation looks clean and protocol-aligned.

Good use of the workflow event constant for observability and clear edge-case handling in rolling_average.

Also applies to: 72-114

mkdocs.yml (1)

119-119: Docs navigation update is correct.

The new Design entry is placed consistently and improves discoverability of the new architecture doc.

CLAUDE.md (1)

13-13: Documentation updates are coherent with the feature expansion.

The page-count and engine scope adjustments are consistent with the new ceremony scheduling subsystem.

Also applies to: 58-58, 96-96

src/synthorg/engine/workflow/strategies/__init__.py (1)

8-14: Package export surface is clean and explicit.

Re-exporting TaskDrivenStrategy with a tight __all__ keeps the strategies API predictable.

src/synthorg/api/auto_wire.py (2)

66-77: LGTM!

The MeetingWireResult extension correctly adds ceremony_scheduler as a guaranteed non-None field with accurate docstring. The named tuple signature ensures type safety at the wire boundary.


291-300: LGTM!

The CeremonyScheduler instantiation and structured logging follow the established pattern for other auto-wired services. The observability event API_SERVICE_AUTO_WIRED is correctly emitted with the service key.

src/synthorg/engine/workflow/ceremony_bridge.py (3)

1-17: LGTM!

Module setup follows guidelines: proper logger initialization via get_logger(__name__), event constant import from the workflow domain module, and TYPE_CHECKING guard for the SprintCeremonyConfig import.


20-33: LGTM!

The build_trigger_event_name function provides a deterministic, namespaced event naming scheme. The format ceremony.<name>.<sprint_id> ensures uniqueness per ceremony per sprint.


36-92: LGTM!

The bridge function correctly handles both paths:

  • Frequency-based: creates MeetingTypeConfig with frequency set, trigger defaults to None.
  • Trigger-based: creates MeetingTypeConfig with trigger set, frequency defaults to None.

Both satisfy the MeetingTypeConfig model validator requiring exactly one of frequency or trigger. The logging at lines 84-91 now correctly derives has_trigger from the constructed meeting_type.trigger rather than ceremony.policy_override.

src/synthorg/engine/workflow/sprint_config.py (2)

22-84: LGTM!

The SprintCeremonyConfig extensions are well-designed:

  • The name pattern constraint (^[a-z0-9_-]+$) ensures deterministic event naming compatibility.
  • Making frequency optional with the _validate_scheduling_source validator enforces that at least one scheduling source exists.
  • The policy_override field enables per-ceremony strategy customization.

The frozen Pydantic model with allow_inf_nan=False follows project conventions.


118-125: LGTM!

The ceremony_policy field with default_factory provides sensible defaults (task-driven strategy, auto-transition enabled, 100% threshold). Using a lambda ensures fresh instances per model creation.

tests/unit/engine/workflow/test_ceremony_scheduler.py (4)

1-88: LGTM!

The test helpers are well-structured:

  • _make_sprint provides flexible fixture construction with sensible defaults.
  • _make_config centralizes policy configuration.
  • _ceremony_with_trigger abstracts trigger-based ceremony creation.
  • _make_mock_meeting_scheduler uses AsyncMock correctly for the async trigger_event method.

90-145: LGTM!

Activation tests cover key behaviors: state transitions, sprint start ceremony firing, and double-activation handling. The assertions verify both running state and active_sprint reference.


147-267: LGTM!

Task completion tests thoroughly cover:

  • every_n_completions cadence firing
  • Auto-transition at threshold
  • No transition below threshold
  • Inactive scheduler passthrough
  • One-shot midpoint/end firing exactly once

The test structure is clear and assertions are specific.


268-377: LGTM!

Error handling tests validate critical behaviors:

  • Non-MemoryError exceptions are logged and swallowed (line 287 asserts ACTIVE status preserved).
  • MemoryError propagates (line 306-307).
  • One-shot ceremonies aren't marked fired on failure, allowing retry (lines 319-349).
  • Activation hook failures trigger cleanup (lines 376-377 assert running=False, active_sprint=None).
docs/design/ceremony-scheduling.md (1)

1-692: Comprehensive design documentation.

The design doc thoroughly covers the ceremony scheduling subsystem: 8 strategy types, protocol interfaces, 3-level config resolution, velocity tracking extensions, and implementation roadmap. This provides clear architectural guidance for both the shipped foundation and follow-up issues.

src/synthorg/engine/workflow/ceremony_policy.py (5)

1-18: LGTM!

Module setup follows guidelines with proper logger initialization and event constant import from the workflow domain module.


21-60: LGTM!

The CeremonyStrategyType enum cleanly enumerates all 8 strategies documented in the design spec. The trigger constants and module-level defaults provide a single source of truth for shared values.


63-103: LGTM!

The CeremonyPolicyConfig model correctly uses optional fields with None defaults to support 3-level inheritance. The transition_threshold constraints (gt=0.0, le=1.0) enforce valid percentage ranges.


105-148: LGTM!

The ResolvedCeremonyPolicy model enforces non-nullable resolved values. The _validate_threshold_with_auto_transition validator appropriately warns (not errors) when transition_threshold is customized but auto_transition is disabled, alerting users to a likely misconfiguration.


151-228: LGTM!

The resolve_ceremony_policy function implements clean field-by-field resolution:

  • Layers are ordered least-to-most specific (project → department → ceremony).
  • _resolve_field iterates in reverse, taking the first non-None value.
  • The generic _resolve_field[T] uses Python 3.12+ syntax, compatible with the project's Python 3.14 target.
  • Structured logging captures the resolution outcome with levels_evaluated.
src/synthorg/engine/workflow/ceremony_scheduler.py (9)

1-55: LGTM!

Module setup includes proper logger initialization, event constant imports, and clearly defined thresholds. The _ONE_SHOT_TRIGGERS frozenset provides an efficient membership check for one-shot ceremony tracking.


57-116: LGTM!

The CeremonyScheduler class is well-documented with __slots__ for memory efficiency. The asyncio.Lock at line 106 ensures all public async methods are serialized, preventing counter corruption from concurrent task-completion events.


118-179: LGTM!

The activate_sprint method correctly:

  • Deactivates any existing sprint before activation (line 143-144).
  • Validates strategy config before committing state (line 146-148).
  • Uses PEP 758 except syntax except MemoryError, RecursionError: (line 163).
  • Cleans up on partial activation failure (line 171).
  • Emits structured logging on success (lines 174-179).

181-226: LGTM!

Deactivation handling is robust:

  • _deactivate_sprint_unlocked handles the no-op case with debug logging (lines 192-197).
  • Strategy hook failure is logged but doesn't prevent deactivation (lines 199-211).
  • State is fully cleared regardless of hook outcome (lines 215-221).

228-280: LGTM!

The on_task_completed method follows a clear flow:

  1. Early return if not running (lines 250-256).
  2. Update state and build context (lines 259-262).
  3. Delegate to strategy hook with error handling (lines 263-277).
  4. Evaluate ceremonies and one-shot triggers (lines 278-279).
  5. Return potentially auto-transitioned sprint (line 280).

The error handling correctly swallows non-fatal exceptions while propagating MemoryError and RecursionError.


282-340: LGTM!

Ceremony evaluation logic is correct:

  • Skips already-fired one-shots (line 290-291).
  • Skips one-shot triggers during per-task evaluation (line 294-295).
  • Resets counter on successful firing (line 310).
  • Auto-transition respects auto_transition is False explicitly (line 322) and only transitions from ACTIVE status (line 330).
  • Uses immutable sprint pattern via sprint.with_transition(target) (line 338).

342-405: LGTM!

One-shot ceremony handling uses asyncio.TaskGroup for parallel execution (line 399), correctly marking ceremonies as fired only on success (line 404-405). The threshold checks at lines 376-378 correctly use >= for both midpoint and end triggers.


407-472: LGTM!

Context building correctly addresses the previous review comment about code duplication:

  • _compute_sprint_progress (lines 409-421) extracts shared progress calculation.
  • Both _build_context and _build_ceremony_context use this helper.
  • Budget fields are placeholder zeros with a clear follow-up reference (#972).

474-529: LGTM!

Trigger execution correctly:

  • Logs the trigger event before attempting (lines 499-504).
  • Uses PEP 758 except syntax (line 511).
  • Returns success/failure boolean for one-shot tracking (lines 520-521).
  • The _get_trigger helper safely handles None policy_override and strategy_config (lines 524-529).
src/synthorg/engine/workflow/strategies/task_driven.py (6)

1-61: LGTM!

Module setup includes proper logger initialization and clearly defined constants. The _VALID_TRIGGERS and _KNOWN_CONFIG_KEYS frozensets enable efficient validation in validate_strategy_config.


63-123: LGTM!

The should_fire_ceremony method correctly:

  • Extracts config from ceremony's policy override (line 101).
  • Returns False for missing trigger (line 104-105).
  • Delegates to _evaluate_trigger for condition evaluation (line 107).
  • Logs at appropriate levels (INFO for triggered, DEBUG for skipped).

The # noqa: ARG002 on line 85 is appropriate since sprint is required by the protocol but unused in this stateless implementation.


125-156: LGTM!

The should_transition_sprint method correctly:

  • Only transitions from ACTIVE status (line 143-144).
  • Guards against empty task lists (line 145-146).
  • Falls back to _DEFAULT_TRANSITION_THRESHOLD when config value is None (lines 148-152).
  • Uses >= comparison for threshold check (line 154).

158-206: LGTM!

All lifecycle hooks are correctly implemented as no-ops for this stateless strategy. The method signatures match the CeremonySchedulingStrategy protocol, ensuring compatibility with the scheduler.


208-256: LGTM!

Metadata properties and validation:

  • strategy_type returns CeremonyStrategyType.TASK_DRIVEN (line 211).
  • get_default_velocity_calculator returns VelocityCalcType.TASK_DRIVEN (line 215).
  • validate_strategy_config rejects unknown keys, invalid triggers, and out-of-range parameter values with clear error messages.

258-306: LGTM!

Internal helpers are well-implemented:

  • _get_ceremony_config safely handles None policy_override and strategy_config (lines 265-267).
  • _evaluate_trigger covers all valid trigger types with appropriate threshold checks.
  • The fallback at lines 300-305 logs a warning for unrecognized triggers, providing defensive handling for potential future trigger types.

- Fix "builtin" -> "built-in" hyphenation in design page
- Extract __post_init__ into _normalize_collections + _validate_fields
  helpers to stay under 50-line function limit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 1, 2026 20:34 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 43564a9 into main Apr 1, 2026
30 of 31 checks passed
@Aureliolo Aureliolo deleted the feat/sprint-ceremony-scheduler branch April 1, 2026 20:42
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 1, 2026 20:42 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 2, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.5.6](v0.5.5...v0.5.6)
(2026-04-02)


### Features

* calendar + hybrid ceremony scheduling strategies
([#985](#985))
([59a9b84](59a9b84)),
closes [#969](#969)
[#970](#970)
* landing page interactive components
([#984](#984))
([49868cb](49868cb))
* log aggregation and shipping (syslog, HTTP, compression)
([#964](#964))
([84be9f8](84be9f8))
* restructure builtin templates into inheritance tree
([#982](#982))
([3794c12](3794c12))
* sprint ceremony runtime scheduler with pluggable strategies
([#983](#983))
([43564a9](43564a9))


### Maintenance

* add no-bash-file-writes rule to CLAUDE.md
([#968](#968))
([a854dcc](a854dcc))
* bump web dependencies (lodash, eslint-react v4, storybook, playwright,
esbuild, codemirror)
([#987](#987))
([c344dfb](c344dfb))

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

---------

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: sprint ceremony runtime scheduler with task-driven cadence support

2 participants