Mount the agent studio. Don't hand-build it.
<AgentStudio> is a themeable, host-wired admin surface for editing agent config in the database, the companion to the DB-driven agent-config surface. Mount it and an operator gets the roster, a full editor for every editable field, tool and skill grants, audiences, and version history with a field-level diff and CAS-safe publish and rollback. No bespoke list, editor, or version UI to build.
Experimental. The @nightowlsdev/react/studio subpath may change shape in minor releases until it stabilizes. The package root and every other subpath keep normal semver discipline; nothing here touches them.
Reach for this when
- operators edit agent config from the database, not from redeploys;
- you want a diff, CAS-safe publish, and append-only rollback for free;
- a change that weakens governance should read as a loud red path;
- you would rather mount a studio than hand-build a roster, editor, and history UI.
Reach for something else when
- agents are code-defined and immutable in your deploy — there is nothing to edit at runtime;
- you also manage skills, knowledge, or tools — wrap this section in <AIStudio> rather than mounting it bare;
- you are driving your own UI — talk to the config-surface routes directly;
- a frozen admin API matters more than convenience — this subpath is experimental.
What the studio edits
The editor covers every editable field of an agent version: persona (markdown with a live preview), model, delegates, tool and skill grants over the capability catalog, memory, rules, and workflow. Audiences (who can reach the agent) are edited separately, because they ride the head row rather than the version axis, so no publish or rollback can silently widen exposure. Saving a field publishes a new immutable version; audiences save through their own write.
Mount it, in one import
Pass a client. createEndpointsClient derives the whole route family from one base (any single route can be overridden), and getToken is the same bearer seam the chat provider uses.
import { AgentStudio, createEndpointsClient } from "@nightowlsdev/react/studio";
// One import mounts the whole studio. createEndpointsClient derives the FR-032 route family from a single
// base; getToken is the same bearer seam as <SwarmProvider>. theme is the usual theme system (name / object).
export function AdminAgents({ getToken }: { getToken: () => Promise<string | null> }) {
return <AgentStudio client={createEndpointsClient({ base: "/api/swarm", getToken })} theme="ink" />;
}Wire the routes: authorization is server-side
The client targets six admin routes. Mount them with @nightowlsdev/runner-nextjs, all behind guardDefinitionAdmin.
// app/api/swarm/agent-config/route.ts
// The runner is createNextjsRunner({ …, guardDefinitionAdmin }); the admin plane is DEFAULT-DENY:
// leave guardDefinitionAdmin unset and every route below returns 403.
export const { GET } = runner.configRoute(); // roster + one agent's full detail
export const { POST } = runner.publishRoute(); // CAS-guarded, no-op-aware publish
// The rest of the family, one route file each (all behind guardDefinitionAdmin):
// agent-rollback → runner.rollbackRoute()
// agent-versions → runner.versionsRoute()
// capability-catalog → runner.catalogRoute()
// agent-audiences → runner.audiencesRoute() // FR-031 head-row exposure, never a version publishThe component is untrusted UI. Every write is authorized server-side by guardDefinitionAdmin (default-deny). The studio never constructs an actor and never sends tenant or user ids from the browser. Identity is derived server-side from the request, exactly as the chat provider does. The read-only affordances below are UX only, never the enforcement boundary.
Version history, diff, and the red path
Before you commit a change, the studio surfaces a field-level diff so it is never a blind act: a dirty draft exposes a diff preview beside Publish, and the rollback confirmation always shows the diff of exactly what reverting does. Publishing is CAS-protected: if the head moved under you, it surfaces the conflict with reload or overwrite rather than clobbering someone else's version. And when a change weakens governance (a rule removed, an enforce level downgraded, a match or scope narrowed, a strict workflow dropped, or an approval step lost), it renders as a loud red path: the weakening banner stays visible even with the preview collapsed, and a destructive rollback requires re-typing the agent slug to confirm. Rollback is append-only (it republishes a prior snapshot as a new head, a git revert rather than a reset), and it never changes audiences.
Theme, compose, and degrade gracefully
Theme it with the same system as SwarmChat, override any built-in slot through its own StudioComponents registry (the chat components are untouched), and override any string for copy or light i18n.
// Override any built-in slot or string; theme with the same system as SwarmChat.
<AgentStudio
client={client}
theme="ink"
strings={{ rosterHeading: "Team" }}
components={{ PersonaEditor: MyPersonaEditor }}
/>;
// No runner routes? Pass any object implementing AgentStudioClient in place of createEndpointsClient(...).
// The studio only ever talks to that interface.- No admin grant, canWrite: false, or readOnly → the whole studio renders read-only; a per-agent writable: false (a bundle-managed agent) locks just that agent.
- No capability catalog → the grant picker falls back to read-only chips; a degraded catalog shows a “catalog incomplete” banner.
- No model or audience enumeration → those pickers become free text, with the model input carrying a not-validated warning.
- Rules and workflow sections gate on the active engine's capabilities, with an unenforced-honesty banner when the engine can't enforce them.
Visual rule and workflow builders
As of @nightowlsdev/react@2.7.0, the default RuleBuilder and WorkflowBuilder slots are visual editors. The rule builder gives you condition pickers over a rule's when (agent, tool, and model multiselects fed from the swarm), a deny or ask action, an explicit tool-versus-generation seam control, a normalized preview, and inline validation that mirrors defineRule. The workflow builder gives you ordered step cards (agent, tool, and human steps with {$ref} helpers, branching, and onError) plus a read-only SVG DAG (WorkflowDag) that flags unreachable steps as advisory and dangling targets as errors. Loading an existing rule or workflow into the visual editor and saving it back is round-trip lossless, so you can switch an agent between the visual and JSON editors freely. The builders are client-only (the engine wall holds); the authoritative validation stays server-side.
The JSON editors stay exported as an escape hatch. Alongside the visual defaults, the new RuleBuilderVisual, WorkflowBuilderVisual, WorkflowDag, and useStudioFeeds exports let a host bring its own slot that still reads the pickers' feeds, or render the DAG on its own.
// Visual by default as of @nightowlsdev/react@2.7.0. Keep the JSON editors by passing them as slots:
import { JsonRulesEditor, JsonWorkflowEditor } from "@nightowlsdev/react/studio";
<AgentStudio
client={client}
components={{ RuleBuilder: JsonRulesEditor, WorkflowBuilder: JsonWorkflowEditor }}
/>;
// A custom slot still gets the pickers' data from the feeds seam:
import { useStudioFeeds } from "@nightowlsdev/react/studio";
function MyRuleSlot(props) {
const feeds = useStudioFeeds(); // swarm roster, capability catalog, model allow-list
// render your own control, save through props
}
// The read-only workflow DAG is usable on its own:
<WorkflowDag workflow={workflow} strings={strings} />;The studio, the routes, or the shell
The same agent config can be reached three ways, and they stack. The studio is one UI over the routes; the routes are the actual authority; the shell embeds the studio as one section among several.
| Layer | What it is | Reach for it when |
|---|---|---|
| <AgentStudio> | the mounted admin UI (experimental subpath) | you want roster + editor + history without building them |
| Config-surface routes | the FR-032 route family + guardDefinitionAdmin | you drive a UI of your own, or a script |
| <AIStudio> | the multi-plane shell; embeds this as its agents section | you also manage skills, knowledge, tools, or the graph |
Whichever you mount, the routes are the same and the boundary is the same: authorization is server-side, and the UI never constructs an actor or sends an identity from the browser.
Common pitfalls
- Reading readOnly as security. It is UX only. Every write is authorized server-side by guardDefinitionAdmin (default-deny); leave the hook unset and every route 403s, studio or no studio.
- Clobbering a concurrent publish. Publishing is CAS-guarded: if the head moved under you, the studio surfaces the conflict with reload or overwrite rather than overwriting someone else's version silently.
- Expecting rollback to reset. Rollback is append-only — it republishes a prior snapshot as a new head (a git revert, not a reset), and it never touches audiences.
- Assuming a publish can widen exposure. Audiences ride the head row, not the version axis, so no publish or rollback can silently change who can reach an agent; they save through their own write.
- Trusting the visual builder to validate. The builders are client-only (the engine wall holds) and round-trip lossless, but the authoritative validation stays server-side — the inline checks mirror defineRule, they do not replace it.
- Pinning to the studio subpath in a major-only host. ./studio may change shape in a minor until it stabilizes; the package root and every other subpath keep normal semver.
Where to go next
The AI Studio
Wrap this studio as one section of a multi-plane operator console.
Agent config surface
The server-side routes the studio drives: catalog, validate, CAS-safe publish.
@nightowlsdev/react
The ./studio subpath, the chat UI, and the headless hooks layer.
@nightowlsdev/runner-nextjs
The definition-admin route family and guardDefinitionAdmin.
Building on Night Owls? See the source on GitHub.