Pluggable Subsystems: Canonical Examples¶
On-demand reference. The rule in CLAUDE.md is: new cross-cutting subsystems follow a protocol + strategy + factory + config discriminator pattern, with safe defaults so the behaviour is opt-in. This page catalogues the canonical implementations.
Pattern recap¶
- Define a
Protocolinterface. - Ship concrete strategies that implement it.
- Register them in a factory keyed by a config discriminator.
- Plumb the active selection through frozen config.
- Ship safe defaults so nothing ever silently regresses.
Registries¶
Three sibling registries replace the hand-rolled if config.type == "...": ... elif ... chains every factory used to carry. Each is immutable after construction (MappingProxyType-backed) and emits structured registry.* events for built / lookup / failure paths.
synthorg.core.registry.StrategyRegistry[T]: generic strategy dispatch keyed by aconfig.typediscriminator. Used by the four strategy-family factories (pruning, propagation, identity store, evolution proposer) plus the per-op rate-limit and inflight stores.synthorg.persistence.registry.PersistenceBackendRegistry: domain-specific dispatch keyed byPersistenceConfig.backend; preserves the lazy import of the optionalpostgresextra.synthorg.memory.registry.MemoryBackendRegistry: domain-specific dispatch keyed byCompanyMemoryConfig.backend; the composite-backend child loop reuses a separate "leaf" registry to keep the wiring acyclic.
Each subsystem still owns its config discriminator; the registries replace only the dispatch step that translates the discriminator into a constructor call.
Canonical examples¶
Classification pipeline¶
engine/classification/protocol.py:Detector,ScopedContextLoader,ClassificationSink.budget/coordination_config.py: dispatcher.
Verification graders¶
engine/quality/decomposer_protocol.py:CriteriaDecomposer.engine/quality/grader_protocol.py:RubricGrader.engine/quality/verification_factory.py+engine/quality/verification_config.py.
Chief of Staff¶
meta/chief_of_staff/protocol.py:OutcomeStore,ConfidenceAdjuster,OrgInflectionSink,AlertSink.meta/chief_of_staff/config.py: discriminator.meta/factory.py::build_confidence_adjuster().
Analytics / telemetry¶
meta/telemetry/protocol.py:AnalyticsEmitter,AnalyticsCollector,RecommendationProvider.meta/telemetry/config.py: discriminator.meta/telemetry/factory.py::build_analytics_emitter().
Rollout strategies¶
meta/rollout/clock.py:Clock.meta/rollout/roster.py:OrgRoster.meta/rollout/group_aggregator.py:GroupSignalAggregator.meta/rollout/inverse_dispatch.py:RollbackHandler+ 4 mutator protocols.meta/factory.py::build_rollout_strategies()+build_rollback_executor().- All plumbed through frozen
SelfImprovementConfig, with safe defaults (RealClock,NoOpOrgRoster, null aggregator) so the behaviour is opt-in.
API rate limits¶
api/rate_limits/protocol.py:SlidingWindowStore.api/rate_limits/in_memory.py.api/rate_limits/config.py::PerOpRateLimitConfig: discriminator.api/rate_limits/factory.py::build_sliding_window_store().
API per-op concurrency¶
api/rate_limits/inflight_protocol.py:InflightStore.api/rate_limits/in_memory_inflight.py.api/rate_limits/inflight_config.py::PerOpConcurrencyConfig: discriminator.api/rate_limits/inflight_factory.py::build_inflight_store().api/rate_limits/inflight_middleware.py::PerOpConcurrencyMiddleware(Litestar middleware that readsopt[per_op_concurrency]from each route handler).
Escalation queue¶
communication/conflict_resolution/escalation/protocol.py:EscalationQueueStore,DecisionProcessor.- In-memory / SQLite / Postgres implementations.
communication/conflict_resolution/escalation/config.py::EscalationQueueConfig: discriminator.communication/conflict_resolution/escalation/factory.py::build_escalation_queue_store().
Assignment ranking and pool filtering¶
engine/assignment/protocol.py:TaskAssignmentStrategy(the public Protocol; strategies are still selected by thestrategyconfig string).engine/assignment/pool_filter_protocol.py:CandidatePoolFilter(pre-scoring narrowing ofavailable_agents;IdentityPoolFilteris the default,HierarchicalPoolFilternarrows to subordinates of the task's delegator).engine/assignment/ranker_protocol.py:CandidateRanker(post-scoring ordering:ScoreDescendingRanker,WorkloadAscendingRanker,CostDescendingRanker,AuctionBidRanker).engine/assignment/scoring_based.py::ScoringBasedAssignmentStrategy: composes(scorer, pool_filter, ranker). The five logical assignment strategies (role_based,load_balanced,cost_optimized,auction,hierarchical) are allScoringBasedAssignmentStrategyinstances with different filter/ranker pairs.engine/assignment/registry.py::build_strategy_map(): the factory; preserves the public string discriminators.
HR pillar scoring¶
hr/evaluation/pillar_protocol.py:PillarScoringStrategy(the public per-pillar Protocol).hr/evaluation/metric_extractor_protocol.py:MetricExtractor(per-pillar sub-metric extraction). Implementations live underhr/evaluation/extractors/(one file per pillar: intelligence, efficiency, resilience, governance, experience).hr/evaluation/configurable_scorer.py::ConfigurablePillarScorer: composes(pillar, extractor)to satisfyPillarScoringStrategy. Owns the shared "redistribute weights -> weighted-average -> clamp -> confidence -> log ->PillarScore" pipeline so the per-pillar extractors stay focused on data extraction.hr/evaluation/evaluator.py::EvaluationService: factory + orchestrator. Each pillar has a_default_<pillar>()method that returnsConfigurablePillarScorer(pillar, <Pillar>MetricExtractor()). Callers can substitute any compatiblePillarScoringStrategyper pillar via the constructor's<pillar>_strategykeyword arguments.
Services are a distinct pattern (not pluggable subsystems)¶
A service wraps one or more repositories to keep controllers thin and centralise audit logging, and MAY orchestrate multiple repositories (e.g. WorkflowService spans workflow_definitions + workflow_versions; MemoryService spans fine-tune checkpoints + runs + settings).
The Protocol + Strategy + Factory + Config pattern applies only to genuinely cross-cutting subsystems that ship multiple interchangeable implementations selectable at runtime. Services do not need that machinery because there is exactly one service per domain.