A preset is a base tool with a partial set of keyword arguments baked in, exposed
as a new named tool. It lets you save a configured variant of a tool once and call
it by name, and it rides the platform’s versioning spine
so the configuration can evolve without breaking callers.
A preset’s organizational metadata — a display name, a folder, user tags, and
visibility — is not part of the preset record; like any tool, a preset carries it
in the unversioned tool-organization overlay,
keyed by its tool name.
The bind kernel
At the centre of the feature is a bind kernel that builds the live tool: it takes
a base tool and fixes some of its arguments, producing a hidden transform of the
original. The baked values fill in behind the scenes, so a caller supplies only
the arguments that were left open. The base tool is any registered tool —
including a management operation projected as a tool,
so you can save a configured variant of a live-ops action just as you would any
other tool.
The store view
A preset persists through a typed store view over the platform’s generic
versioned-document store, tagged kind="preset". Every preset is a versioned,
store-backed record — there is one tier, not two. Create writes a durable store
row and then registers the live tool; every save appends a new version, one version
is active, and you can roll back to an earlier one. A preset always rides the
versioning spine.
The contract owns the PresetStore protocol and the PresetBody model; the
skeleton owns the concrete view and the bind kernel.
Across the fleet
A preset mutation lands locally — the store write plus the live rebind — and then
broadcasts on the worker bus so every worker sharing the
manifest re-reads the active body and rebinds. In a single-process deployment the
local apply is the whole fleet. The store write and the local rebind have already
landed by the time the broadcast runs, so the response is the plain preset record;
a preset broadcast that does not converge is not embedded in that response but
surfaced server-side as a loud non-convergence ERROR log that names the workers the
change did not reach — departed, timed_out, or missing. Re-run the mutation,
or a fleet reload_config, to reconverge the named workers.
Conflicted records
A preset whose stored body can no longer bind — its base tool or an extension it
names was removed since it was authored — is quarantined rather than dropped.
It stays visible in the listing with conflicted: true and a human-readable
conflicted_reason explaining the cause (null when the record is healthy). A
conflicted record is delete-only: save-version, rollback, and rename all reject
it with a 409, and only delete clears it (a hard store removal that touches no
registration). This field is the only place the cause is reported — resolve a
conflict by deleting the record and re-creating it once its base is back.
Plugin-shipped defaults
Beyond the tools, extensions, and agents a plugin can
carry, a plugin may also ship default presets — preset definitions it declares
in code, which the platform seeds at boot. A declared default is created when it is
absent and upgraded in place when the plugin’s shipped definition changes across
releases, so a fresh install and an upgrade both land the current default without
any operator step. The seeding is one-directional and deferential: a seeded default
stays plugin-owned only while it is untouched, and the first time an operator edits
it the preset becomes operator-owned and the plugin never overwrites it again. A
seed likewise fills a default’s display name, tags, or folder only where the
organization overlay leaves them unset, never over an
operator’s own label.
Baked values are a may-run boundary
A preset’s baked fixed_kwargs are a may-run boundary, not a may-read one.
They are hidden from framework-manufactured surfaces — the tool input schema and
validation-error messages — so a caller authorized only to run the preset does not
see them through those channels.
The baked values are present in the running tool’s context and may appear in
anything the tool or an agent emits — output, tool-call arguments, traces, error
text — which the framework cannot generically scrub. Bake provider or secret
references (names resolved server-side, such as an llm_provider name or a
connector token injected at runtime), never raw credentials. If the baked config
is sensitive, treat run authorization as “may run”, not “may read the config”.
Managing presets
Create, version, roll back, and rename presets from the CLI:
Beyond the create/version/rollback core, the surface carries four more doors:
validate — a dry-run that reports whether a draft would be accepted,
running the exact pre-write checks the create (new name) or save-version
(existing name) route runs, without writing anything. A malformed request is a
400; a valid or invalid draft is both a 200 carrying a valid verdict.
referees — every live reference a rename of this name would strand, listed
so a UI (or you) can preflight the rename. The list is the full union: the other
presets whose authored-agent composition names this one as a tool, plus every
behavioral holder that wires against the name — a schedule that fires it, a hook
that targets it, a conversation route pointing at it, a tool-extensions map entry
carrying it, a parked interaction whose resume continuation is it, and any
plugin-held reference a plugin declares through the rename-referee seam. Each
holder is named individually.
rename — a preset’s name is its live tool name, so a rename rebinds the
tool (new name bound before the old is torn down). Rename integrity is enforced
atomically at this one gate: it runs create’s name pre-checks on the new name
and blocks with a 409 listing every holder — the same full referee union
above (referencing presets and behavioral wiring alike) — so you update those
references first rather than have the rename silently strand them. It also rejects
a conflicted record (409) and a no-op rename (400). Display metadata is not a
behavioral reference: a tool’s organization overlay
follows the name across a rename rather than blocking it.
set-version-tags — replace one version’s tags annotation. Tags are
labels on an immutable version body, so this never rebinds the live tool.
Those “pre-write checks” are extensible per base tool. A base-tool plugin can
register a validator against its own base_tool name so that every preset written
over it is checked before it persists — the money-pinning preset for the
Stripe checkout tool,
for instance, is the kind of constraint such a validator can enforce.
A plugin registers one when its tool module loads:
The preset write path consults the validator registered for a body’s base_tool
on every write — create, save-version, and rollback — and the validate
dry-run door runs the same check without persisting. The registry is rebuilt on
each start(), so a reload re-imports the tool modules and re-registers cleanly;
registering the same base_tool twice within one load raises loudly rather than
silently swapping a base tool’s write gate.
A preset can declare the exposed named tool’s own typed input schema when its
base tool supports one — a base tool registers support for it at load, the same
register_*-at-load pattern as a write validator. When it does, the bind kernel
routes the caller’s validated object into the base tool’s invocation. Most base
tools declare no support: their typed schema is fixed. The shipped one that does
is sandbox_exec — see the
sandbox concept.
The declared input_schema is part of the versioned body, not a create-only
field: it is set at create and updated through the save-version door just like
the baked kwargs, so a schema change is a new version you can roll back to an earlier
one.
When a preset bakes the base tool’s payload argument itself, that baked object is
treated as a set of partial defaults rather than an absolute constant: the
caller’s validated object deep-merges over it, caller-wins-per-key — nested
objects merge recursively, while a list or scalar the caller supplies replaces the
baked one (lists never concatenate). So a preset can pin default fields while still
letting a caller override any of them. Every other baked kwarg stays a fixed, hidden
constant.
State binding on a version
A preset version’s body may carry a
state_binding — the door-side binding that
injects an input-purpose template jq’s value before the tool runs and applies an
update-purpose op batch after it. The binding is part of the versioned body, so it is
validated and attached on every write — create, save-version, and rollback alike. A rollback
is not a bare re-point: it validates and attaches the target version’s binding exactly as create
and save-version do. Attaching is attach-on-use and idempotent, so a template that was merely
detached since the version was authored — but whose document still exists — is re-attached
automatically and the rollback succeeds. The rollback is refused (400) and nothing is
re-pointed only when the named template document no longer exists (it was deleted) or its
attach path is occupied — re-create the template document first (a merely detached template
needs nothing).
Admin registration tier
A base tool can declare the authorization tier that fences it. Declaring the admin
fenced (or secret) tier gates both authoring a preset over the base tool —
enforced at the shared preset chokepoint on every authoring door — and running
it, so a preset authored over a fenced base inherits the fence at run time. The
default stays the presets’ own write for a base tool with no declaration;
sandbox_exec declares fenced, because registering and running a runnable code
tool is an admin act. See fenced tools and the
sandbox concept.
See the create-and-version-a-preset guide
for the full workflow, the versioning spine for the
store presets ride on, and the CLI reference for the
complete tai presets command set.