> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ionworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AI protocol authoring

> Describe a battery test in plain English and get back a validated UCP protocol, ready to run or save as a template

**AI protocol authoring** turns a plain-English description of a battery test
into a validated [Universal Cycler Protocol](/simulate/universal-cycler-protocol)
(UCP). Type what you want to run — for example, `"1C/1C cycling for 200 cycles
between 4.2 V and 2.5 V with 10-minute rests"` — click **Generate**, and the
generated YAML lands directly in the protocol editor.

It sits above the picker, upload, and [Interactive Builder](/simulate/protocol-builder)
row on the test-request form as a fourth authoring path. Use it when you know
what test you want in words but do not want to hand-write YAML or click through
the visual editor.

<Note>
  Every protocol the generator returns has already passed the same validation
  as [`POST /protocols/validate`](/simulate/api) and contains no unresolved
  `input["..."]` placeholders, so you can save it as an
  [experiment template](/simulate/experiment-templates) or attach it to a
  [planned measurement](/operate/planned-measurements) directly.
</Note>

## Where you'll find it

The prompt appears at the top of the **Protocol** section of the
**Test scheduler → Request test** form (see
[Planned measurements](/operate/planned-measurements)). That is currently the only
place it is available — the [Protocols](/simulate/protocols) list, the
[Interactive Builder](/simulate/protocol-builder), and the protocol editors
elsewhere in Studio do not yet render the prompt.

The exact placeholder changes based on context:

* **No protocol loaded yet** — `Enter prompt to generate protocol using AI, e.g. "1C/1C cycling for 100 cycles between 4.2 V and 2.5 V"`
* **A protocol is already loaded** — `Enter prompt to change protocol using AI, e.g. "make it 200 cycles"`

When a protocol is already loaded, the prompt is treated as an *edit* to it
(`Update`) rather than a fresh protocol (`Generate`). This is the fastest way
to tweak a saved template — say what to change instead of hunting for the
step in the editor.

## Writing a good prompt

Be specific about the values that change what actually runs on a channel:

* **C-rate or current** — `1C`, `C/3`, `10 A`
* **Voltage window** — `between 4.2 V and 2.5 V`
* **Cycle count**, **rest durations**, **temperatures**
* Any **termination** conditions beyond the voltage window (capacity fade,
  time cap, etc.)

