> ## Documentation Index
> Fetch the complete documentation index at: https://flow.engineer/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Models and configuration

> Choose a model and configure your messaging agent's runtime behavior.

Configure the model and `createAgent` options in `src/agent.ts`. The starter already connects
the agent to its tools, test people and hosting environment.

> [!NOTE]
>
> These options apply to the messaging agent. An MCP server or CLI tool command does not need
> a model key; the host supplies its model or invokes a tool directly.

## Models

`openAICompatible` works with OpenAI-compatible APIs. With no explicit `apiKey` or `baseURL`,
it uses the first non-empty key in this order:

| Order | Environment variable | Provider             |
| ----- | -------------------- | -------------------- |
| 1     | `OPENROUTER_API_KEY` | OpenRouter           |
| 2     | `OPENAI_API_KEY`     | OpenAI               |
| 3     | `ANTHROPIC_API_KEY`  | Anthropic            |
| 4     | `GEMINI_API_KEY`     | Gemini               |
| 5     | `FLOW_KEY`           | Flow's hosted models |

With no key, it stops immediately and names the supported variables.

```ts Environment defaults
import { openAICompatible } from "@flow-engineer/flow";

const model = openAICompatible({ model: process.env.MODEL });
```

```ts Local OpenAI-compatible server
import { openAICompatible } from "@flow-engineer/flow";

const model = openAICompatible({
  baseURL: "http://localhost:8000/v1",
  model: "your-model",
});
```

Use your local server's address and model id in the second example. A server that needs a key
can also receive `apiKey` explicitly.

`MODEL` sets the default model id. The starter passes `CAFE_MODEL` when set. Otherwise each
provider has a default in
[`src/model.ts`](https://github.com/flow-engineer/flow/blob/main/src/model.ts).
Set `contextTokens` to tell Flow the model's context-window size.

### Use Flow's model key

`flow setup` writes `FLOW_KEY`, which can use the account's $2 of trial model credit. A provider
key takes priority over it. `flow status` reports the remaining credit without printing the key.

`flowModel({ model })` explicitly selects Flow's hosted models; its default is
`anthropic/claude-haiku-5.5`. `GET /models` on the configured Flow model server lists the models
it offers. Set `FLOW_MODEL_URL` to use another Flow model server.

> **Rotation and exhausted trial credit**
>
> `flow setup --rotate key` replaces `FLOW_KEY`. The old key works for another 24 hours;
> `--now` ends it immediately. Neither command prints the full key.
>
> Exhausted credit returns `trial_credit_used` with a next step. Add your own provider key to
> continue with that provider.

### Transcribe a voice note

For a local audio file named `voice.ogg`:

```ts Transcription
import { readFile } from "node:fs/promises";
import { flowTranscribe } from "@flow-engineer/flow";

const audio = await readFile("voice.ogg");
const { text } = await flowTranscribe(audio, {
  filename: "voice.ogg",
  mimeType: "audio/ogg",
});
console.log(text);
```

This uses the same Flow key and model-server address. It accepts a Blob, File, Buffer,
Uint8Array or ArrayBuffer, up to 15 MB. `language` is optional and detected when omitted.
Errors include a message, next step, `code` and HTTP `status`.

## `createAgent` options

The tables below cover common options. The TypeScript
[`AgentOptions` definition](https://github.com/flow-engineer/flow/blob/main/src/agent.ts)
is the complete contract.

### Instructions and tools

| Option         | Purpose                                                         |
| -------------- | --------------------------------------------------------------- |
| `dir`          | Folder containing `AGENTS.md` and `skills/`                     |
| `files`        | Supply those files as text when there is no folder              |
| `model`        | A `Model`, usually from `openAICompatible`                      |
| `tools`        | Your tool definitions                                           |
| `inlineSkills` | Include every skill in the prompt rather than loading on demand |

### Channels and identity

| Option       | Purpose                                                             |
| ------------ | ------------------------------------------------------------------- |
| `channels`   | `whatsapp()`, `sms()`, `customUI()` or provider channel definitions |
| `resolve`    | Resolve a channel identity to your account key                      |
| `testPeople` | People the playground may play, as `[{ name, phone }]`              |
| `profile`    | Account data fetched on demand through `get_user_profile`           |
| `context`    | Small facts added before every turn; keep slow lookups out of it    |
| `timeZone`   | The business time zone, used when the person's is unknown           |
| `quietHours` | Hours to hold business-started messages; `false` disables them      |
| `transcribe` | Speech-to-text for incoming voice notes                             |

See [messaging and people](https://flow.engineer/docs/conversations.md) for identity and time-zone behavior, and
[reminders and nudges](https://flow.engineer/docs/nudges.md) for delivery rules.

### State and hosting

| Option     | Purpose                                                                                                                                                                                 |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `database` | Durable storage; defaults to an in-memory SQLite database                                                                                                                               |
| `store`    | Supply your own store instead of a database                                                                                                                                             |
| `records`  | Collections of the agent's own small records, kept in the same store ([records](https://flow.engineer/docs/records.md))                                                                 |
| `wakeUps`  | `false` removes the model's `wake_me_up` and `cancel_wake_up` tools and their list from its prompt; wake-ups set by code still run ([wake-ups](https://flow.engineer/docs/wake-ups.md)) |
| `host`     | Hosting options passed as `host: options.host` from `buildAgent`                                                                                                                        |
| `routes`   | Your HTTP handlers, keyed by path                                                                                                                                                       |
| `events`   | Event sinks receiving runtime events                                                                                                                                                    |
| `traces`   | Trace export settings; defaults come from the environment                                                                                                                               |

[Self-hosting](https://flow.engineer/docs/self-hosting.md) covers persistence and restart handling.
See [Deployment](https://flow.engineer/docs/hosting.md) for Flow hosting and [Traces](https://flow.engineer/docs/traces.md) for observability.

### Turn timing and history

| Option                    | Default                             | Purpose                                                                |
| ------------------------- | ----------------------------------- | ---------------------------------------------------------------------- |
| `debounceMs`              | 300                                 | Quiet time after a message before a reply starts                       |
| `deadlineMs`              | 60,000                              | How long a reply waits for a tool before moving it to the background   |
| `partialAfterMs`          | 5,000                               | How long a finished tool result waits for other tools in that response |
| `maxSteps`                | 8                                   | Model steps per turn                                                   |
| `history.maxTokens`       | 60% of `contextTokens`, else 50,000 | History carried into each model call                                   |
| `history.toolResultChars` | 12,000                              | Limit for a tool result in history                                     |

Durations are milliseconds. A tool's own `deadlineMs` overrides the agent's value for that tool.

### Message hooks and templates

| Option             | Purpose                                                                     |
| ------------------ | --------------------------------------------------------------------------- |
| `beforeSend`       | Change or refuse the final message before validation, sending and recording |
| `intercept`        | Answer an incoming message without a model call                             |
| `templates`        | Your declared WhatsApp templates                                            |
| `fallbackTemplate` | The template used when a nudge meets a closed WhatsApp window               |
| `shareUpdates`     | Enable, disable or customize live status guidance in web and app interfaces |

> **beforeSend return values**
>
> `beforeSend(message, { channel, address, info, origin })` runs for messages, carousels and
> templates on every channel. Return nothing to send the input, another message to replace it,
> or `{ refuse: "why" }` to tell the agent to try again.
>
> It runs before platform content checks. History, transcripts and test judges see what was
> actually sent.
>
> An edit's new text runs through it too, as a message with only `text`, and
> `context.edit.messageId` names the message being edited. A replacement is what goes out; a
> refusal sends nothing.

> **Edits, unsends and message ids**
>
> On channels whose info has `edits` and `unsends` (Telegram and iMessage on Flow Messaging, or
> a custom UI with `editMessage` and `unsendMessage`), the agent is offered `edit_message`
> (`{ message_id, text }`) and `unsend_message` (`{ message_id }`). Its send results there name
> each message (`{"delivered":true,"channel":"imessage","message_id":"msg_..."}`). WhatsApp and
> SMS offer neither tool.
>
> The platform decides how late an edit or unsend may come. Flow Messaging refuses a late one
> when it is made (`422 unsupported_content` with `edit_window_closed`, `unsend_window_closed`,
> `first_message_not_editable` or `not_on_sms`), and the agent gets that refusal as its tool
> result, for example `Not edited: unsupported_content (edit_window_closed, on imessage): iMessage
>     allows edits within 15 minutes of sending; ...`. Nothing is sent instead.
>
> Your own code gets the id too: a send's `SendReceipt.messageId`, the `messageId` on
> `message.out` events, and a sent nudge's `NudgeResult.messageId`. `message.edited` and
> `message.unsent` events record each change. Requires `@flow-engineer/flow` 0.7.0 or later.

> **intercept and keyword messages**
>
> `intercept(conversationId, text, message)` sees the incoming channel and any STOP, START or
> HELP classification in `message.keyword`.
>
> Its answer is stored in history and the transcript. STOP and START are enforced even if
> the hook returns an answer or throws. See [keyword handling](https://flow.engineer/docs/nudges.md).
