---
title: "Agent workflows"
description: "Drive Rosette with AI coding agents like OpenCode, Claude Code, or Cursor."
canonical_url: "https://www.rosette.dev/docs/guides/agent-workflows"
markdown_url: "https://www.rosette.dev/docs/guides/agent-workflows.md"
source_url: "https://github.com/PreFab-Photonics/rosette/blob/f086d7670645fd36c05362d696d442a2b2e74850/www/content/docs/guides/agent-workflows.mdx"
docs_channel: "main"
docs_revision: "f086d7670645fd36c05362d696d442a2b2e74850"
---

# Agent workflows

Most Rosette designs are written by a person and an AI coding agent working
together. This guide is the practical playbook for that workflow:
what `rosette init` puts in your project for the agent, how the
prompt-build-check loop runs, and where things go wrong.

For the design philosophy behind this approach, the
[Agent-Driven Design article](/blog/agent-driven-design) covers the
"why." This page is the "how."

## What `rosette init` gives the agent

When you run `rosette init` and pick an AI tool (OpenCode or Claude Code),
the project layout is set up so an agent has everything it needs to read
your code, write new designs, and verify them on its own.

```txt
my-chip/
├── AGENTS.md            # or CLAUDE.md, agent instructions
├── rosette.toml         # project config: layers, DRC, DFM
├── components/          # editable component library
├── designs/             # your design scripts
├── output/              # GDS build artifacts
├── .agents/skills/      # focused skills (or .claude/skills/)
└── .rosette/
    ├── index.md         # compact task-to-context router
    ├── contracts/       # focused API contracts by task
    ├── api.pyi          # complete API fallback
    ├── cli.json         # complete CLI fallback
    └── manifest.json    # generated reference provenance
```

Each piece plays a specific role:

* **`AGENTS.md` / `CLAUDE.md`**: short instruction file the agent reads
  on startup. It sends the agent to the compact index, requires semantic
  project layers, keeps composition-oriented designs thin, and allows one-off
  exploratory geometry to remain local to a design.
* **`.rosette/index.md`**: tells the agent which focused contract, skill,
  project configuration, and local source apply to its current task.
* **`.rosette/contracts/`**: task-specific API slices for layout composition,
  routing, verification, and component authoring. They are generated from the
  complete contract, so signatures have one source of truth.
* **`.rosette/api.pyi`**: complete API fallback for uncommon operations not in
  a focused contract. It is not loaded by default.
* **`.rosette/cli.json`**: complete CLI fallback for exact commands, flags,
  exit behavior, and JSON output schemas. Agents read only the relevant command.
* **`rosette.toml`**: layer definitions, DRC rules, DFM config. The
  physical constraints of your design, machine-readable. Agents pick
  semantic layer names from `[layers]` instead of guessing GDS numbers.
* **`components/`**: editable Python source for waveguides, bends,
  MMIs, grating couplers, ring resonators, and more. The agent reads
  the docstrings and signatures, then composes them. The generic template
  includes the full catalog; the blank template starts with shared helpers
  for components you add later.
* **Focused skills**: routing strategy, the build-check-inspect loop, and
  component-authoring conventions. Skills are installed for both templates.



> **Note: Restore references after cloning**
>
> `.rosette/` is gitignored because its references match the installed Rosette
> version. After a fresh clone, run `uv run rosette update` to recreate
> the index, task contracts, and complete fallbacks. Do not rerun `rosette init`
> in an existing project.





> **Note: Pick a tool when you init**
>
> `rosette init` accepts `--tool agents` (also `opencode`, `codex`, or `cursor`)
> or `--tool claude` if you want to skip the interactive prompt. Pass
> `--tool agents,claude` for both harnesses or `--tool none` to skip agent files.
> The same instruction body and skills are projected to `AGENTS.md` plus
> `.agents/skills/`, or `CLAUDE.md` plus `.claude/skills/`.





> **Warning: Keep contracts and runtime aligned**
>
> Run project commands through `uv run rosette`, not a global `rosette` or `ro`
> executable. If a canonical import from a generated contract fails, do not add a
> compatibility shim. Run `uv run rosette update` and align the installed package
> before continuing. Rosette checks both package version and structural API identity.



## The loop

The workflow is short. You describe the design in plain language, the
agent writes the script, and then it iterates against the build and
check commands until they pass.

```txt
prompt -> agent reads .rosette/index.md and rosette.toml
       -> agent loads the task contract, skill, and relevant components/
       -> agent writes designs/<name>.py
       -> uv run rosette build designs/<name>.py
       -> uv run rosette check designs/<name>.py
       -> agent reads violations, fixes, repeats
```

Every Rosette design exports a top-level `Cell` named `design`. That's
the convention agents follow:

```python
# designs/loopback.py
from rosette import Cell, Layer
from rosette.project import load_layer_map
from rosette.routing import Route
from components import grating_coupler

layers = load_layer_map()
gc = grating_coupler(layers.silicon.layer, waveguide_width=0.5)

gc_in  = gc.at(0,   0)
gc_out = gc.at(0, 127)

route = Route(layers.silicon.layer, width=0.5, bend_radius=10.0)
route.start_at_port(gc_in.port("opt"))
route.to(40, 0)
route.to(40, 127)
route.end_at_port(gc_out.port("opt"))

design = Cell("loopback")
design.add_ref(gc_in)
design.add_ref(gc_out)
design.add_ref(route.to_cell("route").at(0, 0))
```

The agent then runs the verify steps:

```bash
uv run rosette build designs/loopback.py
uv run rosette check designs/loopback.py
```

A passing run looks like this:

```txt
drc  designs/loopback.py  22 rules, 4 polygons

  passed (0.8ms)

checks  designs/loopback.py  4 ports, 1 connections

  passed (0.1ms)
```

And a failing run is just as readable:

```txt
drc  designs/foo.py  22 rules, 8 polygons

  FAIL  Lsilicon.no_overlap on 1/0, 1/0: Forbidden overlap (449.488 um²) at (-12.0, -12.0) to (12.0, 12.0) (within 'ring')
  FAIL  Lsilicon.allowed_angles on 1/0: Edge angle 95.6 deg not in allowed angles [0.0, 90.0]
  ...

  65 violations (65 errors) in 2.8ms
```

That's all the agent needs. Each violation has a rule name, the offending
geometry, and a coordinate. The agent reads the output, figures out which
parts of the design need to change, and edits the script.

## Machine-readable output

The prose above is tuned for humans. For a fully reliable verify loop —
no ANSI stripping, no regexing severities out of prose that might
reword — add `--json` to `check`, `drc`, or `dfm`. The command then
emits a single JSON object on stdout (and nothing else), with a stable,
versioned schema:

```bash
uv run rosette drc designs/foo.py --json
```

```json
{
  "schema": 2,
  "command": "drc",
  "design": "designs/foo.py",
  "passed": false,
  "elapsed_ms": 12.4,
  "summary": { "violations": 1, "errors": 1, "warnings": 0 },
  "violations": [
    {
      "severity": "error",
      "rule_name": "min_spacing",
      "rule_type": "spacing",
      "layer": "1/0",
      "layer2": null,
      "message": "spacing 0.180 < 0.200",
      "cell_name": "ring",
      "cell_name2": "ring",
      "bbox": [[12.34, 5.12], [12.52, 5.30]]
    }
  ],
  "suppressed": 0,
  "skipped_cells": 0,
  "waived": 0
}
```

The agent reads `passed` directly instead of matching prose, and each
`bbox` is in microns — the same coordinate space as
[`rosette shot`](/docs/guides/snapshots), so the agent can render the
offending region with `uv run rosette shot --bbox <xmin,ymin,xmax,ymax>` to see
what went wrong.

`uv run rosette check --json` returns one combined object with `drc`, `checks`,
and `dfm` keys (the last is `null` unless `--include-dfm` is passed) plus
a top-level `passed`. Exit codes are unchanged in JSON mode: a failing
run still exits `1` alongside `"passed": false`.

In every JSON mode, read `passed` to gate the loop — it is present on
every object, including skips and errors. Each slot in the combined
`check` object (`drc`, `checks`, `dfm`) also carries its own `passed`,
plus `skipped`/`error` flags when it didn't produce a normal result.

Config-error handling matches the human `check` flow. A bad DRC config
(or missing `rosette.toml`) is fatal: `check` emits a top-level error
object (`{"schema": 2, "command": "check", "passed": false, "error":
"..."}`) and exits `1`. A bad `[dfm]` section or checks config is
non-fatal under `check` — its slot becomes
`{"command": ..., "passed": false, "skipped": true, "error": true,
"reason": ...}` while the top-level `passed` reflects only DRC and
connectivity. (The standalone `uv run rosette dfm --json` treats the same DFM
config error as fatal and exits `1`.) A `dfm` slot of
`{..., "error": false, "reason": ...}` is a benign skip — no `[dfm]`
section configured.



