Skip to content

feat(api): auto-wire backend services at startup#555

Merged
Aureliolo merged 5 commits intomainfrom
feat/auto-wire-backend-services
Mar 18, 2026
Merged

feat(api): auto-wire backend services at startup#555
Aureliolo merged 5 commits intomainfrom
feat/auto-wire-backend-services

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Auto-create all backend services in create_app() when not explicitly provided, eliminating 503 errors on dashboard pages when running via Docker/uvicorn factory mode
  • Phase 1 (construction time): auto-wire message_bus, cost_tracker, task_engine, provider_registry -- these don't need connected persistence
  • Phase 2 (on_startup after persistence connects): auto-wire settings_service + settings_dispatcher -- these need persistence.settings which requires a connected backend
  • Add auto_wire.py module extracting all auto-wire logic from app.py
  • Add AppState.set_settings_service() for deferred Phase 2 injection (also creates ConfigResolver + ProviderManagementService)
  • Merge API channels into auto-wired bus config for MessageBusBridge compatibility
  • Update health check tests for auto-wired bus being always available

Test plan

  • Phase 1 auto-wiring tests (message_bus, cost_tracker, task_engine, provider_registry)
  • Phase 2 auto-wiring tests (settings_service with/without bus)
  • Explicit services not overridden by auto-wiring
  • No-persistence warning logged correctly
  • AppState.set_settings_service creates resolver + management
  • Double-set guard raises RuntimeError
  • Auto-wired bus includes API channels
  • Provider registry param opt-out works
  • Health check tests updated for auto-wired bus
  • Full unit suite: 9277 passed, 0 failed
  • mypy strict: 0 errors
  • ruff: 0 errors

Pre-reviewed by 8 agents, 13 findings addressed.

Closes #550

🤖 Generated with Claude Code

Aureliolo and others added 4 commits March 18, 2026 20:10
Auto-create all backend services in create_app() when not explicitly
provided, eliminating 503 errors on dashboard pages when running via
Docker/uvicorn factory mode.

Phase 1 (construction time): message_bus, cost_tracker, task_engine,
provider_registry -- these don't need connected persistence.

Phase 2 (on_startup after persistence connects): settings_service and
settings_dispatcher -- these need persistence.settings which requires
a connected backend.

- Add API channel merging into auto-wired bus config for bridge compat
- Add AppState.set_settings_service() for deferred Phase 2 injection
- Add API_SERVICE_AUTO_WIRED event constant for structured logging
- Update health check tests for auto-wired bus being always available
- Update server.py docstring to reflect auto-wiring capability

Closes #550

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mypy cannot track that set_settings_service() mutates _settings_service
through a method call, so it narrows has_settings_service to always
False from the constructor default. Add type: ignore[unreachable] on
the follow-up assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address findings from 8 review agents (13 items):

- Extract Phase 1 + Phase 2 auto-wiring into auto_wire.py (reduces
  app.py from 1018 to 915 lines, fixes file size regression)
- Fix partial state corruption: move set_settings_service() call after
  dispatcher.start() so AppState isn't mutated if dispatcher fails
- Fix name shadowing: rename _auto_wire_settings bool to _should_auto_wire
- Remove dead code in on_startup except block (_auto_wired_dispatcher
  can never be non-None when assignment fails)
- Add provider_registry parameter to create_app() for opt-out parity
- Wrap all Phase 1 constructors in try/except with structured logging
- Add contextual logging for SettingsEncryptor.from_env() failures
- Add debug log for ConfigResolver + ProviderManagement creation
- Update CLAUDE.md Package Structure to mention auto-wiring
- Add missing tests: provider_registry, channel merging, param override

Pre-reviewed by 8 agents, 13 findings addressed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use isinstance narrowing for InMemoryMessageBus before accessing
_config attribute to satisfy mypy's protocol type checking.

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

github-actions bot commented Mar 18, 2026

Dependency Review

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

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 42a73c2e-63eb-41ce-a0b1-55ec05da9175

📥 Commits

Reviewing files that changed from the base of the PR and between 71cc6e1 and a71b39f.

📒 Files selected for processing (8)
  • CLAUDE.md
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • src/synthorg/observability/events/api.py
  • tests/unit/api/test_app.py
  • tests/unit/api/test_health.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). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations — Python 3.14 has PEP 649 native lazy annotations
PEP 758 except syntax: use except A, B: (no parentheses) — ruff enforces this on Python 3.14
Line length: 88 characters (ruff)

