---
name: openscience-flexinference
description: Point OpenScience at FlexInference with config only - a flexinference provider on @ai-sdk/openai-compatible, the OPENSCIENCE_CONFIG and OPENSCIENCE_CONFIG_DIR search order, per-model start_within and reasoningEffort, model variants, the /flex session plugin and the plugins directory scan that loads it, and agent keys that carry stored routing defaults. TRIGGER when wiring OpenScience to FlexInference, adding a FlexInference provider or model to an openscience.json, setting a flex deadline or thinking level for OpenScience, or debugging why an OpenScience request did not route through FlexInference or why a local plugin did not load. SKIP for FlexInference API work with no OpenScience in it, and for OpenScience questions that never touch model routing.
---

# OpenScience on FlexInference

Pointing OpenScience at FlexInference needs config only. No source change, no fork, no patch. A
stock OpenScience build reads a provider entry out of a JSON file and every request goes to the
router.

## The provider entry

```json
{
  "$schema": "https://syntheticsciences.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,
            "cache_read": 0.5,
            "cache_write": 6.25
          },
          "options": { "start_within": "default" }
        }
      }
    }
  }
}
```

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 OpenScience 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, limits, and routing.

## Where the file goes

OpenScience merges config from several places, and a later source wins over an earlier one:

- The global user config in OpenScience's config directory.
- `OPENSCIENCE_CONFIG`, a path to one config file.
- `synsc.jsonc`, `synsc.json`, `openscience.jsonc`, and `openscience.json` found walking up from the
  working directory to the worktree root, unless `OPENSCIENCE_DISABLE_PROJECT_CONFIG` is set.
- `OPENSCIENCE_CONFIG_CONTENT`, holding the JSON inline.
- Those same four file names inside each `.openscience` or `.synsc` directory found on that walk and
  under your home directory, plus any directory named by `OPENSCIENCE_CONFIG_DIR`.

Two of those take a path, and they are not interchangeable. `OPENSCIENCE_CONFIG` names one file and
nothing else about its directory matters. `OPENSCIENCE_CONFIG_DIR` names a directory, and OpenScience
treats that whole directory as config: it reads the four file names out of it and also scans it for
commands, agents, and plugins. Use the directory form for a bundle that carries anything besides the
JSON file:

```bash
OPENSCIENCE_CONFIG_DIR=/path/to/config bun run dev
```

## 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" }
}
```

Setting `"start_within": "default"` on every model is the safe starting point. A model picked with
no session selection then routes standard instead of racing by accident.

**Per variant.** `variants` on a model entry declares named bodies you switch between without
changing model. Cycle them with `ctrl+t`:

```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 by placing it, not by naming it.** Put the file at `plugins/flex.js` inside the config
directory and leave `plugin` out of `openscience.json` entirely:

```
config/
  openscience.json
  plugins/
    flex.js
```

```bash
OPENSCIENCE_CONFIG_DIR=/path/to/config bun run dev
```

OpenScience scans each config directory for `{plugin,plugins}/*.{ts,js}` and loads what it finds as
a local file. A `.openscience` directory on the walk up from your working directory, or under your
home directory, is scanned the same way.

Writing the path into `plugin` instead does not work. Every entry in that array that does not start
with `file://` is read as an npm package name and handed to `bun add`, so `"./plugins/flex.js"`
fails to install and takes plugin startup down with it. An absolute `file://` URL is the only string
form that loads a local file, and it hardcodes a machine path into a config file, which is why the
directory scan is the config-only route.

Two more rules the scan brings with it. It is one level deep, so `plugins/lib/helper.js` is not
loaded, but it takes every `.js` and `.ts` sitting directly in `plugins/`. Keep shared modules a
plugin imports outside that directory, or OpenScience loads them as plugins in their own right.

The first time OpenScience loads config from a directory it writes `package.json`, `bun.lock`,
`node_modules/`, and a `.gitignore` covering them into it. Expect those, and keep them out of Git.

`/flex gpt-5.6-sol 2m high` selects a model, a two minute window, and high thinking.
`/flex claude-opus-4-8 off medium` 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`. OpenScience does not surface response
headers in the workspace. Showing a flex rate, a time to first token, or a side-by-side comparison
against a direct OpenAI call means patching the client.
