The operations layer
Every management capability is an operation — one decorated async function that carries its own metadata: a name, a summary, the typed errors it may raise, and flags such asdestructive. An operation is the single source, and three
surfaces project from it:
- an HTTP route under
/api/*, the door the CLI and the Studio drive; - a CLI command in the
taigroups; - an MCP tool, registered through the normal tool binding so an
extension wraps it, a preset
bakes over it, and
tai tools runor an agent dispatches it — subject to curation (below).
- Tools — re-register one app tool from its stored definition (
reload_tool), drop a tool from the live registry (remove_tool), and run a registered tool by name (run_tool). - Config — soft-restart in place by refreshing the environment and reloading
the manifest registries (
reload_config). - Manifest — replace the whole persisted manifest, fleet-wide
(
update_manifest). The change is written through to durable config and then broadcast, so it survives a restart and reaches every worker. - MCP servers — list the servers a viability check skipped (
list_failed_mcps), re-probe and re-attach one server or every failed one (reload_mcp,reload_failed_mcps), and detach a server’s tools by title (deregister_mcp).
Curating the MCP surface
Which operations project as MCP tools is set by the manifest’sapi_tools block. Two exclusion tiers
guard the surface regardless of what a manifest asks for:
- Tier 1 — never projectable. A meta-executor — an operation that runs any
tool by name,
run_tool— is a universal authorization bypass, so it is hardcode-blocked from the MCP surface even when named inapi_tools.include. It stays a route, thetai tools runcommand, and an internal dispatch target. - Tier 2 — off by default, includable. An authority-changing operation — one
that mints or scopes keys, edits policy, replaces the manifest
(
update_manifest), or restores unshipped state (a backup import) — is off the default MCP surface but projects when anapi_tools.includenames it explicitly.
destructiveHint annotation on its
projected tool (and an x-destructive marker on its route in the
OpenAPI spec); it only projects when
api_tools.expose_destructive is on.
Authorization on every MCP surface
AuthzMiddleware authorizes every projected-operation dispatch. It runs at tool
call time — before the extension chain, so before any backend enqueue — on every
MCP-serving instance: the main server and each sub-MCP
mount. It resolves the dispatched tool to its base operation and, for a projected
operation, runs the access-control check; a denial raises a loud tool error. A
tool that is not a projected operation passes straight through — its authorization
is a separate concern.
The mutation pipeline
A mutating operation runs one pipeline: it validates the change, persists it transactionally to durable config, reloads the calling process, then broadcasts it on the worker bus and awaits a per-origin outcome from every worker sharing the manifest. Persist-before-broadcast is why a mutation survives a restart and why the persisted state and the live state cannot drift apart through a supported path. The broadcast reaches every process on the bus — serving HTTP workers and any backend runtime alike — not just the one that received the request. Fan-out is the app’s own concern, carried by the bus; it is not a backend capability. Every mutation response embeds the fleet report, so a worker the change did not converge is named —departed,
timed_out, or missing — never a fake success. A failed local apply raises;
a remote miss is reported and logged. Inspect the fleet and re-drive a reload with
the tai fleet group.
Because a mutation reaches every worker, the deployment shapes that run more
than one process need the bus: the boot rules refuse a multi-worker server, a
registered backend, or k8s mode without TAI_BUS_REDIS_URL.
Reloading is a first-class state
While a reload is in flight, the routes that dispatch a run or mutate live registries return a retriable503 carrying a reloading marker and a Retry-After
header, rather than serving against a half-swapped registry. A client treats it as
a transient condition and retries — the reload is a state the API describes, not an
error.
Soft restart
reload_config is the soft restart: it refreshes environment values and reloads the
manifest registries in place. Combined with live config
and manifest updates, it lets a server pick up configuration and capability changes
without dropping its process. Across a fleet, tai fleet reload-config
soft-restarts every worker on the bus (or only --target ones) and returns the
per-origin report — see Manage a fleet.
The kinds summary
A server reports which pluggable kinds it actually resolved at startup. Every kind — identity and accounts providers, monitoring, storage, backend, channels, webhook verifiers, config, and Studio plugins — gets one status row, in three states:- active — a real plugin is registered and serving the kind.
- default — nothing is configured, but a built-in fallback is serving in its place (the no-op monitoring recorder, or the file config provider). The kind works; it is just running on the built-in.
- off — nothing is registered and the kind has no built-in fallback. This is a legal, reported state, not an error.
[kinds] summary block in the
server’s startup log (and again after any in-place reload), served at the authed
GET /api/system/kinds route, and rendered in the Studio’s System kinds
table. Read them from a terminal with:

