ADR-090: One extension until 1.0, with documented split seams
- Status
-
Accepted
- Date
-
2026-07-19
- Authors
-
Netresearch DTT GmbH
Context
nr_llm has grown well beyond a provider abstraction. It now bundles several
subsystems that are, in principle, independently useful:
- the core three-tier abstraction (Provider → Model → Configuration), the middleware pipeline (fallback, budget, usage, cache) and the completion / embedding / vision feature services;
- specialized services — DeepL / LLM translation, DALL·E / FAL image
generation, Whisper / TTS speech (
Classes/Specialized/); - the tool / agent system — the builtin tool set, RAG site-search retrieval,
the tool loop, human-in-the-loop approval and agent-run persistence
(
Classes/Service/Tool/, ADR-042 ff., ADR-084); - the guardrail / redaction safety pipeline (
Classes/Service/Guardrail/, ADR-085 ff.); - the backend UI — Tool Playground, analytics dashboard, setup wizard and
skills management (
Classes/Controller/Backend/).
Different consumers want different subsets: an agency embedding the provider
core in its own product does not need the backend dashboards; a site that only
translates does not need the agent stack or its attack surface. That makes a
split into focused extensions (nr_llm core plus optional feature packages)
an attractive long-term shape.
It is, however, the wrong move now. The extension is under rapid pre-1.0 development: the internal contracts between these subsystems still change often. Splitting today would freeze those contracts into public, cross-extension APIs prematurely, and impose coordinated multi-repo releases and an extension-version compatibility matrix — friction that would slow exactly the iteration that is still happening.
Decision
Ship ``nr_llm`` as a single extension until the 1.0 release, and revisit the split with or before 1.0 — once the internal seams have stabilized.
Until then, keep the architecture split-ready rather than split: preserve
clean module boundaries so a future extraction is a packaging change, not a
re-architecture. The phpat architecture tests (Tests/Architecture/) already
enforce the vertical layering — Controller → Service → Provider / Domain
(e.g. controllers use the tool registry rather than concrete adapters, services
do not depend on controllers or concrete provider adapters, domain models stay
free of repositories/HTTP). Since Tests/Architecture/ModuleSeamTest.php they
also police the horizontal seams between the feature modules. The
enforced rules are directional, not symmetric: specialized and tool/agent may
not depend on each other in either direction, guardrail may depend on neither,
and nothing outside the backend package may depend on Controller or
Widgets. Calls in the invoking direction stay allowed — specialized and
tool code reaching a guardrail is how the safety pipeline runs at all, and the
backend depends on everything by design. Cross-module coupling that would block
an extraction now fails CI rather than only review.
Anticipated split seams (candidate extensions):
| Package | Depends on | Scope |
|---|---|---|
nr_llm (core) | — | Provider → Model → Configuration, middleware pipeline, the completion /
embedding / vision feature services, LlmServiceManager. The
mandatory base of every other package. |
nr_llm_specialized | core | Translation (DeepL / LLM), image (DALL·E / FAL), speech (Whisper / TTS), and their per-modality provider adapters. |
nr_llm_tools | core | The builtin tool set, RAG site-search retrieval, the tool loop, human-in-the-loop approval and agent-run persistence. |
nr_llm_guardrail | core | The guardrail / secret-redaction safety pipeline. May instead remain in core, since "secure by default" is a core promise. |
nr_llm_backend | core (+ installed feature packages) | Tool Playground, analytics dashboard, setup wizard, skills management — the backend modules and their widgets. |
A subsystem is a candidate for extraction only once all of the following hold:
- its contract with core has been stable across several releases (few or no breaking changes);
- a concrete consumer benefits from installing it separately (smaller footprint or reduced attack surface); and
- the 1.0 public-API freeze is planned or in progress.
Consequences
- Now: one repository, one release pipeline, one version — fast iteration. The cost is a larger install footprint for consumers who want only a subset, and a broader default attack surface (mitigated by the tool availability gating and guardrail defaults).
- Split-ready discipline: module boundaries must stay clean. The phpat
architecture tests guard both the vertical layering and, since
ModuleSeamTest, the horizontal seams — core reaching into the backend UI, or one feature module reaching into another, fails CI. A deliberate new dependency across a seam therefore has to change this ADR and the rule together, which is the point. - At 1.0: re-evaluate against the criteria above. If the seams have held,
the split is largely a
composer.json/ext_emconf.phprepackaging plus moving files along the documented boundaries; if a consumer need is real before 1.0, a single package (most likelynr_llm_specializedornr_llm_tools) can be extracted early without committing to the full split.
Alternatives considered
- Split now. Rejected: premature. It would freeze still-churning internal contracts as public APIs and add coordinated-release friction during the phase where iteration speed matters most.
- Never split; stay monolithic. Rejected: the subsystems are genuinely separable and real consumers want subsets. Committing to a permanent monolith would, over time, invite the cross-module coupling this ADR exists to prevent.