Tools
You register a function as a tool with one decorator on the sharedtai42_app
handle. The function’s signature becomes the tool’s input schema, and its
docstring becomes the tool’s description:
api_tools curates it out), so an
extension wraps it, a preset bakes over it, and tai tools run or an agent dispatches it exactly as with any other tool.
A tool can declare a registration tier (read / write / fenced / secret);
a fenced or secret tool is admin-only to author a preset over and to run,
enforced at the shared tool-run chokepoint on every door — see
fenced tools.
Retrying a tool call
A tool declares its own retry policy, and the host dispatch seam honours it. With no declaration a dispatch is one attempt, exactly — retry is opt-in per tool, never a platform-wide default.max_attempts is the total attempt budget with the first attempt included, so
1 means no retry. idempotent is the author’s explicit claim that re-firing the
body is safe: it is required, and a retrying policy without it is rejected — the
double-send guard.
retryable is the classification door, an explicit allowlist rather than a
blanket. True retries the default transient kinds, a tuple of ErrorKind retries
exactly those kinds, and False turns kind-based retry off. In every mode an error
that carries its own boolean retryable verdict wins in both directions — it
admits a failure that is off the list and vetoes one that is on it, because the
raiser knows its own failure better than any kind bucket. An error with neither a
verdict nor an allowlisted kind is never retried.
Backoff is exponential: attempt n waits
min(cap_seconds, initial_seconds * multiplier ** (n - 1)) before the next one. A
server that answers with its own retry_after widens that wait when it asks
for longer — the cap bounds the platform’s own growth, not the server’s explicit
request.
A policy is declared on the registered base tool name. A
preset runs the base tool’s body with baked constants, so an
undeclared preset inherits the first declared ancestor’s policy — the idempotency
claim travels with the body. An extension branch inherits nothing: its stack can
compose or relocate execution the base never spoke for, so only an exact-name
declaration arms it.
ToolRetryPolicy,
ToolRetryBackoff, and the retryable-kind constants.
Extensions
An extension is a plain callable registered against anExtensionKind and applied
to a tool. There are three kinds:
WRAPPER— wraps the call around the tool without changing its input schema (for example, tracing or caching the call).TRANSFORMER— transforms the tool into a new shape, changing what inputs it presents.BACKEND— routes the tool’s execution through a backend.
monitor to mytool binds a
mytool_monitor branch, leaving mytool itself untouched.
Clipping an extension on
Extensions are attached in the manifest, separately from tool selection. A tool’sextensions map names the branch to build:
[ext]) or a list of combos ([[ext], [ext, other]]),
and a stacked combo ([[a, b]]) layers both extensions onto one branch. The
extension’s module must also be loaded — through extensions_modules — before you
can attach it.
Built-in and contrib extensions
The skeleton ships two built-in extensions that depend on skeleton features:monitor (a WRAPPER that traces a standalone tool call as one live span) and
ask_external (a TRANSFORMER that turns a callback-URL tool into an
external interaction). Generic, self-contained
extensions — chain, batch, cache, and more — live in the tai42-toolbox
contrib package and load the same way. The
standard toolbox guide lists them.
An extension extends a single tool. A plugin —
a connector, storage backend, or config provider — extends the whole platform.
The two are different things.
ExtensionKind
enum and the tool registry surface, and the guides on
building a tool and
applying an extension.
