Skip to content

Playground

Edit a zidane agent and run it in your browser, with nothing to install.

The playground runs a zidane agent in your browser tab. The agent is a set of files you edit: its code, its tools and its prompt. The agent, the sandbox its tools use and the model all run on your machine: no server and no API key. For a bigger hosted model, add an OpenRouter key.

What runs where#

A WebContainer is a Node.js runtime that runs in the browser. A page can boot only one, so each one lives in its own iframe.

PieceRuns inDoes
AgentWebContainer in iframe 1Runs your agent's files on Node with zidane
SandboxWebContainer in iframe 2Runs the agent's shell commands and file operations
ModelWeb Worker, on WebGPURuns the model with transformers.js, or the page calls OpenRouter
PageYour tabShows the workspace and the chat, and relays between the three

The agent uses zidane's local provider. Its model requests and tool calls go out as lines on the process's stdout. The page answers them from the model worker or the sandbox, and writes the answers to stdin.

The workspace#

The playground fills the window under the site's header. What the panes hold never makes them bigger: a long prompt scrolls inside the composer, and only the edges below change a pane's size.

PartWhat you do there
Agent and Sandbox tabsSwitch between the agent's files and the sandbox's files
File treeClick a file to open it. It updates when the agent, a terminal or a save changes a file
EditorEdit with syntax highlighting. CmdS or CtrlS saves. TypeScript files in the agent project are type-checked (below)
TabsDrag a tab, or press AltShift← / → on it, to move it. Middle-click closes a tab. A dot marks unsaved changes
PreviewThe page a server in the sandbox serves, alone or beside the editor (below)
TerminalsA jsh shell in each container, one tab each
ChatSend a prompt, pick the model, read the run. A file you attach also lands in the sandbox under attachments/, and the prompt tells the agent where

A file the agent changes while you have it open reloads in the editor. If you have unsaved edits, a bar asks you to Reload (take the new file) or Overwrite (keep yours).

On a phone the parts are tabs: Files, Editor, Terminal and Chat.

Resize and hide the panes#

On a desktop, drag the edge between two panes to resize them:

EdgeResizes
Right of the file treeThe tree's width
Above the terminalsThe terminals' height
Left of the chatThe chat's width
Left of the preview, beside the editorThe preview's width

Double-click an edge to put its pane back to the default size. The editor always keeps at least 360 px by 200 px: in a narrow window the tree gives way first, then the chat.

The buttons in the workspace's top row hide and show the file tree, the terminals and the chat. The terminals' own row has a button that hides them too.

The playground remembers the sizes and the hidden panes in this browser, and opens with them the next time.

Check types#

The editor checks the agent's TypeScript files as you type, about 300 ms after you stop. The TypeScript compiler runs in a Web Worker, against zidane's type declarations and Node's. It downloads (about 2 MB compressed) the first time you open a .ts file.

ActionResult
Write a type errorThe line shows the error under it, and the chip on the tab row counts errors
Hover a nameA card shows its type and docs
Cmd-click or Ctrl-click a nameOpens its definition. A definition in zidane or Node opens read-only
Find references in the hover cardLists every use, click one to open it

Node still runs your files without checking them: a type error does not stop a run.

Preview a server#

