Skip to main content
The Studio’s left sidebar leads with the Dashboard row, then groups every feature into five intent sections:
  • Capabilities — Tools, Agents, Presets.
  • Connections — Connectors.
  • Doors — Conversations, Hooks, Scheduling, Served endpoints, States.
  • Activity — Interactions, Notifications.
  • Administration — Members, Settings, Storage, Marketplace, Manifest, System.
Each screen drives one slice of the server’s /api/* surface, and every screen renders its own loading, empty, and error states rather than a blank page. This page tours the surfaces you reach most. A Studio plugin adds its own links to one of these sections, or to a single shared Plugins section that follows them. Wherever a form asks you to choose a tool, an agent, a preset, a route target or a channel, the choice opens a list with a search box above it (Search tools, Search agents, Search presets, Search targets, Search channels). Typing narrows the list to the options whose label or description contains the text, ignoring case — for a tool, the label holds both its display name and its real name, and an option with a description shows it as a quieter line under its label. Each change to the text moves the highlight to the first match and shows the list from its top. Arrow Up and Arrow Down move through the options, Home and End jump to the first and last one, Enter chooses one without submitting the form, and Escape closes the list. Tab and Shift+Tab close the list without choosing and move on to the next or previous control. Typing a letter on the closed field opens the list with that letter already in the search box. A checklist of tools carries its own filter box that narrows the same way. A form drawn from a JSON Schema — a tool’s run form, a state record, a preset’s settings — handles a schema that contains itself (an object with a field of its own type): it draws the nested levels the value actually holds, and below the deepest one shows an Add button that adds the next level. A field that takes one of several shapes (a oneOf or anyOf union) offers each shape under a name read from its own schema: its discriminator tag, else its title, else the name of the definition it refers to as words (MediaRef reads Media ref), else its constant or its allowed values (One of a, b, c, …), else a description of its shape — Text, Whole number, Number, True or false, None, Date, Email address, Link, List of text, Object with name, size, Map of number, or Any value. Members of a nested union join with “or” (List of text or whole number), and a title keeps its own case (List of URL). Two shapes that would read the same gain their position, as in Text (2).

Tools

The Tools screen lists every tool the manifest loaded — your own functions, mounted MCP servers, the standard toolbox, and any extension variants (a _batch or _chain branch appears as its own runnable tool). Select one to open its run panel. A link that names a tool (/tools?tool=<name>) selects it and opens the folder it is filed in, so the selected row shows in place; you can then browse to any other folder.
The Tools screen with a list of loaded tools.The Tools screen with a list of loaded tools.

The Tools screen: the master list of loaded tools and the run panel.

Running a tool has two modes — a synchronous run and a detached background run. Running tools covers the trade-off in full. The Tools screen is also where you organize the namespace through the tool-organization overlay. The list is a current-directory explorer: a breadcrumb, the current folder’s subfolders, then the tools filed there, with unfiled tools at the root. The folders are the one folder tree that the Presets and Agents screens also show; Tools shows the whole tree. Each tool renders its display name (its real name stays visible beside it) and groups by its merged tags — the tool’s read-only plugin-native tags unioned with your editable user tags. A tool whose effective visibility is hidden is excluded from this list outright — there is no toggle to reveal it; hiding is a display convenience, never a security boundary — a hidden tool stays callable. To bring one back, set its visibility to Shown from the CLI or API (tai tool-meta set <tool> --visibility shown); once shown it appears here again. The per-tool edit dialog is the one surface that edits all five overlay fields: display name, user tags, badges, folder, and a three-way visibility control (Default defers to the plugin’s declaration, Shown forces it visible, Hidden forces it out of the lists). Its Edit button appears once the tool overlay has loaded, so the dialog always opens on the tool’s stored values; a failed save shows the server’s error in the dialog. Above the list, a Filter tools box narrows the current folder to the tools whose real name or display name contains the text, ignoring case, and a row of tag chips (Filter tools by tag) narrows it to the tools carrying any selected tag of their merged tags, with an Untagged chip for tools that carry none. A failed read of the tags shows its error with Retry and no chips, so the chips never filter on missing data; past eight chips the unselected rest collapse into a +N more count. The filter lives in the page URL (?q= for the text, ?tags= for the tags), so it survives a reload, Back and a shared link; the text is applied on Enter or when you leave an edited box. The Presets and Agents screens carry the same filter, under the same rule. Each folder row carries Rename, Move and Delete actions. Move offers the folder tree as a parent picker (with inline create) and excludes the folder and its own descendants, so a folder can never become its own ancestor. Delete removes an empty folder only; the server refuses one that still holds tools or folders, and the dialog shows that refusal. The actions appear once the tool overlay has loaded, each only for a session allowed to make that change. A move, rename or overlay edit made on one screen shows on the Tools, Presets, Agents and Scheduling screens at once, without a reload: they all read the overlay under one shared query. With no tool at all, the screen shows No tools available with a Browse marketplace link, or No visible tools when every installed tool is hidden. Any folders still show below that line, each with its actions, so a screen with nothing filed can still rename, move and delete its folders. The Presets and Agents screens behave the same way with their own words. The screen also carries the app-tool live-registry admin card — Reload re-registers one app tool and Remove detaches it from the running registry, each named by its reloader kind and name and applied across the fleet. Reload is non-destructive; Remove asks the house confirm first. The card renders only for a session whose projection reaches those fenced doors.

Agents

The Agents screen lists every agent in one explorer: the agents the manifest registered and the agents you authored, each row and card marked with its kind. The explorer has the same folders, breadcrumb and list or card view as the Tools screen, rooted at All agents, and shows its own view of the folder tree. The list’s columns are Name, Kind, Description, Base agent, Active version and Actions; an authored agent’s card shows its base agent and its active version, the values its preset holds. A Filter agents box narrows the list to the agents whose name or description contains the text, and tag chips (Filter agents by tag, with Untagged) narrow it by the agents’ overlay tags. The chips show once the overlay has loaded. The filter lives in the URL as ?q= and ?tags=, the same as on the Tools screen. With no agent registered, the screen shows No agents registered with a link to the marketplace, and any folders still show below it with their actions. Each kind carries its own actions:
  • A registered agent has Run, which opens its streaming run panel, and a link to its run tool on the Tools screen. Clicking the row opens that run tool.
  • An authored agent has Run and Manage, a link to the Presets screen, where its versions, rollback and delete live. Clicking the row opens it there.
  • Either kind has Move, which files the agent into a folder, for a session allowed to edit the tool overlay; it appears once the overlay has loaded.
Compose agent, above the explorer, authors a new agent as a saved spec — a prompt, a tool selection, and presets baked over a base agent — that versions and rolls back like a preset. It shows only for a session allowed to create presets; with no agent installed that can be authored over, the screen shows No authorable agent installed with a link to the marketplace instead. Above the explorer the screen also reports on the authored agents as a group: a loading placeholder while they load, and a No authored agents yet card when there are none, which adds “Compose an agent to bake a prompt, tools, and presets over a base agent.” for a session that can compose. A failed read of the authored agents or of the folders shows its own error with Retry, and the rest of the list stays usable.
The Agents screen with the No authored agents yet card above the agents explorer, listing a registered tools_agent with a Run control.The Agents screen with the No authored agents yet card above the agents explorer, listing a registered tools_agent with a Run control.

The Agents screen: Compose agent and the authored-agents card above the agents explorer, each agent with its kind and a Run action.

States

The States screen drives the platform state store — the declared documents every door reads and writes per subject. It lists each declared state with its subject kinds, record and consumer counts, and last update, above a Declare state action and an Upload action that routes a .json file to the state or state-template upload door by its kind.
The States screen listing declared states with subject-kind badges and record and consumer counts.The States screen listing declared states with subject-kind badges and record and consumer counts.

The States screen: declared states with their subject kinds, record and consumer counts.

A Pending saves card sits above the list whenever the store is on: the pending saves not yet applied, refreshed every ten seconds, with a summary of how many are outstanding and how many failed. It opens on the Failed tab (or All outstanding when nothing has failed) and pages with Load more. Each row shows the save’s status badge (Pending, Calls queued, Calling, or Failed), its first subject and how many more, the calls it runs, its run, age, attempts, and last error. A failed row offers Retry, which reports whether the save applied, was queued again, or failed again, and Discard, behind a confirm that warns its writes and calls will never be applied.
The Pending saves card listing a failed save with its validation error, Retry and Discard actions, below the newer pending save on the same subject.The Pending saves card listing a failed save with its validation error, Retry and Discard actions, below the newer pending save on the same subject.

The Pending saves card on its All outstanding tab: a failed save with Retry and Discard, and the newer save on the same subject waiting behind it.

Opening a state leads to a detail page with four tabs. The Declaration tab edits the base JSON Schema as a nested field tree — attached subtrees are read-only and badged with their template — or points it at a stored template whose rendered value is the schema, with the resolved schema shown beneath; both sit beside the subject section (the kinds and the default kind; person keys are person ids from the conversations identity store) and the retention setting. Once a state has records the editor accepts only additive changes: removing or changing an existing field is refused.
The Declaration tab editing a state's base schema as a field tree with the subject kinds and retention settings.The Declaration tab editing a state's base schema as a field tree with the subject kinds and retention settings.

The Declaration tab: the base schema as a field tree beside the subject and retention settings.

The Templates tab lists the state’s attachments — template, path, and the parameter and declaration counts — with Edit declarations and Detach actions and an Attach template form that renders the template’s parameter and declaration schemas as fields. Below the attachments, a State templates section lists the platform state-template documents, each with its attached-on count and a shipped default badge, and an Upload template action; deleting a template that is still attached is refused. A Detach that a binding still uses is refused: the detach confirm reads what still uses the attachment when it opens and lists each holder under “Still used by — change these bindings first:” — a preset version, a conversation-route config, a hook, a schedule, or a holder a plugin declares — each linked to the screen that fixes it, with Detach disabled while any remains. Holders the caller’s role cannot read show as one plain-text count line per kind, not as links. Without access to read states, the confirm says the check needs read access to states, and the server decides at confirm.
The detach confirm listing a preset version under Still used by, with the Detach button disabled.The detach confirm listing a preset version under Still used by, with the Detach button disabled.

A held Detach: the confirm lists the preset version that still binds the attachment, as a link, with Detach disabled.

The Templates tab listing one attachment with its template, path, parameter and declaration counts and the edit and detach actions, above the State templates section with its Upload template action.The Templates tab listing one attachment with its template, path, parameter and declaration counts and the edit and detach actions, above the State templates section with its Upload template action.

The Templates tab: the state's attachments above the platform state templates.

The Records tab looks up one subject — a kind, a key, and a conversation target — lists a state’s subjects with a keyed Load more, and searches records by document content. A found record opens the record page. When some of the listed subjects are held by a failed pending save, a line above the table counts them, says their rows show the last applied data, and links to the Pending saves card.
The Records tab's Subjects table of thread subjects t-001 and t-002, below a line saying one subject on this state is held by a failed pending save and shows its last applied data, with a link to Pending saves.The Records tab's Subjects table of thread subjects t-001 and t-002, below a line saying one subject on this state is held by a failed pending save and shows its last applied data, with a link to Pending saves.

The Records tab's subjects with the line that counts the subjects a failed pending save holds.

The Records tab with a subject lookup form above a list of the state's subjects.The Records tab with a subject lookup form above a list of the state's subjects.

The Records tab: the subject lookup form above the state's subjects.

Below the subjects, a Check records against the schema card runs the stored-records check against the state’s current effective schema. A Kind select narrows it to one subject kind, and Check records walks every page of the state’s records and reports a summary — N checked — M do not fit — above a table of the misfits only: each row carries the subject, its target, when it was last updated, and its reasons as path — message, and links to the record. A state whose records all fit shows the count with no table; a store that is off reports the surface as unavailable rather than an empty result, and a page that fails part-way through the walk keeps the partial summary and misfits on screen beside the error, with a Retry that picks the walk up again at the page that failed. A line above the table names any subjects a failed pending save made the check skip.
The Check records against the schema card: a Kind select set to thread and a Check records button above the summary 2 checked — 1 do not fit, and a table with one misfit subject t-001 for tool studio_demo_echo, its last update, and the reason $.items[0].id — 7 is not of type 'string'.The Check records against the schema card: a Kind select set to thread and a Check records button above the summary 2 checked — 1 do not fit, and a table with one misfit subject t-001 for tool studio_demo_echo, its last update, and the reason $.items[0].id — 7 is not of type 'string'.

The Check records card after a check: the count of records checked and the misfits, each with its target, last update, and reasons.

The Consumers tab is the union of everything that binds the state — hooks, schedules, agents, presets and plugin consumers — each with an Open link to the surface that owns it. A consumer family the deployment cannot serve (no scheduling backend, for instance) is shown as a muted line rather than swallowed.
The Consumers tab listing what binds the state: one hook, notes-updater, supplying kind thread, with an Open action.The Consumers tab listing what binds the state: one hook, notes-updater, supplying kind thread, with an Open action.

The Consumers tab: everything that binds the state, each with an open link.

The record page shows one subject’s document with an editor over the effective schema (or a validated JSON textarea when the schema cannot be rendered as a form), a Fold into card that folds this subject into another behind a destructive confirm, and the Writes audit table — each write’s time, door, actor, consumer (with a preview of the write’s meta beneath it when it carries one), run id, and the paths it touched.
The record page showing a subject's document, a fold-into card, and the writes audit table.The record page showing a subject's document, a fold-into card, and the writes audit table.

The record page: the subject's document beside its fold action and its writes audit table.

Every States tab reports its own loading, empty, and error state, and the whole screen reports the store as unavailable when its database is unbound rather than inventing an empty list.

Dashboard

The Dashboard reads the server’s monitoring backend — run counts, cost, latency, and token totals over a time range, with a per-model breakdown and a trend chart. The demo instance below is populated from a monitoring backend seeded with example runs; with no backend registered, it renders a clean empty state instead. The per-model breakdown shows a distinct “unavailable for this range” notice when its figures could not be computed, so an unavailable breakdown never reads as an idle one.
The observability Dashboard with summary tiles and a trend chart.The observability Dashboard with summary tiles and a trend chart.

The observability Dashboard: run, cost, latency, and token summaries.

Conversations

The Conversations screen, under Doors in the sidebar, is the operator’s window over the conversation bridge — every route, whichever door it listens on, in one place. It both manages routes and monitors their traffic, and drills down in three levels, each of them a URL you can share or bookmark: the route picker, that route’s threads, and one thread’s transcript. The landing carries three tabs — Routes, Per-target configs, and Failed deliveries — so only the open one reads its door; on a no-backend deployment each reports its own off state rather than three stacked empties. The Routes tab is the route picker: it lists every route with the door it answers (a channel’s registry name, or the authed API), the identity that medium reaches you at, and the agent or tool a turn runs. Create route opens an upsert dialog, and each row edits or deletes its route. The dialog picks each name from the deployment’s own lists:
  • Target — a Target kind of Agent or Tool, then an Agent or Tool picker over the registered agents or tools. The group’s help says what a turn runs: an agent runs each turn as a threaded conversation; a tool is dispatched once per message, with no thread memory.
  • Door — Channel or API. The help explains the choice: a channel answers on its own medium; the API door answers to a callback URL, or the caller polls. A channel door picks an installed Channel and takes Our identity, the address on that channel that clients message; its help is the channel’s own hint for what to enter. An API door takes a Callback URL.
  • Execution key — the api key each turn runs as. Every tool call the turn makes is authorized against that key’s live grants, so prefer a least-privilege service key over a human’s broad one. The picker lists the api keys you own (an admin sees every key) and offers an api-key session its own identity when the list does not hold it; an account session is told to mint a key first. A stored key outside the list is flagged not a listed key, and when it is not the session’s identity the dialog says so before save (for a non-admin, that the server will refuse it). With access control off the dialog has no Execution key field, and the route runs as the gate-off identity.
A stored value the deployment no longer lists stays selected and is flagged — not registered for an agent or tool, not installed for a channel — with “Pick another, or saving is refused.” When the deployment lists none, the picker says so and links to the Agents screen or to the marketplace’s plugins or channel plugins. A picker the session may not list becomes a text box named by its field, which takes the exact name and keeps a stored value, with a note under it:
  • Agent — “This session cannot list agents, so type the agent’s exact name. Saving checks that the agent is registered.”
  • Channel — “This session cannot list channels, so type the channel’s exact name. Saving checks that the channel is installed.”
  • Tool — “This session cannot list tools, so type the tool’s exact name. Saving checks that the deployment serves it.” The same holds on every tool field in the Studio except the MCP include and exclude lists, which report the failed read instead.
The server checks each typed name on save and refuses one it does not know, with its own message.
The Create route dialog's Door group with the Channel door and a channel picked, showing that channel's hint for the Our identity field.The Create route dialog's Door group with the Channel door and a channel picked, showing that channel's hint for the Our identity field.

The Door group with a channel picked: the channel's own hint under Our identity.

The Create route dialog's Target group with an Agent target, showing the Agent text box and the note to type the agent's exact name.The Create route dialog's Target group with an Agent target, showing the Agent text box and the note to type the agent's exact name.

The Target group when the session may not list agents: the Agent field takes a typed name.

The Edit route dialog's Execution key field with a stored key flagged as not a listed key and not this session's identity.The Edit route dialog's Execution key field with a stored key flagged as not a listed key and not this session's identity.

Editing a route bound to a key outside the list: the key is flagged as not a listed key and not this session's identity.

The Create route dialog's Execution key field with this session's own identity picked and the line saying the route runs as that identity with its live grants.The Create route dialog's Execution key field with this session's own identity picked and the line saying the route runs as that identity with its live grants.

An api-key session with no listed keys picks its own identity as the execution key.

The rest of the route is authored through a schema form whose rules come from the contract’s published route document: a route name of lowercase letters, digits and hyphens, an optional absolute https callback URL with no user name or password (leave it blank for a poll-only route), a :-free channel name, and an error reply of up to 2,000 characters. A value that breaks its rule is refused in the form with that field’s own message, before anything is sent. Either target kind carries the route’s jq programs — start_expr, reply_expr, and the door-contract cancel_expr / resume_expr / extras_expr. Each is the route document’s own templated-text field with the document’s own wording: its Inline text side renders the visual jq editor in place, and its Stored template side picks a stored template and its kwargs. Editing a route keeps a stored template and its kwargs exactly as saved. An api-door save that sets a callback_url mints a fresh callback_secret and reveals it exactly once in the dialog under a loud caption — every such save rotates it and no door can re-read it, so copy it then; a channel save and a poll-only api save (no callback_url) carry no secret and close straight away.
The Create route dialog for an agent target, showing the door-contract jq fields and the Execution key picker.The Create route dialog for an agent target, showing the door-contract jq fields and the Execution key picker.

The Create route dialog for an agent target: the door-contract jq fields and the execution key.

Selecting a route opens its threads — one row per client address, with when it last spoke, how many exchanges it holds, and where its newest answer’s delivery stands. Selecting a thread opens its transcript beside the list; below a 1024px viewport the split collapses to whichever pane the selection names, with a Back link.
The Conversations screen with a route's thread list beside a selected thread's transcript.The Conversations screen with a route's thread list beside a selected thread's transcript.

The Conversations screen: the thread list for a route beside the selected thread's transcript.

The thread list also lets an operator reset many threads at once. Each row carries a checkbox, and a header checkbox ticks or clears every currently loaded row (and shows a mixed state when only some are ticked). The selection is always the rows on screen — “select all” means the loaded page, never an unbounded server-side set. A Reset selected (N) danger action, offered only to a session that may delete on the route, forgets every ticked thread behind the house confirm, whose copy and confirm label both name the count and state that each thread’s transcript and the agent’s memory of the conversation are forgotten and that it cannot be undone. The reset fans the same per-thread delete door out one thread at a time — there is no “reset everything matching a filter” server action. Results are reported per item: threads that reset cleanly drop from the list, while a thread whose reset failed — for example a 409 because a turn is in flight — stays as a failed row carrying its reason and a Retry, so a partial failure is visible and recoverable thread by thread.
A route's thread list with every row and the header checkbox ticked under a Reset selected (3) danger button, each row showing its address, last activity, message count, and delivery status.A route's thread list with every row and the header checkbox ticked under a Reset selected (3) danger button, each row showing its address, last activity, message count, and delivery status.

Bulk reset: the header checkbox ticks every loaded thread and the Reset selected action names the count.

The Reset selected threads dialog asking to reset 3 selected threads, saying each thread's transcript and the agent's memory of the conversation are forgotten and that it cannot be undone, with Cancel and Reset 3 threads buttons.The Reset selected threads dialog asking to reset 3 selected threads, saying each thread's transcript and the agent's memory of the conversation are forgotten and that it cannot be undone, with Cancel and Reset 3 threads buttons.

The bulk-reset confirm: its copy and its confirm label both name the count and say it cannot be undone.

The thread list after a bulk reset: a summary reading 2 threads reset, 1 thread could not be reset, the failed thread's reason that a turn is in flight beside a Retry button, and the one remaining failed thread still selected.The thread list after a bulk reset: a summary reading 2 threads reset, 1 thread could not be reset, the failed thread's reason that a turn is in flight beside a Retry button, and the one remaining failed thread still selected.

The per-item result: threads that reset drop from the list, and a thread that failed stays with its reason and a Retry.

A transcript live-tails: it re-reads the newest page every few seconds, so a new exchange lands with no interaction, and the foot of the pane says so. Load older messages pages backwards through history; past a few pages of history the tail pauses — it says it has, and Jump to latest puts it back on the newest page and resumes it. The thread list refreshes on its own cadence and pauses the same way when paged deep, with Back to the newest to resume. Each exchange renders the visitor’s message verbatim as plain text, the agent’s answer as markdown, and two chips: the answer’s outcome and its delivery state. When the visitor’s message carried inbound attachments, they render in the visitor bubble beside the text — an image inline, a document as a labelled download card, and audio or video in a native player — each loaded from the platform’s own served-media reference. A failed delivery takes the danger tint plus a rule down the exchange, so a failure never reads as an ordinary row. An admin also gets a Delivery detail disclosure carrying the attempt count, each part sent with its length and provider message ids, and the retained error. A message an operator sent by hand (its record’s origin is operator) has no visitor side: it shows as one bubble on the agent’s side, labelled Operator and set apart by an accent border, so a human’s reply reads as distinct from the target’s. A message the platform sent on its own (a send record) is a bubble on the agent’s side, labelled by what the record proves. A send made inside a turn to its participant is drawn inside that turn’s exchange, labelled Agent, or Agent · question for a question. Any other send is its own exchange, in neutral colours with a dashed border, labelled Sent message, Question, Platform notice or Channel notice. A question’s reply over the channel shows under it as Visitor · reply; for a question asked as sensitive it reads “Reply received · not stored (sensitive)”. A send whose outcome is unknown takes the danger tint and the rule down the exchange, like a failed one, with the chip Sent, outcome unknown. A message that replied to an earlier one shows a quote block above it, a muted inset with a leading rule: Replying to and the quoted text in full. When the thread’s record does not hold the quoted message it reads “Replying to a message this thread’s record does not hold. It may be in an earlier thread of this conversation, whose text is not shown here, or it expired, was forgotten or was never recorded.” with the message id; a quote of an answer to a sensitive question reads “Replying to an answer to a sensitive question. Its text is not kept.”; a quoted message with no text reads “Replying to a message with no text.” A reply to a question shows the same block on its Visitor · reply bubble.
A conversation transcript with a visitor's inbound image and document attachments in the visitor bubble.A conversation transcript with a visitor's inbound image and document attachments in the visitor bubble.

A thread transcript with the visitor's inbound attachments — an inline image and a document download card — rendered in the visitor bubble.

A conversation transcript showing an agent message sent mid-turn inside the visitor's exchange; a Sent message bubble whose open Delivery detail lists part 1 with its length and provider message id; a Question bubble with a reply received but not stored as sensitive; a Platform notice; a failed Channel notice; and a Sent message with the chip Sent, outcome unknown, whose open Delivery detail shows one attempt and the error that the send did not answer in time and its outcome is unknown.A conversation transcript showing an agent message sent mid-turn inside the visitor's exchange; a Sent message bubble whose open Delivery detail lists part 1 with its length and provider message id; a Question bubble with a reply received but not stored as sensitive; a Platform notice; a failed Channel notice; and a Sent message with the chip Sent, outcome unknown, whose open Delivery detail shows one attempt and the error that the send did not answer in time and its outcome is unknown.

A thread transcript with send records: a send made during a turn inside that turn's exchange, a sent message and a sensitive question asked outside any turn, a platform notice, a channel notice whose send failed, and a sent message whose outcome is unknown, both open on their delivery detail.

A conversation transcript where one visitor bubble carries a Replying to block with the assistant's earlier answer, and another carries the line saying this thread's record does not hold the quoted message, with its message id.A conversation transcript where one visitor bubble carries a Replying to block with the assistant's earlier answer, and another carries the line saying this thread's record does not hold the quoted message, with its message id.

A thread transcript with two replies to earlier messages: one quoting the assistant's earlier answer, shown in full, and one quoting a message this thread's record does not hold.

Beside the transcript, an open thread carries two operator actions over the otherwise read-only monitor:
  • A control-mode toggle — Agent replies automatically — reads the thread’s mode and flips it between agent (the target answers new messages) and manual (new messages wait for a human).
  • A compose box at the foot sends a reply into the thread by hand, delivered from the route identity; on success the tail shows it at once.
Both are the door’s write action. A capability-scoped or read-only session may set neither: the refusal is shown inline, never swallowed. An open thread also carries two destructive actions, each behind the house confirm:
  • Delete thread forgets this one thread — its transcript records and the agent’s checkpoint memory of it — so a later message on the same address starts fresh.
  • Erase person is the GDPR right-to-erasure action, offered only when the thread’s transcript names a linked person. It erases the person entirely — their aggregated thread and checkpoint, their person row, and every address-to-person link — across every route they ever wrote under. The confirm states that blast radius plainly. A thread whose transcript reads as not found (every record pruned) names no person, so the action is not offered there; erasure stays reachable through DELETE /api/conversations/persons/{person_id} and the CLI.
A route’s drill-in also shows the panel its channel’s Studio plugin contributes, when one does — for a web route, the web channel’s panel, where the deployment lists tai42_channel_web under studio_plugins. It has two sections. The Entry gate carries the Require an entry code toggle and the route’s live codes, each with its label, creation time, expiry, and a Revoke action. Words carries one input per word the route can set: the refusal pages’ headings and texts, and the sentences the chat draws when one of its requests is refused.
The Entry gate panel with Require an entry code checked and one code labelled launch that never expires, with a Revoke action.The Entry gate panel with Require an entry code checked and one code labelled launch that never expires, with a Revoke action.

A gated web route's drill-in: the Web chat panel's Entry gate section with one live code, and its Words section below it.

A route with no live code says so in the panel.
The Entry gate panel with its toggle off and a No entry codes notice.The Entry gate panel with its toggle off and a No entry codes notice.

An ungated web route's drill-in: the Web chat panel's Entry gate section with no entry codes.

Mint entry code opens a dialog that shows the new code and its chat link — the route’s chat URL with ?tai_entry=<code> appended — each with a copy control. The code is shown once: only its hash is stored, so a lost code is revoked and a new one minted.
The Mint entry code dialog showing the new code and the chat link with copy controls, a never-expires note, and a Done button.The Mint entry code dialog showing the new code and the chat link with copy controls, a never-expires note, and a Done button.

The mint dialog: the shown-once entry code and the chat link that carries it.

Words draws the route’s set words in their inputs and leaves the rest blank; a blank input draws the built-in English. A save replaces the route’s whole set, and a clear removes it.
The Words section listing the refusal pages' heading and text inputs, with Entry refused set to Members only and Ask for a fresh link to join.The Words section listing the refusal pages' heading and text inputs, with Entry refused set to Members only and Ask for a fresh link to join.

The Web chat panel's Words section, with the entry-refused page's own heading and text set.

When the web channel’s routes are not mounted on the deployment, both sections say so — Entry gate unavailable and Words unavailable — and nothing can be read or changed here until the web channel is loaded.
The Entry gate panel reading Entry gate unavailable, explaining that the web channel's routes are not mounted on this deployment.The Entry gate panel reading Entry gate unavailable, explaining that the web channel's routes are not mounted on this deployment.

The Web chat panel on a deployment where the web channel's routes are not mounted.

The Per-target configs tab is the per-target admin surface: each row is a stored config for one target (its kind and name), whether the target opts into person linking, and its first-contact greeting. Create config opens an upsert dialog; each row edits or deletes its config behind a confirm. The greeting is a str.format template — plain text, not jq — and may reference at most the {pairing_code} placeholder minted at greeting time; a blank template means no greeting. The Failed deliveries tab lists every answer whose delivery ended failed, across every route and caller, each rendered with the route and address it belongs to and an admin-only error disclosure. It is an admin-only door — a scoped session’s refusal reads as a capability boundary — and it refreshes on its own so a delivery a retry has since cleared drops off.
A thread listing spans every caller on a route, so its door is admin-only — a capability-scoped session is told that as a capability boundary, not a failure. The screen needs the conversations Redis backend; with none configured, every door refuses 501. See the read doors.

Marketplace

The Marketplace screen browses the marketplace plugin index through the server. A Browse tab runs an item-level search with facet chips for kind, category, and tags and a sort control; an Installed tab lists the plugins this server has installed and their update availability. Each tab has a Card view / List view switch: Browse opens on cards and Installed on the table, and each tab remembers its choice in the browser. In either view, clicking a card or a row opens the plugin’s detail, and Load more adds the next page of results. Selecting a listing opens its detail — the items it carries, its version history, its description, and homepage and repository links; when the plugin is installed, any security advisories affecting the installed version show there too. The detail drives install, update, and uninstall against the server’s /api/marketplace/* routes, with progress surfaced in place. The install, update, uninstall and Upgrade all result shows the fleet report and the worker recycle report. A session that is not an administrator sees, in place of Install, Update or Uninstall on a plugin’s page and of Upgrade all on the Installed tab, a line naming the route it lacks.
The Marketplace screen on its Browse tab, with the search box, facet chips and the plugin cards.The Marketplace screen on its Browse tab, with the search box, facet chips and the plugin cards.

The Marketplace screen: the Browse tab's search, facet chips and plugin cards.

System

The System screen is the operational read-out: a health badge from the server’s liveness probe. It is the fastest way to confirm a server is up and answering.
The System screen showing a 'Healthy' badge from the server's liveness probe.The System screen showing a 'Healthy' badge from the server's liveness probe.

The System screen: a populated 'Healthy' badge from the server's liveness probe.

The health badge reads Healthy on this demo instance, confirming the server is up and answering its liveness probe.

Connectors

The Connectors screen, under Connections, is the directory of outside apps and MCP servers. A search box and filter chips narrow it; each card is marked Installed or In marketplace, and the marketplace listings load page by page with Load more. A provider card lists each of its connections with its tools, a Pick tools action and a Fix action for a connection that needs it, and Add another account for a second account. Connecting runs three steps: connect, sign in, pick tools; a sign-in that redirected the whole tab resumes the flow back here. Installing a marketplace listing happens inside its Connect step and needs an administrator; any other session sees a line naming the install route in place of the listing’s Connect. Below the directory sit the connections list and Add a custom server. The picks are the MCP entry’s include / exclude:
  • A server in exclude mode stays in exclude mode when its picks are saved. In exclude mode a Save with nothing ticked is allowed (every tool the server lists now is unmounted; tools it adds later mount); in include mode at least one tick, or mount every tool, is needed.
  • A name the server no longer advertises shows Not advertised and stays picked until unticked.
  • A picked tool that is not mounted says why: the server failed, a preset owns the name, its input schema cannot be used, or the server did not list it at the last load.
  • Unticking a tool a preset or another reference uses is refused (see Unpicking a used tool), and the section lists those holders as links; holders the caller’s role cannot read show as one plain-text count line per kind.
  • With several sub-services, one Save writes them one after another, and Save remaining retries the rest after a failure.
A session missing a read sees the directory draw what it can read, with a line naming each missing route.
The Connectors screen with its search box, filter chips and directory cards above the connections list.The Connectors screen with its search box, filter chips and directory cards above the connections list.

The Connectors screen: the directory of apps and MCP servers above the connections list.

The MCP status and the configuration editor for the manifest’s mcp array live on the Manifest screen. That editor and the environment editor under Settings each carry an unsaved-changes guard: navigating away or a full-page unload with edits in flight first confirms, so a half-finished edit that would reload the fleet is never dropped silently.

Served endpoints

The Served endpoints screen, under Doors, manages the derived sub-MCP servers — the private tool sets that serve a curated subset of tools under their own slug and MCP endpoint. It lists every entry with its transport and its concrete /app/{slug} connect URL (copy-to-clipboard) and a delete control behind a confirm. Create builds a new entry from a slug, a Tools checklist (each tool by its display name, with a Filter tools box that narrows the list and keeps ticked tools selected while they are hidden), and a transport; a slug that already exists is flagged and the replacement confirmed, because register is a silent-swap upsert.
The Served endpoints screen with the sub-MCP server list above the create form.The Served endpoints screen with the sub-MCP server list above the create form.

The Served endpoints screen: the sub-MCP list above its inline create form.

Manifest

The Manifest screen, under Administration, is the config-artifact inspector. It holds:
  • The manifest — the preserved manifest document the server loaded, !ENV markers intact, as an expandable tree.
  • MCP servers — every mounted MCP server, healthy or failed, each with a Reload control (a failed server can be reloaded, or deregistered, in place), and the configuration editor that writes the manifest’s mcp array: a form view (one editor per entry: a schema-driven transport form plus a tool composer for the include / exclude lists) and a raw-JSON escape hatch over the same working list. A connector-owned entry renders read-only — it is kept in sync by its connection, so you remove it by disconnecting. A malformed edit is a loud inline error and no request is sent. Both views check the include and exclude names against the tools the server advertises before the entry’s own selection. The server keeps a name it does not advertise (it may bind later, or through a stored preset), so the editor warns and never refuses the save. In the form view each such name’s chip reads “<name> (not advertised)”, and a warning line follows the list: “This server does not advertise <names>. Saving keeps them, but they bind no tool from this server.” for include, or ”… but they hide nothing.” for exclude. The add row offers every advertised tool, found by its name or its description. A server the status does not carry yet reads “This server is not mounted yet, so its tools cannot be listed. Save it to pick from its tools. Existing entries above stay editable.”, and a failed read shows its error in place of the picker. The raw-JSON view gives the same warning under the text, naming the server and the list (“The server “<title>” does not advertise <names> in its include list. …”); a failed read there shows its error with Retry, and its names stay unchecked until the read succeeds. Both views share one read per server, and a save reads it again.
  • Extensions — the extensions the server loaded, grouped by kind, and the manifest’s extension settings: extensions_modules, backend_module and the per-tool extension maps of tools, mcp and api_tools. Extensions are attached to a tool on its Tools page or in a preset; see Tools and extensions.
  • Section doors — the tools and agents per-entry add and remove controls (an entry is added by pasting its JSON with its title validated, removed by naming its title), and the api_tools include/exclude curation (a name is added to or removed from a list).
Every write crosses the config pipeline and reloads the fleet, so a save re-reads the artifact and reports any failed propagation; removes ask the house confirm. Each door renders only for a session whose projection can reach it. (The derived sub-MCP servers live on Served endpoints.)
The Manifest screen showing the manifest document as an expandable tree, the MCP servers block and the Extensions section with the loaded extensions by kind and the manifest's extension settings.The Manifest screen showing the manifest document as an expandable tree, the MCP servers block and the Extensions section with the loaded extensions by kind and the manifest's extension settings.

The Manifest screen: the manifest document, the loaded extensions by kind and the manifest's extension settings.

Flows

When the Babelfish Flows plugin is installed, it contributes a top-level Babelfish Flows page and its own sidebar entry (and a companion Babelfish State page for the flow engine’s declared state). A plugin entry that names one of the built-in sections joins that group after its core rows; an entry that names none renders in the single shared Plugins section that follows the core sections, each such entry carrying its own host-applied provenance badge. The Babelfish Flows page is the flow engine’s own editor — its flow canvas, the node edit panels, the Router card and its dispatch modes, the visual jq editor, and per-node state bindings — documented on the Flows editor page.

The scoped Studio

There is one Studio, not a separate portal per role. When you sign in with an owned key — a capability-scoped credential rather than a full-privilege one — the shell renders from that key’s capability projection (GET /api/auth/me): the sidebar trims to the features the key can reach, each list slices to what its scopes cover, and the data-bearing inboxes filter to the key’s own identity. Nothing is hidden by the client; the projection is the same derived, jq-exact view the request gate enforces, so the UI never shows a door the server would then deny. The screen below is the same Tools screen as above, but signed in as an owned key: the sidebar carries only Tools, Interactions, Notifications, and Settings — the operator surfaces (Dashboard, System, Marketplace, and the rest) are gone. This key’s projection reaches the tool surface, so its catalog is the full list; the scoping visible here is the trimmed navigation, and the genuinely sliced views are the identity-filtered inboxes below. A key whose scopes did not reach the tool surface would list fewer tools — or carry no Tools link at all.
The Tools screen under a capability-scoped owned key, with a trimmed sidebar.The Tools screen under a capability-scoped owned key, with a trimmed sidebar.

The scoped Tools screen under an owned key: the operator surfaces are gone from the sidebar; this key reaches the tool surface, so its catalog is the full list.

The Interactions and Notifications inboxes are isolated by identity. A restricted key sees only messages addressed to its own key identity by audience — the pending questions and notifications keyed on that key itself (its own island, not shared with sibling keys under the same owner), never another identity’s slice and never an operator’s broadcast.
The Interactions inbox under an owned key, showing only the questions addressed to this identity.The Interactions inbox under an owned key, showing only the questions addressed to this identity.

The scoped Interactions inbox: only the questions addressed to this owned key's identity.

The Notifications inbox under an owned key, showing only the notifications addressed to this identity.The Notifications inbox under an owned key, showing only the notifications addressed to this identity.

The scoped Notifications inbox: only the notifications addressed to this owned key's identity.

You hand an owned key to another device without exposing the secret by minting it from the API-keys tab and turning it into a one-time QR claim link in the create dialog. The owned-keys guide walks the full flow.
The API-keys create dialog turning a newly minted key into a one-time QR claim link.The API-keys create dialog turning a newly minted key into a one-time QR claim link.

The mint dialog's claim-link step: a newly minted key rendered as a one-time QR claim link.

The rest of the sidebar

Every screen follows one of a few layouts; a screen with tabs under its header (Settings, Dashboard, Marketplace, the Conversations landing) has one per tab:
  • Browse and inspect — a list beside the open item’s detail, the selection kept in the URL: Tools, Presets, States, Storage, and a Conversations route with its threads beside the open transcript.
  • Catalog — a directory of cards with search and filters: Connectors, the Tools card view, and the Marketplace Browse tab.
  • Registry — records in tables or lists, with create and row actions in dialogs and a destructive action confirmed before it runs: Scheduling, Members, the Conversations Routes and Per-target configs tabs, the Marketplace Installed tab, and the Profiles, API keys and Roles tabs of Settings.
  • Registry with inline create — the records first, then the create forms on the page below them: Hooks and Served endpoints.
  • Replaced detail — one item takes over the page or the tab: a Marketplace plugin’s page, a trace on the Dashboard’s Tracing tab, a state template or a state’s records.
  • Run surface — a list, then the run view in its place: Agents.
  • Inbox — newest first, with a button at the foot for the rest: Load more on Interactions, where the server pages, and Show more on Notifications, where the whole feed is already loaded.
  • Status and configuration — stacked cards of read-only views and in-place editors: Manifest, System, the Dashboard tab, the Settings, Environment and Backup tabs of Settings, and the Conversations Failed deliveries tab.
The screens not toured above:
  • Presets (Capabilities) — versioned tool-wraps and their version history; each preset also renders and edits its tool-organization display name and tags (folder navigation and hide controls stay on the Tools screen). See Presets for the create dialog and the fixed-kwargs editor.
  • Scheduling (Doors) — scheduled tool runs.
  • Members (Administration) — the people and pending invitations of the accounts providers, with the actions they declare; see Members.
  • Settings (Administration) — server configuration, environment values, and API keys (see Admin console and API keys). An environment save of a recycle-class setting shows the worker recycle it rolled. A Reload config control re-reads env and re-initializes the deployment from its manifest in place, applied on the serving worker and broadcast to the fleet; a worker whose reload left a recycle-class setting waiting for a restart is listed with it, a worker the stored configuration did not build on is listed apart, and the worker recycle the reload rolled shows under them. The System screen’s Workers card reloads the fleet or the workers selected in it, and shows the same report. The report’s first line states which workers it covers: Reload converged across the fleet., Reload converged on the 2 selected workers., or, for a worker with no other to reach, Reload applied. Only this worker reloaded; no other worker was on the worker bus when it ran. or Reload applied. Only this worker reloaded; no worker bus is configured. A warning line ends with the same statement, unless the reload covered the fleet. A reply with one row that does not show the worker was alone reads This reload's report has a row only for serve-1. The line then says what the reply states of each other worker it names: the sections below cover it, it confirmed it needs no restart and was left running, a new life of it came up that the report ties to no restart, or it takes over the worker restarts after the reply and its log holds their outcome. The line stays a success line only when every worker it names recycled or confirmed it needs no restart; otherwise it is a warning. Neither says the reload was applied.

Hooks

Hooks (Doors) — event hooks wired to the server, the topic verifiers and the trigger links; see Hooks and trigger links.
The Hooks screen listing the registered hooks above the Register hook form.The Hooks screen listing the registered hooks above the Register hook form.

The Hooks screen: the registered hooks above the register form.

Interactions

Interactions (Activity) — the human-in-the-loop inbox of pending questions.
The Interactions inbox listing pending questions, newest first.The Interactions inbox listing pending questions, newest first.

The Interactions inbox: the pending questions, newest first.

Notifications

Notifications (Activity) — the notifications inbox.
The Notifications inbox listing notifications, newest first.The Notifications inbox listing notifications, newest first.

The Notifications inbox: the notifications, newest first.

Storage

Storage (Administration) — one screen over the stored resources. The storage list or the templates list opens it, whichever your access reaches, and each action shows only where your access and the doors the deployment mounts reach it: the storage doors serve a caller with the storage grant, and the templates doors serve a caller holding only the templates grant. A deployment that mounts neither, or a caller who reaches neither list, reads “Storage is not served here”. Opening an object shows it by kind:
  • Text — its content, with a Render preview where your access reaches the template render.
  • Document — the extracted text, marked “Extracted text; this object is not a template.”, with no template action.
  • Binary — “Binary object: no text preview.”
An object the server cannot show — over the size cap, text that is not UTF-8, or a file its extractor cannot read, such as an encrypted PDF — shows the server’s message with Download and Delete still offered. Upload writes one text object or a batch of files; before anything is sent it flags ids that already exist or repeat in the batch and, for a caller who stores text only, files that are not UTF-8 text. Clear cache clears the template cache and says “Template cache cleared.”. Channel media is not listed here: the server leaves it out of both lists. A Delete that the server refuses because something still names the object keeps the dialog open and lists each holder, as a link, under “Still used by — remove these first:”. Holders the caller’s role cannot read show as one plain-text count line per kind.
The Storage screen with the resource list beside the open welcome-email.md text object.The Storage screen with the resource list beside the open welcome-email.md text object.

The Storage screen: the resource list beside an open text object.

The Storage Delete dialog for jq/gate, refused, listing the hook whose condition names it.The Storage Delete dialog for jq/gate, refused, listing the hook whose condition names it.

A refused Delete on the Storage screen: the hook that still names the object, listed as a link.

Several of these surfaces are backed by Redis or Postgres in the running server; a screen whose store is not provisioned says so plainly instead of showing a fabricated state. See the Studio SDK reference for the components and hooks these screens are built from.