Skip to content

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 tool module, ^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.

The Tool settings tab, with binding writes turned off

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 update access on the entity
  • apply also requires edit access 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:

A suggestion recorded by the tool, waiting for an editor on the node 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.