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.
An extension whose branches can pause — a branch that dispatches a pausing tool and
passes its park through — registers with pauses=True:
tai42/pauses or when any extension in
its stack registered with pauses=True; any other branch is plain, and a park it returns
is refused like any undeclared tool’s.
Settings
An extension that takes settings declares their JSON Schema (draft 2020-12, root"type": "object") with config_schema= when it registers, and its factory takes a
config parameter. A factory that takes config must declare a schema and one without
config must not; registration refuses either mismatch and a schema that breaks the JSON
Schema rules. An extension without a schema accepts no settings.
{"name": "label", "config": {"label": "nightly"}}.
The server checks them against the declared schema before any write is stored and at every
bind. Every manifest or preset write also binds each combo it names once before it is
stored, and discards that branch, so a value the factory refuses is refused at the door that
wrote it, naming the extension and the tool. A factory is therefore called again for a combo
it already bound: whatever it does beyond building its branch must be repeatable. The
extension catalog serves each schema, and the Studio draws the settings form from it; a
setting declared as a $ref to the draft 2020-12 meta-schema is itself a JSON Schema and gets
the schema editor. A setting stored as an !ENV marker is served by the tool’s extension
reads as the marker, never its value, and the Studio shows it sealed, by its variable name.
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.