When a server in the sandbox starts answering, its page opens in the Preview, beside the editor (Split, in the workspace's top row). Preview in the same row shows it alone. The preview has a reload button, an address bar with the site's icon, and a button that opens the page in a new tab. Type a path such as /about in the address bar to go there.

Switching between the Agent, Sandbox and Preview views keeps the page as it was. The preview shows the last server that started answering. With more than one, the ports menu (the plug button) picks another. The page reloads when a server starts again on its port, and goes when its server stops. While nothing answers with a page, the preview tries again a few times, then says it waits for the server, with a reload button. A server in the agent container gets no preview: both containers share one preview address, which the sandbox owns.

To try it, pick Start from > Scaffold a Vite app and run this in the Sandbox terminal:

Terminal
npm create vite@latest app -- --template vanilla
cd app && npm install && npm run dev

The agent starts a server the same way, with its run_in_terminal tool. Its shell tool waits for the command to end, so the default prompt tells the agent not to start servers with it.

Edit the agent#

The agent container holds these files. Each prompt runs the files as they are saved at that moment.

FileHolds
agent.tsA function that returns the agent, with createAgent
system.mdThe system prompt
zidane.config.jsonname, tools (the tool files to load), codemode, mini, and an optional maxTurns (without it, a prompt runs until the model answers)
tools/<name>.tsOne tool each: its default export is a zidane tool

agent.ts gets everything it needs from the playground. The default file:

TypeScript
import type { PlaygroundEnv } from 'zidane-playground'
import { createAgent, localReliabilityBehavior } from 'zidane'
import { capToolOutput, MINI_BEHAVIOR } from 'zidane-playground'

export default function agent({ config, system, tools, provider, execution, session }: PlaygroundEnv) {
  const agent = createAgent({
    name: config.name,
    system,
    provider,
    execution,
    session,
    tools,
    behavior: {
      ...localReliabilityBehavior(config.mini ? MINI_BEHAVIOR : {}),
      maxTurns: config.maxTurns,
      codemode: config.codemode ? { mode: 'only' } : false,
    },
  })
  if (config.mini)
    capToolOutput(agent)
  return agent
}

provider is the model you picked, execution is the sandbox and session keeps the conversation between prompts. Node runs the TypeScript itself: it strips the types and does not check them. A syntax or runtime error fails the run: the error shows in the transcript with its file and line, and in the agent's terminal. So does anything your code logs with console.

Write a tool#

Add tool (the + in the agent's file tree) asks for a name, writes tools/<name>.ts and adds the name to tools in zidane.config.json. A tool is a spec the model reads and an execute that runs when the model calls it:

TypeScript
import type { ToolDef } from 'zidane'

const tool: ToolDef = {
  spec: {
    name: 'line_count',
    description: 'Counts the lines of a file in the sandbox.',
    inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
  },
  async execute(input, ctx) {
    const text = await ctx.execution.readFile(ctx.handle, String(input.path))
    return `${text.split('\n').length} lines`
  },
}

export default tool

Most tool files start as zidane's own tools (basicTools from zidane/presets). Change a description, or wrap execute to change what the tool returns. See Tools for the full tool shape.

Two tool files belong to the playground:

ToolDoes
run_in_terminalTypes a command into the sandbox terminal and returns what it printed after wait_ms (default 5,000 ms, at most 30,000). The command keeps running, so the agent uses it for servers.
agent_filesLists, reads and writes the agent's own files (agent.ts, system.md, zidane.config.json, tools/<name>.ts). A change applies from the next prompt.

Start from a template#

Start from replaces the files in both containers. It asks first when it will replace files you changed. It leaves the model and the chat as they are. Your saved projects show above the templates, under Your projects.

TemplateAgentSandbox
EmptyThe default agent: eight tools, codemode on, no turn capEmpty
Explain a projectFive tools (no list_files, edit or multi_edit)A word-count script to explain
Fix a failing testThe default agentsum.js with a bug and its test
Scaffold a Vite appThe default agentA README asking for a Vite app
MiniSmall prompt and tools for small windows (below)Empty

Start the sandbox project#

When files come in (Start from, a saved project or a share link), the playground starts the sandbox's project as you would by hand. If the sandbox has a package.json, the playground types a command into the Sandbox terminal, from what the file lists:

package.json hasCommand
Dependencies and a dev scriptnpm install && npm run dev
A start script and no devnpm run start (after npm install when it lists dependencies)
Dependencies, no dev or startnpm install
NeitherNothing

The Sandbox terminal shows (it opens if you hid it), and the keyboard stays where it was. Whatever ran in that terminal stops first (CtrlC). A server that starts answering opens its page in the Preview. Of the templates, only Explain a project has something to run: its start script prints the word count.

npm install writes package-lock.json. It does not count as a file you changed: Start from replaces it without asking.

Pick a model#

The model pill in the composer lists the models with their window, download size and a Cached mark. The first send with a model downloads it, with the progress under the composer, and keeps it in the browser's cache. Later visits load it from there.

These numbers come from an Apple M4 Pro (20-core GPU) in Chrome 156. The window is the number of tokens the conversation can grow to, prompt and replies together. Prefill is the wait while the model reads the prompt, before the first word of its reply.

ModelDownloadWindowSpeedPrefill at 8K tokensIn real runs
Qwen3 4B Instruct 25072.9 GB16,384 tokens43 tokens/s33 sDefault. Created and ran a file in 3 of 3 runs, fixed the failing test in 3 of 3
Qwen3 1.7B1.1 GB32,768 tokens96 tokens/s13 sWith Mini, creates and runs a file in about 2 s. With codemode, its scripts often print instead of writing the file
Qwen3 0.6B570 MB32,768 tokens119 tokens/s9 sWrites the file but rarely runs it

onnxruntime-web fails on a single pass over about 2,000 prompt tokens, so the worker reads the prompt in 1,024-token chunks. The models measured and left out, and why, are listed in website/src/playground/models.ts. The playground also downloads the ONNX runtime once (27 MB, 7 MB compressed).

Run OpenRouter models#

OpenRouter serves hosted models from many vendors behind one API. With your OpenRouter API key, the agent runs on them instead of your GPU.

  1. Open Providers in the status row and turn on OpenRouter.
  2. Paste your key (create one at openrouter.ai/keys) and click Test key. The panel shows the key's credit and how many models it added.
  3. Pick a model in the OpenRouter group of the model pill and send. Nothing downloads.

The picker lists the OpenRouter models that support tool calling, with their window, a Reasoning mark and the price per million tokens (prompt, then reply). The agent needs tools, so the panel counts the models it leaves out. The page fetches the list at most once an hour.

QuestionAnswer
Where is the key?In this browser's localStorage, under zidane-playground:openrouter:v1. Clear key removes it
Who gets it?Only openrouter.ai. The page sends each request from your browser to OpenRouter. The agent's container sends its requests to the page, which adds the key, so the container never sees it
What does it cost?OpenRouter bills your account at the model's price. Free models cost nothing and have tighter rate limits
What if a request fails?The transcript and the panel show OpenRouter's error: a refused key (401), no credits (402), a rate limit (429)

Fit a small window#

The meter next to the project tabs shows the tokens of the agent's first request against the model's window, for example 3,157 / 16,384 tokens. It counts your files: the agent container builds the request as a run would, and the page counts it with the model's tokenizer. It recounts after every save. The page has no tokenizer for an OpenRouter model: it estimates 4 characters a token and marks the count with ≈.

When the prompt and a full reply (1,024 tokens) do not fit, the meter turns red and offers Start from Mini. The Mini template shrinks what the model reads:

PartDefaultMini
system.mdSix lines on how to workOne line
ToolsEight, with zidane's descriptionsshell, read_file, write_file, with one-line descriptions and only their main parameters
CodemodeOnOff
"mini": trueNoTool output cut to 1,200 characters, reads of 120 lines or 4 KB at most, old tool results stubbed past 4 KB
Max turnsNo cap4

Codemode's own instructions take about 800 tokens. Small models also tend to call it without a description or with a broken script, which is why Mini turns it off. A small model can also repeat the same tool call forever, and Mini's compaction keeps the window from ever filling, so Mini stops a prompt after 4 turns.

Save and share#

Save (next to Start from) keeps both containers' files and the conversation in this browser (IndexedDB). A model-made title names the project, or the first prompt does. Start from › Your projects opens, renames and deletes saved projects. Opening one leaves the model you picked as it is. If the browser refuses a save, an open, a rename or a delete, the reason shows next to the buttons.

Share in the chat header uploads the conversation and both containers' files to the site's share API (/api/share). It gives a link, /playground?s=<id>, that anyone can open in their own playground. A share holds 2 MB at most.

Save and Share keep the agent's own files and the sandbox's files, at most 500 per container. They leave out node_modules, .git, binary files and files over 200,000 characters.

ActionResult
Create linkUploads the session as it is now. Later messages are not in the link.
Update linkUploads the session again. The old link stops working.
UnshareDeletes the share. Only the browser that made the link can delete it.

A restored or shared conversation shows in full, but the agent starts without its memory of that session.

Limits#

LimitDetail
BrowsersChrome and Edge on a desktop. Firefox only with WebGPU turned on. Safari lacks the credentialless isolation policy WebContainers need here. Phones lack the GPU memory for the model
ModelsSmall models pick the wrong tool, loop, or claim a fix they did not make. Treat a run as a demo of the agent loop
TypesNode strips TypeScript types without checking them, and refuses syntax it cannot strip (an enum). The editor's check leaves out Node modules an agent rarely uses (http2, inspector, sqlite, test and a few more): their types read as any
PreviewThe address bar follows the page through links and history changes. A reload loads the page again from its server
Sandbox shellWebContainer's jsh: node, npm and ls, but no grep, find or sed. It merges stderr into stdout
Toolszidane's basic set without background tasks and sub-agents
SessionsUnsaved files and chats end when you reload the page. Save keeps them in this browser (below)

The Browser chip in the status row checks for WebGPU and cross-origin isolation on load. When one is missing, the chip says which, and the chat column explains it. Without WebGPU the files, terminals and preview still work. Without isolation the containers cannot start: the editor shows the starter files, read-only.

Host the playground#

WebContainers need an API key on a production origin. The site reads WEBCONTAINER_API_KEY from website/.env at build time and puts it in the bundle of the two container iframes, so anyone can read it there. Restrict the key to the site's origins in your StackBlitz settings. On localhost the page boots WebContainers without the key.

Cross-origin isolation applies to /playground and its files only:

HeaderValue
Cross-Origin-Opener-Policysame-origin
Cross-Origin-Embedder-Policycredentialless

vite dev and vite preview set them. For hosting, website/public/_headers lists them in the format Netlify and Cloudflare Pages read. On another host, set the same headers on /playground and /playground/*.

To run it locally, build zidane at the repo root (the agent container mounts that build), then start the site and open localhost:4321/playground. Add ?model=scripted to the URL for a scripted model with no real download.

Terminal
bun run build
cd website && bun run dev