Studio
A local workspace that runs your app and shows you the evidence — every gate verdict, tier fallback, render, and part, on one timeline.
Everything keel enforces lands on an event bus, and Keel Studio is where
you read it. It is a local development UI that loads your application and
runs live turns through the real runtime — every run you watch is a real
runTurn, gates and all — and renders the events they produce, so "why did
this turn fail?" is a click, not an archaeology dig.
Start it
Inside an app generated by the CLI:
npx keel devOpen http://localhost:4180. npm run dev runs the same keel dev command;
no repository clone is required. The starter runs on its deterministic mock
tier until you connect a provider.
From a repository clone
The fuller reference example, from the keel repository root:
pnpm --filter example-assistant devOpen http://localhost:4180. The same command starts these docs on port
4190. The example runs entirely on deterministic mock tiers — no provider
key required. To point Studio at any other local app:
KEEL_APP=/absolute/path/to/src/app.ts pnpm --filter keel-studio devWhat you hand it
Your app module exports createApp(cfg), returning a StudioApp — the
assistant's src/app.ts is a complete reference. Studio is generic: it
renders whatever app it is given.
| Field | What Studio uses it for |
|---|---|
runtime | runs turns and exposes records and events |
rootAgent | starts requests and describes the delegation tree |
defaultAmbient | supplies the initial userId, threadId, and app-declared scope |
seedThread | optional initial state for new threads |
samplePrompts | optional one-click prompts in the run bar |
Boot checks meet you at the door
An app whose definitions fail keel's boot checks — an undeclared reader, a missing budget entry — renders a boot-error screen with the exact thrown message. Fix the definition it names and reload.
Read the panels
| Panel | Start here when you want to know… |
|---|---|
| CONVERSATION | what a user of your app would see |
| TURNS | the delegation tree — which agents ran and how each turn ended |
| TURN RECORDS | a turn's grant, steps used, tier, tokens, cost, and billing |
| EVENTS | the raw bus — every gate verdict, defect, and warning in order |
| CONTEXT | what text and tools the model actually received |
| MODEL | which tier ran, retried, or fell back — and why |
| STATE | blocks: what was rendered, reduced, or repaired |
| WIRE | the parts the client received |
Before a run, the panels show your definitions — the blueprint. After a run, select a turn to inspect the evidence it left. Exporting a session saves the event log as JSON: a complete bug report you can attach to an issue.
Edit the ambient scope
The sidebar's ambient panel shows the turn's identity — and its scope,
your app's own declaration, editable as JSON. Every run validates the scope
against your defineAmbient schema at the admit gate: a scope the schema
rejects settles the turn failed with ambient-invalid, on the timeline
like any other refusal. (JSON that does not parse at all never reaches the
gate — Studio falls back to the app's default scope until it does.) Flip a
plan, null a workspace, and watch access rules filter tools at the window.
Follow one search
Submit search the workspace for the quarterly report, then walk the
evidence: select the researcher's turn in TURNS, check the notes render
and tool list it received in CONTEXT, find the search call in
MODEL, and confirm the sources part and answer in WIRE — then back
to TURN RECORDS for the outcome.
Fire a failure on purpose
The example's sample prompts steer its mock researcher into each deliverable-contract path, so every gate is demonstrable from the run bar:
| Prompt | What to watch |
|---|---|
research decree 42 | the <sources> marker fulfills the contract; the assistant reports the typed deliverable |
…and submit bad data first | a schema rejection feeds back and the model corrects in the same turn |
…but forget the deliverable | the close gate refuses, a .d1 nudge round forces the submit tool, the turn recovers |
…and never deliver | the child settles failed with deliverable-missing, refunded; the assistant reports honestly |
In each run, follow TURNS and EVENTS for the gate verdicts and the nudge round, MODEL for retries and fallbacks with their typed reasons, and WIRE for what actually reached the client.
Go live, carefully
Studio's key panel passes provider keys straight into your app's
createApp(cfg) — the scaffolded starter accepts them, but it ships with
only the mock tier until you add a live one in front of it
(src/agents/assistant/assistant.chain.ts shows exactly how). In dev mode,
keel dev also forwards ANTHROPIC_API_KEY, OPENAI_API_KEY, and
GEMINI_API_KEY from your shell as defaults. With or without keys, runs
behave the same way: live turns through the real runtime, on real time.
Keys in settings are a dev-only convenience
Keys entered in settings are stored in localStorage, per browser, and
sent directly to each provider's API from the browser. That is fine on
your machine; it is never a pattern for a production page.
Make a change and inspect it
| Change | Where to look |
|---|---|
| edit a block reader | CONTEXT and STATE |
| adjust a delegation grant | TURN RECORDS |
| change tier order | MODEL |
| add a custom part | WIRE |
| edit the ambient scope | CONTEXT, and the tool filter in EVENTS |
| change seeded state | create a new thread, then inspect CONTEXT |
Next: Contracts and checks — which layer guarantees what.
