Architecture
The pieces
| Piece | What it does | Where it runs |
|---|---|---|
@frankieai/react | The chat widget: transport, a state reducer and the UI | Your app's bundle |
Data plane (/v1/*, /mcp/*) | Sign-in handshake, the agent, chat history | Frankie cloud |
| Control plane + dashboard | Workspaces, agents, actions, settings | Frankie cloud |
A single turn
- The widget posts to
/v1/chatand reads an event stream. - The server authenticates, applies limits, loads the workspace's actions, and runs the agent loop in-process.
- The model layer sends the conversation to the configured model. The default is Kimi K3 on Fireworks; about 30 providers (Anthropic, OpenAI, Google, Groq, OpenRouter and more) can be swapped in by config.
There is no subprocess and no translation layer: the only thing between the browser and the model provider is Frankie's own event mapping.
| Agent event | What the widget receives |
|---|---|
| Text being written | text_delta |
| An action starts | tool_start |
| An action finishes | tool_end |
| An action is blocked by policy | tool_denied |
| The turn ends | turn_end, with usage and cost |
Actions
Frankie turns two kinds of source into actions, merged into one list the agent can't tell apart:
| Source | Action names |
|---|---|
| MCP servers | mcp__<server>__<tool> |
| OpenAPI documents | api__<name>__<operationId> |
Two choices worth knowing:
- Read-only by default. An OpenAPI source builds actions for
GETonly until you widen it. An agent pointed at a whole API can't delete anything on day one. - The request body is nested under
body, separate from path and query parameters, so a body field and a query parameter with the same name can't collide.
Policy is applied twice: when actions are built (a forbidden action never reaches the model), and again when each call runs (which catches anything added mid-turn, and produces the tool_denied event).
Connections are made per turn, not pooled: no connection outlives the request that authorised it, and every tool call runs on a connection made with the current user's credentials. To keep setup fast, what a source offers is cached briefly between turns: a spec URL is re-read at least every 30 seconds (with a conditional request, so an unchanged spec costs a 304), and an MCP server's tool list is re-listed at least every minute. A cached tool list is only reused for the same user presenting the same credentials, never shared between users. Changing a connection, or testing or refreshing it in the dashboard, drops its cached copy at once. A source that is down is logged and skipped, so one broken server doesn't take the assistant with it; a spec URL that fails to load falls back to a copy read in the last 10 minutes. Set FRANKIE_TOOL_CACHE=off on the server to read every source on every message instead.
Sessions
A session owns its transcript. It's handed to the agent at the start of each turn and written back when the turn settles, so:
- the agent loop keeps no state between turns,
- a reply keeps running if the user closes the tab, and is there when they come back,
- and every transcript is available for review in Conversations.