By Sagar Shankaran, Founder of CallSphere
Understand Microsoft UFO's known limitations with complex UI controls, high-DPI displays, and time-sensitive interactions, along with practical workarounds and hybrid strategies for production reliability.
Key takeaways
Every automation tool has limitations. Knowing UFO's boundaries helps you decide when to use it, when to fall back to traditional approaches, and how to handle edge cases gracefully.
Many applications render their UI using custom drawing code instead of standard Windows controls. Games, CAD software, media editors, and some modern applications use DirectX, OpenGL, or custom canvas rendering. These controls do not appear in the UIA accessibility tree.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
Impact: UFO cannot identify or interact with individual elements inside custom-rendered regions.
Workaround: Fall back to coordinate-based clicking. The vision model can still identify visual elements in the screenshot, even without UIA metadata:
def coordinate_fallback_action(screenshot: bytes, task: str) -> dict:
"""Use vision model to identify click coordinates directly."""
prompt = """The application uses custom-rendered controls not in
the accessibility tree. Identify the target element and return JSON:
{"x": 450, "y": 320, "action": "click", "confidence": 0.85}"""
response = call_vision_model("gpt-4o", prompt, screenshot)
action = json.loads(response)
if action["confidence"] < 0.7:
raise LowConfidenceError("Confidence too low")
# Execute with pyautogui
import pyautogui
pyautogui.click(action["x"], action["y"])
return action
Web-like loading spinners, progress bars, and dynamically updating content can confuse UFO. If the agent captures a screenshot while content is loading, it may try to interact with placeholder elements or miss the actual content.
Impact: Actions may target loading indicators instead of real controls, or the agent may incorrectly conclude a task is complete.
Workaround: Use perceptual image hashing to detect when the UI has stopped changing before taking the next action:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
import time
import imagehash
def wait_for_ui_stable(window, threshold: int = 3, max_wait: int = 30) -> bool:
"""Wait until the UI stops changing between screenshots."""
previous_hash = None
stable_count = 0
for _ in range(max_wait):
screenshot = window.capture_as_image()
current_hash = imagehash.phash(screenshot)
if previous_hash and (current_hash - previous_hash) < 5:
stable_count += 1
else:
stable_count = 0
if stable_count >= threshold:
return True
previous_hash = current_hash
time.sleep(1.0)
return False
Windows display scaling (125%, 150%, 200%) can cause misalignment between the coordinates UFO calculates from the screenshot and the actual control positions.
Impact: Clicks land in the wrong position, especially on high-DPI displays with scaling factors above 100%.
Workaround: Detect the scaling factor using ctypes.windll.gdi32.GetDeviceCaps and divide click coordinates by the scale ratio. Set DPI awareness at process startup with ctypes.windll.shcore.SetProcessDpiAwareness(2) to ensure consistent coordinate mapping. Alternatively, set your display scaling to 100% when running UFO tasks.
Unexpected modal dialogs (save confirmations, error messages, update prompts) can block UFO's planned actions. The agent expects to see the main application window but instead encounters a dialog.
Impact: The agent may not recognize the dialog or may try to interact with the grayed-out main window behind it.
Workaround: Add dialog detection before each action step. Query the window's child controls for dialog-type windows, enumerate their buttons, and ask the vision model how to handle the dialog in context of the original task:
def detect_modal_dialog(window) -> dict | None:
"""Check if a modal dialog is blocking the main window."""
dialogs = window.children(control_type="Window")
for dialog in dialogs:
if dialog.is_dialog():
return {
"title": dialog.window_text(),
"buttons": [
btn.window_text()
for btn in dialog.children(control_type="Button")
],
}
return None
Each UFO step requires an LLM API call with an image attachment. This takes 1-5 seconds per step depending on model and network latency. A 20-step task takes 40-100 seconds.
Impact: UFO is too slow for time-sensitive operations, high-frequency tasks, or real-time interactive workflows.
Workaround: Use a hybrid approach — direct UIA calls (via pywinauto) for simple, well-known controls and UFO's vision pipeline only for complex or ambiguous interactions. This cuts LLM calls by 50-80% for forms with known automation IDs while reserving UFO for custom dropdowns and dynamic controls.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
UFO sends screenshots to cloud-based LLM APIs. Sensitive information visible on screen (passwords, financial data, PII) is transmitted to the API provider.
Impact: Compliance and privacy concerns for regulated industries.
Workaround: Redact sensitive regions before sending to the LLM, or use local vision models:
def redact_sensitive_regions(
screenshot: Image.Image,
sensitive_controls: list[dict],
) -> Image.Image:
"""Black out sensitive UI regions before sending to LLM."""
redacted = screenshot.copy()
draw = ImageDraw.Draw(redacted)
for control in sensitive_controls:
if control.get("sensitive", False):
rect = control["rect"]
draw.rectangle(
[rect[0], rect[1], rect[2], rect[3]],
fill="black"
)
return redacted
UFO captures the window on its current monitor. Windows split across monitors produce partial screenshots with unpredictable behavior.
Workaround: Consolidate all target windows to a single monitor before starting:
def consolidate_windows_to_primary(app_names: list[str]):
"""Move all target application windows to the primary monitor."""
import pywinauto
desktop = pywinauto.Desktop(backend="uia")
for app_name in app_names:
windows = desktop.windows(title_re=f".*{app_name}.*")
for w in windows:
w.move_window(x=50, y=50, width=1200, height=800)
Yes. You can configure UFO to use a local vision-language model through an OpenAI-compatible API endpoint. Models like LLaVA or CogVLM can run locally with sufficient GPU resources (16+ GB VRAM). Accuracy will be lower than GPT-4o but eliminates cloud dependency and privacy concerns.
Enable screenshot saving in the configuration (SAVE_SCREENSHOTS: true). After a failed run, review the annotated screenshots in the log directory to see exactly what UFO saw and which element it selected. Compare the model's "thought" output with the actual screenshot to identify where the visual understanding went wrong.
UFO has a SAFE_GUARD configuration option that requires user confirmation before executing potentially destructive actions (delete, send, format). Enable this for workflows involving irreversible operations. For fully automated scenarios, implement checkpoint-and-rollback patterns in your orchestration layer.
#UFOLimitations #EdgeCases #ProductionTips #UIComplexity #DesktopAutomation #HybridAutomation #MicrosoftUFO #AIWorkarounds

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Master cross-application automation with Microsoft UFO by building workflows that transfer data between Excel, Outlook, browsers, and desktop applications through coordinated multi-app sequences.
Step-by-step guide to installing Microsoft UFO, configuring API keys, setting up the configuration files, and running your first automated Windows task with natural language.
Explore how UFO captures, annotates, and sends Windows application screenshots to GPT-4V for UI element detection, control identification, and intelligent action mapping at each automation step.
Learn what Microsoft UFO is, how its dual-agent architecture combines HostAgent and AppAgent with GPT-4V vision to translate natural language requests into Windows UI actions automatically.
Understand when to use Microsoft UFO for Windows desktop automation versus browser tools like Playwright or Selenium, with use cases for legacy apps, native software, and hybrid approaches.
Practical examples of automating Microsoft Office applications with UFO — from Excel data manipulation and Word document formatting to Outlook email workflows, with multi-step task descriptions and result verification.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI