Automation software for Windows that stops and asks the human instead of guessing
Power Automate Desktop, UiPath, RoboTask, Ui.Vision. Every piece of automation software for Windows in the top search results records actions and plays them back deterministically. When the UI is ambiguous the tool picks the first match, the first match is wrong once in twenty, and the wrong data ships to production. Terminator is developer-focused automation software for Windows whose MCP agent ships six typed elicitation schemas. When the tool hits an ambiguous selector, a risky action, or an unrecoverable error, it pauses and routes a structured JSON question to whichever connected MCP client declared supports_elicitation() at init.
The quiet failure mode every Windows RPA tool shares
A teammate records a workflow against the company's internal dashboard. There are three buttons labeled “Export” in the toolbar: Export CSV, Export PDF, Export to S3. The recorder saves a selector that reads “Button with Name Export”. On replay, Power Automate Desktop finds three matches, picks index 0, and fires a PDF export at 3am every night. Nobody notices for a week.
This is not a Power Automate bug. UiPath does the same thing with its SearchScope defaults. AutoHotkey does not model disambiguation at all. Ui.Vision picks the first visual template hit. The shared assumption across every piece of consumer-grade automation software for Windows is that the first match is the right match, because stopping a workflow to ask a human would kill the unattended-RPA pitch.
Terminator is a developer framework, not unattended RPA. It does not need to pretend selectors are always unambiguous. When the tool cannot decide, it routes the question to a human via the Model Context Protocol and waits for a typed answer. The six schemas below are the complete set of shapes that question can take.
The six schemas, straight from schemas.rs
Every shape is registered with elicit_safe!() at lines 131 through 136, which is what makes the JSON schema derivable and the struct safe to round-trip across the MCP boundary.
WorkflowContext
business_purpose (String), target_app (Option<String>), expected_outcome (Option<String>). Asked once at the start of a new flow so downstream tools know what good looks like.
ElementDisambiguation
selected_index (usize, 0-based) and optional reason. Raised when a selector matches more than one UIA element and the tool refuses to guess.
ErrorRecoveryChoice
action (ErrorRecoveryAction enum) plus optional additional_context. The enum ships five variants and nothing else.
ActionConfirmation
confirmed (bool) and notes (Option<String>). Gatekeeps destructive or irreversible steps. The tool does not proceed until confirmed is true.
SelectorRefinement
element_description (String), element_type (ElementTypeHint enum of nine variants from Button to ListItem), visible_text (Option<String>). Called when a selector returns zero matches.
UserResponse
answer: String. The generic fallback when a tool needs a free-form answer that does not fit any of the other five shapes. Still structured, because the field is typed.
The anchor fact: five recovery variants, no more
If you read one file in the repo, read crates/terminator-mcp-agent/src/elicitation/schemas.rs. The ErrorRecoveryAction enum has five variants and nothing else. Every error-recovery path Terminator's MCP tools can take is enumerated here. There is no sixth “do something clever with the LLM” branch.
anchor fact
0 typed elicitation schemas. 0 error recovery variants. 0 element type hints. One dual-peer router at helpers.rs:90.
Grep the open-source repo and you will find all three numbers on the first try.
How the dual-peer router decides who gets the question
The MCP agent can be connected to more than one client at once. Claude Code runs the tool invocation. Mediar-app, the companion workflow builder, has a UI for the question. The router treats the two as separate Peer handles and sends the elicit request to whichever one declared support at MCP init time.
try_elicit picks the first peer that declared supports_elicitation
The loop is short. Check the stored peer. If it declared elicitation at init, use it. Otherwise check the calling peer. If that declared elicitation, use it. Otherwise return None and the tool falls back to a default. No retries, no timeouts, no LLM-mediated negotiation.
The router source, unedited
One live call: ambiguous selector into a clean click
Elicitation round-trip on an ambiguous click
Tool invoked
Claude Code calls click_element with a selector. The MCP agent runs the tool and finds the UIA tree returned three matches.
Tool builds a typed question
try_elicit::<ElementDisambiguation> is called with a message string and the two peer handles.
Dual-peer router picks an elicitation-capable peer
stored_peer (mediar-app) declared supports_elicitation at init. Calling_peer (Claude Code) did not. Router picks mediar-app.
JSON schema round trip
peer.elicit serializes the ElementDisambiguation schema, sends CreateElicitationRequestParam, waits for the user.
User answers in the second client
mediar-app renders a three-option picker. User taps index 1, adds a short reason, submits.
Tool resumes with the typed answer
The Rust struct deserializes on the server side. click_element fires on candidates[1]. Workflow continues.
The tool side: what the click implementation actually does
Inside a Terminator MCP tool, the call looks like this. No guesswork, no confidence thresholds, no silent first-match. Either the user picks an index or the tool returns an error.
Inside try_elicit: four decisions, no surprises
What happens between peer.elicit() and your Rust struct
Check the stored peer
helpers.rs line 99: acquire tokio::sync::Mutex, read Option<Peer>, test supports_elicitation.
Fall back to the calling peer
helpers.rs line 125: if no stored peer matched, test the calling peer's supports_elicitation. If it says no too, return None and the tool falls back to a default.
Send the elicit request
helpers.rs line 139: peer.elicit::<T>(message). T is one of the six elicit_safe! types; its JsonSchema is derived at compile time and shipped as the requested_schema field.
Branch on the action verb
Accept maps to Some(data). Decline and Cancel both map to None. An outright error also maps to None. Every path is covered, no panics, no unwraps.
Terminator vs the rest of the automation software on Windows
Every row is a concrete behavioral difference on an ambiguous or risky step. Terminator pauses, asks, and waits. The others proceed.
| Feature | Power Automate / UiPath / Ui.Vision | Terminator |
|---|---|---|
| On ambiguous selector, raises a structured question to the human | Picks the first match, proceeds silently | ElementDisambiguation schema at schemas.rs line 33 |
| Bounded error recovery options defined by the enum | Free-text error message, ad-hoc recovery | ErrorRecoveryAction: 5 variants, schemas.rs lines 59-71 |
| Destructive actions gatekept by an explicit confirm | Logged warning at most | ActionConfirmation with confirmed: bool, line 76 |
| Routes the question to a different client than the one running the tool | Single-client model, no routing | Dual-peer try_elicit at helpers.rs line 90 |
| Graceful default when the client does not support the feature | Feature is simply unavailable | elicit_with_fallback returns default, helpers.rs line 30 |
| Question shape is compiled JSON schema, not a prompt string | Free-text prompt or a static form | JsonSchema-derived on every elicit_safe! type |
| Open-source MIT license, grep-verifiable source | Proprietary runtime, black-box decision path | github.com/mediar-ai/terminator, 240 LOC elicitation module |
Verify the numbers yourself
Everything above is grep-able in under thirty seconds. Here is the session. The file is 136 lines; the enum lives on lines 59 to 71; the six elicit_safe! lines are 131 through 136. No mystery.
Who this actually helps
Teams shipping AI agents on Windows. If a Claude-powered workflow needs to book a vendor every Friday, and the vendor portal ships a new date picker layout, the unattended-RPA tool will click the wrong field and submit the wrong booking. Terminator's agent hits the ambiguous selector, pauses, sends a SelectorRefinement request to mediar-app, the on-call engineer taps a screenshot in the client, the workflow resumes with the correct selector, and the vendor booking lands on the right date. The recording of the interaction updates the selector permanently, so the next Friday does not require another tap.
QA engineers running flaky desktop tests. Instead of a hard failure when a dialog is non-deterministic, the test asks the human once which branch to take, writes the decision into the YAML workflow, and becomes deterministic from then on.
Want to see a live elicitation round-trip on your own Windows flow?
Bring a workflow that keeps picking the wrong element. We wire up Terminator plus the second-peer pattern in one session and you watch it stop, ask, and resume with the right answer.
FAQ
What makes this different from other automation software for Windows?
Power Automate Desktop, UiPath, RoboTask, and Ui.Vision all follow the same shape: you record a workflow, they play it back deterministically, and if the UI does not look identical on replay they either pick the wrong element or throw a hard failure. Terminator is developer-focused automation software for Windows that adds a third option via the Model Context Protocol: the tool pauses, serializes a structured question into JSON, and routes it to whichever connected MCP client (Claude Desktop, Claude Code, mediar-app, Cursor) declared support for elicitation. The user answers inside the client's UI, the answer is deserialized back into the expected Rust struct, and the tool resumes. The schemas are defined once in crates/terminator-mcp-agent/src/elicitation/schemas.rs and reused across every tool that may need disambiguation.
Exactly which schemas ship, and what are their fields?
Six, all annotated with elicit_safe!() at crates/terminator-mcp-agent/src/elicitation/schemas.rs lines 131 to 136. WorkflowContext asks for business_purpose (String), target_app (Option<String>), expected_outcome (Option<String>). ElementDisambiguation asks for selected_index (usize) and an optional reason. ErrorRecoveryChoice wraps ErrorRecoveryAction plus additional_context. ActionConfirmation is a confirmed: bool plus optional notes. SelectorRefinement asks for element_description, ElementTypeHint, and visible_text. UserResponse is a single answer: String for free-form questions. Every field is JsonSchema-derived, so the client sees a proper JSON schema, not a free-text prompt.
What does ErrorRecoveryAction actually contain?
Exactly five variants at schemas.rs lines 59 to 71: Retry, WaitLonger, TryAlternativeSelector, Skip, Abort. Retry re-runs the same call. WaitLonger re-runs with a larger implicit timeout. TryAlternativeSelector asks the client to supply a different selector string in additional_context. Skip advances the workflow past the failing step. Abort terminates the run and bubbles the error up. Those are the only five options the enum exposes; there is no free-text recovery path. Which means the AI recovery logic is bounded and auditable, which is the opposite of 'the LLM decides at runtime'.
What happens when the connected MCP client does not support elicitation?
Two things, both graceful. If you call elicit_with_fallback at helpers.rs line 30, the function checks peer.supports_elicitation() first and immediately returns the supplied default if the client said no, logging at debug level. If you call try_elicit at helpers.rs line 90, the function first looks up a separately stored peer (inside Arc<TokioMutex<Option<Peer>>>) and uses that one if it supports elicitation, then falls back to the calling peer, then returns None if neither supports it. This dual-peer routing is what lets a tool invoked by Claude Code (no elicitation today) still ask a question to mediar-app (which does support it) when both are connected to the same MCP server.
Which Windows automation tools fail silently on ambiguous selectors?
Most of the big consumer and RPA tools treat the first match as the right match. Power Automate Desktop's UI Automation selector will click the first element whose class and name match, without surfacing that seven siblings also matched. UiPath Studio's Click activity has a SearchScope but falls back to the first descendant when the scope is under-specified. AutoHotkey has no concept of disambiguation at all. Ui.Vision matches by image template and returns the first visual hit. When any of these picks the wrong element, the downstream action runs anyway and the failure surfaces three steps later, usually on a cryptic field-not-found. Terminator's MCP tools route the ambiguity up to the user before the wrong click happens.
Is elicitation actually usable today if Claude Desktop does not support it?
Yes. The repo ships a second-peer pattern specifically for this. Terminator's MCP agent can be connected to two clients at once: Claude Desktop or Claude Code (runs the tools but cannot elicit), and mediar-app (the Windows workflow builder that does implement the elicitation UI). When the tool needs a decision, try_elicit_raw at helpers.rs line 176 finds the stored elicitation-capable peer and sends the CreateElicitationRequestParam there, not to the calling peer. The user sees the question in mediar-app, answers, and the answer flows back to the tool that was called from Claude Code. The documentation comment at crates/terminator-mcp-agent/src/elicitation/mod.rs line 17 calls this 'graceful fallback for unsupported clients' and it is the reason the feature is shippable before every MCP client updates.
Does the elicitation path add latency to every tool call?
No, because the check is a single boolean. peer.supports_elicitation() is populated at MCP initialization time from the client's declared capabilities, so the server already knows the answer before any tool fires. If the client never declared elicitation, the helper functions short-circuit in a few nanoseconds and the tool proceeds with the default. Latency is only added when the tool actively decides to call elicit::<T>, at which point the cost is one MCP round trip to the client plus however long the user takes to answer. On a tool that never elicits (say, list_windows), there is no added overhead whatsoever.
How do I verify this myself against the source?
Clone the repo and grep. git clone https://github.com/mediar-ai/terminator then grep -n 'elicit_safe!' crates/terminator-mcp-agent/src/elicitation/schemas.rs — you will see six lines, one per schema. grep -n 'supports_elicitation' crates/terminator-mcp-agent/src/elicitation/ — you will find the calls in helpers.rs at lines 34, 102, 125, 188, 206. grep -n 'ErrorRecoveryAction' crates/terminator-mcp-agent/src/elicitation/schemas.rs — one enum, five variants, line 59 to 71. The whole elicitation module is 240 lines across three files. Read all of them in under ten minutes.