Files:

  • tests/unit/api/test_health.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • tests/unit/api/test_app.py
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow
Async: asyncio_mode = "auto" — no manual @pytest.mark.asyncio needed
Timeout: 30 seconds per test
Parametrize: Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Property-based testing in Python uses Hypothesis (@given + @settings)
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
Test files: use @pytest.mark decorators (unit, integration, e2e, slow) for categorization; avoid sequential test execution — always use -n auto

Files:

  • tests/unit/api/test_health.py
  • tests/unit/api/test_app.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases

Files:

  • tests/unit/api/test_health.py
  • src/synthorg/observability/events/api.py
  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • tests/unit/api/test_app.py
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Type hints: all public functions, mypy strict mode
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)
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
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)
Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves
Never mix static config fields with mutable runtime fields in one model
Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use @computed_field for derived values instead of storing + validating redundant fields
Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators
Async concurrency: 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
Functions: < 50 lines, files < 800 lines
Errors: handle explicitly, never silently swallow
Validate: at system boundaries (user input, external APIs, config files)

Files:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.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() / print() in application code
Variable name: always logger (not _logger, not log)
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> import EVENT_CONSTANT
Structured kwargs: always 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, entry/exit of key functions
API reference: auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports); document all public APIs with Google-style docstrings
Observability: use structured logging via get_logger() and observability.events constants; correlation tracking for multi-agent workflows

Files:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
src/synthorg/{api,auth,backup,budget,cli,communication,config,core,engine,hr,memory,persistence,observability,providers,security,settings,templates,tools}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Google style docstrings for all public classes/functions in the API subpackage and other modules; includes parameter, return, raises, and example sections

Files:

  • src/synthorg/observability/events/api.py
  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
src/synthorg/api/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

REST API: RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail); use problem detail responses for all API errors

Files:

  • src/synthorg/api/server.py
  • src/synthorg/api/state.py
  • src/synthorg/api/app.py
  • src/synthorg/api/auto_wire.py
🧠 Learnings (15)
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
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> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/api.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to 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/api.py
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/{api,auth,backup,budget,cli,communication,config,core,engine,hr,memory,persistence,observability,providers,security,settings,templates,tools}/**/*.py : Google style docstrings for all public classes/functions in the API subpackage and other modules; includes parameter, return, raises, and example sections

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/server.py
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/**/*.py : API reference: auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports); document all public APIs with Google-style docstrings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine: manage hiring, firing, onboarding, offboarding; track agent performance (task metrics, collaboration scoring, LLM calibration sampling); handle promotion/demotion with approval strategies

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/backup/**/*.py : Backup: use BackupService orchestrator with per-component handlers (PersistenceComponentHandler, MemoryComponentHandler, ConfigComponentHandler); tar.gz compression and SHA-256 checksums

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail); use problem detail responses for all API errors

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/{core,config}/**/*.py : Agents: use frozen Pydantic models for agent config (Agent, AgentRole, AgentCapability models); define agents in YAML company config

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence: pluggable PersistenceBackend protocol with SQLite initial; use SettingsRepository for namespaced settings CRUD

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/app.py
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/security/**/*.py : Security: SecOps agent with rule engine (soft-allow/hard-deny, fail-closed); output scanner with response policies (redact/withhold/log-only/autonomy-tiered)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/**/*.py : Observability: use structured logging via get_logger() and observability.events constants; correlation tracking for multi-agent workflows

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication: use message bus with dispatcher and messenger; implement loop prevention and conflict resolution

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/auto_wire.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.

Applied to files:

  • src/synthorg/api/app.py
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in `ProviderConfig`

Applied to files:

  • src/synthorg/api/app.py
📚 Learning: 2026-03-18T11:08:24.271Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T11:08:24.271Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings: runtime-editable settings stored in SQLite with Fernet encryption for sensitive values; typed definitions in `definitions/` namespaces; use ConfigResolver for accessor methods

Applied to files:

  • src/synthorg/api/app.py
🧬 Code graph analysis (3)
tests/unit/api/test_app.py (5)
src/synthorg/api/state.py (13)
  • AppState (40-455)
  • task_engine (184-186)
  • has_message_bus (164-166)
  • has_task_engine (189-191)
  • cost_tracker (174-176)
  • persistence (159-161)
  • message_bus (169-171)
  • has_settings_service (266-268)
  • has_config_resolver (271-273)
  • has_provider_management (289-291)
  • settings_service (261-263)
  • set_settings_service (423-455)
  • provider_registry (328-333)
