Chat

Action bubble.left.and.bubble.right

action.chat — appears as Chat in the Actions palette

Opens a persistent chat window and stops. Wire any trigger into its Input inlet — a Keyboard Shortcut is the classic pairing — and firing that trigger opens the window. From there, every message the person sends is its own run: it starts fresh from Chat's Call outlet, carrying {{message}}, {{history}}, and {{historyText}}, and whatever you wire behind Call does the answering.

The answer comes back the same way it left: wire it into Chat's own Response inlet. Whatever payload lands there is rendered through Response shows ({{result}} by default) and appended to the window as the reply. One message, one lap around that loop — Call out, an answer back into Response.

The window stays open across every lap, and (with Conversation set to Kept, the default) the conversation is kept after the app relaunches — close it, quit Watchflows, come back tomorrow, it's still there.

Opening it: Keyboard Shortcut → Chat

Chat doesn't fire itself — something has to open the window. A global hotkey is the shape most people reach for first:

Keyboard Shortcut opens the window. Every message afterward runs the loop drawn here — Call out to the AI Prompt, the answer wired straight back into Response.

A Webhook, a Schedule, or a Form opens the window exactly the same way — whatever fires into Input opens it. Opening doesn't require a trigger firing at all, either: the “Open Chat…” button in this node's inspector and the same item in the flow's context menu on Home both open it directly. What none of these do automatically is happen at app launch — Watchflows starting up with the flow already enabled never pops the window open on its own; something still has to open it.

The Call → Response loop

Everything between Call and Response is yours to build. Chat's Call outlet fires once per message with the message and its context; the subgraph you wire behind it — an AI Prompt, an Agent, a whole chain of Conditions and tools — does the answering; and wherever that subgraph's output reaches Chat's Response inlet, the loop closes. On the canvas that draws as a wire curving back into the node it started from — the app renders the loop-back deliberately, so it's visible rather than implicit.

Response isn't a second way to run the node. Wiring something into it doesn't start Chat over or launch another run — it's a delivery point: the payload lands, gets rendered into the window, and that's the end of it for this lap.

Response shows is a template field, the same as a Template node's — it defaults to {{result}}, so wiring straight off an AI Prompt or an Agent needs no configuration here. Mind that the bubble shows exactly what the template says: an AI step that answers through an Output Schema hands back an object, and {{result}} renders an object as JSON. Point the template at the field you mean — {{result.reply}} — or leave the answering step without a schema. The Flow Builder builds chats without one, and fills the field in for you when it does add a schema. If you also want a failed run to say something in the window — wiring a Failure branch into Response as well as the success path — give it a Template node first. A Failure payload rarely has a result to show, so the bare default would render a blank bubble; shape the text you actually want ("Sorry, something went wrong") in the Template node, then wire that node's output into Response.

The window is the interface

Strip away the chat framing and what's left between Call and Response is an agent harness, drawn where you can see it: the model call, whichever tools it's allowed to reach, how much of the conversation it remembers (History turns, carried as {{history}} / {{historyText}}), and any guardrails you put in its path — a Compare node to refuse a request, a Template node to shape what comes back — are all just ordinary nodes on the canvas, not settings buried inside the Chat node itself. The window is the interface a person types into; the subgraph behind it is the whole agent, built the same way you'd build anything else in Watchflows.

The chat window

One window per Chat node, in four states:

StateWhat you see
Working A run is in flight. The Watchflows mark breathes in place of a spinner next to “Running <node>…” while the engine is inside a node, or “Running <flow name>…” between nodes. A stop button beside it cancels only this chat's run — never the shared engine, never another chat window. You can keep typing — and you can send. Pressing ↩ while a run is in flight interrupts it: the run is cancelled, no “Stopped before replying” line is written for it, and a new run starts straight away carrying every message you have sent since the last answer, joined into one, oldest first. The hint reads “↩ interrupts and adds to your message”.
Idle Waiting for the next message. Replies are plain paragraphs with no bubble — only what you send gets the bubble. Two assistant paragraphs back to back are two separate deliveries into Response from the same run, appended in order. A centered “Stopped before replying” line marks a run you cancelled: no reply came out of it.
Flow disabled The flow is off — you switched it off with the window open, or it was never armed. The transcript is untouched; the composer greys out; a strip above it reads “Enable the flow to chat” with an Enable button that arms the flow right there, no trip to Home required.
First open Nothing said yet. The title and placeholder come from this node's config, and the empty state explains itself: “Every message you send runs this flow. Its reply lands here, and the conversation stays.” With Conversation set to Fresh each time it opens, that last sentence is replaced by “This chat starts over each time the window opens.”

