Fundamentals
Architecture
This page explains how Teaflask runs an agent inside your product: where each part runs, how a conversation moves forward, and what happens when something goes wrong along the way. Most of the behaviour you'll see while building and running an agent follows from the five ideas on this page.
The parts
A live conversation involves four parts:
| Part | Runs in | Does |
|---|---|---|
| The Teaflask Assistant | The user's browser, inside your product | Shows the conversation, sends the user's messages, and carries out work that has to happen in the page or as the user |
| The Teaflask runtime | Teaflask's infrastructure | Runs the agent harness, stores conversations and enforces every grant and policy |
| The model provider | The provider's infrastructure | Hosts the model. The runtime calls it; it never talks to your product |
| Your backend | Your infrastructure | Performs operations for the signed-in user and checks their permissions |
Your product + Teaflask Assistant <----> Teaflask runtime <----> Model provider
(the user's browser)
|
| actions, with the user's session
v
Your backend
Three terms are worth keeping apart, because "agent" is often used loosely for all of them:
| Term | Means |
|---|---|
| Agent | What you configure in the dashboard: its instructions, its model, and the knowledge, tools and actions it's granted |
| Model | The language model from your model provider. It writes text and chooses which tools to call |
| Agent harness | The program on the Teaflask runtime that runs an agent. It gives the model the conversation and the agent's tools, runs the tools the model calls, enforces grants and policies, and repeats until the work is done |
The harness always runs on the Teaflask runtime. The browser never runs it. The browser shows what the harness is doing, and carries out the parts of the work that only a browser can.
1. Conversations run on the runtime, not in the browser
Each message a user sends starts a turn: the harness's work on that message, from the first model call to the end of the reply. The runtime records the message and runs the turn as a durable process. From then on the turn doesn't depend on the page that sent it.
So closing the tab, reloading or losing the connection doesn't stop a turn. The work carries on, and the result is there when the user comes back. Only the Stop button stops a turn.
A turn runs with the agent's configuration as it was when the turn started. Publishing a change affects the next turn, never one already running.
2. The harness works in rounds
Inside a turn, the harness works in rounds. In each round it sends the model the conversation so far and the tools this agent is granted. The model replies with text, tool calls, or both. The harness runs the tool calls and starts the next round with their results. The turn ends when the model replies without calling another tool.
Each tool runs where the thing it needs is:
| Runs on | Tools |
|---|---|
| The Teaflask runtime | Teaflask's built-in tools: document search and retrieval, memory, the workspace (an isolated sandbox for each conversation) and sub-agents |
| The user's browser | Everything that needs the page or the user: your actions, reading the page, highlighting, filling in forms, navigating, and questions to the user |
Your actions run in the browser because only the browser holds the user's session for your backend. The model only ever sees the tools the agent is granted, so a tool you didn't grant isn't something it can call.
3. A turn pauses when it needs someone
Some tool calls can't finish on the runtime alone. An action set to ask first needs the user's approval, a question needs an answer, and a browser tool needs a page to carry it out. When a round produces one of these, the turn pauses and sends the request to the conversation's open pages.
How the pause ends depends on what happens next:
| What happens | Result |
|---|---|
| The request is answered within a few minutes | The turn continues where it paused |
| Nothing answers in that time | The turn parks. It stops running but keeps its place. A parked turn doesn't expire |
| An answer arrives for a parked turn, even much later | The turn resumes from where it paused, with the configuration it started with |
| The user moves on with a new message instead | The unanswered requests are abandoned and recorded as having an unknown outcome |
A browser tool runs only while a page with the conversation is open, because only that page can run it. If the user leaves first, the turn parks, and when they open the conversation again, the page carries out the request then. A page never runs the same request twice. If a page closes in the middle of a request, the request isn't retried: the model is told the outcome is unknown, and asks the user to check.
4. Every step is recorded
The runtime records the result of each round before it starts the next. If the machine running a turn crashes, or a deploy restarts it, another machine picks the turn up from the last recorded round. The user's message is never sent twice, and an action is never sent to your backend twice, because actions run in the browser and each one is recorded once.
The same record is what users see. Everything a turn does (its text, tool calls, results and pauses) is written to a log for the conversation. The browser doesn't receive the turn directly; it reads that log. That's why:
- any number of tabs and surfaces can show the same conversation at once,
- a dropped connection picks up where it left off,
- a reload replays the conversation from the log, and
- text appears as the model writes it, and if the live connection degrades, it arrives in larger pieces with nothing missing.
5. How a turn ends
| Ending | What happened |
|---|---|
| Succeeded | The model finished its reply |
| Stopped | The user pressed Stop |
| Failed | Something went wrong that retrying couldn't fix. The user sees a sentence saying what |
| Parked | It waited too long for an answer (see section 3) |
| Superseded | The user moved on with a new message while it was parked |
Stop takes effect within seconds, even in the middle of a round: the round is cancelled, along with the tool calls running in it. A request your backend has already received can't be called back. A stopped turn is billed for the model use up to the point it stopped.
What a turn works from
The agent's configuration and model are fixed when a turn starts. The rest is read as the turn goes:
| Input | Detail |
|---|---|
| Configuration | The agent's published configuration. Unpublished edits don't reach users, and an agent that has never been published doesn't answer |
| Knowledge | Released docs only, never drafts. A newly released doc is usually searchable within seconds |
| Memory | If the agent has memory on, relevant memories are added at the start of each turn, and new ones are extracted from completed turns. See Memory |
| The conversation | Long conversations don't fit in a model's context, so the harness measures the conversation before every model call, and once it comes close to filling the model's window it folds the oldest messages into summary notes and carries on from there. This is how every agent works; there's nothing to choose. The messages behind a fold are still stored, and the model can read them back when it needs them. Users always see the whole transcript. See Model |
| The model | The agent's model, with its fallbacks. The harness falls back to the next model when a provider is throttled, overloaded or down, and when the model that took over can no longer serve the message: the conversation outgrew it, or the pocket paying for it changed. If the reply had already started streaming, what the user saw is taken back and the round runs again on the next model. A fallback holds for the rest of that message. See Model |
Funding is decided when a turn starts. Model use is paid from your Teaflask balance, or billed to your own provider account if you've added a provider key. If neither can pay, the runtime refuses the message before a turn exists, and the user is told the assistant is unavailable. A turn that has started isn't stopped by the balance running low.
Sub-agents
When an agent delegates, each sub-agent runs as its own background turn on the runtime, with its own record and its own workspace. It uses the parent agent's configuration, but it can't use the browser-only tools, ask the user questions, write memories or start sub-agents of its own. When a sub-agent needs an approval, or needs the browser to run an action, the request appears in the user's conversation like any other.
The parent doesn't wait for its sub-agents. When they finish, the runtime starts a new turn in the conversation by itself, with no user message, so the results can be reported. See Delegation.
The trust model
Every request answers three questions, each with its own mechanism:
| Question | Answered by | Where it lives |
|---|---|---|
| Which agent replies? | A publishable key | In your page. It's public by design, and serves only the origins listed for it |
| Who is asking? | An end-user token your backend signs, if you identify users. Without one, the visitor is anonymous | Signed on your server. The signing secret never leaves it |
| What may be done? | The agent's grants and each action's policy, then your backend's permission checks | Grants and policies on the runtime, permissions in your backend |
The runtime enforces grants and policies before a request leaves it. An action set to ask first never reaches the browser without the user's approval, and a denied action is never offered to the model at all.
The user is identified twice, and the two never cross. The end-user token tells the runtime who the user is, so their conversations belong to them; it never reaches your backend. The user's session in your product, meaning their cookies or access token, is what your backend checks when an action runs; it never reaches the runtime. Teaflask holds no credentials for your backend.
What data goes where
Everything a tool returns becomes part of the conversation: the docs it retrieves, the page content it reads and your backend's responses. The runtime stores it with the conversation and sends it to the model provider as context for the next round. Design actions to return what the model needs, not everything your backend knows about a record.
Results from the browser are capped in size before they reach the model:
| Result | Cap |
|---|---|
| An action's response body | About 16,000 characters, then cut with a marker |
| A page read | About 16,000 characters |
| Anything the browser sends back | About 20,000 characters. Over that, the result is dropped and the model is told so |
The preparation path
Everything above is the live path. Separately, your connected repositories give Teaflask read-only access to your code. From it, Teaflask helps draft your docs, checks released docs as the code changes, and discovers the API operations and pages your agents can use. Everything it finds is a proposal. Nothing reaches the live path until you release the docs, approve the actions and publish the agent. See GitHub.
More resources
- Overview – what Teaflask is, what it runs and what you own
- Grounding – what the agent works from, and the limits on what it can do
- Assistant UI – how the Teaflask Assistant behaves inside your page
- Actions adapter – the code in your page that makes action requests
- Model – model routing, fallbacks and long conversations
- Delegation – sub-agents and what they can use