> ## Documentation Index
> Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Take-ownership pattern

> Use the ownerUri provenance flag on accepted attribute values so AI agents, event subscriptions, and automation pipelines stop overwriting reviewer edits.

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 an `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 in `vendor_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 an `ownerUri` 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                                                                   |

The UI surfaces `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.

<Tip>
  You don't have to call any special API to claim ownership for a user. Any write through `currentObject.setAttribute(...)` from the workspace sets `ownerUri = user://<reviewer email>` automatically. The V2 [`attributeCopyButton`, `attributeCopyAction`, and `attributeRowPromote`](/guides/data-forms/data-components) promote/copy actions take a different write path (`addDataAttributeToDocument` / `updateDataAttributeInDocument`) and stamp the same `ownerUri = user://<reviewer email>` explicitly on the target, so a promoted or copied field shows the same edited-value indicator as a hand-typed one. The canonical way to claim ownership of *unchanged* values when a reviewer clicks an action is the `takeOwnershipForPaths` action property described in Step 1.
</Tip>

## 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.

```yaml theme={null}
metadata:
  actions:
    - uuid: accept-action
      type: accept
      label: "Accept"
      properties:
        targetStatus: approved
        statusSlug: approved
        icon: check
        color: green
        keybind: "a"
        onlyEnabledIfNoOpenExceptionsForPaths:
          - { taxonomySlug: invoice-taxonomy, taxonPath: invoice/total_amount }
          - { taxonomySlug: invoice-taxonomy, taxonPath: invoice/vendor_id }
        takeOwnershipForPaths:
          - { taxonomySlug: invoice-taxonomy, taxonPath: invoice/vendor_id }
          - { taxonomySlug: invoice-taxonomy, taxonPath: invoice/total_amount }
```

What this does at click time:

1. The platform walks every listed `{ taxonomySlug, taxonPath }` pair against the matching data objects on the task.
2. For each matching attribute it sets `ownerUri = user://<reviewer email>`, even if the value itself is unchanged.
3. 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.
4. The task is saved as part of the action; status transitions to `approved`.

The result is a hard, queryable contract: every path you listed is now stamped as user-owned, and every event subscription, AI agent, and module that reads `ownerUri` (Steps 2 and 3 below) will defer to the reviewer.

<Tip>
  The action editor in Studio renders `takeOwnershipForPaths` and `onlyEnabledIfNoOpenExceptionsForPaths` as a polished picker with taxonomy filtering, path search, group-taxon support, and a custom-path fallback for paths not in any bound taxonomy — no need to type slugs by hand.
</Tip>

<Note>
  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.
</Note>

<Note>
  Pair this with the gate from [Gating actions on exceptions](/guides/task-templates/gating-actions-on-exceptions). The ownership write only happens when the reviewer was actually allowed to click — broken data never gets an approval signature.
</Note>

## 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.

```yaml theme={null}
- name: invoice
  group: true
  eventSubscriptions:
    - name: enrich-vendor-id-from-bridge
      on: "changed:dataAttribute:vendor_name"
      script: |
        if (!currentObject) return;

        // Defer to the reviewer if they have already taken ownership.
        var existingId = currentObject.getAttributeByName("vendor_id");
        if (existingId && (existingId.getOwnerUri() || "").indexOf("user://") === 0) {
          log.debug("vendor_id is user-owned, skipping enrichment");
          return;
        }

        var name = currentObject.getFirstAttributeValue("vendor_name");
        if (!name) return;

        var match = serviceBridge.call("/vendors/lookup", { name: name });
        if (match && match.id) {
          currentObject.setAttribute("vendor_id", match.id);
        }
  children:
    - name: vendor_name
      label: Vendor Name
      taxonType: STRING
    - name: vendor_id
      label: Vendor ID
      taxonType: STRING
```

The pattern is always the same: read the `ownerUri` first, bail if it starts with `user://`, otherwise proceed.

<Tip>
  For a stricter rule, also bail if the `accepted_by` tracking attribute is non-empty — that handles the case where the reviewer did not touch `vendor_id` directly but did approve the enclosing object as a whole.
</Tip>

## Step 3 — Make AI agents and modules respect user ownership

The same rule applies to anything writing through the [Kodexa Document](/guides/kodexa-document/index) APIs from a module or agent step:

```python theme={null}
# Pseudocode in a Python module
attr = data_object.get_attribute_by_name("vendor_id")
if attr and (attr.owner_uri or "").startswith("user://"):
    # Reviewer already took ownership — leave it alone.
    return
data_object.set_attribute("vendor_id", lookup_vendor(name))
```

The shape of the check matches the event-subscription example. Make the guard the first thing the writer does, before any work that would be wasted.

## Step 4 — Surface ownership in the UI

The data form renderer already shows an **"Edited Value"** indicator next to attributes whose `ownerUri` 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`.

```yaml theme={null}
metadata:
  options:
    - name: accepted_by
      label: "Accepted By"
      type: text
      hint: "Set automatically when a reviewer clicks Accept."
    - name: accepted_at
      label: "Accepted At"
      type: date
      hint: "Set automatically when a reviewer clicks Accept."

  actions:
    - uuid: accept-action
      type: accept
      label: "Accept"
      properties:
        targetStatus: approved
        takeOwnershipForPaths:
          - { taxonomySlug: invoice-taxonomy, taxonPath: invoice/vendor_id }
          - { taxonomySlug: invoice-taxonomy, taxonPath: invoice/total_amount }
        attributes:
          - taxon:
              taxonomySlug: invoice-taxonomy
              taxonPath: invoice/accepted_by
            valueType: metadata
            metadataKey: currentUserEmail
          - taxon:
              taxonomySlug: invoice-taxonomy
              taxonPath: invoice/accepted_at
            valueType: metadata
            metadataKey: currentTimestamp
```

Bind the form controls for `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:

1. **Add a "Re-open" action** that does *not* set `takeOwnershipForPaths` and (if you used the tracking-attribute pattern) clears `accepted_by` and `accepted_at` via the same `attributes` block with empty values. Reviewers in the next pass will see clean fields. Note that `ownerUri` from the original Accept stays in the audit trail; the next user write naturally replaces the live value.
2. **Add an "Override" action** that writes a different ownership marker (for example `accepted_by_supervisor`) and updates a status to `re-review`. This preserves the original signature for audit while moving the workflow forward.

Either approach is a normal task-template action — the difference is just what the action's `properties` write.

## Recipe summary

1. On the Accept action, list the critical paths under `takeOwnershipForPaths` using the Studio picker — `{ taxonomySlug, taxonPath }` objects are the canonical form.
2. In every event subscription that may overwrite a reviewer's edit, read `getOwnerUri()` first and bail when it begins with `user://`.
3. Apply the same guard in any module or agent code that writes the same fields.
4. Optional: layer an `attributes` block on top to record `accepted_by` / `accepted_at` for in-form visibility.
5. Optional: add a Re-open action for workflows that need to restart.

## See also

* [Task templates in Studio](/studio/project/task-templates) — visual editor for actions, `takeOwnershipForPaths`, and `attributes`
* [Event-Based Scripting](/guides/data-definitions/event-subscriptions) — the `currentObject` and attribute APIs used in the guard
* [Validation and Conditional Formatting](/guides/data-definitions/validation-and-conditional-formatting) — the source of the exceptions you gate the Accept action on