src/synthorg/api/app.py (1)
  • create_app (654-840)
src/synthorg/api/auto_wire.py (1)
  • auto_wire_settings (170-243)
src/synthorg/settings/dispatcher.py (1)
  • stop (102-114)
src/synthorg/settings/registry.py (1)
  • get_registry (111-113)
src/synthorg/api/app.py (4)
src/synthorg/api/auto_wire.py (2)
  • auto_wire_phase1 (45-129)
  • auto_wire_settings (170-243)
src/synthorg/config/schema.py (1)
  • RootConfig (461-730)
src/synthorg/settings/dispatcher.py (2)
  • SettingsChangeDispatcher (48-224)
  • stop (102-114)
src/synthorg/api/state.py (6)
  • persistence (159-161)
  • message_bus (169-171)
  • backup_service (401-403)
  • task_engine (184-186)
  • provider_registry (328-333)
  • cost_tracker (174-176)
src/synthorg/api/auto_wire.py (4)
src/synthorg/api/state.py (6)
  • task_engine (184-186)
  • AppState (40-455)
  • persistence (159-161)
  • message_bus (169-171)
  • cost_tracker (174-176)
  • set_settings_service (423-455)
src/synthorg/persistence/protocol.py (1)
  • PersistenceBackend (31-195)
src/synthorg/settings/encryption.py (2)
  • SettingsEncryptor (21-125)
  • from_env (91-125)
src/synthorg/settings/service.py (1)
  • SettingsService (152-637)
🔇 Additional comments (20)
src/synthorg/observability/events/api.py (1)

34-34: LGTM!

The new API_SERVICE_AUTO_WIRED event constant follows the established naming convention and is correctly placed in the file.

CLAUDE.md (1)

117-117: LGTM!

The documentation accurately describes the two-phase auto-wiring mechanism, listing the correct services for each phase.

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

24-26: LGTM!

The docstring accurately reflects the new auto-wiring behavior and provides clear guidance on when explicit service injection is needed.

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

422-455: LGTM!

The set_settings_service() method follows the established pattern of other deferred initialization setters:

  • Guards against double-setting
  • Logs errors before raising
  • Creates dependent services atomically after the guard check passes
tests/unit/api/test_health.py (2)

65-87: LGTM!

The test expectations are correctly updated to reflect auto-wired message bus behavior. The comments at lines 65-66 clearly explain the rationale for the changes.


138-140: LGTM!

Exception path tests correctly updated: when the auto-wired bus remains healthy, the overall status is degraded rather than down.

Also applies to: 179-180

tests/unit/api/test_app.py (4)

335-430: LGTM!

Comprehensive Phase 1 auto-wiring tests covering:

  • Auto-wiring of message bus, cost tracker, and task engine
  • Verification that task engine requires persistence
  • Preservation of explicitly provided services
  • Warning emission when persistence is unavailable

432-540: LGTM!

Phase 2 auto-wiring tests thoroughly cover:

  • Settings service creation with dispatcher
  • Behavior without a message bus
  • Phase 2 flag detection
  • Skipping Phase 2 when settings service is explicitly provided

543-602: LGTM!

AppState.set_settings_service() tests correctly verify:

  • Creation of ConfigResolver and ProviderManagementService
  • RuntimeError on double-setting

605-648: LGTM!

Edge case tests verify:

  • API channels are merged into auto-wired bus
  • Provider registry behavior with/without providers configured
  • Explicit provider_registry is preserved
src/synthorg/api/app.py (6)

30-30: LGTM!

New imports for auto-wiring utilities and ProviderRegistry are correctly added.

Also applies to: 68-68


181-209: LGTM!

The _build_lifecycle signature is cleanly extended with keyword-only parameters for Phase 2 control. The docstring accurately describes the new parameters.


223-268: LGTM!

Phase 2 auto-wiring in on_startup:

  • Correctly guards with all required conditions
  • Triggers _safe_shutdown on failure before re-raising
  • Uses proper PEP 758 exception syntax

275-298: LGTM!

Shutdown correctly handles the auto-wired dispatcher by stopping it before the regular shutdown sequence.


669-670: LGTM!

Phase 1 auto-wiring integration:

  • New provider_registry parameter added to create_app
  • auto_wire_phase1 is called with all relevant services
  • Results correctly reassigned to local variables