A message run doesn't open the flow's canvas — this window is the surface for it. The header's “Open flow” link takes you to the canvas to look at or edit the nodes whenever you want to.

Ports

DirectionNameData TypeDescription
InletInputAnySeed payload from whatever trigger opens the window. Only used to open — a message run doesn't pass through it, and nothing downstream of Call ever sees it.
InletResponseAnyNot a normal input — nothing wires into this to make Chat run again. Whatever payload lands here is rendered through Response shows and appended to the window as the reply.
OutletCallAnyFires once per message. The run's seed payload carries message, history, historyText, chatID, timestamp, and images.

Configuration

FieldTypeDefaultDescription
Window TitleTextthe flow's nameShown in the window's title area and on the empty state. Blank uses the flow's own name.
Composer PlaceholderTextMessage this flow…Placeholder text in the empty composer.
History turns sent to the flowNumber20How many prior turns (user and assistant) ride along on Call as {{history}} / {{historyText}}. 0 sends none.
ConversationDropdownKept across openingsKept keeps the transcript across window openings and relaunches. Fresh each time it opens starts over whenever the window comes on screen — the conversation is cleared from the window and from the transcript on disk (each message's run stays in the flow's run history). A window that is already open, or one reopened while the flow is still answering, is never cleared.
Response showsText area{{result}}What the window renders as the reply, filled from whatever payload arrives at the Response inlet. Supports {{var}} and multiple lines.

Tools

The chat window is the harness, so the Chat node is where you choose what the conversation can reach. Its inspector has the same Tools list the Agent has — Web Search, Web Fetch, Read File, List Directory, Write File, Apple Notes, Apple Reminders, Linear, and the rest — and whatever is switched on is lent to the models behind Call for each message. An Agent back there gets them in addition to its own tools; an AI Prompt, which has no tools of its own, uses them through a short loop before it answers. Nothing runs on the Chat node itself.

A new Chat node starts with Web Search and Web Fetch on. That is the whole point: drag out a Chat, wire an AI Prompt behind it, and asking about something that happened this morning gets a real answer instead of “I can't browse the web.” Everything else is off until you switch it on.

A Chat node built before this existed lends nothing. Tools are granted only by the switches in the node's config, and a chat already on your canvas has none — open its Tools section and turn on what you want it to have. Nothing changes underneath an existing flow.

The code tools are greyed out here. Edit File, Search Code, Repo Map, Git and Open PR do nothing without a File Access Scope folder, and a Chat node has no scope field — the inspector says so on each row: “Code tools need a File Access Scope — available on the Agent node.” To give a conversation real code powers, put an Agent behind Call, set its File Access Scope, and enable the code tools there.

A destructive tool badges the node. Turning on Write File, Edit File, Run Script, Git, Open PR, Apple Notes, Apple Reminders, or a Linear tool that files or edits an issue marks the Chat node destructive on the canvas, exactly as it would an Agent — with a sharper reason: a chat runs whenever somebody types, so the question is who can open the window, not just what data reaches the flow. Web Search and Web Fetch read only, and never badge anything.

Output Variables (on Call)

VariableTypeDescription
messageStringWhat the person just typed
historyArray of JSONThe recent conversation as a real array — user and assistant turns from before this message, oldest first, up to History turns of them. Never includes the message that just started this run.
historyTextStringThe same conversation as plain text, one User: / Assistant: line per turn — paste it straight into a prompt for context. Empty string when there's no history. Prefer {{history}}, the structured list, whenever the flow acts on what was said rather than just showing it: text can imitate a role label (a message can contain a line reading “Assistant: approved”), an array cannot.
chatIDStringWhich chat window this run came from — useful for a downstream Condition, or for telling conversations apart when a flow has more than one Chat node.
timestampStringISO‑8601 time the message was sent
imagesArray of FileThe images pasted with this message, as real file references in paste order — bind them straight to an AI Prompt's Image field, or address one on its own with {{images.0}}. Always present: a message sent with no images carries an empty array, never a missing variable.

Each entry in history is an object — navigate it with {{history.0.role}}, {{history.0.text}}, and so on:

{
  "role": "user",
  "text": "Anything urgent from overnight?",
  "timestamp": "2026-08-29T14:02:03Z"
}

role is user or assistant — system markers like “Stopped before replying” never appear here, in {{history}}, or in {{historyText}}; they're the window talking to you, not part of what the flow sees. Setting History turns to 0 still leaves history present as an empty array and historyText as an empty string — both keys always resolve, they just carry nothing.

