ADR-019: A conversation starts about a page, with a skill
Status: Accepted
Date: 2026-10-08
Context
A guided process — optimise the SEO of one page, improve its content — is a
skill, not an extension (nr-llm ADR-214). The chat has to start such a process
for one page in one language, and an editor has to be able to pick a process
in a running conversation. The conversation stored only where the user was in
the backend (view_context: page and module), createConversation()
took no input, and the module URL read only &conversation=<uid>. nr-llm
has no API yet to invoke a skill by identifier per run; ADR-214 describes one
("explicit invocation") that is being designed.
Decision
A conversation can start with a page, its language and a skill.
POST /ai-chat/conversations/create takes pageUid, languageUid and
skill; the module URL takes the same as &pageUid=, &languageUid=
and &skill= and replaces them with &conversation=<uid> once the
conversation exists, so a reload opens it instead of starting another. The
checks are the user's own: the page must be one they may show
(BackendUtility::readPageAccess() with PAGE_SHOW), the language one
of the page's site that they may edit (checkLanguageAccess(); a page
outside every site has language 0 only), the skill one of the catalogue.
A refused start creates nothing.
The language is part of the view context. view_context gains
languageId, kept only with a page. The browser sends it where the module
URL carries it; the page module usually keeps it in its module data, so a turn
on the same page without a language keeps the one the conversation started
with. The system prompt names it next to the page, when the user may edit
that language.
The skill is stored on the conversation and passed to every run through
SkillCatalogueInterface, the one place that knows how nr-llm takes a skill
per run. The catalogue is the enabled skills attached to the chat's
configuration or Task (the Task the user's groups map to, ADR-015). A process
skill goes out as an invocation (below); any other skill goes as a forced
skill (RunAugmentation), which nr-llm composes like any skill — as fenced
reference data, under its trust and data-class ceilings — and deduplicates
against the attached ones. Where nr-llm lacks the classes and members this
needs, the catalogue is empty and every run goes without a skill; an
identifier is still stored, so nothing has to be repeated once nr-llm can
take it. The column is written on its own, like the instructions, so a turn
that settles later cannot put an old skill back.
"/" in the input picks the skill. A / with no space after it opens
the catalogue as a list on the input (ARIA combobox on the textarea: arrow
keys, Enter or Tab to pick, Escape to close). Picking sets the
conversation's skill (POST /ai-chat/conversations/skill) and sends no
message; the skill is shown above the input with a button to remove it.
An invocation, with the page as its subject. nr-llm ADR-214 passes a
process skill as an invocation — the skill's uid and a subject record —
rather than as a forced skill on every turn, and skips process skills on
the forced path. NrLlmSkillInvocation (behind
SkillInvocationInterface) builds nr-llm's SkillInvocation: the
skill's uid from the catalogue and the conversation's page as the subject
record (pages, uid, the page's language; 0 when the chat does not know
it). Only a skill the catalogue marks as a process is invoked; any other
skill goes as a forced skill on every turn, as before. The skill's uid is
also kept beside the identifier (column skill_uid, 0 when the catalogue
did not know it).
The invocation goes out once; later turns name their predecessor. nr-llm
ADR-228 derives a running process from the run before: a turn names that run
(predecessorRunUuid) and carries no invocation. The conversation stores
its last run in last_run_uuid after every run nr-llm recorded — completed,
failed, refused, stopped or waiting — and, unlike approval_run_uuid,
keeps it through every status. A run nr-llm could not record has no uuid and
leaves the previous one. The invocation goes out on the conversation's first
run and on the turn after a process skill is picked in the "/" list
(skill_start_pending, set by the pick and cleared once a recorded run
carried the invocation); that turn is a new start and names no predecessor.
A turn whose skill has left the catalogue still names its predecessor, so
nr-llm can report that it dropped the process.
A dropped process is said once. When nr-llm drops the process — its skill
was detached from the configuration, the turn runs on another configuration,
the run it continues from is gone or not the user's, or too many turns in a
row could not continue it — it records a
dropped step with the reason, which the run result carries on every
outcome. The chat adds one line before the answer, stored language-neutral
with the notice processDropped and the reason; the reader sees a sentence
for that reason in their own language (detached,
configuration_changed, predecessor_gone, refused_too_often; a
generic one for a reason
the chat does not know), and the model is not sent the line. nr-llm records no process for such a run, so the next turn
continues none. DroppedSource and DroppedSourceReason are marked
internal in nr-llm; the chat reads them until nr-llm declares them API.
No skill on a four-eyes configuration. nr-llm ADR-214 decides a guided
process only on the chat card; where the configuration requires a second
approver (nr-llm ADR-172), the run's owner cannot release their own write
there. A skill is therefore refused at the start and when it is picked
(409, error.skillSecondApprover) when the chat Task's configuration
requires a second approver. nr-llm marks process skills
(Skill::isProcess(), nr-llm ADR-214), and only those are refused. A skill picked before the
configuration was switched to four-eyes keeps running.
Consequences
- New columns
skill_identifier,skill_uid,skill_start_pendingandlast_run_uuid, new routesai_chat_skillsandai_chat_conversation_skill. Run the database analyzer after upgrading. - A process skill goes out as nr-llm's
SkillInvocationand is continued throughpredecessorRunUuid; nr-llm decides from its approval which version runs (nr-llm ADR-214, ADR-228). Any other skill reaches the model as a forced skill, as reference data rather than instructions. - A skill picked while a turn runs is refused, as the instructions are.
- The chat requires nr-llm 0.41, which carries
SkillInvocation,SubjectRecordandpredecessorRunUuid(nr-llm ADR-228).