Also applies to: 730-742


796-810: LGTM!

Phase 2 flag calculation is correct: auto-wire settings only when settings_service is not provided AND persistence is available.

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

1-43: LGTM!

Clean module structure with:

  • Clear module docstring explaining the two-phase approach
  • Phase1Result NamedTuple for typed return values
  • Proper TYPE_CHECKING imports for type-only dependencies

45-129: LGTM!

auto_wire_phase1 implementation:

  • Each service auto-wired only when caller passes None
  • Proper error handling with logging before re-raise
  • Logs each auto-wired service individually
  • Helpful warning when persistence unavailable (aids debugging 503s in production)

132-167: LGTM!

_auto_wire_message_bus correctly:

  • Merges API channels with default config channels
  • Uses deferred import for InMemoryMessageBus
  • Handles errors with proper logging

170-243: LGTM!

auto_wire_settings Phase 2 implementation:

  • Creates SettingsService with encryptor from environment
  • Builds and starts dispatcher BEFORE mutating AppState (prevents partial state if dispatcher fails)
  • Only mutates AppState after all fallible operations succeed
  • Returns the dispatcher for lifecycle management

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced automatic service initialization and configuration during application startup
    • Backend services are now auto-wired from configuration and environment variables (e.g., SYNTHORG_DB_PATH)
    • Simplified deployment by eliminating the need for manual service provisioning
  • Documentation

    • Updated deployment guidance to reflect automatic service initialization
  • Tests

    • Added comprehensive test coverage for service auto-wiring behavior and startup lifecycle

Walkthrough

This PR implements production auto-wiring of backend services at startup in two phases: Phase 1 wires non-persistence-dependent services (message bus, cost tracker, provider registry, task engine) at app construction, and Phase 2 wires settings-dependent services (SettingsService, ConfigResolver, ProviderManagementService) after persistence connects during startup.

Changes

Cohort / File(s) Summary
Auto-wiring Infrastructure
src/synthorg/api/auto_wire.py
New module introducing Phase 1 auto-wiring function that conditionally creates message bus, cost tracker, task engine, and provider registry from configuration; Phase 2 async function that creates SettingsService, starts dispatcher, and injects components into AppState; includes private helper for message bus creation with channel merging and robust error handling throughout.
App Factory Integration
src/synthorg/api/app.py
Extended create_app() to accept optional provider_registry parameter; refactored to perform Phase 1 auto-wiring at construction and determine Phase 2 eligibility; modified _build_lifecycle() to accept should_auto_wire_settings and effective_config flags; updated startup handler to conditionally execute Phase 2 auto-wiring with error-triggered shutdown; stores and stops auto-wired dispatcher during shutdown; updated AppState construction to include provider registry.
AppState Extensions
src/synthorg/api/state.py
Added set_settings_service() method to AppState that guards against reconfiguration, stores the settings service, and initializes ConfigResolver and ProviderManagementService with current app state and config.
Observability
src/synthorg/observability/events/api.py
Added new event constant API_SERVICE_AUTO_WIRED for tracking auto-wiring operations.
Documentation
src/synthorg/api/server.py
Updated run_server() docstring to reflect that backend services are auto-wired by create_app() from configuration and environment variables, with explicit injection needed only for testing.
Auto-wiring Tests
tests/unit/api/test_app.py
Comprehensive unit test suite covering Phase 1 auto-wiring of message bus, cost tracker, and task engine at construction; Phase 2 settings auto-wiring with dispatcher lifecycle; preservation of explicitly provided services; edge cases including missing persistence, missing bus, provider registry behavior, and error handling.
Health Check Tests
tests/unit/api/test_health.py
Updated health check test expectations to account for auto-wired message bus now being present by default, adjusting expected status outcomes for various persistence and bus health scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant App as create_app()
    participant Config as RootConfig
    participant Phase1 as auto_wire_phase1()
    participant Persistence as PersistenceBackend
    participant Phase2 as auto_wire_settings()
    participant AppState
    participant Dispatcher as SettingsChangeDispatcher

    App->>Config: Extract effective_config
    App->>Phase1: Call with config, no persistence
    Phase1->>Phase1: Create InMemoryMessageBus from config
    Phase1->>Phase1: Create CostTracker from config.budget
    Phase1->>Phase1: Create ProviderRegistry from config.providers
    Phase1->>Phase1: Log warning (no persistence for task_engine)
    Phase1-->>App: Return Phase1Result (bus, cost_tracker, registry)
    
    App->>AppState: Construct with wired services from Phase1
    
    App->>App: Build lifecycle with should_auto_wire_settings=True
    
    Note over App: On startup signal...
    App->>Persistence: _init_persistence (already connected)
    Persistence-->>App: Connected
    
    App->>Phase2: Call auto_wire_settings with persistence now available
    Phase2->>Phase2: Create SettingsService(persistence, registry, config)
    Phase2->>Phase2: Build and start dispatcher
    Dispatcher-->>Phase2: Started successfully
    Phase2->>AppState: set_settings_service(settings_service)
    AppState->>AppState: Initialize ConfigResolver
    AppState->>AppState: Initialize ProviderManagementService
    Phase2->>App: Return started dispatcher
    
    Note over App: Phase 2 complete, full app operational
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(api): auto-wire backend services at startup' clearly and concisely summarizes the main change: implementing automatic wiring of backend services during app initialization.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing Phase 1/Phase 2 auto-wiring approach, new modules, test coverage, and CI results.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #550 objectives: auto-wires message_bus, cost_tracker, task_engine, provider_registry (Phase 1) and settings_service (Phase 2) with proper dependency ordering, override support, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are within scope of issue #550: new auto_wire.py module, Phase 1/2 implementation, AppState.set_settings_service(), health check updates, and test additions directly support the auto-wiring objectives.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auto-wire-backend-services
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/auto-wire-backend-services
📝 Coding Plan
  • Generate coding plan for human review comments

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

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

