Core concepts
Tools
A tool is a capability your product already has, described so a model can decide when to reach for it. The catalog you register is the complete set — Tether cannot call anything else.
Anatomy
{
"name": "get_open_tickets",
"description": "Lists open tickets for a site, newest first. Use when the user asks what is outstanding, unresolved, or still open. Does not include tickets closed today.",
"domain": "orders",
"is_write": false,
"requires_scope": true,
"parameters": {
"type": "object",
"properties": {
"limit": { "type": "integer", "description": "Max rows. Default 20, max 100." }
},
"required": []
}
}
Every field is documented in the tool spec reference.
Descriptions are the interface
The description is the only thing the model uses to decide whether this tool answers the question in front of it. It is not documentation for you; it is a prompt.
Write it to disambiguate against your other tools:
| ✗ | "Gets tickets." |
| ✓ | "Lists open tickets for a site, newest first. Use when the user asks what is outstanding or still open. Does not include tickets closed today — use get_ticket_history for those." |
Empty descriptions are rejected at registration. A tool with no description is a tool the model calls at random.
Naming
Names must match ^[a-z][a-z0-9_]{2,63}$ — lowercase, starting with a letter, underscores
allowed. This is a provider constraint, not a preference; anything else is rejected upstream.
Prefer verb-first names that read as what happens: get_open_tickets, void_ticket,
schedule_visit.
The write flag
is_write decides whether a tool executes inline or gets staged for confirmation. It is
declared. Tether never infers it.
Validation
Every registration runs the same checks, and reports all failures at once rather than making you fix them one at a time:
| Rejected when | Message |
|---|---|
| Name breaks the pattern | names must match ^[a-z][a-z0-9_]{2,63}$ |
Named tether_handoff, which is reserved | use a name reserved for a capability the engine provides itself |
| Description is empty | no description, so the model would call them at random |
| Two tools share a name | declared more than once |
| Both surfaces disabled | registered but offered on no surface |
| A read declares a risk level | declared a risk level but not declared a write, so nothing would gate them |
That last one deserves attention: setting "risk": "high" on something you forgot to mark
is_write looks careful and gates nothing. Rather than silently ignoring the field, Tether
refuses the catalog.
POST /v1/tools/validate runs identical validation without registering. See
Validating your catalog in CI.
Visibility
Not every tool should be offered on every turn. Four flags narrow it:
| Flag | Effect |
|---|---|
chat_eligible | Offered on the conversational surface |
agent_eligible | Offered on the agent/actions surface |
deep_analysis_only | Only in capabilityMode: "Deep" |
allow_with_image | May be offered on a turn carrying an image |
Narrowing is not only about permissions. Every tool in the catalog costs tokens on every turn, and a model choosing between eight relevant tools is more accurate than one choosing between sixty. Offering fewer tools is usually a quality decision before it is a cost one.
Versioning
PUT /v1/tools replaces the catalog and increments a version. Previous versions are retained
rather than overwritten, for two reasons: a turn already choosing tools keeps the catalog it
started with, and a customer who breaks their catalog can see exactly what changed.
Registration is atomic. A rejected catalog leaves the previous one live.
Related
- Tool spec reference — every field
- The tool endpoint — implementing the other side
- Tools API