.. SPDX-License-Identifier: GPL-2.0-or-later .. SPDX-FileCopyrightText: Netresearch DTT GmbH .. include:: /Includes.rst.txt .. _adr-015: ================================================================== ADR-015: nr-llm Task per Backend Group via Extension Configuration ================================================================== **Status:** Accepted **Date:** 2026-09-23 Context ======= The chat ran every user on one nr-llm Task (``llmTaskUid``). Installations want different models or prompts for different audiences — editors on a cheaper model, administrators on one with a larger context window, a press team with its own instructions (NEXT-172). The Task is the unit nr-llm uses for exactly that: it names the Configuration (provider, model, system prompt) and adds a prompt template. Options considered: - **A TCA table** mapping ``be_groups`` to Tasks, edited as records. It gives relations with integrity and a list view, at the cost of a table, TCA, a place in the page tree (or root-level records) and permissions for who may edit it — for a handful of rows an administrator sets once. - **A field on ``be_groups``** holding the Task. Natural to edit, but it extends a core table from an extension that is a proof of concept, and the precedence between several groups of one user becomes hidden in record order. - **An extension configuration string** of ``groupUid:taskUid`` pairs, next to ``llmTaskUid`` and ``allowedGroups`` (ADR-009), which are configured the same way. Decision ======== Use an extension configuration setting ``groupTaskMapping``: comma-separated ``groupUid:taskUid`` pairs. The first pair, in the order of the setting, whose group the user belongs to decides; users in none of them get ``llmTaskUid``. A malformed pair is skipped rather than invalidating the whole setting. ``ExtensionConfiguration::getLlmTaskUid()`` answers for the current backend user. Every caller — the status endpoint, the toolbar item, the turn in the worker — already runs as the owner of the conversation (the worker initialises it), so the one method gives the same answer everywhere, and no caller had to change. Membership is the *effective* one, ``userGroupsUID``, which includes subgroups. ``allowedGroups`` (ADR-009) compares the directly assigned ``usergroup`` only; the mapping deliberately does not copy that, because a Task per group is a group *setting*, and group settings in TYPO3 are inherited by subgroups. ADR-009 is not changed here. Consequences ============ - One field in Admin Tools > Extension Configuration; no table, no records, no permissions to manage. - Precedence is explicit: the order of the pairs. - The Task decides the model and the prompts, not the permissions. What the assistant may do stays with nr-llm's per-user tool policy and the Configuration's own group restriction, so a mapping cannot widen a user's rights. The agent runtime does not check the Configuration itself, so ``ChatService::resolveConfiguration()`` does, before every turn: the Configuration must be active, and if it is restricted to backend groups, ``ConfigurationResolver::actorMayUse()`` must accept the user (admins always pass). Otherwise the turn fails with a message naming the Configuration and the reason. The same check applies to ``llmTaskUid``. - Referential integrity is not checked: a deleted Task or group leaves a pair that matches nothing or fails the turn with "Task not found". With a TCA table this would be a dangling relation just the same. - If the mapping grows beyond a few pairs, or needs per-site variants, a TCA table is the next step; the resolution stays in ``getLlmTaskUid()``.