API Startup Lifecycle¶
The API boots in two phases. Construction (the create_app body) wires synchronous services. On-startup (_build_lifecycle.on_startup) wires services that need a connected persistence backend. The ordering invariants below are load-bearing: getting them wrong races a dependency and 503s a controller forever, or poisons startup on a transient boot.
Construction-phase ordering invariants¶
agent_registrymust be built BEFOREauto_wire_meetings.tunnel_provideris wired unconditionally (not gated byintegrations.enabled).message_bus.set_quadratic_alert_sink(DispatcherQuadraticAlertSink(...))is called after the notification dispatcher is built, binding the in-memory bus's quadratic-fan-out enforcer to the dispatcher through theMessageBusprotocol seam (the NATS backend is a no-op). This is a protocol call, not anisinstance(InMemoryMessageBus)+ concrete-attr read.ConflictResolutionServiceis built and installed onCommunicationStateSlicebywire_conflict_resolution_service(api/_comms_conflict_wiring.py): the hierarchy comes from the boot company snapshot and the human resolver reuses the already-wired escalation store/processor/registry. The sharedLlmJudgeEvaluatoris built here too, from the provider that serves the pinnedCONFLICT_JUDGEmodel (resolve_feature_provider, not a naive first pick); a missing/non-serving provider leaves the judge unwired and the debate/hybrid resolvers fall back to authority._wire_meeting_conflict_bridgeruns AFTERwire_conflict_resolution_servicein the same pass (the bridge needs the built service) and AFTERauto_wire_meetings(it installs the bridge on the orchestrator viaset_conflict_escalation_hook). It also stores the bridge onCommunicationStateSlice.conflict_escalation_bridgeso the on-startup resolver rebind (_wire_resolver_dependents) can inject the persistence-backed resolver, making themeeting_conflict_escalation_enabledkill switch honour dashboard overrides. A no-op when either the orchestrator or the service is absent.PeerDiscoveryClientis built from the peer registry + SSRF network validator and placed onA2aStateSlice.peer_discoveryso the gateway'sskills/query/skills/negotiatehandlers can resolve learned peers.
On-startup ordering invariants¶
- Durable security state wires best-effort after persistence connects, inside
install_runtime_servicesviadurability_wiring.py:_try_wire_audit_chain_persistenceattaches aDurableAuditChainWriterto each liveAuditChainSink(hydrate + verify + drain). It is idempotent and degrades to in-memory-only on failure, logging the dedicatedAPI_AUDIT_CHAIN_PERSISTENCE_DEGRADEDevent. - The closed-loop
EvalLoopCoordinatoris built from the performance tracker + training service inwire_eval_loopand published onHrStateSlice; the periodicEvalLoopCycleSchedulerthat drivesrun_cycleis opt-in (hr.eval_loop_cycle_enabled) and re-readshr.eval_loop_cycle_pausedeach tick. - The dynamic-scaling pipeline is ghost-wired:
wire_scalingconstructs theScalingService(viabuild_scaling_service) plus the durableHiringServiceand anOffboardingService, publishing the service onHrStateSlice.scaling_servicewhenever its collaborators are present (a connected persistence backend plus a wired registry / tracker / approval store), regardless ofhr.scaling_enabled. Thehr.scaling_enabledswitch (off by default) is enforced live at the/scalingendpoints viaensure_feature_enabled, so toggling it takes effect on the next request with no restart. It runs after_wire_pruningand on construction callsHiringService.attach_persistence(request_repo=persistence_of(app_state).hiring_requests)+hydrate()so an approved hire survives a restart between approval and instantiation. A missing collaborator leaves the service absent and the/scalingendpoints 503. -
The observability bridge config arm is applied alongside the notification dispatcher:
ObservabilityBridgeSettingsSubscriberwatches theobservability.*keys and recomposesget_observability_bridge_config()so console-level / telemetry-enabled edits take effect via the settings dispatcher. -
SettingsServiceauto-wire must precedeWorkflowExecutionObserverregistration, so it picks up the resolver-drivenmax_subworkflow_depthinstead of the seed default. OntologyServicewires afterpersistence.connect()via_wire_ontology_service.- Cost-dial services (
BudgetConfig,CostForecastRepository,BenchmarkScoreRepository, thebudget.benchmark_provider-selectedBenchmarkScoreProvider,CostForecaster,ParetoAnalyzer) wire via_try_wire_cost_dialAFTER persistence connects. It is best-effort (logs anAPI_APP_STARTUPwarning and the controllers 503 if it fails or persistence is absent) and idempotent (skips when already wired), so a transient shared-app boot does not poison startup. The benchmark provider, repo, andParetoAnalyzer'sModelTierMapare built inapi/_benchmark_wiring.py(stubdefault /measured;seed_benchmark_scoresboot-seeds the measured arm from the committedbenchmark_seed.json). The approved forecast'sforecast_id+ceiling_amountare stamped onto theTaskin the work pipeline's intake phase (WorkPipelineService._link_forecast) so the in-loopBudgetCheckerenforces the per-brief ceiling and the engine can stamp halt context for the resume banner. - Knowledge substrate wires via
wire_knowledge_engineAFTER persistence connects; best-effort and ghost-wired (built regardless ofknowledge.enabled), gated only onhas_persistenceANDhas_memory_backend(logs anAPI_APP_STARTUPwarning and the knowledge controllers + MCP handlers 503 if either is absent), so a missing memory backend in dev does not poison startup.knowledge.enabled(Cat-1, default on, hot) is not a wiring gate; it is enforced live per request at the knowledge controllers + MCP handlers via the config resolver, so toggling it takes effect on the next request with no restart. EnvironmentService(per-project reproducible environments) wires in_install_runtime_servicesbehindhas_persistenceand is threaded intoAgentEngineExecutionServiceviabuild_runtime_services; the worker provisions ambiently (ActiveSandboxEnvironmentcontextvar) before the engine run, so a missing workspace logsENVIRONMENT_PROVISION_SKIPPEDrather than silently dropping the declared env.- Mid-flight steering splits its wiring in two by dependency: the steering INBOX (read path) is built from
persistence.project_brainand injected into the bootAgentEnginein the runtime-services step (persistence-only, memory-independentlist_currentprojection), while the steering SERVICE (write path) wires in_wire_steering_serviceAFTER_wire_project_brain(memory-gated brain) via partialapp_state.wire(CockpitStateSlice, ...)(NOTswap_slice, so the construction-phasesteering_notifierand the latersteering_servicecoexist on the slice). Wiring the service inside_wire_cockpit_serviceswould race the brain and 503 forever. - The red-team report repo is published on
SecurityStateSlice.red_team_reportsduring_install_runtime_services(decoupled from the review gate, via partialapp_state.wire), and_wire_deliverable_receiptsreads it so a receipt'sred_teamsection degrades to empty rather than erroring when the subsystem is off. agent_workspace_rootis resolved viaresolve_agent_workspace_root_env()INSIDE the_install_runtime_servicesstartup closure (its install guard runs it once), NOT at construction: the resolver fail-fasts on a non-absoluteSYNTHORG_DB_PATH, and that must not crash pure app construction (dev /:memory:/ OpenAPI export, which build the app without running startup). The resolvedPath | Noneis injected intoinstall_runtime_servicesrather than re-read from the environment inside it._apply_telemetry_db_layerruns immediately BEFOREtelemetry_collector.start. The collector is built pre-persistence (env > default), so this hook re-resolvestelemetry.enabledwith full DB > env > default precedence and callstelemetry_collector.apply_enabled(enabled=...)beforestart()reads it, so a DB/settingstoggle takes effect on restart. Best-effort: aNoneresolver or a resolution failure logs anAPI_APP_STARTUPwarning and leaves the construction-time value untouched.- The construction-phase notification dispatcher is started by
_start_construction_dispatcherONLY when it is still the live dispatcher onNotificationsStateSlice. The bridge-config startup step (_apply_notification_dispatcher_config, run via_apply_bridge_config) may have rebuilt a replacement with DB-resolved timeouts, started it, and swapped it onto the slice, leaving the construction dispatcher orphaned; starting that orphan would open sinks nothing routes through and that on-shutdown (which closes the live slice dispatcher) never tears down. On a hot config-apply,_apply_notification_dispatcher_configstarts the new dispatcher BEFORE swapping it in (a failed start aborts the swap and keeps the running dispatcher, and aCancelledErroror non-critical failure mid-start closes the partially-started one), so no built-but-unstarted dispatcher silently drops events. - The JWT secret is resolved (
resolve_jwt_secret) and the dev-auth-bypass flag (resolve_dev_auth_bypass, envSYNTHORG_DEV_AUTH_BYPASS) BEFOREpersistence.migrate(), so a missing secret fails fast.SYNTHORG_DEV_AUTH_BYPASSis DEV-ONLY and defaultsFalse: when on it stampsAuthConfig.dev_auth_bypass=True, addsPOST /auth/dev-loginto the auth-exclude set, and emits a prominentAPI_APP_STARTUPsecurity warning. That endpoint mints a real admin session for the existing CEO with no password (404s when the flag is off, 401s when no admin exists, never creates one). It MUST never be enabled in production. The frontend dev bootstrap (web/.env'sVITE_DEV_AUTH_BYPASS, only honoured underimport.meta.env.DEV) calls it on load so the local Vite dashboard auto-logs-in.
Late-startup gated wiring hooks¶
wire_quota_poller: gatedbudget.quota_poller_enabled+ connected persistence + wiredquota_tracker; appended AFTER_wire_features; idempotent; stopped inlifecycle_runner_shutdown.wire_risk_override_service: gated on aTieredTimeoutConfig+ persistence + scheduler; rebuilds the tiered policy's classifier wrapped inSecOpsRiskClassifierseeded from the durable override repo and swaps it viaApprovalTimeoutScheduler.set_timeout_policy; publishesRiskOverrideServiceonSecurityStateSlice; controllers + MCP 503 when unwired.wire_org_memory_backend: insidewire_features_on_startupBEFORE_wire_pruning; persistence-only gate; best-effortconnect(); publishesMemoryStateSlice.org_memory_backendconsumed by offboarding + ontology sync, degrading toNonewhen absent._wire_task_activity_observer: registers theTaskActivityObserveron theTaskEnginein the sametryblock as_wire_workflow_observer, gated on task engine + a liveChannelsPlugin(threaded viaMessageBusBridge.plugin) + a wiredHrStateSlice.performance_tracker; idempotent (checks_observersfor an existing instance). Publishes every persisted task transition toCHANNEL_TASKS(dashboard live-activity feed) and records aTaskMetricRecord(org health + completions sparkline) only for a classifiable, attributable executed terminal run (fresh into aTERMINAL_RUN_STATESmember, with a resolvedRunOutcomeand an assignee); a run whose outcome cannot be resolved (artifact-count fault) or that carries no assignee, and FSM-terminal-but-never-ran states (CANCELLED/REJECTED), are published but not recorded, so unclassifiable runs never skew health. LogsAPI_SERVICE_AUTO_WIREDon success andAPI_APP_STARTUPon each skip; a missing prerequisite disables the live feed rather than failing boot.HealthMonitoringPipeline(judge + triage + dispatcher) is built inbuild_runtime_servicesand threaded intoAgentEngineExecutionService(health_pipeline=/health_enabled=), re-readingengine.health_monitoring_enabledper run; absent dispatcher means no pipeline.
Controller-facing read services¶
ConversationalResumeServicelives inmeta/chief_of_staff/(NOTapi/services/, so the earlyapi_core_stateimport chain never pulls thecommunicationenum modules and trips the cold-import cycle gate), is published onMetaStateSliceby_wire_conversational_repositories_and_reconcile, and is deliberately UNGATED (wraps only the persistence repos, never the toggle-gated Chief-of-Staff feature services) so a decided conversational approval still resolves after its feature switches off.- The single
WorkflowExecutionService(_wire_workflow_observer) caches theconfig_resolverand resolvesmax_subworkflow_depthper call so settings hot-reload is preserved (constructing with a resolved int would freeze the seed default; aNoneresolver logs a warning and falls back toEngineBridgeConfig().max_subworkflow_depth). _wire_webhook_request_serviceswireswebhook_replay_protectorunconditionally andwebhook_activity_serviceonly on connected persistence. Idempotency keys on mutating endpoints MUST bind the resource id (f"{resource_id}:{idempotency_key}") so a reused key cannot collide across resources.
Self-extending toolkit (toolsmith)¶
Autonomous detection wires in wire_toolsmith AFTER install_dynamic_tool_layer (gated on self_improvement.tool_creation_enabled + provider + connected persistence): install_capability_gap_sink(runtime.service) routes every capability_gap MCP envelope into the gap store (fire-and-forget; write failure logs via safe_error_description, never an unhandled task-exception traceback), and a ToolsmithCycleScheduler (cadence toolsmith.cycle_interval_seconds, floor 60s) drives run_cycle periodically with a meta.toolsmith_cycle_paused kill-switch (re-read each tick, fail-safe to enabled). The ToolsmithStateSlice carries service + cycle_scheduler as a both-or-neither paired invariant; lifecycle_runner_shutdown stops the scheduler. The cycle only PROPOSES (human approval still gates apply).
Periodic model-refresh¶
Wires in wire_model_refresh (_wire_model_refresh, AFTER wire_toolsmith), gated on providers.model_refresh_mode != off AND a built provider-management service AND connected persistence (off-by-default, so a normal boot skips it). The cadence modes (detect_only / reconcile_recommend) build + start() a ModelRefreshScheduler BEFORE publishing ModelRefreshStateSlice and roll back (no publish) if start fails; manual_only wires the on-demand service with no scheduler. The slice's both-or-neither invariants: a scheduler implies a service, and a service implies a recommendation_repo. lifecycle_runner_shutdown stops the scheduler. The scheduler re-reads the live mode + auto-apply flag each tick (fail-safe to off), and stop() does NOT reset its lifecycle lock/event to None (it passes reset_primitives_on_stop=False to the shared AsyncCycleScheduler base: clearing them under the held lock opens a rebind race).
Runtime tool-call failure feedback¶
Wires in wire_tool_call_feedback (AFTER _wire_model_refresh), gated on a built provider-management service AND connected persistence (NOT on providers.tool_call_feedback_enabled: the ToolCallFeedbackTracker re-reads that setting live per observation so the feature toggles without a restart while the cheap sink stays installed). Best-effort + idempotent: a missing resolver / management / persistence logs an API_APP_STARTUP warning and returns (never an invisible skip). The tracker is published on ToolCallFeedbackStateSlice, then install_tool_call_signal_sink(tracker) runs LAST so a partial build leaves no dangling sink; lifecycle_runner_shutdown uninstalls the sink + clears the slice. The tracker keeps its instance lock OFF the capability-writer call (the writer takes ProviderManagementService._lock), so the two locks never nest. The matcher hard-fails requires_tools agents on tool_calls_verified is False (authoritative over the optimistic supports_tools path); a genuine tool-call success only re-enables a truly-downgraded (False) model, never promotes an untested (None) one.
Stakes-gated red-team completion¶
_wire_red_team_completion threads stakes_routing.red_team_min_stakes (default HIGH) onto ReviewGateService via set_red_team_min_stakes, so the gate fires only for task.stakes >= red_team_min_stakes; a below-threshold completion logs RED_TEAM_GATE_SKIPPED and dispatch_completion runs it INLINE (a missing task also runs inline so complete_review raises TaskNotFoundError synchronously) rather than deferring a no-op to the background.
Self-improvement rollback executor¶
Wires in api/lifecycle_helpers/meta_apply_wiring.py::_wire (best-effort + idempotent, off by default with the self-improvement feature): _build_rollback_executor constructs the six mutators (config/prompt/principle-removal/architecture/code unconditional, branch gated) and threads the executor into SelfImprovementService, which dispatches the applier-MATERIALISED inverse operations (carried on RolloutResult.applied_rollback_operations, NOT the proposal's static plan) on a post-rollout regression, flipping REGRESSED to ROLLED_BACK. The branch/revert_branch handler is gated on code_modification.github_token + github_repo (absent means branch revert omitted, matching the applier's credential gating); github_api_url is https-validated (token rides the Authorization header). A materialised op with no registered handler logs ERROR reason="unregistered_operation_type" and keeps REGRESSED (never a silent swallow).
Runtime services selection¶
synthorg.workers.runtime_builder.build_runtime_services selects behind ONE provider-present switch and returns a RuntimeServices pair (worker execution service + multi-agent coordinator) built from a SINGLE shared boot AgentEngine: AgentEngineExecutionService + a build_coordinator(...) coordinator with a provider, or NoProviderExecutionService + None coordinator as the empty-company backstop. The provider-present arm validates coordination.decomposition_model is non-blank before building the coordinator and raises CoordinationConfigError at boot when it is empty (the setting ships blank, so an operator must set a model id from their catalogue). The _install_runtime_services boot hook installs both via the AppState.worker_execution_service and AppState.coordinator seams; it is appended FIRST after the persistence/SettingsService hooks so the once-only set_worker_execution_service and if-absent set_coordinator_if_absent seams cannot lose the race with the worker property's lazy LifecycleAdvancingExecutionService default. Empty-company rejects task creation at the controller (AgentRuntimeNotConfiguredError, 4014) and /coordinate honestly 503s (no coordinator). swap_worker_execution_service / swap_coordinator / swap_provider_registry hold a lock (synchronised against lazy reads).
Setup completion¶
post_setup_reinit() (provider reload, agent bootstrap, AND runtime-services rebuild + dual hot-swap of the worker execution service and coordinator, defined in src/synthorg/api/controllers/setup/_runtime_wiring.py) propagates failures, and settings_svc.set("api", "setup_complete", "true") only runs if reinit returns clean. The whole check/validate/reinit/persist sequence is serialised under COMPLETE_LOCK (also in _runtime_wiring.py) so two concurrent /setup/complete requests cannot race on the flag write. A half-configured runtime presenting itself as "complete" is worse than a clear error the operator can retry after fixing the underlying provider config.