TENIR-Gov

The Execution Control Plane for Agentic AI

Scene 1 · Normal Admissible Operation

An AI agent proposes a $10 payment. Both kernels evaluate it independently, budget is reserved atomically, and the provider confirms execution.

Two-Kernel Architecture

Kernel 1
Scoring
S = K / (P·V + ε)
Continuous risk
⊕
Kernel 2
Membrane
Invariants I3–I9
Discrete checks
→
Composition
Decision Lattice
PASS / FLAG
HOLD / HARD_VETO

Proposed Action

from tenir_governance import DualKernelCompositionClient, Agent, Passage, Resource from tenir_governance.operations.provider import MockProvider provider = MockProvider(behavior="confirmed") client = DualKernelCompositionClient(store_path="ledger.json") client.governor.provider = provider client.register_agent(Agent(agent_id="alice", authority_score=0.9)) client.register_resource(Resource(resource_id="budget", capacity=100.0)) p = Passage(agent_id="alice", action_type="payment", amount=10.0) result = client.adjudicate(p, pressure=0.2, velocity=0.2, capacity=0.9, resource_id="budget", amount=10.0)

Adjudication Result

Scoring kernel decisionFLAG
Membrane kernel decisionPASS
Composed decisionFLAG
Operation stateRESERVED
Ledger valid✓ True
Budget consumed$10.00 / $100.00

Dispatch & Execute

# Atomically claim dispatch, then call the provider dispatch_result = client.governor.dispatch_and_execute( p.idempotency_key, resource_id="budget", amount=10.0 )
Operation stateCONFIRMED
Executed✓ True
Provider referencemock_ref_op-bac61e4c…

State Machine Path

CREATED
→
RESERVED
→
DISPATCHING
→
CONFIRMED

The idempotency key is durably claimed at CREATED — before any kernel evaluation. Budget is atomically reserved at RESERVED. The provider is called only after DISPATCHING is claimed.

Scene 2 · Duplicate Adjudication Blocked

The same operation is submitted again. TENIR blocks it — the idempotency key was already claimed at CREATED.

First Submission (already processed)

Idempotency keyop-bac61e4c-ea54-…
Current stateCONFIRMED

Second Submission (same key)

# Same passage — same idempotency key result2 = client.adjudicate(p, pressure=0.2, velocity=0.2, capacity=0.9, resource_id="budget", amount=10.0)
Composed decisionHARD_VETO
ReasonIdempotency key already claimed

Why This Works

Step 1
Operation registered in CREATED state — key durably claimed before any kernel evaluation
Step 2
Both kernels evaluate the action and compose a decision
Step 3
On second submission, the registry sees the key already exists → HARD_VETO
Recovery
Use resume_existing() to recover an interrupted operation — never re-adjudicate

Scene 3 · HOLD Lifecycle

When evidence is insufficient, TENIR doesn't deny or blindly proceed. It HOLDs — a designed suspension of agency under pressure. HOLD is TENIR's signature concept.

HOLD vs QUARANTINE_LOCK

HOLD

Per-operation suspension. The action is not denied — it's pending evidence or review. A timer (τ_K) counts down.

QUARANTINE_LOCK

Per-entity lock. Blocks ALL admission and dispatch for an entire LEI until authorized release. Distinct from HOLD.

HOLD Exit Paths

CREATED
→
HOLD
├→
RESERVED
Evidence sufficient → re-adjudicate
├→
ESCALATED
Escalate to risk officer (absorbing state)
└→
FAILED
τ_K expires → fail-closed

Escalation → Authorized Exit

1 · Register
Operation registered in CREATED state
2 · Enter HOLD
hold_manager.enter_hold(key, tau_k_seconds=300) → state HOLD, τ_K = 300s
3 · Resolve
New evidence arrives → resolve_hold(key, passage, pressure=0.1, ...) → RESERVED
4 · Escalate
New operation → HOLD → escalate_hold(key, escalated_by="risk-officer") → ESCALATED
5 · Authorize Exit
authorize_exit(key, authorized_by="ciso@org", scope="post-incident", authorization="ceremony-G-R5-001")
Exit Receipt
Authorized byciso@org
Scopepost-incident-review
Authorization mechanismceremony-G-R5-001
ResultRESERVED

Key Doctrine: ESCALATED is Absorbing

ESCALATED has no time-based auto-exit. It remains until an authorized human signs an exit receipt. τ_K expiry in ESCALATED produces UNKNOWN, not resolution (v7.1 §19). This is deliberate — escalation requires human judgment, not a timer.

