Tether
DocsGitHub

Core concepts

Reads and writes

The single most consequential field in your catalog is a boolean. It decides which of two very different paths a tool call takes.

The two paths

A read is dispatched immediately. Tether signs an envelope, posts it to your endpoint, feeds the JSON back to the model, and the turn continues. Reads may be retried.

A write is not dispatched at all. Tether persists a pending action, returns a preview to the caller, and the turn continues without your endpoint hearing about it. Only a separate confirmation call causes a request — once, never retried.

read    model asks ──▶ your endpoint ──▶ model continues

write   model asks ──▶ staged ──▶ ...your user confirms... ──▶ your endpoint

Why it is declared

is_write is a field you set at registration. Tether will not guess it from the name, the schema, or the description.

A declared field cannot fail that way. If you forget it, the tool is a read and behaves conservatively; if you set it, the gate exists. There is no third state where it looks gated but is not — which is exactly what the validator's "declared a risk level but not declared a write" rule protects.

What counts as a write

Anything that changes state your customer would notice, or that costs money.

Obvious: creating, updating, deleting, cancelling, approving, sending, refunding.

Less obvious, and worth flagging anyway:

  • Anything that notifies a human. Sending an SMS is not reversible.
  • Anything metered. A read against a paid third-party API costs money even though nothing changed.
  • Anything that consumes a limited resource. Claiming a slot, reserving inventory.
  • Anything that starts a workflow. Even if the first step is harmless, the chain may not be.

When it is genuinely ambiguous, declare it a write. The cost of a false positive is one extra tap; the cost of a false negative is a model changing production on its own.

What each path guarantees

ReadWrite
Calls your endpoint during the turnyesno
Retried on a transient failureup to 3 attemptsnever
Carries an idempotency keyyesyes
Requires a second authenticated callnoyes
Tenant boundary re-checked at executionn/ayes
Can require an explicit acknowledgementnoyes, by risk level
Offered on a turn carrying an imageyesnever

Reads are not free of consequence

A read still reaches your data. Scope it in your endpoint by tenant.organization_id and tenant.scope_id on every query — Tether tells you who is asking, but it cannot enforce your row-level rules.

A read is also the most common way to leak. If get_customer returns a full record including payment details, those details enter the model's context and may be repeated back. Return the minimum the tool's description promises.