keel

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 dev

Open 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 dev

Open 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 dev

What 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.

FieldWhat Studio uses it for
runtimeruns turns and exposes records and events
rootAgentstarts requests and describes the delegation tree
defaultAmbientsupplies the initial userId, threadId, and app-declared scope
seedThreadoptional initial state for new threads
samplePromptsoptional 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

PanelStart here when you want to know…
CONVERSATIONwhat a user of your app would see
TURNSthe delegation tree — which agents ran and how each turn ended
TURN RECORDSa turn's grant, steps used, tier, tokens, cost, and billing
EVENTSthe raw bus — every gate verdict, defect, and warning in order
CONTEXTwhat text and tools the model actually received
MODELwhich tier ran, retried, or fell back — and why
STATEblocks: what was rendered, reduced, or repaired
WIREthe 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.

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:

PromptWhat to watch
research decree 42the <sources> marker fulfills the contract; the assistant reports the typed deliverable
…and submit bad data firsta schema rejection feeds back and the model corrects in the same turn
…but forget the deliverablethe close gate refuses, a .d1 nudge round forces the submit tool, the turn recovers
…and never deliverthe 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

ChangeWhere to look
edit a block readerCONTEXT and STATE
adjust a delegation grantTURN RECORDS
change tier orderMODEL
add a custom partWIRE
edit the ambient scopeCONTEXT, and the tool filter in EVENTS
change seeded statecreate a new thread, then inspect CONTEXT

Next: Contracts and checks — which layer guarantees what.