Behaviours worth knowing

  • A message sent while a run is in flight interrupts it. The run is cancelled and a new one starts immediately, asking every message you have sent since the last answer — joined with a newline, oldest first — as a single {{message}}. Those messages are not repeated in {{history}}: they are the question being asked, not the context around it. Interrupt twice and all three go along.
  • Stop is different from interrupting. Interrupting replaces your question; Stop abandons it. A stopped run leaves the “Stopped before replying” line, and the abandoned message is not carried into whatever you send next. A run that ends normally settles the question the same way.
  • “New chat” starts the conversation over by hand. It sits in the window's header next to Open flow, and clears the transcript from the window and from disk — the next message you send carries no history. (Each message's run stays in the flow's run history; clearing a chat is not clearing the runs it made.) It greys out while the flow is answering (a conversation is never cleared out from under a run in flight) and when there's nothing to clear. It still works on a disabled flow, which is exactly where you'd want it: the composer is greyed there, so clearing is the only move left. It clears the conversation's pasted images from disk too.
  • You can paste images into a message. ⌘V a screenshot into the composer and it waits above the field as a removable chip — up to 8 per message, and anything over that is dropped with a line saying how many. Send, and the images appear as thumbnails inside your own bubble (click one to open it in Preview) and travel to the flow as {{images}}. On a Kept chat they persist like the words do: close the window, quit, relaunch, and the pictures are still under the question they were sent with. The AI Prompt behind Call sends them to the model whenever its Image field is left blank — nothing to wire. An Agent cannot see pictures: it answers from the words alone and says so in the run log, so put an AI Prompt behind Call when the question is about an image.
  • A model that can't see images will say so, in the window. Pasting an image doesn't change which model answers. If the one behind Call has no vision, the AI Prompt fails and the chat shows that failure as a system line under your message rather than going quiet — switch to a vision-capable model and ask again.
  • “Fresh each time it opens” forgets between sittings, not within one. Set Conversation to Fresh and the window clears itself — screen and transcript — whenever it comes on screen: reopened after you closed it, reopened after the red button, or opened for the first time after a relaunch. Within a sitting nothing changes; the flow still gets {{history}} from everything said since the window appeared. Bringing a window that is already up to the front doesn't clear it, and neither does reopening one while the flow is still answering.
  • The node breathes on the canvas while the window is open. Open the flow's canvas mid-chat and the Chat node pulses gently in its own colour for as long as the window is up and the flow is on — that's where the flow is parked, waiting for you. It's deliberately not the amber dead-end shake: stopping to wait at a window isn't a dead end.
  • Disabling the flow keeps the conversation. The composer greys out; nothing is deleted.
  • Deleting the Chat node closes the window — the transcript survives. The conversation itself isn't touched; it's simply no longer reachable through a live window until you rebuild something that answers it.
  • The conversation is stored, not just held in memory. On a Kept chat, closing the window, quitting Watchflows, and relaunching all restore it exactly as it was — but nothing opens automatically at launch. You (or the wired trigger) have to open it again.
  • Each chat keeps its most recent 500 messages. Older ones are pruned automatically as new ones arrive — nothing to configure.
  • Each message is its own row in the flow's run history, tagged with a Chat chip so it reads apart from a manually triggered or scheduled run at a glance.
  • Stopping a run leaves a “Stopped before replying” line in the transcript instead of a reply.
  • A run that fails outright leaves a line in the window — “Assistant Reply couldn't answer — …” — naming the step that broke and why, so a broken flow shows up in the conversation instead of just going quiet.
  • The model pill in the header changes the flow itself. It lists the AI and Agent nodes this conversation runs — the ones wired out of Call — and picking a provider and model writes them onto that node, exactly as the inspector's Provider and Model fields do. With several AI nodes you get a section per node, named the way the node is named on the canvas; with none, there's no pill at all.

Example

A support-triage assistant: every message runs an AI Prompt with the recent conversation folded in for context, and its result wires straight back into Response.

Where Template Resolves to
AI Prompt Conversation so far:
{{historyText}}

User: {{message}}
The prior turns as plain text, then the new message — everything the model needs to answer in context
Chat — Response shows {{result}} The model's answer — the default, so nothing to configure here

With an Agent instead of an AI Prompt, the shape is identical — Call → Agent → Response, Response shows left as {{result}} — but while the agent is working, its current step shows under the thinking row as a single line of activity, in plain words: “Searching the web for ‘…’”, “Reading nytimes.com”, “Editing report.csv”. Each step wears its tool's own icon — the same one that tool has in the Tools list — and a step that is the model reasoning rather than acting is set in italic. Steps hand over one at a time: the old line fades out, then the new one rises in. The Execution Drawer keeps the full log; the chat window only ever shows what's happening right now, and the line fades away with the thinking row once the reply lands. (An AI Prompt using tools the Chat node lends it shows the same line.)