> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flow.engineer/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 theme={null}
import { openAICompatible } from "@flow-engineer/flow";

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

```ts Local OpenAI-compatible server theme={null}
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 theme={null}
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://docs.flow.engineer/conversations.md) for identity and time-zone behavior, and
[reminders and nudges](https://docs.flow.engineer/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://docs.flow.engineer/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://docs.flow.engineer/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://docs.flow.engineer/self-hosting.md) covers persistence and restart handling.
See [Deployment](https://docs.flow.engineer/hosting.md) for Flow hosting and [Traces](https://docs.flow.engineer/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://docs.flow.engineer/nudges.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.