Scene 4 · In-Memory vs Persistent Registry

The default SDK client uses an in-memory registry. Idempotency claims are lost on restart. With a persistent store_path, they survive.

In-Memory (default)

DualKernelCompositionClient()

No store_path → registry lives in process memory only.

⚠ After restart: same operation re-adjudicated successfully. Idempotency claim lost!

Persistent (store_path)

DualKernelCompositionClient(store_path="ledger.json")

Store file on disk → claims survive process restart.

✓ After restart: re-adjudication blocked (HARD_VETO). Idempotency preserved!

require_persistence Guard

# Fail-fast in production — refuse to start without persistence client = DualKernelCompositionClient(require_persistence=True) # Raises ValueError if store_path is None
BehaviorValueError
Messagerequire_persistence=True requires a store_path…

Runtime Warning (v5.3.0+)

Since the audit fix, initializing without store_path emits a visible warning:

UserWarning: DualKernelCompositionClient initialized without store_path. The operation registry is in-memory only — idempotency claims will be lost on restart, allowing duplicate adjudication. Pass store_path or require_persistence=True for production use.

Side-by-Side Test

In-memory
Adjudicate → RESERVED. Simulate restart (new client). Re-adjudicate → FLAG (idempotency lost)
Persistent
Adjudicate → RESERVED. Simulate restart (same store). Re-adjudicate → HARD_VETO (blocked)

Scene 5 · Provider Timeout → UNKNOWN → Reconciliation

When the provider times out, TENIR doesn't assume the payment failed. It marks the operation UNKNOWN and triggers reconciliation. A timeout does NOT prove no effect occurred.

Dispatch with Timeout

provider = MockProvider(behavior="timeout", reconcile_outcome=ProviderOutcome.CONFIRMED) client.governor.provider = provider dispatch_result = client.governor.dispatch_and_execute( p.idempotency_key, resource_id="budget", amount=30.0 )
Operation stateUNKNOWN
Executed✗ False
ReasonProvider returned UNKNOWN — reconciliation required.

State Machine: Timeout Path

CREATED
→
RESERVED
→
DISPATCHING
→ timeout
UNKNOWN

UNKNOWN is not FAILED. It means the outcome is uncertain. The operation is not terminal — it can be reconciled.

Reconciliation

# Ask the provider: did the payment actually happen? recon = client.governor.reconcile_unknown(p.idempotency_key)
Reconciled✓ True
Operation stateCONFIRMED
OutcomePayment did happen

Why Not FAILED?

Per doctrine v7.1: a timeout does not prove no effect occurred. The payment may have succeeded — we just didn't get the response. Treating this as FAILED could lead to a double-charge (retry a payment that already went through). UNKNOWN triggers reconciliation, not auto-failure.

UNKNOWN
├→
CONFIRMED
Provider confirms effect
└→
FAILED
Provider confirms no effect

Scene 6 · Guarantee Boundary

TENIR-Gov provides three tiers of guarantee. A governance decision does not, by itself, prove that an external effect occurred exactly once.

Guarantee Tiers

GuaranteeLevel
At-most-once dispatch through the governance gateLocal (in-process / single-host)
Fail-closed compositionLocal
Mandatory invariants not skippable by healthy scoreLocal
Atomic budget reservationsLocal (threading.Lock)
Cross-process serialization (single host)Local (fcntl.flock)
Cross-host budget / idempotency coordinationRequires distributed infrastructure
Exactly-once external effectsDepends on downstream provider
PyPI artifact integrityPackaging ecosystem + verification

The Precise Guarantee Boundary

A governance decision does not, by itself, prove that an external effect occurred exactly once. TENIR-Gov guarantees that at most one dispatch is permitted through the governance gate on a single host.

Exactly-once external effects require provider-side idempotency (e.g., Stripe's Idempotency-Key header) and, for multi-host deployments, distributed coordination infrastructure.

Fail-closed is the default: when either kernel errors, when data is missing, or when the outcome is indeterminate, the composition produces HOLD — never PASS.

What This Means in Practice

Single host
TENIR provides at-most-once dispatch. The governance gate will not permit the same operation to dispatch twice.
Multi-host
Replace the file-backed registry with a transactional database. The in-process budget coordinator needs a distributed lock service.
External effects
The provider must support idempotency keys (e.g., Stripe). TENIR passes the governance-assigned key; the provider deduplicates on its side.
Uncertainty
Timeouts produce UNKNOWN, not FAILED. Reconciliation asks the provider what happened. If still unknown, a human reviews.