The problem
An AI agent working inside a real organization takes instructions from more than one place at once: a data owner, a security policy, a project runbook, a standard that was retired last quarter. Sooner or later those instructions disagree.
The usual answer is to let the language model decide which one wins. That puts an ungoverned authority decision in the hands of the least accountable component in the system, and it happens silently.
What it does
DataHub Agent Arbiter is a deterministic pre-action gate. An agent proposes an action. Before anything runs, the Arbiter reads the competing instruction sources from DataHub through the DataHub MCP Server and returns one of three verdicts:
ALLOW— an applicable, current, complete authority permits itBLOCK— the applicable authority forbids it, or a required condition failsREVIEW_REQUIRED— two equally ranked current authorities disagree
The verdict comes from a pure Python function. No model call, no clock, no randomness, no I/O. Same inputs, same verdict, every time.
The Arbiter does not decide who should have authority. It enforces authority metadata that an organization has already defined.
How it decides
Six gates, evaluated in a fixed order:
- Are there any authority sources at all?
- Do they apply to this action's scope?
- Is their metadata complete?
- Is any of them deprecated?
- Which sources hold the highest authority rank?
- Do those top-ranked sources disagree?
Scope, completeness and deprecation are all checked before rank is ever consulted. The highest-ranked source in the demo catalog is a deprecated one that says ALLOW, at rank 120 — the top of the catalog. The verdict is still BLOCK, with reason code DEPRECATED_AUTHORITY_SOURCE. Retired authority cannot authorize anything, no matter how senior it used to be.
When two current sources of equal rank give opposite directives, the Arbiter returns REVIEW_REQUIRED with reason code EQUAL_AUTHORITY_CONFLICT. It does not break the tie. There is no defensible winner, so it refuses to invent one and escalates instead.
Evidence
Only an ALLOW runs the action. When it does, the run is: act, write the decision evidence back to DataHub, verify the SHA-256 on read-back, then revert the action under a hash guard.
The evidence lands on the dataset as custom properties:
arbiter.last_decision.decision "ALLOW"
arbiter.last_decision.reason_code "AUTHORITATIVE_ALLOW"
arbiter.last_decision.source_urns [ ... ]
arbiter.last_decision.sha256 "b1f6d3f5..."
The hash is computed over the canonical decision record, so the verdict, the reason code and the sources that produced it are all covered by one digest.
If DataHub cannot be read, no decision is made, no evidence is written, and no action is taken. The catalog read fails before the action is ever reached, and nothing catches it to carry on regardless. Fail-closed by construction.
What it is not
This is a bounded prototype, and the boundaries are deliberate:
- The four instruction sources are synthetic, created by the demo itself so the arbitration behaviour can be inspected safely.
- The gated action is small and reversible — it creates and then removes a marker file. The arbitration in front of it is the product, not the action behind it.
REVIEW_REQUIREDreturns a verdict. There is no human-review queue yet.
It demonstrates the arbitration contract. It is not a production authorization platform, and the repository says so.
Verification
From a clean clone, with pip install -r requirements.txt:
- 21 of 21 tests pass
- all four scenario decision hashes reproduce the accepted baseline bit-for-bit
- the bounded action is created and reverted with no residue on disk, and the working tree is left clean
- fail-closed behaviour is verified with DataHub absent — no evidence file, no marker
Built with
Python, the DataHub MCP Server, DataHub v1.6.0 via Docker quickstart, pytest. Apache-2.0.
While building a reproducible local verification path for this project I found that the pinned upstream quickstart compose file starts only one of its seven services when run directly, and filed it upstream: https://github.com/datahub-project/datahub/issues/18637
Log in or sign up for Devpost to join the conversation.