---
title: "Architecture Decision Records"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:architecture-decision-records@0.35"
source: "Adr/Index.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# 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 becomes `Accepted` in the change that accepts it, which also writes
    the amendments it recommended.

-   **`Accepted`**

    Current. The decision and the facts it reasons from still hold.

-   **`Accepted` with 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.

-   **`Superseded` with 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/Unit/AdrLifecycleTest.php` fails when only one end of an
**ADR-to-ADR** link is written.

Not every successor is an ADR. [ADR-012](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-012@0.35) 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](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-122@0.35) declined to
build a side-effecting tool contract because no tool wrote. That premise expired
with [ADR-135](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-135@0.35), 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
`ResumeCoordinator.php:204` for an authorisation check a later commit pushed
to :205; it cited `AgentRunController.php:267` 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** — `ResumeCoordinator::approve()`, or a class docblock named
    as such. Survives every edit that does not rename it.
-   **A** `:ref:` **into the record being quoted**, with the quote in the prose.
    `Tests/Unit/AdrLifecycleTest.php` 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: `9b902993` corrected 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/Unit/AdrCodeCitationTest.php` asserts the string is still there.

A countable claim carries the command that counts it, next to the number:
[ADR-169](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-169-q6@0.35) 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.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-001@0.35)

**ADR-002: Feature services architecture**

Translation, vision, embeddings, completion as
injectable services.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-002@0.35)

**ADR-003: Typed response objects**

Immutable value objects for all LLM responses.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-003@0.35)

**ADR-007: Multi-provider strategy**

Fallback chains and provider selection logic.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-007@0.35)

**ADR-013: Three-level configuration**

Provider -> Model -> Configuration hierarchy.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-013@0.35)

### TYPO3 integration

**ADR-004: PSR-14 event system**

Extension points via TYPO3 events.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-004@0.35)

**ADR-005: Caching framework**

Instance-default backend, `nrllm` cache group.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-005@0.35)

**ADR-012: API key encryption**

Superseded — now via nr-vault envelope encryption.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-012@0.35)

### API design

**ADR-006: Option objects vs arrays**

Typed option objects for API calls.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-006@0.35)

**ADR-008: Error handling strategy**

Exception hierarchy and retry logic.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-008@0.35)

**ADR-009: Streaming implementation**

Chunked transfer for real-time output.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-009@0.35)

**ADR-010: Tool/function calling**

Provider-agnostic tool call abstraction.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-010@0.35)

**ADR-011: Object-only options API**

Removed array support, typed objects only.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-011@0.35)

### Modern architecture (v0.4+)

**ADR-014: AI-powered wizard system**

Natural language -> structured configuration
generation with fallback defaults.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-014@0.35)

**ADR-015: Type-safe domain models**

PHP 8.1+ enums, DTOs, and value objects.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-015@0.35)

**ADR-016: Thinking block extraction**

Reasoning blocks from Claude, DeepSeek, Qwen.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-016@0.35)

**ADR-017: SafeCastTrait**

PHPStan level 10 compliance for mixed input.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-017@0.35)

**ADR-018: Model discovery**

Multi-provider model listing with fallback
catalogs.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-018@0.35)

**ADR-019: Internationalization**

XLIFF + locale-aware features with {lang}
placeholders.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-019@0.35)

**ADR-020: Output format rendering**

Client-side plain/markdown/HTML toggle.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-020@0.35)

**ADR-037: Backend AJAX admin guard**

Shared trait requires a backend admin on every
backend AJAX endpoint (403 otherwise).

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-037@0.35)

### Skills

**ADR-035: Skill ingest**

GitHub-hosted `SKILL.md` sources: host
allowlist, SHA-pin + checksum, disabled-by-default
review.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-035@0.35)

**ADR-036: Skill injection**

Attach skills to tasks/configurations; compose into
the user prompt (text-gen only), budgeted and
checksum-verified.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-036@0.35)

### 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.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-038@0.35)

**ADR-039: Global tool availability**

Site-wide per-tool enable/disable override
(`tx_nrllm_tool_state`, no TCA) intersected with every
run's allow-list — a hard admin kill switch.

[Read](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-039@0.35)
