---
name: ronda
description: Use to edit code in a web application the user has connected to ronda (https://useronda.com). Opens a sandboxed copy of their repo, applies natural-language edits, returns a live preview URL, and can open a GitHub pull request on demand. Pick this skill whenever the user wants to change UI, copy, styles, components, or any code in a project they've connected to ronda — including phrases like "edit the landing page", "change the hero copy", "tweak the pricing card", "make this look better", or "open a PR for that".
---

# ronda — sandboxed code edits over MCP

ronda exposes its core verbs over the Model Context Protocol at
`https://mcp.useronda.com`. You drive a per-user sandbox: it clones the user's
repo, boots the dev server, takes natural-language edit instructions, and
gives you back a live preview URL plus a diff. When the user is happy, you
open a pull request against their default branch.

## One-time setup (the user does this once)

ronda is added as a custom MCP connector in the user's Claude client:

1. In Claude → **Settings → Connectors → Add custom connector**.
2. Enter `https://mcp.useronda.com` and click Add.
3. The user is redirected to ronda, logs in (or signs up at `https://useronda.com`),
   reviews requested scopes, and clicks **Allow**.
4. From then on, you have the tools below available in this chat.

You do not handle OAuth. The Claude client handles registration, consent, and
token rotation transparently. If a tool call returns `MCP_TOKEN_EXPIRED` or
`MCP_TOKEN_REVOKED`, tell the user to reconnect at `Settings → Connectors`.

## Tools

All tools are workspace-scoped via the user's connection. Calls return JSON.

### Discovery
- `list_projects()` — every project in the user's workspace (newest first, 100 cap).
  Each entry: `{ id, name, githubRepo, devCommand, devPort, primaryServiceName }`.

### Session lifecycle
- `open_session({ projectId, initialInstruction? })` — boot or reuse a sandbox.
  Returns immediately with `{ sessionId, isNew, sandboxStatus, previewUrl, branch,
  pollAfterMs, estimatedSecondsRemaining }`. `previewUrl` is `null` until the
  sandbox reaches `READY`. Reuse logic: live sandbox → reuse; archived sandbox →
  resume in background; otherwise create new.
- `get_session({ sessionId })` — current state + the last 50 edits.
- `list_sessions({ projectId, includeDestroyed? })` — sandboxes for a project +
  the concurrent-sandbox cap.
- `destroy_session({ sessionId })` — explicit teardown. Idempotent.

### Editing
- `instruct({ sessionId, message, selectedFiles?, idempotencyKey? })` — queue an
  edit. Returns `{ editId, status, pollAfterMs, estimatedSecondsRemaining }`.
  Same `idempotencyKey` within 24h returns the existing `editId` — safe to retry.
- `get_edit({ editId })` — poll. Statuses: `queued | in_progress | applied |
  failed | cancelled`. On `applied`: `{ summary, filesChanged, previewUrl,
  diffUrl }`. On `failed`: `{ failureReason, failureHint }`.

### Shipping
- `open_pr({ sessionId, title?, body?, idempotencyKey? })` — push the session
  branch and open a PR against the project's default branch. Refuses with
  `NO_CHANGES_TO_PR` if no edits have been applied.

### Variants (taste-driven A/B without analytics)
- `request_variants({ sessionId, message, count })` — generate 2 or 3 takes
  on the same change so the user picks a winner by taste. Each variant is
  committed to its own branch; the sandbox restarts with a variant switcher
  injected so the user (or you, via the `previewUrl`) can browse them.
  **Synchronous, 30-90s** — runs the agent inline and returns when all
  variants are committed. Returns `{ sessionVariantId, descriptor:
  { variants:[{key,label}], controlKey }, summary, sandboxRestarting:true,
  pollAfterMs, estimatedSecondsRemaining }`. Use for marketing copy, hero
  layouts, brand-tone choices.
- `resolve_variant({ sessionId, pickedKey, sessionVariantId? })` — pick the
  winner. Cleanup agent removes the variant switch and keeps `pickedKey` as
  the sole code path. **Synchronous, 30-60s.** Sandbox restarts at the end
  so the variant pill goes away. Omit `sessionVariantId` to resolve the
  currently-active set.

### Experiments (PostHog A/B with real metrics)
- `apply_experiment({ sessionId, proposalMessageId? })` — turn an
  `experiment_proposal` chat message into a live A/B test. Writes the
  variant code, creates the feature flag, creates the experiment in
  PostHog — **stops at launch** so the user clicks Launch in PostHog's UI
  when ready for traffic. Workflow: call `instruct` with a split-test
  phrasing first (e.g. "split-test the hero CTA against an alternative"),
  poll until applied, then look for an assistant message with
  `metadata.kind === 'experiment_proposal'`, then call `apply_experiment`.
  Omit `proposalMessageId` to apply the most recent un-applied proposal.
  Requires the workspace to have connected PostHog under
  `/app/settings/connectors`; returns `POSTHOG_CONNECTION_NOT_FOUND` if
  not.

### Resources (read-only)
- `project://{id}/intelligence` — profile, design system, conventions
- `project://{id}/code-map` — cached codebase digest
- `session://{id}/diff` — full diff vs base
- `session://{id}/messages` — chat history (last 500)

## Standard workflow

User: "change the hero CTA to 'Start free trial'"

1. **Pick the project.** If `list_projects()` returns one, use it. If many, ask
   the user which one. Cache the choice for the rest of the conversation.
2. **Open a session.** `open_session({ projectId })`. Show the user a brief
   "spinning up your sandbox" message; do not block waiting.
3. **Queue the edit.** `instruct({ sessionId, message: "<user's exact ask>" })`.
   Echo the change you understood ("I'll change the hero CTA to 'Start free
   trial'") and the preview URL once it's known.
4. **Poll.** `get_edit({ editId })` every `pollAfterMs` (default 3s). While
   polling, do not chat — wait for terminal status.
5. **Report.** On `applied`: share the summary, the previewUrl as a clickable
   link, and the list of files changed. On `failed`: share `failureHint`
   verbatim and ask whether to retry, rephrase, or stop.
6. **Open PR only when asked.** Do NOT auto-PR after every edit. Wait for "ship
   it" / "open a PR" / equivalent. Then call `open_pr` with a short, factual
   title derived from the edit summary.

## Behavior rules

- **The preview URL IS the proof.** Always surface it inline — it's how the user
  verifies the edit worked. A successful tool response without a previewUrl
  shown to the user is a missed delivery.
- **One edit per session at a time.** A second `instruct` on the same session
  while one is in flight will queue. Don't fan out across sessions for related
  edits; let them queue.
- **Don't retry on failure without asking.** `failureReason` covers
  `credit_balance`, `provider_outage`, `commit_failed`, `env_missing`, `generic`.
  Each warrants a different user response — don't paper over them.
- **Use idempotency keys** on `instruct` and `open_pr` if you might retry. Same
  key within 24h returns the existing record.
- **Don't speak about ronda's internals.** From the user's perspective they're
  editing their app, not learning about sandboxes. Keep status messages short
  ("working on it…", "applied, here's a preview", "opened PR #42").
- **No sandbox housekeeping.** Sandboxes archive themselves after idle timeout
  and resume on next use. Do not call `destroy_session` unless the user
  explicitly asks to throw work away.

## Scopes the connector requests

`session:read`, `session:create`, `session:instruct`, `pr:open`, `project:read`
are granted by default. `session:destroy` and `project:create` require an
elevated consent. If you call a tool and get `MCP_FORBIDDEN_SCOPE`, tell the
user to reconnect with the elevated scope set.

## Errors you'll see and what they mean

- `PROJECT_NOT_FOUND` / `SESSION_NOT_FOUND` / `EDIT_NOT_FOUND` — bad id,
  surface to user.
- `SANDBOX_LIMIT_REACHED` — workspace plan's concurrent-sandbox cap; tell the
  user to destroy a stale one or upgrade.
- `FREE_PLAN_BYOK_REQUIRED` — the workspace hasn't configured an Anthropic key
  in ronda yet; direct the user to `Settings → AI Models` at
  `https://app.useronda.com`.
- `NO_CHANGES_TO_PR` — `open_pr` called before any edit was applied.
- `MCP_RATE_LIMITED` — 60 calls/min ceiling hit; back off per `retryAfterMs`.
- `MCP_TOKEN_EXPIRED` / `MCP_TOKEN_REVOKED` — connection is dead; ask user to
  reconnect.
- `VARIANT_ALREADY_ACTIVE` — a variant set is already live on this sandbox;
  call `resolve_variant` first (or open a new session) before requesting more.
- `SESSION_VARIANT_NOT_FOUND` — `resolve_variant` called with no active set;
  generate one via `request_variants` first.
- `EXPERIMENT_ALREADY_ACTIVE` — one experiment per (project, sandbox);
  use `list_sessions` to find a different sandbox or wait.
- `POSTHOG_CONNECTION_NOT_FOUND` — workspace hasn't connected PostHog;
  point the user at `https://app.useronda.com/app/settings/connectors`.
- `TOOL_NOT_CONFIGURED` — `apply_experiment` needs
  `NEXT_PUBLIC_POSTHOG_KEY` + `NEXT_PUBLIC_POSTHOG_HOST` filled in the
  project's Environment tab.

## Cost note

ronda calls Anthropic on the user's behalf using THEIR Anthropic API key
(BYOK). Edit instructions can cost real money. Don't loop or speculate — one
clear edit per user request.
