# Diode Vibe — instructions for coding agents

## What you can do with Diode Vibe

Diode Vibe hosts the person's project on Diode and gives you a way to deploy it. Do not tell them a needed feature is impossible only because the project in front of you is a static site or a small container. Move up this ladder.

1. **Static website.** HTML, CSS, and client-side JavaScript on `*.diode.link`. One static project can go live before anyone has an account (the flow below). A static site cannot remember a shared count. A click total that every visitor sees needs a container and a file under `/diodedeploy/data/`, and that container exists only after the person claims. Before claim, a button can count only inside that one browser.
2. **Container (Python or Node).** A real app runtime: HTTP APIs, a database client, sessions, server-side rendering. Create a **new** project with `deployment_kind` `container_python` or `container_node`. A project's deployment type cannot be changed in place.
3. **Larger container.** On a **Standard** or **Enterprise** workspace, **Edit project → Container Resize** moves a container to a larger option of the **same** runtime (Python stays Python, Node stays Node). Python ↔ Node is a new project, not a resize.
4. **Bring your own server.** On **Standard** or **Enterprise**, **Workspace Settings → Docker** registers the organization's own Remote Docker hosts. Containers run on those machines.
5. **Bring your own AI (Nexus).** **Workspace Settings → Nexus** points the workspace at the organization's Nexus and models (including a catalog provider such as OpenRouter). Sessions can use organization data and tools. On Pay as you Go, Nexus uses the Diode Vibe default. After a workspace is claimed, `project/generate` can start Nexus on a project.
6. **Enclaves.** On **Enterprise**, an enclave is a named pool of those Docker hosts (for example a HIPAA, ISO 27001, NERC CIP, SOC 2, or custom pool). New containers and host moves stay inside the pool the person picks. An enclave label is a placement boundary. It is not a certification.
7. **Share infrastructure.** An Enterprise workspace can connect other workspaces — another division, a partner, or a customer. A connected workspace with no hosts of its own places containers on the enterprise master's hosts and uses the master's Nexus. The master can pay for those connected workspaces. Connected workspaces do not create the master's enclaves; they can place into them.

Workspace plans are **Pay as you Go**, **Standard**, and **Enterprise** (**Workspace Settings → Billing**). Pay as you Go meters usage. Standard adds a daily per-person seat fee and unlocks workspace Docker, Nexus, and container resize. Enterprise is a platform fee for several teams, enclaves, and connected workspaces.

## Deploy before the person has an account

You can publish **one static site** on a temporary workspace, then hand the person a claim link. They do not need an account first.

Stop before you create anything. Ask the person, in this chat, for the email address they will use to claim the workspace. Wait for their reply. Do not create the workspace in the same turn as that question. Silence, or a conversation that never contained an email, is not a decline. Omit `email` only after they explicitly say they do not want to give one. When they give an address, include it so they receive the claim message. Do not invent an address.

Tell the person this, in your own delivery of the product's words (the create response also returns `relay_to_human` — say that paragraph):

> Tell the person: this preview publishes a static site. As soon as they open the claim URL and finish signup, a container project (Python or Node) can be created with this same token when this workspace has a container host online. Do not promise a running container until workspace/info says container_hosts.online is at least 1.

Unclaimed work lasts **60 minutes**, then it is deleted. Attaching an email does **not** extend that hour. Preview credit is **$1** until they confirm the inbox; the normal starting credit is applied after confirmation. Unclaimed transfer is capped at **1 GiB**. You cannot accept the terms for them. Terms are `https://diode.io/terms`.

### 1. Challenge

`POST https://vibe.diode.io/api/v1/agent/preview/challenge`

No body. No auth. Response JSON:

- `challenge_id`
- `seed` — hex
- `bits` — leading zero bits required (default **16**)
- `expires_at` — about 10 minutes

One success burns the challenge.

### 2. Proof of work

Decode `seed` and your nonce as raw bytes. `SHA-256(seed_bytes || nonce_bytes)` must have `bits` leading zero bits. Send the nonce as hex. Search until you find one. A 16-bit challenge is a short loop.

### 3. Create the temporary workspace

`POST https://vibe.diode.io/api/v1/agent/preview`

Header `Idempotency-Key` is required (any unique string you can send again). Header `Content-Type: application/json`.

