|
| 1 | +# Running agents headless |
| 2 | + |
| 3 | +The common embedded use of OpenCode is: start the server, wait until the agents you configured have loaded, run |
| 4 | +one prompt per unit of work, and read the result. Two helpers cover that on both clients. |
| 5 | + |
| 6 | +## Wait until the server is ready |
| 7 | + |
| 8 | +Agents load and MCP servers connect in the background after `opencode serve` starts listening, so the first |
| 9 | +requests can arrive before your agent exists. `wait_until_ready` polls until everything you name is there: |
| 10 | + |
| 11 | +```python |
| 12 | +client.wait_until_ready(agents=["reviewer"], mcp_servers=["github"], timeout=60) |
| 13 | +``` |
| 14 | + |
| 15 | +It treats connection errors and `503` answers as "still booting". After `timeout` seconds it raises |
| 16 | +`TimeoutError` naming what is still missing. |
| 17 | + |
| 18 | +## Run a prompt to completion |
| 19 | + |
| 20 | +```python |
| 21 | +from opencode.types import ModelRef |
| 22 | + |
| 23 | +result = client.session.run( |
| 24 | + "Review the staged changes", |
| 25 | + agent="reviewer", |
| 26 | + model=ModelRef(provider_id="anthropic", id="claude-sonnet-5-5"), |
| 27 | + timeout=600, |
| 28 | +) |
| 29 | +print(result.outcome) # "succeeded", "failed" or "interrupted" |
| 30 | +print(result.text) # text of the last assistant message |
| 31 | +print(result.cost, result.tokens.input, result.tokens.output) |
| 32 | +``` |
| 33 | + |
| 34 | +`run` creates a session with the agent and model, sends the prompt, blocks on `session.wait` until the session is |
| 35 | +idle, then returns a [`opencode.lib.RunResult`](../reference/testing.md#opencode.lib.RunResult): |
| 36 | + |
| 37 | +- `messages`: the messages this run added, oldest first, starting with the prompt |
| 38 | +- `text`, `outcome`, `error`: the final answer and how the run ended |
| 39 | +- `cost`, `tokens`: usage of this run; `session.cost` and `session.tokens` are the session totals |
| 40 | + |
| 41 | +Set `model` together with `agent` when creating a session: otherwise the session can run on a different model |
| 42 | +than the agent's. |
| 43 | + |
| 44 | +Pass `session_id=` to continue a session. `agent` and `model` then switch the session before the prompt, and |
| 45 | +`messages` holds only the new messages. |
| 46 | + |
| 47 | +### Interrupts |
| 48 | + |
| 49 | +If waiting ends early, through `KeyboardInterrupt`, cancelling the task (async), the `timeout`, or a connection |
| 50 | +error, `run` calls `session.interrupt` before re-raising, so the agent does not keep running on the server. |
| 51 | + |
| 52 | +### Files |
| 53 | + |
| 54 | +`files` takes `PromptInputFileAttachment`s. Images and other inline content can be `data:` URLs: |
| 55 | + |
| 56 | +```python |
| 57 | +import base64 |
| 58 | +from opencode.types import PromptInputFileAttachment |
| 59 | + |
| 60 | +png = base64.b64encode(open("screenshot.png", "rb").read()).decode() |
| 61 | +client.session.run( |
| 62 | + "What is wrong in this screenshot?", |
| 63 | + files=[PromptInputFileAttachment(uri=f"data:image/png;base64,{png}", name="screenshot.png")], |
| 64 | +) |
| 65 | +``` |
| 66 | + |
| 67 | +## Async |
| 68 | + |
| 69 | +The same helpers exist on `AsyncOpencode`: |
| 70 | + |
| 71 | +```python |
| 72 | +async with AsyncOpencode(url, password=password) as client: |
| 73 | + await client.wait_until_ready(agents=["build"]) |
| 74 | + result = await client.session.run("Summarize README.md", agent="build") |
| 75 | +``` |
0 commit comments