Skip to Content
DocsIn-Conversation Widgets

In-Conversation Widgets

Agents sometimes need something from you mid-task. It might be an API key, a confirmation, a choice between options. Without widgets, the only way to get that input is for the agent to ask in chat and for you to paste a value back. That breaks two things at once: your flow (the agent now needs to wait, you need to context-switch) and the security guarantee (the value lands in the model’s context window, in logs, in transcripts that survive).

Widgets fix that. When the agent calls a widget tool, a small form renders inline in the transcript. You fill it out. The values flow directly browser → daemon. They never reach the agent. A sanitized “[Agor] User submitted X” prompt is auto-queued into the agent’s next turn so it can pick up where it left off.

Widgets are a primitive, and three types ship on it:

TypeThe agent callsYou get
env_varsagor_widgets_request_env_varsA form for an environment variable, API key, or token the agent needs
oauthagor_widgets_request_oauthA Connect button that runs the real browser sign-in for an MCP server
gateway_tokenagor_widgets_request_gateway_tokenAn admin-only form for a gateway channel’s platform credentials

All three retain their status in the transcript and resume the agent after successful completion.


The env_vars widget

This is the widget the agent calls when it needs an environment variable it doesn’t have. Typical use: an MCP integration needs HUBSPOT_API_KEY and the agent doesn’t want to fail with “please go to Settings”.

Agents should prefer the widget over Settings instructions. Agor’s system prompt and the credential-related tool descriptions (e.g. agor_repos_get) tell agents that when a task needs a secret the user hasn’t set, the correct move is to call agor_widgets_request_env_vars — not to write “open Settings → Environment Variables” or ask the user to paste a value into chat. A written Settings instruction is only a last-resort fallback for when the widget tools are unavailable.

What the agent does

The agent calls the MCP tool agor_widgets_request_env_vars:

{ "names": ["HUBSPOT_API_KEY"], "reason": "Needed to call the Hubspot Private Apps API.", "variable_metadata": { "HUBSPOT_API_KEY": { "description": "Create one under Private Apps with the CRM > contacts > read scope.", "format_hint": "Starts with pat-" } } }

The scope (Session / Global) is the user’s choice in the form — the agent does not set it.

The tool returns the request immediately. The agent can finish its turn while you complete the form; submission queues a follow-up.

What you see

A small form renders inline in the transcript:

  • The variable name(s) the agent is asking for
  • A short reason
  • Optional per-variable description and format_hint text (from variable_metadata)
  • A password input per variable (or text / textarea, per input_type)
  • A scope selector (Session / Global)
  • Dismiss and Save buttons
  • The security posture is conveyed by the lock/shield chrome — never paste values into chat

When you hit Save, the value goes directly from the form to the daemon via POST /widgets/:widget_id/submit. It does not traverse the model. The daemon writes it through the same encrypted-at-rest path as Settings → Environment Variables (AES-256-GCM via the daemon’s master secret).

What the agent sees next

A new user-role message arrives in the agent’s next turn:

[Agor] User submitted HUBSPOT_API_KEY (scope: global). You can now retry the operation that needed it.

The agent retries. Only the name appears in that prompt. The value is read from the encrypted store at executor spawn time, as if you had set it via the Settings panel.

The already_present short-circuit

If you already have the requested variables set in the chosen scope, the widget skips the form entirely. The transcript shows a ”✓ already configured” badge and the agent auto-resumes with:

[Agor] HUBSPOT_API_KEY was already configured. You can proceed.

One fewer click in the common “agent didn’t know I had this” case.

Dismissal

If you hit Dismiss, the widget marks itself dismissed and queues an explicit prompt into the agent so it doesn’t loop:

[Agor] User dismissed the request for HUBSPOT_API_KEY. Do not re-request immediately — ask whether to proceed without, or move on to other work.

The oauth widget

This is the widget the agent calls when you say “connect me to Notion”, or when a task needs an MCP server you have not authorized yet. Before it existed, the honest answer an agent could give was “sign in from an available authentication surface” — advice nobody could act on from a Slack thread.

Agents should prefer the widget over Settings instructions here too. Agor’s system prompt tells agents to call agor_widgets_request_oauth rather than writing “open Settings → MCP Servers” or “open the MCP Catalog”. Settings remains the manual route; it is not the first answer.

What you see

A card in the transcript naming the server, why the agent wants it, and — when the server comes from the MCP Catalog — the entry’s plain-language statement of what connecting grants. You read that before anything is granted. Then one button: Connect.

Clicking it runs the ordinary browser OAuth flow: a pop-up opens, you sign in with the provider, the provider redirects back to Agor, and Agor exchanges and stores the grant. No token passes through the page, and none reaches the agent — the card only ever learns a name, a mode, and whether the attach succeeded.

A workspace-wide (shared) connection says so on the card, and only an admin can mint or complete one: everyone in the Agor workspace would use the account you sign in with.

What happens after you sign in

Agor re-reads the stored grant before it believes anything. The browser saying “I signed in” is not evidence — if the grant is not there, the card stays clickable and you can try again. Once the grant is real, the MCP server is attached to the session and the agent is resumed:

[Agor] User connected "Notion" and it is now attached to this session. Its tools become available on your next turn — use them to continue.

Its tools appear on the agent’s next turn, not the current one — the server is attached after the grant lands, never before, so the agent is never handed a server it cannot authenticate to.

If you are allowed to prompt the session but not to change its MCP servers, the sign-in still counts: the grant is yours and it is real. The card says the attach needs the session owner or an admin, and the agent is told what to ask for rather than being sent to repeat a sign-in that already worked.

If you asked from Slack

