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.

M
Matthew Diakonov
10 min read
4.9from early design partners
Six typed elicitation schemas, one per question shape
ErrorRecoveryAction enum has exactly five variants
Dual-peer routing survives clients that do not support elicitation

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.

crates/terminator-mcp-agent/src/elicitation/schemas.rs

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

Claude Code
Cursor
Claude Desktop
try_elicit
mediar-app
Custom MCP host
Default fallback

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

crates/terminator-mcp-agent/src/elicitation/helpers.rs

One live call: ambiguous selector into a clean click

Elicitation round-trip on an ambiguous click

1

Tool invoked

Claude Code calls click_element with a selector. The MCP agent runs the tool and finds the UIA tree returned three matches.

2

Tool builds a typed question

try_elicit::<ElementDisambiguation> is called with a message string and the two peer handles.

3

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.

4

JSON schema round trip

peer.elicit serializes the ElementDisambiguation schema, sends CreateElicitationRequestParam, waits for the user.

5

User answers in the second client

mediar-app renders a three-option picker. User taps index 1, adds a short reason, submits.

6

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.

Representative tool shape (terminator-mcp-agent/src/tools/click.rs)

Inside try_elicit: four decisions, no surprises

What happens between peer.elicit() and your Rust struct

1

Check the stored peer

helpers.rs line 99: acquire tokio::sync::Mutex, read Option<Peer>, test supports_elicitation.

2

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.

3

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.

4

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.

0elicitation schemas
0ErrorRecoveryAction variants
0ElementTypeHint variants
0LOC in the elicitation module

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.

FeaturePower Automate / UiPath / Ui.VisionTerminator
On ambiguous selector, raises a structured question to the humanPicks the first match, proceeds silentlyElementDisambiguation schema at schemas.rs line 33
Bounded error recovery options defined by the enumFree-text error message, ad-hoc recoveryErrorRecoveryAction: 5 variants, schemas.rs lines 59-71
Destructive actions gatekept by an explicit confirmLogged warning at mostActionConfirmation with confirmed: bool, line 76
Routes the question to a different client than the one running the toolSingle-client model, no routingDual-peer try_elicit at helpers.rs line 90
Graceful default when the client does not support the featureFeature is simply unavailableelicit_with_fallback returns default, helpers.rs line 30
Question shape is compiled JSON schema, not a prompt stringFree-text prompt or a static formJsonSchema-derived on every elicit_safe! type
Open-source MIT license, grep-verifiable sourceProprietary runtime, black-box decision pathgithub.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.

verify.sh

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.

terminatorDesktop automation SDK
© 2026 terminator. All rights reserved.