Drive Contrast with your own agent
Hand the workspace to Claude Code, Codex, or anything that can run a shell command.
Contrast ships its own agents, and it will also step aside for yours. A drive session hands one external agent the project chat, the plan board, the worker squad, the web and iOS previews, and screenshots of the app actually running. Your agent calls the same tools Contrast’s own agents call, under the same authority, so what it gets is the real harness.
What you need
- Contrast running on this machine.
contrast ideandcontrast drivereach a Contrast IDE tab over loopback, so drive mode needs a local Contrast rather than a hosted workspace. - A signed-in CLI.
contrast loginfor a person, orCONTRAST_API_KEYfor a headless agent. SetCONTRAST_ORIGINwhen you want to name the Contrast you mean instead of relying on detection. - A project your account can open.
Bind the checkout to a project
Two files, and they are not the same thing. contrast.json is the committed
project pointer the whole team shares. .contrast/config.json is this clone’s
own routing state and belongs in .gitignore.
--mode local is what routes IDE and drive commands through the loopback
daemon instead of the server-side attach tunnel.
Host a tab for the agent
Drive mode executes in a browser, because that browser is what holds the factory runtime claim that authorizes a project change. The claim is never copied into the CLI. So the first thing your agent does is give itself a tab:
It launches the tab, waits for it to register with the local daemon, and reports what it got:
The tab is an invisible Chrome that outlives the command, so there is no
window in the way. That browser has its own profile and your normal session
does not reach it, so the command signs the tab in as you, using the
credential contrast login stored (or CONTRAST_API_KEY). With neither, it
stops and tells you to run contrast login first. Expect the first open to
take ten seconds or so, and pick your agent’s timeouts accordingly.
If you already have Contrast open and signed in on that project in your own browser on the same port, the same command connects to that tab instead of hosting a second one.
The tab is a resource your session owns. Close it when you are done, and see Troubleshooting if it does not go quietly.
Take the project
Lux stops answering the chat and the Manager stops planning, so your agent is the only thing making decisions. Everything else keeps going: workers, designers, pair review, and QA continue in the browser exactly as before. Messages from the person you are working for still land on the normal project thread, and your replies appear there as Contrast.
Starting is transactional, so a second session cannot also win. It is refused
by name, with project is already driven by "<name>", and repeating start from
the session that already holds the project just succeeds again.
Then look around:
contrast drive tool <name> is the fastest way out of a parameter mistake. It
prints the real schema, which beats guessing at what --params wants.
Do the work
Four things worth knowing before your first call:
- Every app tool names a surface.
surface:"agent"is the hidden working preview your agent drives;surface:"visible"is the pane the person is looking at. They are different documents, so keep one value for a whole inspection flow rather than alternating. --image-outwrites exactly the bytes the tool returned, andapp_screenshotreturns WebP. Name the file.webp; a.pngname gets WebP content and confuses whatever reads it next. The command tells you the type it wrote.--agent <name-or-id>binds file and preview tools to that worker’s active branch, which is how you inspect or fix what one squad member is building instead of the main branch.- Reads never need a drive session, project changes always do. With nothing
driving,
contrast ide tool read_fileruns fine and a tool that changes the project refuses by name:
Every contrast drive call wants a session of its own, reads included, since
the session is the identity it runs as. Once you are driving, the two verbs do
the same thing.
contrast drive watch blocks until the owner’s next message arrives, so an
agent with nothing to do can park on it instead of polling.
Hand back
stop carries the session id your machine holds, so a stale process cannot
release a newer driver’s claim. close shuts down only the hosted tabs this
session started, matched by owner pid, and never touches a colleague’s tab or
your own browser.
Troubleshooting
A write is refused right after the tab reloaded. A reload mints a new runtime claim while the old one is still held, and it clears itself within one 20-second lease:
Retry. It heals on its own, and no drive stop or fresh tab is needed.
Every verb suddenly fails with a Zero initialization error. After a long
session with a lot of hot reloading, the tab’s data binding can come loose and
reads start failing along with writes. The fix is
contrast ide reload host, which reloads the browser page itself.
contrast ide reload is the wrong tool here, because it rebuilds the app
preview through the same binding that is broken.
No tab is connected. Nothing is hosting one, or it exited. Every drive verb
refuses with the exact contrast ide open command for your project and port,
so you can run it straight back.
Two tabs are connected. The CLI never guesses which one you meant. Pass
--project <projectId>.
Your Contrast is not on port 3000. Pass --http-port <n> to every
contrast ide and contrast drive command; they default to 3000 and do not
read the port from the attach config.
contrast ide close says no hosted tab belongs to this session. Each host
logs to /tmp/contrast-ide-tab-hosts/, named for the port it serves. That log
names the host pid and which arm ended it.
Next
- Agents, how the built-in factory frames and lands a goal, and what your agent is standing in for.
- The Factory space, the board and squad your drive session is writing to.
- GitHub sync, if you want the work to land as pull requests.