The Tool plugin¶
ai_disclosure_tool exposes the recorder as one plugin for the contributed
Tool API module, so an agent that writes
content can record the disclosure for what it made through the path any other
module uses. It is a separate submodule because it depends on a contributed
module: the main module depends on core only.
Requirements, and where this is not available¶
This submodule stands on a higher floor than the module it ships with, and the
floor comes from drupal/tool:
- the
toolmodule,^1.0@beta, which is 1.0.0-beta7 at the time of writing - PHP 8.2
- Drupal 10.5
AI Disclosure itself runs on PHP 8.1 and Drupal 10.4, and keeps doing so. On a
Drupal 10 site running PHP 8.1 this submodule is therefore not available at
all: composer refuses drupal/tool, and the submodule's own .info.yml
declares the same floor, so Drupal will not enable it either. Everything else
in the module works there as before. Recording a disclosure from code on such a
site means calling
the recorder directly, which is what this plugin
does on your behalf.
The module's own testing says the same thing. The previous major is tested on PHP 8.1, the floor the module declares, and there this submodule's tests skip themselves and its code stays out of static analysis. The plugin is exercised on the current major, where its dependency can be installed. Working on the module from PHP 8.1 means installing without that development dependency, the way that job does.
The tool¶
One plugin, ai_disclosure:record, with a mode input.
| Input | Required | What it takes |
|---|---|---|
mode |
no | suggest (the default) or apply. |
entity_type_id |
yes | The entity type ID, for example node. |
entity_id |
yes | The entity ID, or the entity UUID. |
grade |
no | A grade machine name. Either this or profile. |
profile |
no | A profile machine name. Either this or grade. |
note |
no | A short note. Only together with a grade. |
It returns outcome, one of suggested, applied or unchanged, and
effective_grade, the grade the entity resolves to after the call.
suggest is the default because it changes nothing: it leaves a proposal an
editor accepts or dismisses on the entity form. apply writes, and a site has
to turn it on first. Until it does, the tool's access check refuses the call,
which is what a runner such as drush tool:run reports, and a caller that
executes without checking access gets a failure naming the setting instead. A
note sent with a profile is refused rather than dropped in silence, because
applyProfile() has nowhere to put it.
Turning binding writes on¶
A single checkbox at Configuration > Content authoring > AI disclosure > Tool, off when the submodule is installed. Turning it on is not enough on its own: the caller still needs permission to edit that content and its disclosure field.

What it inherits, and what it does not¶
The plugin adds no rule of its own, and the recorder's hold unchanged. A write
never lowers the severity already on the content, and such a call comes back
unchanged, which is a success rather than an error. No write claims human
review. An unknown grade or profile is refused.
A call the tool cannot resolve is also written to the log, in the same channel as the recorder's own warnings: the entity that is not there, the entity type that carries no fields, the grade or the profile that does not exist. The caller reads the reason in the failure it gets back, and the site keeps a trace that some automation is pointing at something that is gone.
Permissions are the exception, and they are not inherited, because the recorder checks none. It is a service, and services leave access to their caller. The plugin is that caller:
- every mode requires
updateaccess on the entity applyalso requireseditaccess on the disclosure field, which is what the entity form requires from an editor, plus the setting above
The field's access is checked rather than the permission behind it, so a rule
another module adds to that field applies here too. Tool API's runners call
access() before execute(), and the setting is enforced inside the tool as
well, so a caller that skips that check still cannot write.
Known bug: a refused call in the Tool Explorer also shows an error that looks like a crash
When it happens. The optional tool_explorer submodule is installed,
someone runs this tool from its execute form, and the call is refused,
either because binding writes are off or because the account lacks access.
What you see. The denial, and under it a second message: "Error executing tool: The action result cannot be retrieved until the action ::execute() method has been called".
What it means. Nothing was written and the refusal is correct. The plugin never runs in that path. The explorer asks the tool for a result it never produced, and prints the internal exception. Tracked upstream in the Tool API queue as Tool Explorer execute form: masked exception on access denial, duplicate messages, no validation pass, opened on 31 July 2026 and still open on 10 September 2026.
What a person sees¶
Nothing beyond the checkbox: the plugin is run by agents, and its output is
data. What reaches an editor is the result of a suggest call, on the
content's own form:

The pane still reads "Inherit default", because a suggestion changes nothing on its own. The source names this submodule, so an agent's proposal is told apart from any other module's, and the note is shown to the editor as text.
An apply call leaves no callout of its own: it writes the field and records
its row as already settled, so there is nothing left for an editor to decide.
A suggestion another module left pending is untouched and still shows.