ADR-019: Orchestrator and Service-Runtime Target Decomposition¶
Metadata¶
- Status: Proposed
- Date: 2026-07-29
- Decision Makers: Vijaykumar Singh
- Related ADRs: 001 (superseded original orchestration decision — this ADR records the target
the service-first runtime is converging toward) - Work tracked by: TD-14 (orchestrator regrowth),
TD-15 (services sprawl) - Benchmark: competitive-benchmark-2026-07.md §1
Context¶
TD-14/TD-15 track that the runtime is too large; neither records the target module boundaries the
decomposition should reach. Per the ADR README ("ADRs record decisions; the register records
work"), that target is a decision and belongs here. The register items stay the work tracker; this
ADR is the destination they aim at.
Observed reality (2026-07-29):
victor/agent/orchestrator.pyis 4,690 LOC / 191 methods. Its own docstring already declares
"Facade Pattern" and lists nine extracted components (ConversationController,ToolPipeline,
StreamingController,StreamingCoordinator,LifecycleManager,TaskAnalyzer,ToolSelector,
ToolRegistrar,ProviderManager) — yet it regrew ~34% after TD-R1 declared it resolved at 3,510
LOC. The facade delegates (delegation is real) but still holds behavior.victor/agent/services/holds ~55 files; four are 100k+ chars (planning_runtime.py,
runtime_intelligence.py,turn_execution_runtime.py,tool_service.py) — far beyond the
documented "six canonical services" (Chat, Tool, Session, Context, Provider, Recovery). The
architecture story and the file tree disagree.- The orchestrator reaches into service internals directly (
_chat_service,_tool_service,
_metrics_coordinator, …) rather than exclusively throughExecutionContext.services— the review
counted 40+ such direct accesses. This is facade leakage: it defeats the swap-a-service seam the
service layer exists to provide.
A ratchet guard already landed (tests/unit/runtime/test_hotspot_size_guard.py, 2026-07-02) so the
file cannot silently regrow a third time. What is missing is the target shape the ratchet lowers
toward.
Decision¶
Adopt three invariants as the decomposition target, and lower the ratchet caps toward them:
-
A facade holds no behavior.
AgentOrchestratoris a composition root + session boundary +
compatibility surface only. Any method with branching domain logic moves to a service or a named
component object; the orchestrator retains only wiring, high-level flow sequencing, and post-switch
hooks. Target: < 1,500 LOC, reached by scheduled ratchet steps (not one big-bang PR). -
Six canonical services are the only ownership layer; the runtime modules are reconciled into
it. Each ofplanning_runtime.py,runtime_intelligence.py,turn_execution_runtime.pyis
either (a) folded under its owning canonical service as a private collaborator, or (b) promoted to
a named seventh+ architectural element documented inarchitecture.md— but the tree must not
silently carry undocumented mega-modules. No module inservices/exceeds a declared size cap
(start at current max, ratchet down). -
Non-composition-root code reaches services via
ExecutionContext.services; nothing reaches
service private internals. (Reconciled 2026-07-30.) The original wording — "remove the
orchestrator's direct_servicereach-ins" — was wrong: the orchestrator is the composition
root, so it legitimately holds and delegates to its services, andtest_service_layer_validation.py
in fact requires thoseself._<x>_servicereferences. The real targets of the
"ExecutionContext.services-only" rule are therefore (a) non-orchestrator code reaching
services directly, and (b) any code (orchestrator included) reaching into a service's private
internals. An access-boundary guard is deferred until it is scoped to those two cases; it is not
part of increment 1.
This ADR does not introduce a new orchestration abstraction (explicitly a non-goal in the
evaluation-centric vision) — it finishes the service-first one ADR-001's update already names.
Rationale¶
- First principles. A facade's whole value is that callers depend on a thin, stable surface and
swap what's behind it. A 4,690-LOC facade with 40+ internal reach-ins is a god-object wearing a
facade's name; the abstraction is nominal. The fix is to make the invariant mechanically true
(size caps + access guard), not aspirational (a docstring). - Co-design. The target caps are set with the ratchet guard that enforces them and with the
TD-14/TD-15 work items that execute them — doc, test, and backlog move together. - Leverage. Every other ADR here (TUI, gateway, team durability) adds surface to the runtime; a
smaller, seam-clean core is the precondition that keeps those additions cheap.
Consequences¶
- Positive: testable-in-isolation services; a real swap seam; the ratchet has a destination, so
each decomposition PR can lower a concrete cap. - Negative: a sustained multi-PR effort touching hot code paths; risk of regression in the chat
loop — mitigated by the existing parity/characterization batteries (must stay green per each step). - Neutral: public API (
Agent.create/run/stream) is unchanged; this is internal.
Implementation¶
Incremental, ratchet-gated (no separate FEP — internal runtime, no public-API change):
- Add the access guard (extend
test_service_layer_validation.py); freeze new reach-ins. - Per canonical service, pull orchestrator methods into the owning service; lower the orchestrator
ratchet cap one step each PR. - Reconcile the three runtime mega-modules (fold or promote+document); add per-file size caps to the
hotspot guard. - When the orchestrator clears < 1,500 LOC and no undocumented mega-module remains, mark TD-14/TD-15
Resolved and flip this ADR to Accepted.
Alternatives Considered¶
- Leave as-is (ratchet only). Rejected: a cap with no target just prevents growth, not the
god-object itself. - Big-bang rewrite. Rejected: unacceptable regression risk on the live chat loop; the batteries
gate incremental change far more safely. - New orchestration abstraction / actor model. Rejected: explicit non-goal (vision doc,
"one canonical loop; no parallel abstractions").
References¶
- ADR-001 (superseded; its 2026-05-04 update names the service-first shape)
- architecture.md §Service Layer / §Agent Runtime
victor/agent/orchestrator.py,victor/agent/services/,tests/unit/runtime/test_hotspot_size_guard.py,
tests/unit/framework/test_service_layer_validation.py
Revision History¶
| Date | Version | Changes | Author |
|---|---|---|---|
| 2026-07-29 | 1.0 | Initial ADR — records the decomposition target for TD-14/TD-15 | Vijaykumar Singh |
| 2026-07-30 | 1.1 | Reconciled §3 (orchestrator is the composition root; access-boundary rule re-scoped, guard deferred). Increment 1 shipped: extracted the pure task-report metadata builders to victor/agent/task_report_metadata.py; orchestrator 4690→4600, ratchet lowered. |
Vijaykumar Singh |
| 2026-07-30 | 1.2 | Increment 2 shipped: extracted the tool-supply policy (Tool-Necessity Gate + context-window budgeter) to victor/agent/tool_supply_policy.py (impure deps injected as callables); orchestrator 4600→4402, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.3 | Increment 3 shipped: extracted task-report start/finish metadata assembly into victor/agent/task_report_metadata.py; orchestrator 4402→4400, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.4 | Increment 4 shipped: extracted the edge-model tool-necessity decision into victor/agent/tool_supply_policy.py; orchestrator 4400→4368, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.5 | Increment 5 shipped: extracted KV tool ordering and strategy-setting interpretation into ToolStrategyRuntime; orchestrator 4368→4340, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.6 | Increment 6 shipped: extracted provider-economics session-lock policy into ToolStrategyRuntime; orchestrator 4340→4326, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.7 | Increment 7 shipped: extracted strategy feature checks, context-window lookup, and ToolService utility delegation into ToolStrategyRuntime; orchestrator 4326→4320, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.8 | Increment 8 shipped: extracted context-aware strategy execution and telemetry assembly into ToolStrategyRuntime; orchestrator 4320→4303, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.9 | Increment 9 shipped: extracted tool-strategy metrics event emission into ToolStrategyRuntime; orchestrator 4303→4290, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.10 | Increment 10 shipped: extracted configured KV strategy execution and session-cache updates into ToolStrategyRuntime; orchestrator 4290→4266, ratchet lowered. |
Vijaykumar Singh |
| 2026-08-03 | 1.11 | Increment 11 shipped: deleted uncalled private KV session-lock and Gemini-provider compatibility helpers after repository-wide reference audit; orchestrator 4266→4225, ratchet lowered. | Vijaykumar Singh |