macOS 14+ · Apple Silicon & Intel · ~12 MB awizemann.github.io/harness · Wiki · All releases A native macOS developer tool that drives an iOS Simulator, a macOS app, or a web app with an AI agent so you can run user tests — not
Repo: awizemann/harness
What's inside
A native macOS developer tool that drives an iOS Simulator, a macOS app, or a web app with an AI agent so you can run user tests — not scripted UI tests, but real-user simulation.
You write a goal in plain language ("I want to sign up and create my first list", "delete my account", "find a vegetarian restaurant near me and save it") and a persona ("first-time user, never seen this app"). Harness builds (or just launches) your target, and an LLM agent reads screenshots, clicks/types/scrolls, and pursues the goal — narrating what it sees, flagging UX friction (dead ends, ambiguous labels, unresponsive controls), and stopping when it succeeds, fails, or would give up.
Three artifacts come out of every run:
| Kind | How Harness drives it |
|---|---|
| iOS Simulator | xcodebuild your project + scheme; simctl boot/install/launch; WebDriverAgent for input. |
| macOS app | NSWorkspace launch (pre-built .app or xcodebuild macOS scheme); CGEvent for input; CGWindowListCreateImage for capture. |
| Web app | Embedded WKWebView at a chosen viewport (default 1280×1600 tall desktop, or 375×812 mobile); JS-synthesised events for input; WKWebView.takeSnapshot for capture. The mirror shows a flat browser chrome (no device bezel) so the screenshot fills the full pane and one snapshot covers more page — fewer scrolls per goal, lower API cost. |
Per-app setting: each Application declares its kind once at create time. The agent's tool schema (clicks vs swipes vs key shortcuts vs navigate) and the system-prompt context block re-shape per platform. Run history, replay, and friction reporting are platform-neutral.
Status: v0.7.0 (alpha). Drive Harness from an agent via the MCP server — either autonomous runs (surfaced as first-class, badged history) or step-level UI sessions that let an external client see and act on a web or iOS target directly, with no LLM loop and no API key. The
harness-mcpbinary is now standalone and relocatable — bundle it inside another product and run it from anywhere. Sparkle auto-update built in. All three platforms wired end-to-end with Set-of-Mark targeting on iOS, macOS, and web (numbered overlays on interactive elements; agent clicks by id, not pixel; agent-only, never on disk); Local Mac inference via Ollama (Qwen3-VL 8B, Gemma 4 Vision 9B, Llama 3.2 Vision 11B, plus a custom-model field) alongside cloud providers (Anthropic Opus 4.7 / Sonnet 4.6 / Haiku 4.5; OpenAI GPT-5 Mini / GPT-4.1 Nano; Google Gemini 2.5 Flash / Flash Lite); per-Application credential storage; per-provider Keychain storage; configurable per-model token budgets; unlimited-step option;harness-clidev-time driver. macOS needs Screen Recording + Accessibility permission. Web is WebKit-only; Chrome via CDP is on the roadmap. Seedocs/ROADMAP.md.
harness-mcp tools — start_ui_session / observe_ui / act_ui / end_ui_session / list_ui_sessions — let an external MCP client drive a web or iOS Simulator target itself, one action at a time, without Harness's autonomous agent and without any API key. observe_ui returns the Set-of-Mark screenshot (numbered badges over interactive elements) plus an id → label (role) table; act_ui takes one action from the platform's tool vocabulary, executes it, settles, and auto-observes. A UISessionSupervisor actor caps concurrency at 2, tears down idle sessions (default 600s), bounds each start so a hung build can't wedge the read loop, and cleans up every session on server shutdown. Clean frames land in <artifact_dir>/steps/NNN.png + steps.jsonl; the marked image never touches disk (the "no agent scaffolding on disk" invariant, standards/14-run-logging-format.md §6). See HarnessMCP/README.md.harness-mcp is now standalone and relocatable. Copy the binary anywhere — eventually bundled inside another product — and run it from any working directory; it no longer assumes its source checkout is present. --version / --help are parsed before the app run loop spins up, and --version prints the same identity the MCP initialize handshake reports (single source of truth in MCPServerIdentity). Web sessions need nothing but the binary — no repo, no Xcode. For iOS, WebDriverAgent source resolves in precedence order — HARNESS_WDA_PATH (a set-but-invalid value is a loud, actionable error, never a silent fall-through) → WebDriverAgent/ beside the binary → <repoRoot>/vendor/WebDriverAgent. On a host without usable Xcode command-line tooling, web keeps working end-to-end and an iOS start returns a clean per-tool error naming the missing tool, never a crash or a wedge. See Running standalone.harness-mcp — a development-time stdio MCP server built from the same Harness/ source as the app — lets Claude (or any MCP client) create Applications, Personas, and Actions; stage per-app credentials; and start, poll, and cancel runs (start_run / get_run_status / get_run_result / cancel_run / list_runs). It opens the GUI's on-disk store, so anything an agent creates shows up in the app and vice-versa. An idle watchdog auto-cancels a wedged run. See HarnessMCP/README.md.Local Mac provider runs a vision LLM on your Mac at http://127.0.0.1:11434. Screenshots never leave the machine, runs cost $0, and you can work offline. Curated picker: Qwen3-VL 8B (GUI-trained, recommended), Gemma 4 Vision 9B, Llama 3.2 Vision 11B, plus a Custom local model… field that's sent verbatim to Ollama. Settings → "Local Mac" card has a server reachability pill, base URL field, and copy-paste install commands. First-run wizard adds an "Or run fully local" card. Honest trade-offs surfaced in the per-run picker: ~5-10× slower per step, lower friction-event quality than cloud-class. Talks native /api/chat (not the OpenAI-compat shim) so options.num_ctx is honored. See standards/07-ai-integration.md §12 and the Local-vs-Cloud-Models wiki page for a same-goal-same-site head-to-head with numbers./source?format=json AX tree; macOS probes via AXUIElementCreateApplication. The agent calls tap_mark(id) instead of tap(x, y) on all three platforms — pixel guesswork eliminated for interactive elements. Disk PNGs stay clean; the marked image lives only in the in-memory channel routed to the LLM call (same no agent scaffolding on disk invariant from 0.3). iOS Cell labels roll up child StaticText / Image labels so the LLM sees "Settings — General — About" instead of "(unlabeled)". See iOS-Driver and macOS-Driver.MutationObserver-based DOM-quietness gate from 0.3 gained a requireChildListMutation flag for SPA route transitions (React Suspense keeps the old DOM mounted on route change, so "idle 200ms" was firing on stale pages).harness-cli — development-time driver. New xcodegen target produces harness-cli, a tool binary that shares the entire Harness/ source root with the GUI app and runs against WebDriver / IOSPlatformAdapter / MacAppDriver end-to-end. Same RunCoordinator, same event stream, same on-disk artifacts the GUI produces — minus the SwiftUI shell. Iterate on prompts, models, and the agent loop without rebuilding the Mac app. Cloud credentials come from env vars (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY) with a system Keychain fallback so the GUI's saved keys work for the CLI binary too. Development-only; not Developer-ID signed. See the HarnessCLI wiki page.WebProcess::markAllLayersVolatile: Failed to the unified log multiple times per second. The window is now placed at (0, 0) with alphaValue = 0 and level = .normal - 1 — visually invisible, but WebKit sees a real on-screen window and doesn't try to free its layers. Live-mirror poller cadence dropped from 3fps to 1fps.simctl screenshot exit-code flakes tolerated when the PNG is on disk; WDA waitForReady timeout bumped 45s → 120s for iOS 26.2; WKWebsiteDataStore is now non-persistent (reproducible runs, "what a fresh user sees"); NSAppearance binds to the user's system Dark Mode preference rather than the host app's; per-turn behavior reminders prefix every LLM step ("tap a text field first to focus it before calling type", "prefer tap_mark when marks are available"). 228 unit tests passing (was 223).ScreenshotMetadata.markedImageData channel; the on-disk artifact stays free of dev-tool clutter. Standard 14 §6 documents the new "no agent scaffolding on disk" invariant.ViewThatFits. When no credentials are staged, Persona expands to fill the row naturally.fill_credential(field: "username"|"password") tool for iOS, macOS, and web. Password bytes never enter the model's context, the JSONL log, or any prompt template — tool_call.input for password fills records {"field":"password"} and nothing else. New friction kind auth_required for the "agent hit a login wall and has nothing to fill" case.tap_mark(id) and the WebDriver resolves to the element's center — no more "agent picked y=228, input was at y=242" misses. Coordinate tap(x, y) stays available for unmarked content. Probe pierces open shadow roots so inputs in modern signin / payment widgets get marks. iOS / macOS get the same treatment in a follow-up via accessibility-tree probes (tracked on the wiki Roadmap).dispatchType now uses the native value setter via Object.getOwnPropertyDescriptor, so React's value tracker actually sees the change and re-renders won't reset typed text. Same fix applies to fill_credential. Click-target focus routing now walks <label>, wrappers, and shadow children to focus the actual input, not the styled <div> on top of it.note_friction calls"; the parsers were rejecting anything > 1 block. Each provider's parser now splits action vs note_friction and forwards inline frictions through AgentDecision.inlineFriction → JSONL friction rows.run_started payload gains optional credentialLabel + credentialUsername (decode-if-present so v2 logs round-trip). Standards doc §5 documents the v2→v3 migration and the three credential-redaction invariants.RunHistoryStore adopts @ModelActor. Eliminates the "Unbinding from the main queue. ModelContexts are not Sendable" runtime warning that Swift's strict concurrency was right to flag.Harness vendors appium/WebDriverAgent as a git submodule under vendor/WebDriverAgent (it's how we drive the iOS Simulator's responder chain). The Xcode project is generated from project.yml via xcodegen.
git clone https://github.com/awizemann/harness.git
cd harness
git submodule update --init --recursive
brew install xcodegen
xcodegen generate
open Harness.xcodeproj
The first run builds WDA against your simulator's iOS runtime (~1–2 min). Result is cached under ~/Library/Application Support/Harness/wda-build/<iOS-version>/ and reused on subsequent runs.
Full setup: see Build-and-Run on the Wiki.
standards/INDEX.md — development, code, and architecture standards. Read these before adding code.docs/ARCHITECTURE.md — system architecture overview.docs/ROADMAP.md — build order and milestones.docs/PROMPTS/ — canonical agent prompts (loaded as a bundle resource at runtime).HarnessDesign/ — design system tokens, primitives, and screen layouts.PRs welcome. Read CONTRIBUTING.md first — it covers setup, the architecture rules (MVVM-F, Swift 6 strict concurrency, single subprocess actor), and the public-surfaces sync rule (code changes that affect README / wiki / site update them in the same PR).
MIT — see LICENSE.
.codex/
config.toml
hooks.json
.cursor/
mcp.json
rules/
memophant.mdc
.gemini/
settings.json
.github/
copilot-instructions.md
FUNDING.yml
ISSUE_TEMPLATE/
bug_report.md
feature_request.md
PULL_REQUEST_TEMPLATE.md
.gitignore
.gitmodules
.mcp.json
.memory/
ai/
ai-integration-models-providers-and-inference.md
ai-integration-models-providers-local-inference.md
architecture/
agent-loop-core-mechanism.md
architecture-design-decisions-mvvm-f-strict-concurrency-subprocess-actor.md
architecture-design-decisions.md
platform-drivers-ios-macos-web-set-of-mark-smart-gates-input.md
platform-drivers-ios-macos-web.md
run-lifecycle-orchestration-runcoordinator-actor.md
run-lifecycle-orchestration.md
tool-schema-agent-tools.md
core/
contributing-md-guidelines.md
contributing-rules-public-surfaces-sync.md
development-setup-first-clone.md
documentation-knowledge-structure.md
project-overview-targets.md
project-overview-v0-6-status.md
release-signing-notarization-sparkle-keys.md
standards-index-documentation-structure.md
design/
design-system-harnessdesign-package-unified-tokens.md
design-system-harnessdesign-package.md
features/
per-application-credentials-persona-library.md
workspace-actions-chain-system-swiftdata-v2.md
workspace-actions-chain-system.md
persistence/
run-logging-format-jsonl-v2-append-only-legs-credentials.md
run-logging-format-jsonl-v2.md
standards/
standards-index.md
tools/
harnesscli-development-time-driver-shared-source-same-artifacts.md
harnesscli-development-time-driver.md
harnessmcp-adding-run-screenshots-and-live-mirror-future-work.md
harnessmcp-stdio-mcp-server-to-drive-harness-from-agents.md
AGENTS.md
code/
Modules.md
CONTRIBUTING.md
docs/
ARCHITECTURE.md
PROMPTS/
friction-vocab.md
persona-defaults.md
personas/
macos-defaults.md
web-defaults.md
platforms/
ios.md
macos.md
web.md
system-prompt.md
ROADMAP.md
GEMINI.md
Harness/
App/
AppContainer.swift
AppCoordinator.swift
AppState.swift
FirstRunWizard.swift
HarnessApp.swift
SidebarView.swift
Core/
HarnessPaths.swift
MCPServerIdentity.swift
Models.swift
PromptLibrary.swift
Domain/
AgentLoop.swift
ChainExecutor.swift
Mappers.swift
Pricing.swift
RunCoordinator.swift
Features/
Actions/
ViewModels/
ActionsViewModel.swift
Views/
ActionCreateView.swift
ActionDetailView.swift
ActionsView.swift
ChainCreateView.swift
ChainDetailView.swift
AgentSessions/
ViewModels/
AgentSessionsMonitor.swift
AgentSessionsViewModel.swift
Views/
AgentSessionsView.swift
Applications/
ViewModels/
ApplicationCreateViewModel.swift
ApplicationsViewModel.swift
Views/
ActiveApplicationCard.swift
ApplicationCreateView.swift
ApplicationDetailView.swift
ApplicationsView.swift
FrictionReport/
Models/
FrictionReportModel.swift
ViewModels/
FrictionReportViewModel.swift
Views/
FrictionReportCard.swift
FrictionReportView.swift
GoalInput/
ViewModels/
GoalInputViewModel.swift
Views/
GoalInputView.swift
Personas/
ViewModels/
PersonasViewModel.swift
Views/
PersonaCreateView.swift
PersonaDetailView.swift
PersonasView.swift
RunHistory/
ViewModels/
RunHistoryViewModel.swift
Views/
RunHistoryDetailView.swift
RunHistoryView.swift
RunReplay/
ViewModels/
RunReplayViewModel.swift
Views/
RunReplayView.swift
RunSession/
ViewModels/
RunSessionViewModel.swift
Views/
RunSessionView.swift
Settings/
Views/
SettingsView.swift
Platforms/
iOS/
IOSPlatformAdapter.swift
MacOS/
MacAppDriver.swift
MacOSPlatformAdapter.swift
MarkRenderer.swift
PlatformAdapter.swift
PlatformKind.swift
UXDriving.swift
Web/
LiveWebMirror.swift
WebDriver.swift
WebPlatformAdapter.swift
WebViewWindowController.swift
Resources/
Assets.xcassets/
AppIcon.appiconset/
Contents.json
icon_128x128.png
icon_128x128@2x.png
icon_16x16.png
icon_16x16@2x.png
icon_256x256.png
icon_256x256@2x.png
icon_32x32.png
icon_32x32@2x.png
icon_512x512.png
icon_512x512@2x.png
Contents.json
Harness.entitlements
Info.plist
Services/
ClaudeClient.swift
GeminiClient.swift
HarnessSchema.swift
KeychainStore.swift
LLMClientFactory.swift
LLMShared.swift
OllamaClient.swift
OpenAIClient.swift
ProcessRunner.swift
ProjectPicker.swift
RunHistoryStore.swift
RunLogger.swift
RunLogParser.swift
SimulatorDriver.swift
SimulatorWindowController.swift
ToolLocator.swift
WDABuilder.swift
WDAClient.swift
WDARunner.swift
XcodeBuilder.swift
Tools/
AgentTools.swift
UISessions/
UISessionSupervisor.swift
UISessionSupport.swift
HarnessCLI/
CLIArgs.swift
ConsoleEventPrinter.swift
EnvKeychain.swift
HarnessRunner.swift
Main.swift
NoopBuilders.swift
HarnessDesign/
DesignSystem/
ButtonStyles.swift
Colors.swift
Materials.swift
Theme.swift
Typography.swift
Mocks/
PreviewData.swift
Primitives/
ApprovalCard.swift
EmptyStateView.swift
FlowLayout.swift
FrictionTag.swift
OriginBadge.swift
PanelContainer.swift
PendingStepCell.swift
PersonaGoalForm.swift
Pill.swift
SegmentedToggle.swift
SidebarRow.swift
SimulatorMirrorView.swift
StatusChip.swift
StepFeedCell.swift
TimelineScrubber.swift
ToolCallChip.swift
VerdictPill.swift
WebMirrorView.swift
README.md
Screens/
FrictionReportView.swift
GoalInputView.swift
RunHistoryView.swift
RunReplayView.swift
RunSessionView.swift
HarnessMCP/
AdapterUISessionPreparer.swift
EnvKeychain.swift
fixtures/
ios-app/
project.yml
Sources/
FixtureApp.swift
ui-session-fixture.html
Main.swift
MCPContainer.swift
MCPProtocol.swift
MCPServer.swift
NoopBuilders.swift
README.md
RunBuilder.swift
smoke-test.sh
ToolHandlers.swift
ToolRegistry.swift
ui-session-smoke.py
ui-session-smoke.sh
UISessionTools.swift
icons/
AW Mac OS Applications-macOS-Default-1024x1024@1x.png
AW Mac OS Applications-macOS-Default-128x128@1x.png
AW Mac OS Applications-macOS-Default-128x128@2x.png
AW Mac OS Applications-macOS-Default-16x16@1x.png
AW Mac OS Applications-macOS-Default-16x16@2x.png
AW Mac OS Applications-macOS-Default-20x20@2x.png
AW Mac OS Applications-macOS-Default-20x20@3x.png
AW Mac OS Applications-macOS-Default-256x256@1x.png
AW Mac OS Applications-macOS-Default-256x256@2x.png
AW Mac OS Applications-macOS-Default-29x29@2x.png
AW Mac OS Applications-macOS-Default-29x29@3x.png
AW Mac OS Applications-macOS-Default-32x32@1x.png
AW Mac OS Applications-macOS-Default-32x32@2x.png
AW Mac OS Applications-macOS-Default-38x38@2x.png
AW Mac OS Applications-macOS-Default-38x38@3x.png
AW Mac OS Applications-macOS-Default-40x40@2x.png
AW Mac OS Applications-macOS-Default-40x40@3x.png
AW Mac OS Applications-macOS-Default-512x512@1x.png
AW Mac OS Applications-macOS-Default-60x60@2x.png
AW Mac OS Applications-macOS-Default-60x60@3x.png
AW Mac OS Applications-macOS-Default-64x64@2x.png
AW Mac OS Applications-macOS-Default-64x64@3x.png
AW Mac OS Applications-macOS-Default-68x68@2x.png
AW Mac OS Applications-macOS-Default-76x76@2x.png
AW Mac OS Applications-macOS-Default-83.5x83.5@2x.png
LICENSE
project.yml
README.md
releases/
v0.1.0/
RELEASE_NOTES.md
v0.2.0/
RELEASE_NOTES.md
v0.2.1/
RELEASE_NOTES.md
v0.3.0/
RELEASE_NOTES.md
v0.3.1/
RELEASE_NOTES.md
v0.5.0/
RELEASE_NOTES.md
v0.6.0/
RELEASE_NOTES.md
v0.7.0/
RELEASE_NOTES.md
scripts/
appcast.sh
build-detached.sh
ExportOptions.plist
release.sh
site.sh
site/
landing/
app.js
apple-touch-icon.png
assets/
icon.png
screenshots/
friction-report-dark.png
friction-report.png
goal-input-dark.png
goal-input.png
README.md
run-history-dark.png
run-history.png
run-replay-dark.png
run-replay.png
runsession-hero-dark.png
runsession-hero.png
settings-dark.png
settings.png
target-ios-dark.png
target-ios.png
target-macos-dark.png
target-macos.png
target-web-dark.png
target-web.png
favicon.png
index.html
llms.txt
manifest.webmanifest
robots.txt
sitemap.xml.tmpl
styles.css
standards/
01-architecture.md
02-swiftdata.md
03-subprocess-and-filesystem.md
04-swift-conventions.md
05-design-system.md
07-ai-integration.md
08-run-log-integrity.md
09-performance.md
10-testing.md
12-simulator-control.md
13-agent-loop.md
14-run-logging-format.md
AUDIT_CHECKLIST.md
INDEX.md
Tests/
HarnessTests/
ActionsViewModelTests.swift
AgentLoopTests.swift
AgentToolsSchemaTests.swift
ApplicationsViewModelTests.swift
ChainExecutorTests.swift
CrashResilienceTests.swift
FrictionReportViewModelTests.swift
GeminiClientTests.swift
GoalInputViewModelTests.swift
HarnessPathsTests.swift
KeychainStoreTests.swift
MCPServerIdentityTests.swift
Mocks/
FakeProcessRunner.swift
FakeServices.swift
MockLLMClient.swift
... 66 moreFAQ
harness is a Claude Code plugin with hand-picked skills for testing work, indexed on Flowy. Install it with the command on its page. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.