teaflask

Durable execution for AI agents: pause and resume after approval

Teaflask agents pause for a user's approval, park without expiring, and resume days later from the same step. How durable execution works, and where it stops.

Teaflask runs every agent turn on a durable runtime. When an action is set to Ask, the turn pauses for the user's yes. If nobody answers, it parks without expiring and resumes from the same step, with the same configuration, whenever the answer comes, even days later. Crashes and deploys recover from recorded rounds; an interrupted browser request can have an unknown outcome.

Teaflask is an embedded AI agent platform: the assistant lives inside your product and acts through your API as the signed-in user. Here is what the waiting looks like from the inside.

Runtime behavior is documented in the architecture docs.

What happens to a turn that needs a yes

When an action is set to Ask, the Teaflask runtime stops the turn before the request leaves it and waits for the user's decision. A few minutes without an answer parks the turn. A parked turn keeps its place and doesn't expire, so the same approval can arrive on a Friday afternoon or the following Tuesday. Our changelog sizes the wait as a human deciding "in a minute or next Tuesday", and the runtime has to be ready for either.

  1. Fri 16:40: Ask.

    submit-expense is set to Ask. Ines tells the assistant to file a $920 flight, and the turn stops at that action.

  2. Fri 16:41: Paused.

    The runtime sends the approval request to Ines's conversation and waits a few minutes for her decision.

  3. Fri 16:46: Parked.

    Ines has gone home. The turn stops running, holds its spot and never times out.

  4. Tue 09:12: Resumed.

    She opens the conversation and approves. The page can carry out the action, and the turn continues from the same step with the configuration it started with. Approval resumes the work; it does not guarantee completion.

What happens when a server dies mid-task

If the machine running a Teaflask turn crashes or a deploy restarts it, another machine picks the turn up from the last recorded round. The runtime records the result of each round before the next one starts. That recovery does not establish whether an interrupted browser request completed in your backend.

  1. Recorded first

    The result of each round is written down before the next round starts.

  2. Picked up elsewhere

    Another machine takes the turn from the last recorded round.

  3. Conversation preserved

    The runtime records the message and the turn's progress in the conversation log.

  4. Unknown is explicit

    If a page closes mid-request, that request is not retried. The model is told to ask the user to check the outcome.

That record doubles as the screen. The browser reads a log of the conversation rather than receiving the turn directly, so you can open it in several tabs, reload it, or close the lot, and runtime work can carry on. A browser-dependent step waits for a page to return; a turn can still fail, or the user can stop it.

The five ways a turn ends

A Teaflask turn ends as Succeeded, Stopped, Failed, Parked or Superseded. Parked is the only ending that can still change, because an answer can resume it. Superseded is what a parked turn becomes when the user moves on with a new message instead of answering.

The five ways a Teaflask turn ends, with what each one means
EndingWhat happened
SucceededThe model finished its reply.
StoppedThe user pressed Stop.
FailedSomething went wrong that retrying couldn't fix. The user sees a sentence saying what.
ParkedThe turn waited too long for an answer. It keeps its place and doesn't expire, and a late answer resumes it.
SupersededThe user moved on with a new message while the turn was parked.

Stop takes effect within seconds, even mid-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, and a stopped turn is billed for the model use up to the point it stopped.

When Teaflask can't know, it says so

In two cases Teaflask records an outcome as unknown instead of guessing: a page closing mid-request, and a user moving on while requests wait. It doesn't retry either one. After a mid-request close, the model is told the outcome is unknown and asks the user to check.

  1. Page closes mid-request: Not retried

    An interrupted browser request is not retried. Its backend outcome may need checking before any further action.

  2. User moves on: Recorded as unknown

    The unanswered requests are abandoned and recorded as having an unknown outcome. The parked turn ends as Superseded.

  3. User leaves first: Parks until a page is open

    A browser tool runs only while a page with the conversation is open. When the user returns, the page carries out the request then.

Allow, Ask, Deny: the switch that creates the pause