Anything you leave vague, the generator will either infer from your
[cell specification](/core-concepts/cells) or ask you about — see
[Clarifying questions](#clarifying-questions).

### Examples

```text 1C/1C cycling theme={null}
1C/1C cycling for 200 cycles between 4.2 V and 2.5 V with 10-minute rests
between charge and discharge.
```

```text GITT with 20 pulses theme={null}
GITT at C/10 with 20 discharge pulses, 30 minutes each, followed by 2 hours
of rest.
```

```text Formation theme={null}
Formation: three CC-CV charge/discharge cycles at C/20 between 4.2 V and 3.0 V.
```

## Cell-specification context

If you pass a **cell specification** — the request form on the Test scheduler
picks this from the cell you're testing — the generator receives the cell's
voltage limits, nominal capacity, and current ratings as facts, so it stops
guessing (or asking) about them. This is the single biggest lever on prompt
quality: with a cell attached, `"cycle it at 1C"` is enough.

Without a cell, spell the voltage window and capacity out in the prompt.

## Clarifying questions

If your description leaves something material undecided — the kind of value
that would meaningfully change what runs on the channel — the generator asks
before writing the protocol. Answer each question and click **Generate
protocol**; the answers are added to the exchange and the model tries again.

Suggested options appear as chips you can click, or you can type your own
answer. The exchange is stateless: the full answer history is re-sent on
every retry, so refreshing the page only costs you your typing.

You do **not** need to answer questions the generator can figure out from
context — attach a cell specification instead of debating cutoff voltages.

## Saved-protocol suggestions

Before writing a new protocol, the generator searches your organization's
saved protocols for one that already does what you asked. If it finds a
match, you'll see a **Saved protocols that already do this** panel with a
**Use this** button for each suggestion.

Reuse is preferred to regeneration — a saved protocol is already reviewed
and named. If the suggestions don't fit, click **Write a new one instead** to
skip reuse for that run.

## Explanation and assumptions

Once a protocol is written, a short confirmation appears under the prompt:

* **Explanation** — one sentence describing what the protocol does.
* **Assumed:** — every value the generator picked without being told (typical
  rest durations, default temperatures, etc.). Read this before running —
  anything you disagree with is one edit prompt away.

The generated YAML lands in the source editor below, and the suggested name
lands in the **Protocol name** field. Both are editable before you save.

## Fix with AI

When a protocol fails to parse — you uploaded a broken file or edited the
YAML by hand — the parse-error alert in the **Request test** form offers a
**Fix with AI** button. Like the prompt itself, this action is currently
scoped to the test scheduler; the protocol editors elsewhere in Studio show
the parse error without the repair button.
Clicking it sends the broken protocol and the error message back to the
generator with the instruction *"Fix it, changing as little as possible."*

Use this to salvage a nearly-correct protocol rather than starting over.

## API

Behind the UI is a single endpoint:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.ionworks.com/protocols/generate" \
    -H "Authorization: Bearer $IONWORKS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "prompt": "1C/1C cycling for 200 cycles between 4.2 V and 2.5 V with 10-minute rests",
      "cell_specification_id": "cell-spec-id"
    }'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.ionworks.com/protocols/generate",
      headers={"Authorization": f"Bearer {api_key}"},
      json={
          "prompt": (
              "1C/1C cycling for 200 cycles between 4.2 V and 2.5 V "
              "with 10-minute rests"
          ),
          "cell_specification_id": "cell-spec-id",
      },
      timeout=120,
  )
  result = response.json()
  ```
</CodeGroup>

### Request

| Field                   | Type                                   | Description                                                                                                               |
| ----------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `prompt`                | string, required                       | Plain-English description of the test to run (up to 4000 characters).                                                     |
| `current_protocol`      | string, optional                       | Existing protocol as UCP YAML. When supplied the prompt is treated as an *edit* to it rather than a fresh protocol.       |
| `clarifications`        | list of `{question, answer}`, optional | Answers to questions a previous call returned, oldest first. Send the full history each time — the exchange is stateless. |
| `cell_specification_id` | string, optional                       | Cell the test will run on. Its voltage limits, capacity, and rate limits are given to the generator as facts.             |
| `skip_existing`         | bool, default `false`                  | Skip the saved-protocol search and write a new protocol. Set when the user rejects the suggested matches.                 |

### Response

Exactly one of the three branches is populated:

| Field                                                 | Populated when                                  | Description                                                                                                                                     |
| ----------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol_yaml`, `name`, `explanation`, `assumptions` | The generator wrote a protocol.                 | Validated UCP YAML, a suggested protocol name, a one-sentence explanation, and the list of values the generator picked without being told.      |
| `questions`                                           | The generator needs more information.           | List of `{question, why, options}` items — ask the user, then re-call with the answers in `clarifications`.                                     |
| `matches`                                             | A saved protocol already satisfies the request. | `{template_ids, note}` — surface these as suggestions and let the user pick one, or retry with `skip_existing: true` to write a new one anyway. |

Check `questions` and `matches` before reading `protocol_yaml` — both leave
it empty.

### Errors

* **422** — invalid request (empty prompt, oversized fields, more than 20
  clarifications, etc.). These are request-model validation failures, so they
  arrive as FastAPI's standard validation response, not as a `BAD_REQUEST`.
* **502** — the model could not produce a valid protocol. Rephrase the test,
  or build it with the [Protocol Builder](/simulate/protocol-builder).

Protocols returned by the endpoint are guaranteed to pass UCP validation and
contain no unresolved `input["..."]` references — the generator retries
internally until both hold, so callers do not need to re-validate.

## Limits and tips

* The prompt is capped at **4000 characters**; the optional UCP payload at
  **100,000 characters**; and answers at **20 rounds** per exchange. Beyond
  that, split the request or pin the values yourself.
* The generator uses a frontier model per call. Save the result as an
  [experiment template](/simulate/experiment-templates) once it's right —
  reuse is free.
* For fully-deterministic authoring (no model in the loop), use the
  [Protocol Builder](/simulate/protocol-builder) or write UCP YAML directly.

## Next steps

<CardGroup cols={2}>
  <Card title="Protocol Builder" icon="pen-ruler" href="/simulate/protocol-builder">
    Build a UCP protocol visually, step by step.
  </Card>

  <Card title="Universal Cycler Protocol" icon="arrows-rotate" href="/simulate/universal-cycler-protocol">
    Full reference for the UCP YAML format the generator emits.
  </Card>

  <Card title="Protocols" icon="file-lines" href="/simulate/protocols">
    Save, clone, and configure the generated protocol.
  </Card>

  <Card title="Planned measurements" icon="calendar-clock" href="/operate/planned-measurements">
    Attach the generated protocol to a scheduled lab test.
  </Card>
</CardGroup>
