Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@
"bucketings",
"bucketers",
"consoledonottrack",
"hosters"
"hosters",
"Gemini",
"OpenAI",
"OpenRouter"
],
"ignoreRegExpList": ["https?://[^\\s)]+", "`[^`]*`"]
}
71 changes: 68 additions & 3 deletions content/docs/cli/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ state:
dir: ./.reactor # durable state (receipts, world-models, IR cache)

model:
provider: openrouter
provider: openrouter # openrouter (default) | openai | anthropic | google | <custom>
render_model: google/gemini-3.5-flash
compile_model: google/gemini-3.5-flash
temperature: 0
max_turns: 200
# base_url: ... # optional: override the provider's endpoint
# api_key_env: ... # optional: read the key from a different env var

sandbox:
mode: none # none (default) | docker
Expand All @@ -45,14 +47,76 @@ reactors: [] # optional: a multi-reactor host (see below)

| Key | Default | Meaning |
| --- | --- | --- |
| `model.provider` | `openrouter` | The model provider. |
| `model.provider` | `openrouter` | The model provider. A built-in (`openrouter`, `openai`, `anthropic`, `google`) supplies its own endpoint + key env; any other name requires `base_url` + `api_key_env`. |
| `model.render_model` | `google/gemini-3.5-flash` | The model used for renders at run/serve time. |
| `model.compile_model` | `google/gemini-3.5-flash` | The model used for the compile sessions. It is part of the IR cache key. |
| `model.temperature` | `0` | Sampling temperature. |
| `model.max_turns` | `200` | The per-session turn ceiling. |
| `model.base_url` | (provider default) | Override the OpenAI-compatible base URL. Optional for a built-in; required (with `api_key_env`) for a custom vendor. |
| `model.api_key_env` | (provider default) | The env var holding the API key (e.g. `ANTHROPIC_API_KEY`). Optional for a built-in; set it to read from a non-default variable. |

The compile model is a component of the content-addressed cache key `(contract-set fingerprint, SDK version, model id)`. Changing it invalidates the cache and forces a recompile.

### Choosing a model provider

By default Reactor talks to models through OpenRouter, but it is **not** bound to
it. The CLI drives one bounded `@openai/agents` session per render/compile step,
and any vendor with an **OpenAI-compatible Chat Completions** endpoint plugs in by
naming it in `model:`. Point at OpenAI, Anthropic, or Google **directly** -- no
OpenRouter account required:

```yaml
# Anthropic, directly (set ANTHROPIC_API_KEY in your env or a .env)
model:
provider: anthropic
render_model: claude-haiku-4-5
compile_model: claude-haiku-4-5
```

Each built-in provider resolves to an endpoint + a key env var:

| `provider` | Endpoint | Key env var | Example model id |
| --- | --- | --- | --- |
| `openrouter` (default) | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | `google/gemini-3.5-flash` |
| `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `gpt-4o-mini` |
| `anthropic` | `https://api.anthropic.com/v1/` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` |
| `google` | `https://generativelanguage.googleapis.com/v1beta/openai/` | `GEMINI_API_KEY` | `gemini-2.5-flash` |

For any other OpenAI-compatible vendor (or a self-hosted gateway), name it freely
and supply both `base_url` and `api_key_env`:

```yaml
model:
provider: together
base_url: https://api.together.xyz/v1
api_key_env: TOGETHER_API_KEY
render_model: meta-llama/Llama-3.3-70B-Instruct-Turbo
compile_model: meta-llama/Llama-3.3-70B-Instruct-Turbo
```

`api_key_env` also overrides a built-in's key var -- handy when, say, your
OpenRouter key lives under a different name. Run `reactor doctor` to confirm the
configured provider's key is visible (it reports the exact env var, never printing
the value), and `reactor doctor --live` to drive one real round-trip against it.

<Callout type="info">
The key is read from the named env var first, then a `.env` discovered by walking
up from the project directory. A missing key fails `compile`/`run`/`serve` with a
**non-zero** exit and a message naming the exact variable to set -- it never
silently misdirects you to OpenRouter, and never exits 0 on an auth dead-end.
</Callout>

<Callout type="warn">
**Claude models: prefer `openrouter` over `anthropic` for now.** Compile + render
use a JSON-schema response format, and Anthropic's direct OpenAI-compatible
endpoint rejects it unless `strict: true` (`400 response_format.json_schema.strict`).
OpenRouter, OpenAI, and Google accept it. To run Claude today, use
`provider: openrouter` with a `render_model`/`compile_model` like
`anthropic/claude-haiku-4-5`. `provider: anthropic` (direct) is wired and
authenticates, but the structured compile/run path is blocked by that endpoint
requirement.
</Callout>

## sandbox

The `sandbox` block is the render threat-model knob.
Expand Down Expand Up @@ -96,7 +160,8 @@ Each entry is isolated: its own contracts directory, state directory, substrate,

