Status: Accepted; collected-turn seam implemented, broader consolidation in progress Date: 2026-08-12
ARES has two independent ways to execute a worker turn, and they have drifted into separate implementations of the same thing.
Registry B — framework adapters (fastapi_app/adapters/frameworks.py)
carries all real user chat:
ares-context.tsx::sendMessage → POST /api/chat/start → realtime.py
→ RealtimeService.start_chat → adapters.for_session
→ JaegerAdapter → JaegerBackend.run_turn
Registry A — BackendRegistry / DispatchService
(integrations/workers/cli_backends.py, api/dispatch_service.py) carries
SI plan execution: api/schedule_scheduler.py calls
get_dispatch_service().dispatch_turn(...) to resume paused multi-step
plans. That is a real feature, not dead code — so this is not simply a
matter of deleting one side.
The cost of the split is concrete and was paid during this session:
jaeger_local (JaegerBackend via Registry B,
JaegerAIBackend/JaegerWorker via Registry A). Whichever module
imported last silently won.BackendRegistry while
CliFrameworkAdapter instantiates the same classes from its own hardcoded
map, independent of that registry.POST /api/dispatch/chat/start has no frontend caller, and its docstring
advertised an ARES_CHAT_VIA_DISPATCH=1 “transparent mode” that was never
implemented (the variable is read nowhere). Its handler also accepts
connection_id, workspace, profile and personality and ignores all
four.So the SI pipeline — planner, orchestrator, evaluator, trust engine — is written, tested, and not in the live chat path at all.
The framework adapter layer (fastapi_app/adapters/) is canonical.
It is the single implementation of “run a turn on worker X.”
DispatchService keeps its distinct job — planning, evaluation, trust
gating, plan resumption — but stops carrying its own worker-execution
implementation. Where it needs a turn executed, it invokes the adapter
layer rather than BackendRegistry.
Rationale, in order of weight:
test_fastapi_chat_service_and_router_have_no_framework_imports), which
constrains what may import what.Good:
Costs:
DispatchService currently calls worker.run_turn(...) on
AgenticBackend instances; adapters expose a different, richer interface
(streaming, sessions, profiles). The seam needs designing — this is the
actual work, and it is why this ADR authorises no code yet.BackendRegistry cannot simply be deleted: AgenticBackend subclasses are
what adapters wrap. What is retired is the second execution path, not
the backend classes.Do not start with the migration. In order:
/api/dispatch/chat/start was subsequently retired:
it had no supported caller and wrote replay data to the wrong journal. It
is not the seam for step 3.DispatchService calls it in production and focused tests prove
that adapter failure does not fall back to the legacy registry.schedule_scheduler.py plan resumption reaches that seam through
DispatchService. Remove remaining compatibility injection once downstream
tests no longer construct the legacy registry directly.The SI pipeline’s requirements turn out to need an execution interface the
adapter layer genuinely cannot express. Convenience of the existing
run_turn signature is not such a reason — that is what the seam in step 2
is for.