Status: Accepted Date: 2026-08-12 Extends: ADR-0001 (workers are out-of-process)
“Note on transport” is corrected by ADR-0007. It cited ADR-0005 as settled precedent; ADR-0005 was inaccurate when written. It also applied a browser↔ARES streaming decision to the ARES↔Jaeger boundary — different boundaries. The decision below is unaffected.
The “baseline daemon interface” claim is corrected by ADR-0008. JaegerAI ships no HTTP gateway; that path is vestigial, and ARES↔Jaeger is stdio-bridge-only in practice. The decision below is unaffected.
Three AI agents independently proposed three conflicting architectures for how ARES should reach JaegerAI — protocol-plus-WebSocket, in-process library import of Jaeger’s agent core, and a library/service split — and produced competing in-flight work.
All three re-derived a question ADR-0001 had already answered. That ADR was deleted during the docs reorganization, taking the reasoning with it; without the record, “just import it, it’s faster” looked like a fresh insight rather than a reversal. This ADR exists as much to restore the boundary as to extend it. (The directory has been restored; deleting an accepted ADR is how a deliberate decision gets silently reversed.)
What is genuinely new since ADR-0001: JaegerAI now ships its own Swift client
(jaeger_ai/interfaces/swift/Sources/JaegerOS/Bridge/BridgeProcess.swift),
which spawns its own bridge process and speaks the same NDJSON frames ARES
does. Two independent clients of one agent is a case ADR-0001 did not consider.
ARES accesses JaegerAI exclusively through its versioned client contract
(JrosClient) and does not import its agent core. Jaeger may expose several
transport bindings over that one contract; the existing HTTP/SSE gateway
(ARES_JAEGER_GATEWAY_URL, bearer auth, GET /v1/health, POST /v1/reset) is
the baseline, with stdio retained as the local fallback.
A protocol boundary, a daemon, and a specific transport are three separate decisions. Only the first is settled here.
Good:
JrosClient facade; callers
(companion_control.py, JaegerBackend.run_turn) do not change.Costs:
reset_jros_boot() records that Jaeger’s client
model is fixed at construction. Externalising inference does not by itself
make switching hot; that is a client-construction constraint, not only a
model-loading one.Layering Jaeger’s agent loop away from PySide6, from llama-cpp loading, and from any specific transport is textbook ports-and-adapters and is endorsed as JaegerAI-internal work. It is what makes multiple transport bindings and a swappable model backend clean. Only its conclusion — that ARES should then import that core — is rejected. Good internal layering and external protocol access are complementary.
Each needs its own ADR; none is authorised by this one.
services/controller/mcp_server.py); Jaeger’s side is still a placeholder
(integrations/providers/jaeger/backend.py:217).ADR-0005 already chose SSE over WebSocket and set the bar for revisiting:
streaming becoming “genuinely bidirectional and latency-sensitive.” Mid-turn
approval prompts (request frames) are the strongest candidate, but that case
has not been made with measurements. Do not add a WebSocket transport on the
grounds that Jaeger’s protocol docs mention one. ADR-0005 also warns that
realtime.py’s existing WebSocket route “should not be treated as a second
parallel implementation without a product decision” — that still holds.
Jaeger stops shipping non-Python clients and the multi-worker requirement in ADR-0001 disappears — both, not either. Startup latency alone is not sufficient grounds; it is an argument for a warm daemon, which this ADR leaves open, not for dissolving the boundary.