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
| Read | Write | |
|---|---|---|
| Calls your endpoint during the turn | yes | no |
| Retried on a transient failure | up to 3 attempts | never |
| Carries an idempotency key | yes | yes |
| Requires a second authenticated call | no | yes |
| Tenant boundary re-checked at execution | n/a | yes |
| Can require an explicit acknowledgement | no | yes, by risk level |
| Offered on a turn carrying an image | yes | never |
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.
Related
- Staged writes — the write lifecycle in full
- Choosing risk levels
- Retries, timeouts, idempotency