---
name: openwork-flexinference
description: Point OpenWork at FlexInference with config only - a flexinference provider on @ai-sdk/openai-compatible in the OpenCode config its backend reads, the OPENWORK_DEV_OPENCODE_IMPORT_CONFIG_DIR import path, per-model start_within and reasoningEffort, model variants, the /flex session plugin, and agent keys that carry stored routing defaults. TRIGGER when wiring OpenWork to FlexInference, importing a provider bundle into an OpenWork profile, setting a flex deadline or thinking level for OpenWork, or debugging why an OpenWork request did not route through FlexInference. SKIP for FlexInference API work with no OpenWork in it, and for OpenWork questions that never touch model routing.
---

# OpenWork on FlexInference

Pointing OpenWork at FlexInference needs config only. No source change, no fork, no patch. OpenWork
drives an OpenCode backend, so the file you write is an OpenCode config file and the backend reads
the provider entry out of it.

## The provider entry

```json
{
  "$schema": "https://opencode.ai/config.json",
  "model": "flexinference/gpt-5.6-sol",
  "provider": {
    "flexinference": {
      "name": "FlexInference",
      "env": ["FLEXINFERENCE_API_KEY"],
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "apiKey": "{env:FLEXINFERENCE_API_KEY}",
        "baseURL": "https://api.flexinference.com/v1"
      },
      "models": {
        "gpt-5.6-sol": {
          "name": "GPT-5.6 Sol",
          "reasoning": true,
          "tool_call": true,
          "limit": { "context": 400000, "output": 128000 },
          "cost": { "input": 5, "output": 30 }
        }
      }
    }
  }
}
```

Four fields carry the whole integration. `npm` picks the OpenAI-compatible AI SDK package,
`options.baseURL` sends requests to the router, `options.apiKey` reads the key out of the
environment, and each key of `models` is a model slug the router serves. `env` lists the variable
so the backend marks the provider connected once it is set.

Add a model by adding a key under `models`. The slug is what goes on the wire, so it has to be a
slug the router prices. Everything else in the entry is display and limits.

## Where the file goes

OpenWork keeps the backend on its own isolated profile, so it does not read a config file off your
working directory. Point it at a directory holding `opencode.json` and it copies the whole
directory into the profile on launch:

```bash
OPENWORK_ELECTRON_USERDATA="$PWD/.openwork-flex-userdata" \
OPENWORK_DATA_DIR="$PWD/.openwork-flex-data" \
OPENWORK_DEV_OPENCODE_IMPORT_CONFIG_DIR="$PWD/config" \
OPENWORK_OPENCODE_BIN=/path/to/opencode/binary \
  pnpm dev
```

`pnpm dev` sets `OPENWORK_DEV_MODE=1`, which is what turns the import on. OpenWork copies the
directory into the profile's own OpenCode config directory, then starts the backend with
`OPENCODE_CONFIG_DIR` pointed at that copy. So the backend picks the file up through the OpenCode
search order, at the `OPENCODE_CONFIG_DIR` step:

- The global user config in OpenCode's config directory.
- `OPENCODE_CONFIG`, a path to one JSON or JSONC file.
- `opencode.json` and `opencode.jsonc` found walking up from the working directory to the worktree
  root.
- `opencode.json` and `opencode.jsonc` inside each `.opencode` directory found on that walk, plus
  any directory named by `OPENCODE_CONFIG_DIR`, which is where OpenWork's imported copy lands.
- `OPENCODE_CONFIG_CONTENT`, holding the JSON inline.

The copy runs on every launch, not once per profile, and it overwrites. Edit the bundle, restart on
the same `OPENWORK_ELECTRON_USERDATA` and `OPENWORK_DATA_DIR` paths, and the backend reads the new
file. The one thing it does not do is delete: a file you removed from the bundle stays behind in the
profile copy. Clearing that needs fresh paths for both variables, which starts the profile over and
drops its session history, so reach for it only when a leftover file is actually in the way.

