Tether
DocsGitHub

Core concepts

Staged writes

A staged write is a proposal, not an instruction. It is durable, scoped, expiring, and executable exactly once.

Lifecycle

  1. Proposed. The model calls a tool declared is_write. Tether does not contact your endpoint, except to call the read a write's precondition names. A write your system already refuses is not staged, and the model is told why.

  2. Staged. A pending action row is written: the command type, the arguments, the tenant and scope it belongs to, the user it is attributed to, and an expiry. The turn returns a preview envelope.

  3. Surfaced. The caller receives it in pending_actions and renders a confirmation card.

  4. Confirmed. A separate authenticated call arrives at POST /v1/actions/{id}/confirm.

  5. Guarded. The tenant boundary is re-checked, and any declared risk must be acknowledged.

  6. Rechecked. A write with a precondition has its read called again. If the answer changed while the write waited, it is settled as precondition_failed and never executed. If it changes in the instant between that read and the write, your imported API is the one that refuses, and a refusal is settled with your reason, not held as indeterminate.

  7. Claimed. A conditional update flips pending → executing. Losing this race is how a double confirm becomes one execution.

  8. Executed. Your endpoint is called with "mode": "execute". Success settles the action; failure releases the claim so it can be retried.

  9. Verified. A write with a verify has its read called once more, after the execution is recorded. verified or unverified is recorded with the reason, and an unverified outcome is handed to a person. Nothing is executed again.

The preview envelope

What the tool call returns to the model, and what surfaces to you:

{
  "preview": true,
  "action_id": "9c1e7b6a-3f42-4a51-9d0e-6b2c8f4a1d33",
  "plan_id": "1a7f…",
  "command_type": "void_ticket",
  "summary": "void ticket — awaiting confirmation.",
  "expires_at": "2026-08-20T18:41:07Z",
  "confirmation_required": true
}

The model sees this too, which is deliberate — it knows the action is pending rather than done and phrases its answer accordingly.

Execute-once, under contention

Confirmation is not idempotent by convention; it is idempotent by construction.

The claim is a conditional update: flip this action from pending to executing, and tell me whether you were the one who did it. Exactly one caller wins. The winner proceeds; the loser gets Replayed or NotFound and never reaches your endpoint.

This matters more than it looks. A confirmation button is exactly the kind of control a user double-taps on a slow connection.

Failure and retry

If your endpoint fails, the execution is recorded and the claim is released — the action returns to pending and can be confirmed again. It has not been consumed by a failed attempt.

The exception is a timeout. Tether reports that the write may or may not have landed, and does not retry, because it cannot tell the difference between "never arrived" and "applied, but the response was lost". Your idempotency key is what makes a retry safe on your side, where you can actually check.

The guard

Two things are enforced before anything runs.

The tenant boundary. The action was staged against one tenant; the credential confirming it must be that tenant. This is the check only Tether can make — without it, a valid key for one customer could confirm another customer's staged write.

The risk acknowledgement. A tool registered elevated or high risk requires acknowledgeHighRisk: true on the confirm call. So does an action carrying a declared value at or above the configured threshold — or one whose value could not be read at all.

What the guard cannot do

It does not know your permission model. It cannot know that shift leads may void under $50 and managers may void anything.

Your endpoint sees every write with "is_write": true and can refuse. That is where your rules belong. If you want Tether to ask you before it dispatches, implement an authorizer — but the endpoint remains the authority.

Expiry

Pending actions expire. A proposal made against a five-minute-old picture of the world should not be confirmable an hour later, when the ticket has been closed, the stock has moved, and the shift has changed.

Expired actions return NotFound on confirm, and are cleaned up by a retention job.