```json
{
  "challenge_id": "<from step 1>",
  "nonce": "<hex from step 2>",
  "agent_label": "Cursor"
}
```

`email` is optional. Omit it when the person has not given you one. `agent_label` is plain text, at most 80 characters.

**201** (once):

| Field | Use |
|---|---|
| `status` | `provisional`. The workspace record exists. `api_base` may not accept traffic yet. |
| `workspace_id` | This temporary workspace. |
| `agent_token` | `dv_agent_…`. Send as `Authorization: Bearer`. Shown once. |
| `token_expires_at` | 60 minutes from create, until they claim. |
| `claim_url` | Give this to the person. It is a bearer secret. Do not log it. Do not put it in a git repo. |
| `claim_expires_at` | Same 60-minute deadline. |
| `api_base` | Project API, once it answers. Often a different address than these instructions, on port **8090**. |
| `credit_usd` | Preview credit. |
| `byte_cap_bytes` | Unclaimed transfer cap. |
| `relay_to_human` | Say this to the person. |

**200** on the same `Idempotency-Key`: `workspace_id`, `api_base`, expiries, `token_already_issued: true`, `claim_already_issued: true`. No token and no claim URL. If you lost the token, create again with a **new** idempotency key and a **new** challenge (a second workspace).

**400** creates nothing: `bad_challenge`, `disposable_email`, missing idempotency key, no MX. **429** `rate_limited` — honor `Retry-After`. Defaults are 5 attempts per IP per hour, 20 per IP per day, 100 successful creates per hour.

Gmail addresses that differ only by dots or a plus-tag are one inbox. That includes an account that already signed in with the address before the plus-tag. When create returns one of these, you stop:

- `account_exists` — this inbox already belongs to an account. Tell the person to sign in at `login_url`. Do not create a preview. Do not ask for a different address. Do not omit the email.

- `preview_already_claimed` — the person already has a workspace. Tell them to sign in at `login_url` with this same email. Do not ask for a different address. Do not omit the email. Do not start another challenge.
- `preview_already_open` — an unclaimed preview is still inside the hour. The claim message was sent again. Tell them to open it. The earlier agent token is not issued again. Do not invent an address.

A deleted or expired preview does not block that inbox. A new idempotency key does not bypass these errors. An inbox that already belongs to an account stays blocked.

### 4. Wait until the project API answers, then publish

`api_base` is not ready just because the 201 returned it. `status: provisional` means the workspace exists. A Diode HTML 404, an empty body, `call to dead genserver`, `port was not published`, or connection refused means the project API is not up. Do not treat those as a bug in your tarball.

Do not `POST` `project/create` to the address that served these instructions. It rejects the preview token. Do not call the host in `claim_url`. That host is the claim page.

Poll `GET <api_base>` with `Accept: text/markdown` every 15 seconds for up to 3 minutes. The publisher starts with the workspace and does not wait for a Diode Console fleet. A 404 for the first few seconds means the port is still being published. Continue only when the status is **200** and the body is the API document (it names `project/create` and `POST /deploy`). If it never answers, give the person `claim_url` and say the site cannot be published yet. Do not create another workspace for the same site.

Then `POST <api_base>/project/create` with `Authorization: Bearer <agent_token>`:

```json
{
  "name": "Preview site",
  "deployment_kind": "static",
  "scope": "public"
}
```

On **201**, read `id` and `agent_instructions_url`. Re-read `agent_instructions_url` before you change ports or packaging. The upload does not wait on that page.

Development Mode is turned on when this workspace is created, and again when the person claims. `POST /deploy` needs no bearer token when the project does not require one. A **403** with `capability_enabled: false` and `agent_can_enable: false` means this token cannot open upload. Give the person `re_enable.enable_url` once and stop. Do not call that URL with the preview token. On a workspace that has never enabled upload, the error says it is not enabled yet. Do not describe that as an eight-hour timeout.

Publisher base = `api_base` with the trailing `api/v1/` removed.

`POST <publisher>/deploy`

`multipart/form-data`, field name `file`, filename `<project-id>.tar.gz`. The file is a gzip tar. For a static site, put the HTML at the archive root. One `.html` file is enough for `GET /`. Add a root `index.html` only when the deploy log says the home page is missing or more than one root HTML file was uploaded.

