---
title: "ADR-261: A configuration may exclude single tools"
manual: "LLM Foundation"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-261@main"
source: "Adr/Adr261AConfigurationMayExcludeSingleTools.rst"
rendered: "2026-11-05T19:45:44+00:00"
---

# ADR-261: A configuration may exclude single tools {#adr-261}

-   *Status:* Accepted
-   *Date:* 2026-11-05
-   *Authors:* Netresearch DTT GmbH

## Context {#context}

An LLM configuration narrows the tools of its runs through
**Allowed tool groups** ([ADR-043](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-043@main)). The group is the
smallest unit it can name. To keep one tool from a configuration, an
administrator has to drop its whole group: a configuration that may use every
writing tool except `delete_record` loses all twenty-two tools of `editing`.
The global switch in the Tools module is no answer either, because it takes the
tool away from every configuration.

The tool gate ([ADR-094](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-094@main)) is the one place that decides whether
a tool is offered and whether a call may run. A run resolves its skill
allow-list once when it starts, stores it, and on resume intersects it with the
list resolved then ([ADR-038](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-038@main) item 5), so a change while the run
waits can take a tool away but never add one. The group gate is read live on
every decision instead.

## Decision {#decision}

1.  **A field of single tools.** `tx_nrllm_configuration` gains
    `excluded_tools`, a comma list of tool names, edited as
    **Excluded tools** with a choice of the registered tools grouped by
    tool group. A stored name no tool carries any more stays listed under
    **No longer registered**, so it can be removed. Empty excludes
    nothing; every existing row keeps its tools.
1.  **It only narrows.** `ToolCallPolicy` refuses an excluded tool with the
    new reason `configurationExcluded`
    (`ToolDenialReason::CONFIGURATION_EXCLUDED`). The check runs after the
    global state, the admin requirement, the skill allow-list and the group
    gate, so the reason is reported only when those let the tool through, and
    before the trust zone, so observe mode cannot offer an excluded tool. The
    field grants nothing: it is a deny list, and a name in it never reaches a
    path that adds a tool.
1.  **Offer and execution.** The same decision filters the tools offered to the
    model and is asked again when a call is executed, and by the approval and
    input submit gates of `ResumeCoordinator`. A call the model sends to an
    excluded tool is refused like any call to a tool it was not offered.
1.  **Stored with the run.** The exclusions at run start ride the run's
    `SkillToolAllowList` as `excludedToolNames`, so the queue, a
    suspended run and an approval carry them without a field of their own. The
    gate refuses the union of those and the configuration's live exclusions. An
    exclusion added while a run waits applies at once; one removed meanwhile
    still holds for that run. An intersection keeps the exclusions of both
    sides. The stored form writes the key only when there are exclusions, so
    the stored form of every other run is unchanged, and a run stored before
    reads back without exclusions and is held to the live list. A stored value
    that is not a list cannot say what it excluded and admits no tool, the skill
    load tool included.
1.  **A tool registered later is not excluded.** The field names tools, not a
    pattern or a group. A tool an extension or an MCP server adds later is
    offered until someone selects it.
1.  **No exemption for the skill load tool.** [ADR-214](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-214@main) keeps
    `load_skill` outside the skill allow-list and the group gate, because a
    skill declaring `allowed-tools: []` should not switch loading off. An
    exclusion names that very tool, so it applies to `load_skill` too.
1.  **Visible where tools are managed.** The unavailable-tools list of
    [ADR-201](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-201@main) reports an excluded tool with its own reason. The
    Tools module names, under each tool, the active configurations that exclude
    it, and the governance tab of the overview module lists the excluded tools
    per active configuration. Both read the field through
    `ToolExclusionReadout`, which decides nothing; the simulation on the
    same tab asks the gate itself.

## Consequences {#consequences}

-   An administrator can withhold one tool from one configuration without
    touching the others or the tool's group.
-   A run resumed after its configuration dropped an exclusion stays without the
    tool. Starting a new run gives it back.
-   The dashboard widget of tool denials shows a bar of its own for the new
    reason.
-   Configuration presets do not carry the field. A preset import leaves it as it
    is.

## Alternatives considered {#alternatives-considered}

-   **An allow-list of single tools.** A configuration that names its tools would
    have to be edited whenever a tool is added, and a tool registered later would
    be refused by default. The ticket asks for the opposite: narrow by name,
    leave new tools alone.
-   **Reading the exclusions live only, like the group gate.** Simpler, but a run
    waiting for an approval would get a tool back when its exclusion is removed,
    which the run never had at its start.
-   **A field of its own on every stored run.** It would repeat the storage the
    skill allow-list already has, in the codec, the suspended state and the
    approval path, for the same "may only narrow on resume" rule.
