Context
Depends on #471 (control-plane RFC), the Directive vocabulary issue, and the control_directive event-category issue.
PRs #279 and #280 wire mcp_manager through ExecuteSeedHandler via explicit constructor injection. Each additional handler that needs MCP capability — evolve_step, unstuck, ralph, and any future workflow — must repeat the same injection.
Evidence from #280:
src/ouroboros/cli/commands/mcp.py: bridge is constructed and passed into create_ouroboros_server(..., mcp_bridge=mcp_bridge)
src/ouroboros/mcp/tools/definitions.py::execute_seed_handler added mcp_manager and mcp_tool_prefix parameters
docs/guides/mcp-bridge.md records as a Known Limitation that evolve_step does not yet receive the manager
Problem
Per-handler injection is O(handlers). It has three failure modes:
- A new handler silently omits the injection → runtime failure only.
- Future plumbing (event store, LLM backend, directive emitter) compounds the parameter list.
- Dynamic capability addition (also a stated limitation: "No dynamic server addition after initial connection") has no seam to plug into — each handler holds a static reference.
Proposal
Introduce a single PolicyBus object (name open — ControlBus / DirectiveBus are alternatives) that owns the dependencies currently passed à la carte:
@dataclass
class PolicyBus:
event_store: EventStore
mcp_manager: MCPClientManager | None # from MCPBridge, optional
directive_emitter: DirectiveEmitter # emits ControlDirectiveEmitted
llm_backend: str | None
runtime_backend: str | None
# ... single source of shared dependencies
Construction: once per server lifecycle, at _run_mcp_server time. Handlers receive PolicyBus by reference.
Handler contract change
Before:
ExecuteSeedHandler(
event_store=event_store,
mcp_manager=mcp_manager,
mcp_tool_prefix=prefix,
runtime_backend=rb,
llm_backend=lb,
)
After:
ExecuteSeedHandler(bus=bus)
Dynamic capability support
Because PolicyBus holds a live reference to the manager, future dynamic add_server calls on the manager propagate to all handlers without re-injection — unlocking the second half of #280's Known Limitations.
Migration strategy
- Introduce
PolicyBus as an additive type; existing constructors unchanged.
- Add a
from_bus classmethod to each handler: ExecuteSeedHandler.from_bus(bus) constructs with the legacy arguments derived from the bus.
- Migrate call sites one at a time. Deprecate the legacy constructors.
- Remove legacy constructors in a subsequent release.
Acceptance Criteria
Out of Scope
- Migrating
evolve_step, unstuck, ralph — that is the next sibling issue
- Adding new directives to the vocabulary — that is the Directive vocabulary issue
- Reshaping the Double-Diamond execution pipeline
Risks
- Large blast radius: handler signatures change broadly. Mitigation: classmethod bridge (
from_bus) plus staged migration.
- Hidden coupling: a "god object" bus can accumulate unrelated fields. Mitigation: keep the struct flat and reviewed on every addition; document membership criteria.
Context
Depends on #471 (control-plane RFC), the Directive vocabulary issue, and the
control_directiveevent-category issue.PRs #279 and #280 wire
mcp_managerthroughExecuteSeedHandlervia explicit constructor injection. Each additional handler that needs MCP capability —evolve_step,unstuck,ralph, and any future workflow — must repeat the same injection.Evidence from #280:
src/ouroboros/cli/commands/mcp.py: bridge is constructed and passed intocreate_ouroboros_server(..., mcp_bridge=mcp_bridge)src/ouroboros/mcp/tools/definitions.py::execute_seed_handleraddedmcp_managerandmcp_tool_prefixparametersdocs/guides/mcp-bridge.mdrecords as a Known Limitation thatevolve_stepdoes not yet receive the managerProblem
Per-handler injection is O(handlers). It has three failure modes:
Proposal
Introduce a single
PolicyBusobject (name open —ControlBus/DirectiveBusare alternatives) that owns the dependencies currently passed à la carte:Construction: once per server lifecycle, at
_run_mcp_servertime. Handlers receivePolicyBusby reference.Handler contract change
Before:
After:
Dynamic capability support
Because
PolicyBusholds a live reference to the manager, future dynamicadd_servercalls on the manager propagate to all handlers without re-injection — unlocking the second half of #280's Known Limitations.Migration strategy
PolicyBusas an additive type; existing constructors unchanged.from_busclassmethod to each handler:ExecuteSeedHandler.from_bus(bus)constructs with the legacy arguments derived from the bus.Acceptance Criteria
PolicyBuslives undersrc/ouroboros/orchestrator/(orcore/— final location decided in review)_run_mcp_serverconstructs a single bus and passes it tocreate_ouroboros_serverExecuteSeedHandleraccepts the bus and its unit tests pass unchangedOut of Scope
evolve_step,unstuck,ralph— that is the next sibling issueRisks
from_bus) plus staged migration.