Plugin ring: product families as Cargo features
Status: surface gate landed 2026-09-17 (unverified by a local build — see "Verification" below). Audit and rationale: claudedocs/LEANER_CORE_PLUGIN_AUDIT_2026-09-17.md.
hsm_console used to mount 145 sub-routers and register 139 tools unconditionally. The plugin ring splits that surface into a core and fifteen product families. Each family is a Cargo feature; full (the default) enables all of them and is byte-for-byte the composition that shipped before the families existed.
cargo build --bin hsm_console # full (default)
cargo build --bin hsm_console --features full,otlp-telemetry # the runtime image
cargo build --bin hsm_console --no-default-features # core only
cargo build --bin hsm_console --no-default-features --features flywheel,passkeysMachine-readable map: config/company-os-plugin-ring.v1.json. Contract: node --test scripts/plugin-ring-contract.test.mjs (CI: contracts-and-console); core-only compile: CI job core-only-composition.
What a family gates
| Site | How |
|---|---|
| Routers | src/company_os/http_api/families.rs: one #[cfg(feature)] / #[cfg(not(feature))] pair per family. The core chain in http_api::router() mounts families::<family>_routes() and must never merge a family module directly. |
| Tool registrations | src/tools/mod.rs::register_all_tools: a family's tools are HTTP clients of that family's routes, so they register only with the family — a core-only build never advertises a tool whose endpoint it does not serve. |
| Boot hooks | src/bin/hsm_console.rs: desktop adapter registration, fleet heartbeat, external-adapter and bead-correlation reconcilers. |
| Binaries | required-features on family-owned entrypoints (economics_backfill, hsm_vcs_broker*). |
What a family does not gate yet: the mod declarations. Every family module is still compiled in every configuration. That is deliberate for the first slice — the gate is correct by construction under full and the core-only build cannot fail to compile — but it means the compile-time and link-time win is not yet realised. Promoting a family to a module-level gate means cutting its inbound seams (audit §3.7) with a compiler available and recording the change in the map's gate_level.
The first partial module gates are now recorded in module_gates in the ring manifest: decision_calibration, decision_action_bindings_http, and decision_outcomes_http are compiled only with decision-calibration. decision_episodes_http remains available because the self-improvement family reuses its idempotency and registration helpers. This is a partial seam, so the family remains at surface gate level until the shared helper is split into a core-owned module and every family module can be gated independently.
The families
| Feature | Ring | Modules (src/company_os/) | Also gates |
|---|---|---|---|
desktop-control | R2a | desktop_control, web_run_api | company_web_run tool; desktop adapter boot hook |
openapi-connectors | R2a | openapi_connector, connector_oauth | |
passkeys | R2a | webauthn_api | |
fleet-telemetry | R2a | fleet_telemetry | heartbeat boot hook |
email-control-plane | R2a | email_control_plane, customer_intake | |
beads-bridge | R2a | bead_outbox, bead_correlation | correlation reconciler boot hook |
external-adapters | R2a | external_adapter, adapter_protocol, executor_bridge, gateway, trigger_delivery | adapter reconciler boot hook |
vcs-broker | R2a | vcs_broker, vcs_backend, vcs_workspace_ledger, vcs_rewind | hsm_vcs_broker, _probe, _supervisor binaries |
flywheel | R2b | flywheel_selector, flywheel_dynamics, flywheel_calibration, flywheel_context | |
self-improvement | R2b | self_improvement, trace_skill_promotion, store_promotion, promotion_registry, context_map_promotion, improvement_episodes_http, quality_ratchet, dsh_adaptation_catalog | |
review-intelligence | R2b | review_delivery_store, review_artifacts, review_calibration_store, review_intelligence, review_maps, review_readiness_store, review_annotation_store | |
decision-calibration | R2b | decision_calibration, decision_episodes_http, decision_outcomes_http, decision_action_bindings_http | |
scenario-lab | R2b | scenario_lab, company_resilience, company_navigator, workflow_economics, outcome_radar, outcome_quality | company_scenario_run tool; economics_backfill binary |
radars | R2b | paperclip_parity, investor_demo, dead_star, sentinel, board, gepa_service, intelligence_signals, intelligence_operator, goal_graph, risk_posture, handoff_brief, ccir_registry, business_data, enterprise_context, db_intelligence | |
execution-lanes | R2c | rlm_tools_bridge, rlm_repl, code_mode_driver, code_mode_session, ttsr_runtime, ttsr, run_fork_execution, run_fork, trace_router, hitl_scheduler, validation_broker | company_validation_* tools (5) |
Rings: R2a wraps an external system (plugin by transport); R2b reads ledgers and proposes, owning no authority (plugin by effect class); R2c is an alternate way to run a turn (plugin by adapter). Everything else — identity, tenancy, tasks, runs, agents, admission, receipts, memory, tool registry, MCP registry, extension host, workspaces, policy, migrations — is the core and is not a feature.
Families are flat on purpose. If enabling one family alone fails to compile once module gates exist, that is a seam to cut, not a feature dependency to declare.
Rules
- A new product family is: a feature in
Cargo.toml(listed infull), an entry inconfig/company-os-plugin-ring.v1.json, a router pair infamilies.rs, and gates on its tools and boot hooks. The contract test fails on any half of that. - The core chain never merges a family module. The contract test fails on a leak.
--no-default-featuresis the core-only composition. Operators never get it fromscripts/company-os-up.sh(the launcher's opt-out flag keepsfull); the runtime image namesfullexplicitly.- Legacy, research and optional-integration features (
legacy-roodb,research-tools,email-imap,wasi-plugins, …) are not families and are never infull. - A family module cannot receive Postgres access or bypass admission, receipts or settlement by being a family — the universal extension contract's boundary still applies when a family leaves the process.
Verification
Landed on a workstation with ~1 GB of disk, so no cargo command ran. What was checked:
rustfmt --checkparses cleanly forfamilies.rs,http_api.rs,hsm_console.rs,tools/mod.rs(the only rustfmt diffs intools/mod.rsbelong to a different uncommitted change).node --test scripts/plugin-ring-contract.test.mjs: 6/6.node --test scripts/architecture-extraction-contract.test.mjs: thedefault-must-not-reach-legacy-roodbtraversal passes withdefault = ["full"]; the A8 catalog test was already failing before this change (three binaries missing from the catalog).
Still to run, in order: cargo check --bin hsm_console (full), cargo check --bin hsm_console --no-default-features --features otlp-telemetry (core), cargo test -p hyper-stigmergy --test company_os_routes, then cargo bloat --release --bin hsm_console for both to record the surface win.
Next slices
- Module-level gates, one family at a time, starting with the ones whose only inbound edge was the router merge:
decision-calibration,flywheel(one seam inadmission_control.rs),email-control-plane,fleet-telemetry,desktop-control. Measurecheck --testsmemory before and after. - Browser tools → a platform MCP stdio connector, digest-pinned and promoted at bootstrap (audit §4B).
- One reference plugin that depends on
hsm-extension-sdkonly (audit §4C). GET /api/company/capabilitiesso the Console hides pages for absent families (audit §4E).