This pull request significantly enhances the robustness and ease of deployment for the API by introducing an automatic service wiring system. It ensures that critical backend services are properly initialized at startup, even in environments where they are not explicitly configured, thereby mitigating common 503 errors. The changes streamline the application's lifecycle management by separating service initialization into two distinct phases based on their dependencies, centralizing this logic, and updating how the application state manages these services.

Highlights

  • Automated Service Wiring: Implemented a two-phase auto-wiring mechanism for backend services at application startup, preventing 503 errors when services are not explicitly provided.
  • Phase 1 Auto-wiring (Construction Time): Services like message_bus, cost_tracker, task_engine, and provider_registry are now auto-wired during application construction, as they do not require a connected persistence backend.
  • Phase 2 Auto-wiring (On Startup): The settings_service and settings_dispatcher are now auto-wired during the on_startup phase, ensuring persistence is connected and migrations are complete before their initialization.
  • Centralized Auto-wiring Logic: Extracted all auto-wiring logic into a new dedicated module, auto_wire.py, improving modularity and maintainability.
  • Enhanced AppState Management: Added AppState.set_settings_service() to handle the deferred injection of the settings service, along with the creation of ConfigResolver and ProviderManagementService.
  • Message Bus Compatibility: The auto-wired message bus now includes all API channels, ensuring compatibility with the MessageBusBridge.
  • Updated Health Checks: Adjusted health check tests to correctly reflect the status of auto-wired services, particularly the message bus, which is now always considered available.
Using Gemini Code Assist

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

Invoking Gemini

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

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

Customization

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

Limitations & Feedback

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

Footnotes

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

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 18, 2026 19:35 — with GitHub Actions Inactive
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 18, 2026
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 an auto-wiring mechanism for backend services, significantly improving the application's startup process by automatically configuring services that are not explicitly provided. This change centralizes service initialization logic, reducing boilerplate and potential 503 errors in factory mode. The implementation is well-structured, separating Phase 1 (construction time) and Phase 2 (on_startup) auto-wiring into a new auto_wire.py module. Comprehensive unit tests have been added to cover various auto-wiring scenarios, including explicit service overrides and persistence-dependent services. Documentation has been updated to reflect these changes. The only minor issues identified are the use of deprecated exception syntax in a few places and an outdated docstring for a complex function.