Ask in a Slack thread and the card comes to you: Agor posts a Connect Notion button into the same thread, and edits that one message in place as the request moves on — signing in, connected, expired, cancelled, or replaced by a newer request. One widget, one Slack message, edited rather than replaced. Slack delivery is at least once, so a provider outage or a crash at the send/commit boundary can briefly leave a second copy; Agor reconciles it by editing or deleting the duplicate, and a newer request retires the earlier card. Every copy carries the same expiring, single-use, user-bound link, so a stale one grants nothing.

Tapping it opens an Agor page that checks the link is still live and that you are the person it was issued for, then runs the same browser sign-in the canvas card runs. The link is one-use and short-lived; when it lapses, the card in the thread says so rather than leaving a button that fails. Nothing about the tap grants anything on its own — the sign-in still has to complete in a browser, and Agor still re-reads the stored grant before it believes it.

If your browser blocks the sign-in window — Slack’s in-app browser on a phone commonly does — the page says so and the button stays live: allow pop-ups and tap again, or open the link in your usual browser.

The agent is also handed a link to the Agor session and told to relay it. That is the fallback you will see if the card could not be posted, if an administrator has turned the Slack card off, or if you asked from a thread Agor is not configured to write into. It is not a guarantee: it needs Agor’s configured URL to be reachable from your browser, you to be signed in to Agor with permission to prompt that session, and pop-ups allowed for the sign-in window. When one of those does not hold, connect from the MCP Catalog in Agor instead.

If you asked from Discord or GitHub

The card renders in the Agor transcript, which is not where you are, and Agor does not post a card into these threads. The agent is handed a link to the session and told to relay it — open it, click Connect, and come back.

If you asked from Teams

The agent refuses, in the thread, and there is no setting that changes it. Teams has no way to map its senders to Agor accounts, so a sign-in started there would save the credential under the channel’s own account — see below. Connect from the Agor canvas instead.

If the sign-in worked but the connection did not finish

Signing in is not the last step. Agor stores the grant, then attaches the server to the session and wakes the assistant — and those last two wait on the page you signed in from. Close the tab or lose the connection in between and the sign-in is still saved; the request just is not finished.

You do not have to sign in again. The card in the thread becomes Finish connecting Notion and the canvas card does the same; either finishes it in one tap. If the link has lapsed by then, ask again in the thread and Agor completes it without a second sign-in. Nothing you did is lost, and Agor never tells you nothing was connected when the only missing step is its own.

What fails closed, on every platform

If the gateway channel does not map platform users to Agor accounts, every message in it runs as one shared account, so a sign-in started there would mint a credential the whole channel could drive: the agent refuses and names the setting to turn on, and the Slack card says the same thing in the thread if alignment is switched off after the card was posted. Slack, Discord, GitHub, and Shortcut each have that setting; Teams has none, so it is refused outright and the refusal says so rather than naming a switch that does not exist. A card retired that way stays retired — re-enabling the setting does not make a weeks-old button clickable; ask again and you get a fresh one.

The connection is always looked up and stored under the person who is actually prompting — never the session’s owner. In a shared Slack channel (as opposed to a DM) Agor says so once in the thread, because what a connected account returns becomes readable by everyone in it.

Already connected

If you already hold a usable connection, no button appears. Agor attaches the server and resumes the agent immediately. A second request for the same server retires the earlier card rather than stacking a second button beside it — best effort over the recent transcript rather than a guarantee about every card a long conversation ever produced, and an older card that is missed grants nothing when tapped.


The gateway_token widget

Admin-only, and narrower: when you are wiring up a Slack, Discord, GitHub, or Teams channel, the agent calls agor_widgets_request_gateway_token rather than asking you to paste platform credentials into chat. The same contract applies — the values go browser → daemon, the agent learns only which channel and which field names, and it is told to wait for the verified result before reporting the channel as connected. See Message Gateway.


Security guarantees

Submitted credentials do not enter the agent’s context. Enter them in the widget, never in chat. The browser submits directly to the daemon; OAuth sign-in stores the provider grant through Agor’s callback. Tool responses, transcript state, realtime updates, and auto-resume prompts contain sanitized status and metadata, not secret values.

Variable names and scope remain visible in the transcript. If even a variable name is sensitive, use Settings instead.


Transcript persistence

The widget message persists in the transcript with its final state:

  • submitted → ✅ + names + scope + timestamp
  • dismissed → ⊘ + names
  • already_present → ✓ + names + “already configured”

The names of submitted variables are visible to anyone who can read the transcript. The values are not, and never were. This is the same surface as Settings → Environment Variables, which also shows names without values.

If a particular request is sensitive enough that even the name shouldn’t appear in the transcript, dismiss the widget and add the variable via Settings.


Multi-user behavior

When shared session prompting allows someone other than the Session creator to resolve a widget, the env var is saved to the submitter’s identity. The retry keeps the branch-owned conversation and SDK state, while its new task records the submitter and uses that person’s execution home, Agor-managed variables, and credentials. The submitter is also recorded in result_meta.submitted_by for audit.

Permission is checked before the side effect and again before the automatic retry is admitted, so a revoked branch or sharing switch fails closed. For the full multi-user story see Multiplayer Execution Isolation.


Why widgets, not chat?

Three reasons:

  1. Secrets stay out of the model. The values flow directly to the daemon’s encrypted store. The agent never sees them.
  2. No flow-breaking context switch. You don’t have to leave the conversation to go set up Settings → Env Vars.
  3. Async-friendly. The widget is durable. Submit it now, submit it in an hour, submit it tomorrow. The session picks up wherever it was, on whatever device. Slack and gateway sessions work the same way.
Last updated on