OpenWork passes the whole parent environment through to the backend, so `FLEXINFERENCE_API_KEY`
exported in the shell that starts it reaches the provider entry.

## Routing per request, without code

`start_within` says how long a request may wait before it starts running. It is not a limit on how
long generation takes. Write a duration as `HHh-MMm-SSs` with two digits per field, from 5 seconds
to 10 minutes. Longer windows win the cheap tier more often. Send a tier name instead when you want
one tier and no race: `default` for the normal tier, `priority` for the fastest one, `auto` to let
the provider choose. Claude races a cheaper tier of its own, which a duration reaches on managed
keys, without streaming, inside a window of 3 to 10 minutes. Current constraints put that floor at
3 minutes. Outside those rules a duration on a `claude-*` model returns
`400 flex_unsupported_for_anthropic`.

Two config places put it on the wire, and both are plain JSON.

**Per model.** `options` on a model entry goes into the request body for that model:

```json
"gpt-5.6-sol": {
  "name": "GPT-5.6 Sol",
  "options": { "start_within": "00h-00m-30s", "reasoningEffort": "medium" }
}
```

**Per variant.** `variants` on a model entry declares named bodies you switch between without
changing model:

```json
"gpt-5.6-sol": {
  "name": "GPT-5.6 Sol",
  "variants": {
    "patient": { "start_within": "00h-05m-00s", "reasoningEffort": "high" },
    "now": { "start_within": "priority", "reasoningEffort": "low" }
  }
}
```

## The /flex plugin, for per-session routing

A small plugin adds a `/flex` command so a session picks its own model, start window, and thinking
level. It is optional. Everything above works without it.

The plugin exports two hooks. `command.execute.before` parses the arguments and stores the
selection against the session. `chat.params` writes `start_within` and `reasoningEffort` onto every
FlexInference request in that session, and leaves other providers untouched. Load it from the same
config file, and ship the plugin directory inside the imported bundle so the copy carries it:

```json
{ "plugin": ["./flex-plugin"] }
```

An OpenWork window outlives a backend process, so the plugin also writes the selection to session
metadata through the plugin client and reads it back when the in-memory map is empty. Without that,
a selection made before a restart would silently drop to standard routing.

`/flex gpt-5.6-sol 2m high` selects a model, a two minute window, and high thinking.
`/flex gpt-5.6-sol off` keeps the model on standard routing. A session with no selection sends
`start_within: "default"`.

## Agent keys carry the defaults instead

A FlexInference agent key can hold routing defaults, so a config needs nothing beyond `baseURL` and
the key. Create one in the dashboard under API, then FlexInference keys, with Create agent key.
A key made with the plain create form carries no defaults.

A key stores three fields:

- `start_within`: a duration or a tier name, same values as the body field.
- `on_no_flex`: `default` or `priority`. Required exactly when `start_within` is a duration, and
  rejected otherwise. It is the tier a model with no cheap tier falls back to, so a duration on the
  key never fails the way a duration in the body would.
- `retry`: `count` from 1 to 5, optional `backoff` of `exponential` or `linear`, optional `jitter`.

The router fills a stored default into any request that arrives without that field, and stamps
`x-flexinference-defaults-applied` on the response. The request body always wins. A body value is
used even when it is wrong, so an explicit bad value still returns `400 invalid_start_within`. A key
default stands in for a field you did not send, never for one you sent wrong.

Edits to a key go live within seconds and the key stays the same. Revoking a key clears its defaults
with it.

An agent never creates or revokes a key. A person issues the key in the dashboard and exports it.
The agent only points config at it.

## What config cannot do

Reading the outcome needs a source change. Every successful response carries
`x-flexinference-flex-applied`, which is `true` only when the cheap tier served the request, plus
`x-flexinference-flex-reason` and `x-flexinference-cost`. Neither the backend nor the OpenWork UI
surfaces response headers. Showing a flex rate, a time to first token, or a side-by-side comparison
against a direct OpenAI call means patching both.
