Architecture Decision Records
This section documents significant architectural decisions made during the development of the TYPO3 LLM Extension.
Record lifecycle
An ADR is a record of a decision at a point in time. It is expected to become
historically wrong; what it must never do is look current when it is not. The
:Status: field is how a reader tells the difference.
Proposed- Not yet a decision. The record states the evidence and recommends, and every
recommendation in it is open. Nothing may cite it as settled, and it writes
no
:Amends:field — a proposal that amended something would have decided it. It becomesAcceptedin the change that accepts it, which also writes the amendments it recommended. Accepted- Current. The decision and the facts it reasons from still hold.
Acceptedwith an:Amended:field- Still current as a whole, but a later ADR overturned, widened or expired
part of it. The status line names which part in parentheses; the
:Amended:field names the date and the amending record. Supersededwith a:Superseded:field- No longer current. The field names the date and the replacing record.
Deprecated- What it decided is being removed, with no successor decision.
The link is written from both ends. The newer record declares :Amends: or
:Supersedes:; the older one declares :Amended: or :Superseded: with
the date. Tests/ fails when only one end of an
ADR-to-ADR link is written.
Not every successor is an ADR. ADR-012 was superseded by the nr-vault integration, which no record decided, so its field names that in prose and has no counterpart. A field whose body references no ADR is outside the pairing check by construction — which is also why prose there must stay specific enough to follow.
Two rules follow from the pairing:
An amended record keeps its reasoning. ADR-122 declined to build a side-effecting tool contract because no tool wrote. That premise expired with ADR-135, but the reasoning — do not design a contract ahead of its first consumer — is why the writer shipped without a framework. The record stays; the status says the premise is gone.
Amending is the amender's job. An ADR that overturns part of an earlier one
edits that earlier record's :Status: and :Amended: in the same change.
An accepted ADR with an expired premise and a clean Accepted status is a
defect, not history.
How a record cites code
Records do not cite line numbers. A line number is invalidated by any edit
above it in the cited file, and nothing announces that. ADR-171 cited
Resume for an authorisation check a later commit pushed
to :205; it cited Agent for "the list viewport", which
by then was that file's closing brace for an unrelated method. Both still read
as verified citations, and the second is the dangerous shape: precision is what
makes a reviewer trust a citation.
Three forms replace it, in order of preference:
- A symbol —
Resume, or a class docblock named as such. Survives every edit that does not rename it.Coordinator:: approve () - A
:ref:into the record being quoted, with the quote in the prose.Tests/already fails on a target that does not resolve. Quoting an ADR by line rotted three times here, all three by the same ten lines:Unit/ Adr Lifecycle Test. php 9b902993corrected the module inventory ADR-119 opens with, and every citation into that record from two others moved with it. - A
path#anchor— the file plus a unique string it must still contain, for configuration arrays, Fluid templates and XLIFF, which have no symbols.Tests/asserts the string is still there.Unit/ Adr Code Citation Test. php
A countable claim carries the command that counts it, next to the number: ADR-169 prints its grep beside "40". A bare count is a positional claim with no way to fail — ADR-171 said "nine enforcement points" and the grep now returns ten.
Symbol legend
Each consequence in the ADRs is marked with severity symbols to indicate impact weight:
| Symbol | Meaning | Weight |
|---|---|---|
| ●● | Strong Positive | +2 to +3 |
| ● | Medium Positive | +1 to +2 |
| ◐ | Light Positive | +0.5 to +1 |
| ✕ | Medium Negative | -1 to -2 |
| ✕✕ | Strong Negative | -2 to -3 |
| ◑ | Light Negative | -0.5 to -1 |
Net Score indicates the overall impact of the decision (sum of weights).
Decision records
Foundation
ADR-001: Provider abstraction layer
Unified interface for OpenAI, Claude, Gemini, Ollama, and more.
ADR-002: Feature services architecture
Translation, vision, embeddings, completion as injectable services.
ADR-003: Typed response objects
Immutable value objects for all LLM responses.
ADR-007: Multi-provider strategy
Fallback chains and provider selection logic.
ADR-013: Three-level configuration
Provider -> Model -> Configuration hierarchy.
TYPO3 integration
ADR-004: PSR-14 event system
Extension points via TYPO3 events.
ADR-005: Caching framework
Instance-default backend, nrllm cache group.
ADR-012: API key encryption
Superseded — now via nr-vault envelope encryption.
API design
ADR-006: Option objects vs arrays
Typed option objects for API calls.
ADR-008: Error handling strategy
Exception hierarchy and retry logic.
ADR-009: Streaming implementation
Chunked transfer for real-time output.
ADR-010: Tool/function calling
Provider-agnostic tool call abstraction.
ADR-011: Object-only options API
Removed array support, typed objects only.
Modern architecture (v0.4+)
ADR-014: AI-powered wizard system
Natural language -> structured configuration generation with fallback defaults.
ADR-015: Type-safe domain models
PHP 8.1+ enums, DTOs, and value objects.
ADR-016: Thinking block extraction
Reasoning blocks from Claude, DeepSeek, Qwen.
ADR-017: SafeCastTrait
PHPStan level 10 compliance for mixed input.
ADR-018: Model discovery
Multi-provider model listing with fallback catalogs.
ADR-019: Internationalization
XLIFF + locale-aware features with {lang} placeholders.
ADR-020: Output format rendering
Client-side plain/markdown/HTML toggle.
ADR-037: Backend AJAX admin guard
Shared trait requires a backend admin on every backend AJAX endpoint (403 otherwise).
Skills
ADR-035: Skill ingest
GitHub-hosted SKILL. sources: host
allowlist, SHA-pin + checksum, disabled-by-default
review.
ADR-036: Skill injection
Attach skills to tasks/configurations; compose into the user prompt (text-gen only), budgeted and checksum-verified.
Tools
ADR-038: Tool runtime
DI-tagged tool registry + bounded agent loop on the config's vault key/model/pricing; allow-list gated, admin-only.
ADR-039: Global tool availability
Site-wide per-tool enable/disable override
(tx_, no TCA) intersected with every
run's allow-list — a hard admin kill switch.