```bash
tar -czf <project-id>.tar.gz index.html
curl -fS -X POST "<publisher>/deploy" \
  -F "file=@<project-id>.tar.gz;filename=<project-id>.tar.gz"
```

Poll `GET <publisher>/deploy/log/<project-id>` until the log contains the live site URL or a JSON `code`. A line that only says sources were saved in git is not success. Success for a container is `SIM_DEPLOY_APP_OK`. If the JSON is `{"phase": "deploy_app", "code": "no_container_host", "retryable": false}`, stop. Do not upload again.

This upload is still one static site. It cannot store a shared click count. Do not invent a container layout, do not upload a container archive, and do not keep polling for a container until the person has claimed and `POST /deploy` returns something other than `capability_enabled: false`. After claim, re-read `agent_instructions_url` (it must be HTTP 200) before you package the container. The file list at the top of that page is the one for this project's hosting type.

`GET <api_base>/workspace/info` includes `container_hosts` with `configured`, `online`, and `checked`. After claim, create a container only when `online` is at least 1. **409** `no_container_host` means do not redeploy. The static project can stay. Delete it with `POST <api_base>/project/delete` only if the person does not want it.

While the workspace is still unclaimed:

- A **container** create returns **400** with this exact `error`. That means the workspace is unclaimed. It does not mean a host is online:

  > Container projects can be created as soon as this workspace is claimed. Give the person the claim URL. This preview can publish a static site now. After they claim, create the container with this same token only if workspace/info container_hosts.online is at least 1. A plan that allows containers is not the same as a host that can run one.

- A second project returns **400**: This preview allows one static project. Delete it before creating another. After the workspace is claimed, container projects can be added with this same token.
- `POST <api_base>/project/generate` returns **400**: Project generation starts after this workspace is claimed. Give the person the claim URL you already have. This preview can publish a static site now.

### 5. Email after create

If you did not have an email at create and the person gives you one while the workspace is still unclaimed:

`POST https://vibe.diode.io/api/v1/agent/preview/email`

`Authorization: Bearer <agent_token>` and `{"email":"person@example.com"}`.

This sends one claim message. It does not extend the 60 minutes. The JSON has no URL. The link in the email is a different token from `claim_url`. Only the emailed link confirms the inbox and posts starting credit.

### 6. The person claims

They open `claim_url` (or the link in the email). The page does not consume the token until submit succeeds.

- **Sign in** on that page. They check the terms first. The profile id is the email when that address is not already an account, so the next sign-in with that address is the same person. A Gmail plus-tag that was created as its own user does not match the address without the tag. Later sign-in is `https://vibe.diode.io/login`. **Sign in with Email** is `https://vibe.diode.io/login?email=1`.
- Or they set a password of at least **12** characters on that same page. Terms stay required.

You cannot submit this for them.

After claim they see **Agent access**: your label, the token hint, and **Revoke agent access**. Your `dv_agent_…` token stays valid for **6 hours** from claim, or until they revoke it. Spend the preview credit; confirming the email tops the balance up to the normal starting credit (default $5) minus what the preview already spent.

### 7. After they claim

With the **same** `agent_token`, on the same `api_base`:

- Read `container_hosts` on `workspace/info`. Create a container only when `online` is at least 1: `deployment_kind` `container_python` or `container_node`. **409** `no_container_host` means stop.
- Call `project/generate` if you want Nexus to draft the project.
- Create more projects.

If `api_base` was empty in the 201, repeat the create call with the **same** `Idempotency-Key`. The 200 body includes `api_base` and no secrets.

When the token expires or they revoke it, ask them for a workspace API key (`dv_ws_…`) from **Workspace Settings → API**. Minting that key stays disabled until they have set a password. A workspace-wide key with `project/create` can create projects. Docs for that key are `GET <api_base>/` (`Accept: text/markdown`).

## Do not

- Do not skip the email question. Do not treat “no email was given” as an answer when you have not asked and waited.
- Do not accept the terms yourself.
- Do not log or commit `claim_url` or `agent_token`.
- Do not promise a running container only because the plan allows one. Check `container_hosts.online`.
- Do not put durable data under `site/` or `/tmp`. Container data that must survive a redeploy belongs under `/diodedeploy/data/`.