Every Teaflask action carries one of three policies. Allow runs without approval, Ask requires the user's approval before it executes, and Deny keeps the action out of the agent's available actions entirely. Ask is the setting that pauses a turn for a yes, so you decide which actions are worth waiting for.

  1. Allow: Runs without asking

    Unless the agent's own configuration requires an approval for it.

  2. Ask: Waits for the user's decision

    The request never reaches the browser without the user's approval.

  3. Deny: Never offered to the model

    A denied action isn't in the agent's toolbelt, so it can't be requested.

An agent can tighten an Allow into an approval, but it can't relax an Ask or a Deny. App-access tools take the same switch, one tool at a time, and the table below shows where each one stops.

App-access tools: what each does and what it never does
ToolWhat it doesWhat it never does
Read the current pageGives the agent a structured view of the page's text, controls and field values.Reveal password values or fields marked with sensitive autocomplete values; both are redacted.
Prefill formsEnters values into text inputs, dropdowns, checkboxes and radio buttons.Press submit. Your app's own handlers still run, and the user sends the form.
Highlight on the pageScrolls to a control and spotlights it.Activate the control it points at.
Navigate the appTakes the user to an app-relative path you allow.Go outside the path prefixes you configured.

Sub-agents wait too, and report back on their own

Teaflask sub-agents run as background turns on the runtime, each with its own record and workspace. When one needs an approval, the request appears in the user's conversation like any other. The parent doesn't wait: when the sub-agents finish, the runtime starts a new turn by itself, with no user message, to report the results.

The limits are plain. Delegation goes one level deep, sub-agents have no browser-only tools, can't question the user and can't write memories, and the dispatch itself can sit on Ask.

When you want a workflow engine instead

Reach for a workflow engine

Cross-service back-office orchestration with no user in the loop belongs in an engine. Temporal describes itself as a platform for the durable execution of application code, and its documentation covers long waits and human signals. A payout run across your billing, ledger and email services is its kind of job, and Teaflask isn't trying to replace it.

Reach for Teaflask

Reach for Teaflask when the one waiting is your user, inside your product. The work happens in their session and the pause is theirs to end. The runtime, the Allow, Ask, Deny gate and the assistant interface come as one product, so you aren't assembling an agent backend first.

Durable execution questions

How long can a turn wait for an approval?

A Teaflask turn waits a few minutes for an answer, then parks. A parked turn keeps its place and doesn't expire, and the documentation names no maximum wait. When an answer arrives, even much later, the turn resumes from where it paused, with the configuration it started with.

Does a deploy kill work that is already running?

No. Teaflask records the result of each round before starting the next. If a crash or a deploy restarts the machine running a turn, another machine picks the turn up from the last recorded round. Browser requests have a separate boundary: if a page closes mid-request, Teaflask records an unknown outcome rather than retrying it.

Can an action fire twice?

The architecture docs describe recorded browser requests that are not run twice. If a page closes mid-request, Teaflask does not retry it: the model is told the outcome is unknown and asks the user to check. This is request-replay handling, not a guarantee that every operation in your backend completes exactly once; your backend still owns its transaction and duplicate-handling rules.

What happens if the balance runs out?

Funding is decided when a turn starts. A live turn that has started is not stopped by the balance running low. If neither the Teaflask balance nor a provider key can pay for a new message, the runtime refuses it before a turn exists and tells the user the assistant is unavailable. Only eligible work already waiting for funds resumes automatically after funding is restored. A refused message may need to be sent again; a top-up does not create or resume a turn for that message.

Does Teaflask work with CopilotKit-style UIs?

Teaflask works with its own assistant interface, or with one you build on its headless hooks for the transcript, approvals and questions. Its headless docs describe no CopilotKit or AG-UI integration, so test that pairing rather than assume it. CopilotKit is mostly a toolkit: open-source UI components and hooks plus a runtime you host. Teaflask's agents always run on the Teaflask runtime.

Can someone other than the user approve an Ask action?

Teaflask's documentation describes the approval as the user's decision, made in the conversation where the request appears. It doesn't describe routing a request to a second person, so this page doesn't claim it. Sub-agent approvals work the same way: they are relayed to the user's conversation, with the sub-agent named as the requester.

Go deeper