| Variable | Meaning |
| --- | --- |
| `OPENROUTER_API_KEY` | The live key for the model surface. Required by `compile`, `run`, `serve`, and `trigger`. Read from the process env first, then a `.env` file discovered from the working directory upward. |
| `OPENROUTER_API_KEY` | The live key for the **default** provider. Required by `compile`, `run`, `serve`, and `trigger` when `model.provider` is `openrouter`. Read from the process env first, then a `.env` file discovered from the working directory upward. |
| `<model.api_key_env>` | The live key for a **non-default** provider -- `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or whatever `model.api_key_env` names. Resolved the same way (env first, then a discoverable `.env`). See [Choosing a model provider](#choosing-a-model-provider). |
| `REACTOR_OFFLINE` | Set to `1` (or `true`) to force offline mode. Equivalent to the `--offline` flag. |

## Global flags
Expand Down
74 changes: 74 additions & 0 deletions content/docs/sdk/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,80 @@ const render: RenderOptions = {
};
```

## Bring your own LLM provider

Reactor is **not** bound to OpenRouter. The default render points at OpenRouter's
OpenAI-compatible surface only because it is a cheap, broad gateway -- but the
`provider` field is plain `@openai/agents` configuration, so you point it
anywhere the way **any** `@openai/agents` consumer would: build a scoped
`OpenAIProvider` at the base URL of your choice and hand it in. Nothing about the
harness is OpenRouter-specific.

```ts
import { reactor } from "@openprose/reactor";
import { OpenAIProvider } from "@openai/agents";

// Anthropic, directly -- not through OpenRouter. (OpenAI and Google work the same
// way; only the base URL, key, and model id change -- see the table below.)
const provider = new OpenAIProvider({
apiKey: process.env.ANTHROPIC_API_KEY!,
baseURL: "https://api.anthropic.com/v1/",
useResponses: false, // Chat Completions -- the surface these vendors share
});

const { reactor: r } = await reactor("./my-project", {
directory: "./state",
render: { provider, model: "claude-haiku-4-5" }, // run-phase renders
compile: { options: { provider, model: "claude-haiku-4-5" } }, // compile sessions
});
await r.ingest("source", { wake: { source: "external", refs: [] } });
```

The same scoped `provider` flows to **both** phases: `render.provider` drives the
run-phase renders, and `compile.options.provider` drives the compile sessions
(they are the same bounded-session machinery). Pass it in only one place and the
other still defaults to OpenRouter, so set both when you mean to switch wholesale.

Any vendor with an OpenAI-compatible Chat Completions endpoint drops straight in:

| Vendor | `baseURL` | Key | Example model id |
| --- | --- | --- | --- |
| OpenRouter (default) | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | `google/gemini-3.5-flash` |
| OpenAI | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `gpt-4o-mini` |
| Anthropic | `https://api.anthropic.com/v1/` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` |
| Google Gemini | `https://generativelanguage.googleapis.com/v1beta/openai/` | `GEMINI_API_KEY` | `gemini-2.5-flash` |

<Callout type="info">
**`useResponses: false` is the safe default for a non-OpenAI host.** It selects
Chat Completions, the surface every vendor above implements; the newer Responses
API is OpenAI-only and 404s elsewhere. Against OpenAI's own host you may leave it
unset to use Responses.
</Callout>

Because `provider` is a **scoped** `ModelProvider`, this never mutates the
`@openai/agents` process-global default client -- two reactors in one process can
target two different vendors. The wiring is exercised live (OpenRouter + OpenAI +
Anthropic) in
`packages/reactor/src/adapters/agent-render/__tests__/provider-byo.live.test.ts`.

<Callout type="warn">
**Anthropic's direct endpoint and structured outputs.** Compile sessions and the
render's done/failed signal use a JSON-schema `response_format`. Anthropic's
OpenAI-compatible endpoint requires `strict: true` on that schema and rejects the
default with `400 response_format.json_schema.strict`. OpenAI and Google (and
every model **via OpenRouter**, including Anthropic's) accept it as-is. So for
**Claude models today, route through OpenRouter** (`provider` pointed at
`https://openrouter.ai/api/v1`, model `anthropic/claude-...`); direct
`api.anthropic.com` works for plain-text use but not yet for the structured
compile/render path.
</Callout>

For an API that is **not** OpenAI-compatible at all (a native Anthropic Messages
client, a local model, a bespoke gateway), drop one level to the
[`RenderBackend` injection seam](#the-renderbackend-injection-seam) below: you
own the whole session and the harness keeps its instruction composition, tools,
harvest, and cost capture.

## The `RenderBackend` injection seam

The Tier-C factories let you rebuild the `Agent`/`Runner` while staying inside the
Expand Down
Loading