The hardest bug in a human-in-the-loop workflow is the one where a reviewer carefully fixes a value, clicks Accept, and then a few seconds later watches an event subscription or an AI enrichment quietly stomp on their edit. The fix is provenance: every attribute carries anDocumentation Index
Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
Use this file to discover all available pages before exploring further.
ownerUri, and once it says user://, any automation downstream should treat the value as authoritative and step away.
This recipe shows the full loop — how a click on Accept writes the ownership mark, how event subscriptions check for it before writing, and how AI/automation pipelines should defer to a user-owned value.
The problem
You have an extraction model that fills invendor_id from a service bridge. A reviewer overrides the model’s value because they know the bridge sometimes confuses subsidiaries. They click Accept. Two seconds later an event subscription on vendor_name fires, calls the bridge again, and resets vendor_id back to the wrong answer.
What’s missing is the contract: once a user has touched this value, downstream automation backs off. The platform already records the ownerUri of every attribute write — the cookbook below wires it into the rest of your system.
How attribute ownership works
Every data attribute carries anownerUri indicating who last wrote it:
| Prefix | Set by |
|---|---|
user://email@domain | A reviewer in the UI (typing in an editor, clicking an action that writes attributes) |
model://<modelId> | The extraction engine when an extraction model produces the value |
module://<moduleId> | A processing module |
ownerUri.startsWith("user://") as the “Edited Value” indicator next to each control. That is the same prefix you use as the gate inside event scripts and modules.
Step 1 — Add takeOwnershipForPaths to the action
Pick the paths in the visual picker — when a reviewer clicks the action, every listed path will have its ownerUri flipped to user://<reviewer email> automatically. The change writes through the same audit-trail and delta-export pipeline as a regular reviewer edit, so manager review and second-tab clients see the claim.
- The platform walks every listed
{ taxonomySlug, taxonPath }pair against the matching data objects on the task. - For each matching attribute it sets
ownerUri = user://<reviewer email>, even if the value itself is unchanged. - The write goes through the standard audit-trail path, so the change shows up in the activity feed, in delta exports, and to any second-tab client subscribed to the document.
- The task is saved as part of the action; status transitions to
approved.
ownerUri (Steps 2 and 3 below) will defer to the reviewer.
The legacy plain-string list (
["invoice/vendor_id", "invoice/total_amount"]) is still accepted by the platform — both properties normalize string entries to { taxonomySlug: "", taxonPath: "..." } at runtime — but the object form is preferred and is what the Studio editor saves. Mixing both forms in a single list is supported during a migration.Pair this with the gate from Gating actions on exceptions. The ownership write only happens when the reviewer was actually allowed to click — broken data never gets an approval signature.
Step 2 — Make event subscriptions respect user ownership
In a Data Definition, an event subscription script can read the ownerUri of any attribute it is about to overwrite. If the value already belongs to a human, skip the write.ownerUri first, bail if it starts with user://, otherwise proceed.
Step 3 — Make AI agents and modules respect user ownership
The same rule applies to anything writing through the Kodexa Document APIs from a module or agent step:Step 4 — Surface ownership in the UI
The data form renderer already shows an “Edited Value” indicator next to attributes whoseownerUri starts with user://. Reviewers therefore see, at a glance, which fields they (or takeOwnershipForPaths) have already taken responsibility for.
Optional — Surface who and when in the form
takeOwnershipForPaths flips ownerUri but does not record a separate timestamp or reviewer email — the audit trail has that information, but it isn’t bound into the form. If you want a manager-review or second-pass workflow to see the reviewer email and accept timestamp directly inside the data form, you can layer the older “tracking attribute” trick on top of takeOwnershipForPaths.
Add an attributes block alongside takeOwnershipForPaths. Each entry tells the platform to write a value to a taxon when the action is clicked. Two valueType: metadata helpers exist specifically for this: currentUserEmail and currentTimestamp.
accepted_by and accepted_at as read-only fields. They populate the moment the first reviewer clicks Accept, and the next reviewer can see at a glance who came before them.
This pattern is purely additive — the attributes block writes the tracking values, and takeOwnershipForPaths does the actual ownership claim. Drop the attributes block if you don’t need the form-level visibility; the ownership contract still holds.
What to do when ownership needs to be cleared
Sometimes a reviewer’s previous decision needs to be reset — a downstream system rejected the document, or a different reviewer is taking over. Two options:- Add a “Re-open” action that does not set
takeOwnershipForPathsand (if you used the tracking-attribute pattern) clearsaccepted_byandaccepted_atvia the sameattributesblock with empty values. Reviewers in the next pass will see clean fields. Note thatownerUrifrom the original Accept stays in the audit trail; the next user write naturally replaces the live value. - Add an “Override” action that writes a different ownership marker (for example
accepted_by_supervisor) and updates a status tore-review. This preserves the original signature for audit while moving the workflow forward.
properties write.
Recipe summary
- On the Accept action, list the critical paths under
takeOwnershipForPathsusing the Studio picker —{ taxonomySlug, taxonPath }objects are the canonical form. - In every event subscription that may overwrite a reviewer’s edit, read
getOwnerUri()first and bail when it begins withuser://. - Apply the same guard in any module or agent code that writes the same fields.
- Optional: layer an
attributesblock on top to recordaccepted_by/accepted_atfor in-form visibility. - Optional: add a Re-open action for workflows that need to restart.
See also
- Task templates in Studio — visual editor for actions,
takeOwnershipForPaths, andattributes - Event-Based Scripting — the
currentObjectand attribute APIs used in the guard - Validation and Conditional Formatting — the source of the exceptions you gate the Accept action on