def _build_lifecycle( # noqa: PLR0913
def _build_lifecycle( # noqa: PLR0913, C901, D417
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The D417 noqa indicates that the docstring for _build_lifecycle is missing descriptions for some arguments. It's important to keep docstrings up-to-date for maintainability and clarity, especially for complex functions. Please add descriptions for should_auto_wire_settings and effective_config to the docstring.

Comment on lines +252 to +253
except MemoryError, RecursionError:
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The except MemoryError, RecursionError: syntax is deprecated in Python 3.10 and removed in Python 3.12. It should be except (MemoryError, RecursionError):.

            except (MemoryError, RecursionError):

Comment on lines +78 to +79
except Exception:
logger.exception(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The except MemoryError, RecursionError: syntax is deprecated in Python 3.10 and removed in Python 3.12. It should be except (MemoryError, RecursionError):.

Suggested change
except Exception:
logger.exception(
except (MemoryError, RecursionError):

Comment on lines +91 to +92
except Exception:
logger.exception(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The except MemoryError, RecursionError: syntax is deprecated in Python 3.10 and removed in Python 3.12. It should be except (MemoryError, RecursionError):.

Suggested change
except Exception:
logger.exception(
except (MemoryError, RecursionError):

Comment on lines +105 to +106
except Exception:
logger.exception(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The except MemoryError, RecursionError: syntax is deprecated in Python 3.10 and removed in Python 3.12. It should be except (MemoryError, RecursionError):.

Suggested change
except Exception:
logger.exception(
except (MemoryError, RecursionError):

Comment on lines +160 to +161
except Exception:
logger.exception(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The except MemoryError, RecursionError: syntax is deprecated in Python 3.10 and removed in Python 3.12. It should be except (MemoryError, RecursionError):.

Suggested change
except Exception:
logger.exception(
except (MemoryError, RecursionError):

Comment on lines +210 to +211
except Exception:
logger.exception(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The except MemoryError, RecursionError: syntax is deprecated in Python 3.10 and removed in Python 3.12. It should be except (MemoryError, RecursionError):.

Suggested change
except Exception:
logger.exception(
except (MemoryError, RecursionError):

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 78.78788% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.45%. Comparing base (71cc6e1) to head (e564b5f).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/lifecycle.py 75.00% 25 Missing and 9 partials ⚠️
src/synthorg/api/auto_wire.py 77.17% 19 Missing and 2 partials ⚠️
src/synthorg/api/app.py 95.83% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #555      +/-   ##
==========================================
- Coverage   92.49%   92.45%   -0.05%     
==========================================
  Files         542      544       +2     
  Lines       26655    26783     +128     
  Branches     2544     2554      +10     
==========================================
+ Hits        24655    24762     +107     
- Misses       1598     1615      +17     
- Partials      402      406       +4     

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

- Extract lifecycle helpers (lifecycle.py) from app.py (915->588 lines)
- Fix dispatcher resource leak on post-start failure in auto_wire_settings
- Add BuildDispatcherFn Protocol replacing Callable[..., ...]
- Split auto_wire_phase1 into helper functions (<50 lines each)
- Make set_settings_service atomic (construct locals before assigning)
- Wrap build_dispatcher() call in try/except with specific logging
- Add deferred import comment explaining import cycle
- Remove env var name from error log, redact DB path in info log
- Document all 10 params in _build_lifecycle docstring (remove D417 noqa)
- Improve _should_auto_wire comment clarity
- Remove unused caplog fixture, tighten >= 1 to == 1 assertion
- Add 8 new tests: Phase 2 rollback, dispatcher shutdown, start failure
  invariant, SettingsService creation failure, channel deduplication,
  cost tracker/message bus error paths
- Update CLAUDE.md package structure for lifecycle.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 18, 2026 20:09 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 0e52c47 into main Mar 18, 2026
30 of 32 checks passed
@Aureliolo Aureliolo deleted the feat/auto-wire-backend-services branch March 18, 2026 20:17
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 18, 2026 20:17 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 18, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.5](v0.3.4...v0.3.5)
(2026-03-18)


### Features

* **api:** auto-wire backend services at startup
([#555](#555))
([0e52c47](0e52c47))


### Bug Fixes

* **api:** resolve WebSocket 403 rejection
([#549](#549))
([#556](#556))
([60453d2](60453d2))
* **cli:** verify SLSA provenance via GitHub attestation API
([#548](#548))
([91d4f79](91d4f79)),
closes [#532](#532)


### Performance

* **test:** speed up test suite -- reduce Hypothesis examples and
eliminate real sleeps
([#557](#557))
([d5f3a41](d5f3a41))


### Refactoring

* replace _ErrorResponseSpec NamedTuple with TypedDict
([#554](#554))
([71cc6e1](71cc6e1))


### Maintenance

* **docker:** suppress pydantic v1 warning on Python 3.14
([#552](#552))
([cbe1f05](cbe1f05)),
closes [#551](#551)

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: auto-wire backend services at startup (production service initialization)

1 participant