> **Warning: `rosette build` alone is not enough**
>
> A passing build only means the GDS file was written. It does not mean
> the design is physically correct. Always follow up with `uv run rosette check`
> (or use `uv run rosette build --check` for a build with a DRC pre-check). The
> agent instructions in `AGENTS.md` already say this; it's worth knowing
> yourself so you can call it out if the agent skips the check step.



## Prompting tips

The agent file gives the agent a baseline. A few things you can do on top
of that to get better results.

**Be specific about intent.** "Design a ring resonator" is fine. "Design
a 10 um radius ring resonator with a 200 nm gap, single bus, with grating
couplers on both ends of the bus" gives the agent the constraints it
needs to make the right decisions. The more constraints you state up
front, the fewer iterations you'll need.

**Let the agent read first.** A good agent reads `.rosette/index.md`,
`rosette.toml`, the selected task contract, and relevant `components/*.py`
before writing code.
If your agent has a habit of jumping straight to writing, prepend
something like "Read the Rosette index and relevant component source first"
to your prompt.

**Ask for the check loop explicitly.** If the agent stops at "the build
passed," ask it to run `uv run rosette check` and resolve any violations.
After a few iterations most agents pick up the pattern and run checks
without prompting.

**Surface foundry constraints in `rosette.toml`.** If your DRC rules are
right, the agent will catch its own mistakes. Tighten your
`[drc.layers.*]` constraints to match your actual process; vague
defaults produce vague designs. See the
[Design rule checking guide](/docs/guides/design-rule-checking).

**Keep prompts in plain English.** Rosette's API surface is small enough
that an agent rarely needs nudging on which class to use. Save the API
hints for cases where you genuinely need a specific approach (for
example, "use Euler bends" or "place the array as a single AREF").

## Picking an agent tool

Rosette is tool-neutral. The same instructions, task contracts, skills, and
verify commands work with any agent that can read files and run shell commands.

* **[OpenCode](https://opencode.ai)** reads `AGENTS.md`. Run
  `uv run rosette init --tool opencode`.
* **[Claude Code](https://docs.claude.com/en/docs/claude-code)** reads
  `CLAUDE.md`. Run `uv run rosette init --tool claude`.
* **[Cursor](https://cursor.com)** and other editor-integrated agents
  generally read either file or can be pointed at one manually.

The harness adapter writes both the instruction file and the matching skill
directory. Run `uv run rosette update` after changing harness files or upgrading Rosette.

## Common pitfalls



> **Warning: Hardcoded layer numbers**
>
> Agents sometimes write `Layer(1, 0)` directly instead of
> `load_layer_map().silicon.layer`. This works, but it bypasses your
> project's layer stack and breaks if you renumber. The instruction file
> tells the agent not to do this; if it slips through, ask the agent to
> switch to `load_layer_map()`.





> **Warning: Stopping at a passing build**
>
> A green `uv run rosette build` only means the GDS was written. The design can
> still have unconnected ports, DRC violations, or auto-reduced bends.
> Always end the loop on `uv run rosette check`, not on `uv run rosette build`.





> **Warning: Stale API assumptions**
>
> Agents trained months or years ago will confidently write APIs that
> don't exist or have moved. The agent file points them at
> `.rosette/index.md` and focused contracts; if you see hallucinated method names,
> prompt the agent to reload the relevant contract. The complete `api.pyi` remains
> available as a fallback.





> **Warning: Skipping `components/`**
>
> For photonic designs, the `components/` library is where the real
> domain knowledge lives (port conventions, fiber pitch, taper lengths).
> An agent that ignores it and rolls its own waveguide will produce
> plausible but subtly wrong layouts. If your agent does this, ask it to
> read `components/` first or use the relevant component directly.



## See also

* [Installation](/docs/getting-started/installation): set up a project
  and pick an agent tool
* [Core concepts](/docs/getting-started/core-concepts): the mental
  model the agent works from
* [Design rule checking](/docs/guides/design-rule-checking): tighten
  your `rosette.toml` so checks catch real problems
* [Agent-Driven Design](/blog/agent-driven-design): the philosophy
  behind this workflow, with worked examples
* [An Accidental Convergence](/blog/ai-for-photonics): why code-driven
  photonic design fits LLMs well