In these docs

Execution Layer for Odoo

Verified execution.

What a verified write is, the Action Receipt it returns, and the guarantees around it: atomic execution, post-commit read-back, idempotency keys, all-or-nothing batches, and the truncation contract on reads.

Feature-complete · Odoo 19 Updated 17 August 2026

A verified write is a write that is executed inside an atomic transaction, then read back from the database after commit and compared against the declared intent before success is ever reported. If what persisted does not match what was meant, the AI gets a structured failure, not a success.

The Action Receipt

Every write returns an Action Receipt: the record of the chain Executed, Persisted, Verified, Recorded, with the values that changed.

Action Receipt (representative)
{
"receipt": {
"receipt_id": "rcp_01J...",
"audit_id": 12345,
"tool": "update_records",
"model": "project.task",
"record_ids": [453],
"executed": true,
"persisted": true,
"verified": true,
"audited": true,
"stored_records": [{ "id": 453, "user_ids": [7], "stage_id": [3, "In Progress"] }],
"changed_fields": { "453": ["user_ids", "stage_id"] },
"assertions": { "only_if": "passed" },
"approval": { "request_id": null, "approved_by": null },
"idempotency": { "key": "...", "replay": false },
"context_layer": { "status": "none" },
"timing_ms": { "total": 84, "execute": 21, "readback": 6, "audit": 9 }
}
}

The chain, in order:

  • executed the operation ran inside a transaction.
  • persisted the explicit commit returned.
  • verified a fresh read after commit matched the declared intent and any assertions (relational fields are compared by id).
  • audited the evidence row was written.

The JSON field

In prose and in the Execution Layer app the final step reads Recorded. In the receipt JSON the field is audited. Same step, two names.

When verification fails

If a write commits but the stored value does not match the intent, for example an onchange or a compute rewrote it, the layer returns VERIFICATION_FAILED. It does not report success and quietly move on. The mismatch is audited and raises the default alert. This is the whole point: a success is only a success once the database agrees.

Idempotency

Every write carries an idempotency key, scoped to the database, the user, and the tool. A timeout followed by a retry with the same key and payload replays the original receipt, so the action runs once, not twice. The same key with a different payload returns IDEMPOTENCY_CONFLICT.

Batches

A batch returns a batch_receipt:

Batch receipt shape
{
"batch_receipt": {
"mode": "atomic",
"status": "committed"
}
}

mode is atomic or best_effort; status is committed, rolled_back, or partial. In atomic mode, the default, any single item failing rolls back the whole batch and returns BATCH_ITEM_FAILED with the failing index. Either the whole batch persists, or none of it does.

Reads cannot pose as complete

Every bounded read carries a truncation contract, so a partial list can never be mistaken for the whole:

Fields on every bounded read
{
"count": 14,
"total_count": 23,
"truncated": true,
"has_more": true,
"next_offset": 14
}

The default read limit is 50, the maximum is 200, and an export can return up to 5000. An agent that reads truncated: true, total_count: 23 knows to page through the rest before it answers.

Connect returns receipts too, honestly

On the free Connect tier, without nanti_mcp_verify, you still get a receipt in the same shape, but with verified: false and verification: "not_installed", and stored_records set to the in-transaction read-back. Verification is a Complete feature, and the free tier says so plainly rather than pretending.

Next: governance.