---
title: "ADR-265: Background work writes into the workspace it was started in"
manual: "LLM Foundation"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-265@main"
source: "Adr/Adr265BackgroundWorkWritesIntoItsWorkspace.rst"
rendered: "2026-11-05T21:51:36+00:00"
---

# ADR-265: Background work writes into the workspace it was started in {#adr-265}

-   *Status:* Accepted
-   *Date:* 2026-11-05
-   *Amends:* [ADR-237](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-237@main) (live only: a save in a draft workspace queued nothing), [ADR-236](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-236@main) (a batch read its scope in live only)
-   *Authors:* Netresearch DTT GmbH

## Context {#context}

[ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main) records the workspace a run was started in on its
actor, and the resolver sets the acting user up there. The work that runs in
the background did not use that, measured on 2026-11-05 against TYPO3 14.3:

-   **The field fill** ([ADR-237](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-237@main)) queued nothing for a save in a
    draft workspace, and its writer refused a user in a draft. Its write already
    went through the DataHandler (`ToolDataHandler`), so nothing wrote raw.
-   **The editor-action batches** ([ADR-236](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-236@main), the module and
    `nrllm:editor-action:batch`) recorded the editor's workspace on each run
    (the module's actor is frozen from the request's user), but read their scope
    live: the pages of a tree, which records still had an empty field, and the
    page text a run on a page writes from. A page new in the draft was not in
    the batch, a text the draft had written or emptied was not seen.
-   **The scheduled batch** set its user up in the workspace
    `be_users.workspace_id` named when the command ran, and recorded that
    workspace. A schedule would therefore write into whichever draft its user
    last worked in, once the writers write into a workspace.

Core hands the DataHandler hooks the uid of the **version** when a save in a
workspace changes a live record (`DataHandler::process_datamap()`, the
`$id` set from `autoVersionIdMap` or `getWorkspaceVersionOfRecord()`
before `processDatamap_afterDatabaseOperations`, TYPO3 14.3.10 and 13.4.37),
and the uid of the row itself for a record new in the workspace.

## Decision {#decision}

**The field fill runs in the workspace of the save.** The planner reads the
record as the saving user's workspace shows it, under its live uid — the uid
the hook names may be the version's — and queues the run for the live uid.
Its actor records the workspace. The page text the model reads is the
workspace's: each content element overlaid with its version there, elements
new or moved there included, those deleted there left out, never another
workspace's draft (the row predicate of [ADR-263](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-263@main)). A field
the workspace already holds a value in is not queued.

**The fill writes into that workspace, or nowhere.** The writer resolves the
user in the workspace the actor recorded ([ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main)) and writes
through the DataHandler as that user: core stores a version, or changes a
record new in the workspace in place. It reads the field as the workspace
shows it before the write and after it. The provenance mark goes on the
version ([ADR-239](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-239@main)). The fill joins the draft the save made,
or writes nothing: a draft discarded or published while the run waited is not
made again for the fill alone, and a draft sent on to review or publication is
not written either — core resets the stage to editing with any write
(`t3ver_stage` 0), and an unattended fill must not pull it back. Live is not touched, wherever the editor
works when the worker runs. A run that recorded no workspace writes in live
only, as before. A table that keeps no versions (`ctrl.versioningWS`), or
one core edits live from any workspace (`versioningWS_alwaysAllowLiveEdit`,
which TYPO3 13.4 honours even for a versioned table), is neither planned nor
written in a draft workspace: core would write it live.

**A batch reads its scope in its workspace.** The module's batch takes the
workspace its actor recorded, where that is the user's (as the read tools of
[ADR-263](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-263@main) decide it). Its tree is the tree that workspace
shows — pages new or moved there included, pages deleted there left out —
with page access asked of the live page, as core's page tree asks it. A
record counts as empty when its version there is empty, and the page text a
run on a page writes from is the workspace's. Each run's actor records the
workspace, so the write at approval lands there, whoever approves and wherever
the owner works by then; whether a writer writes into a workspace at all is
the writer's own decision ([ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main)). The runs of a draft batch
are kept per workspace (origin `batch@<workspace>:<tool>:<table>:<uid>`; a
live batch keeps `batch:<tool>:<table>:<uid>` of [ADR-236](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-236@main)),
so a card decided in a draft does not keep its record out of a live schedule,
nor the reverse.

**A schedule runs in live.** `nrllm:editor-action:batch` sets its user up in
live and records live, whatever `be_users.workspace_id` names. A user who
may not work in live cannot be named.

## Consequences {#consequences}

✓ An editor in a draft workspace gets the empty fields of a save filled there;
the fill waits in the workspace module, live stays as it was.

✓ A batch started in a draft proposes for what the draft shows, and its
approved writes stay in the draft — for the writers that write into a
workspace ([ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main), [ADR-262](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-262@main), among them
`set_file_alternative_text` and `update_page_metadata`); one that does not
refuses at approval, after the model was asked.

✗ A fill is kept by the live uid of its record (`fill:` origin, which the
queue parses). A fill open in live holds the same field in every workspace
until the worker has run it, and the reverse.

✗ The fill reads its text when it is queued, as before; a draft rewritten
while the run waits is not read.

✗ A field whose draft was sent to review while the run waited stays empty;
the next save in the draft queues it again. A field whose draft was discarded
stays empty. The stage and the draft are read right before the write; a
review sent in the milliseconds between that read and the DataHandler's write
is reset to editing, the same window [ADR-237](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-237@main) accepts for a
text saved meanwhile (the DataHandler has no compare-and-set).

✗ A batch run queued in a draft before this change kept the live form
`batch:<tool>:<table>:<uid>` (the run table stores no workspace of its own),
and counts as a live run until it is purged.

✗ A fill of a file's metadata is no case: `sys_` tables are not opted in
([ADR-237](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-237@main)).
