Skip to content

feat: add the windows observation read path to the windows adapter - #119

Merged
lahfir merged 19 commits into
feat/windows-adapterfrom
feat/windows-2.4-observation
Aug 3, 2026
Merged

lahfir merged 19 commits into
feat/windows-adapterfrom
feat/windows-2.4-observation

Conversation

@lahfir

@lahfir lahfir commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

An agent can now observe and identify real Windows applications: snapshot returns reffed trees with honest completeness, list-windows/list-apps/list-displays/focused_window are live with recycling-safe identity, and the dense-app cases actually work — Chromium/Electron trees are detected and settled, transparent web wrappers stop eating depth budget, drill-down (snapshot --root @ref) and skeleton (--skeleton) resolve fail-closed, and a Chromium modal is reachable as a sheet surface.

This sub-phase (2.4 of docs/phases.md) lands the full read path on Windows — the first time snapshot returns refs on this platform. It also ships the four P2-O8 evidence fields (subrole, role_description, placeholder, dom_classes) as optional core schema with Windows producers from named, measured sources — they were previously absent on every platform — and the --timeout-ms knob for cold Chromium settles, per probe A16-11.

Related issues

Related: none. Sub-phase branch per docs/phases.md Platform Delivery Model — this PR targets feat/windows-adapter, not main.

Type of change

  • feat: — new feature

How tested

Read-path correctness was established by running it, not by the test suite alone (the sub-phase's carried principle): the release binary was pointed at Notepad, Explorer, the WinForms and WPF fixtures, and Obsidian (Chromium/Electron), and the JSON read and judged. The durable record is docs/dogfood-reports/2026-08-01-feat-windows-2-4-observation-dogfood.md. Verified live:

  • snapshot returns reffed trees on all four UI stacks and Chromium; --skeleton clamps to depth 3 with reffed boundary counts; --root @ref re-resolves and drills down; a missing ref returns STALE_REF.
  • list-windows / list-apps / list-displays return real inventories with corroborated process-generation tokens; the single 96-DPI display reads scale 1.0.
  • Obsidian's settled tree reads 57 refs at full depth via the wrapper skip and no activation loop; the pre-activation shell shape was caught and fixed during the run (tree-end signal, not raw-depth ceiling).
  • The two ref-able arms from 2.3: switch now resolves (fixture ToggleButton); cell still does not on WPF DataGrid cells (they present ControlType.Custom), with the measured reason recorded for the reader.

Gates run on the dev box (Windows Server 2019):

  • cargo fmt --all -- --check
  • cargo clippy --locked -p agent-desktop-core -p agent-desktop-windows -p agent-desktop -p agent-desktop-ffi --all-targets -- -D warnings
  • cargo test --locked -p agent-desktop-core -p agent-desktop-windows --lib — 969 core, 299 windows
  • cargo test --locked -p agent-desktop
  • cargo test --locked -p agent-desktop-ffi --tests
  • cargo check --locked -p agent-desktop-windows --all-targets --target x86_64-unknown-linux-gnu (cross-compile lane)
  • probes/windows/13-ledger-check.ps1 (hunk index bijective)
  • Release binary size 2.03 MB (12.9% of the 15 MiB cap)
  • Note: bash tests/e2e/run.sh is macOS-only and not runnable off-macOS; the Windows read path was instead exercised by driving the release binary directly (the dogfood report).

The macOS lane runs on this PR's CI and is the detector for byte-identical goldens after the core schema change — the U2 unit asserts tests/fixtures/ is untouched.

Checklist

  • PR title uses a conventional-commit prefix (feat: — lowercase, imperative, no capital after colon)
  • cargo fmt and cargo clippy -D warnings are clean
  • All tests pass
  • No unwrap() in non-test code (a deny lint on the crate enforces it)
  • No inline comments added — only /// doc-comments on public items
  • All modified files are within the 400 LOC limit
  • Skill docs updated for the new flags and output fields (skills/agent-desktop/references/commands-observation.md)
  • README / other docs updated when the public interface changed (docs/phases.md corrected in place by U10; CONCEPTS.md gained Window Identity and Web Wrapper)

Session-settled decisions carried from planning: 2.4 planned as docs/phases.md defines it with contradictions corrected rather than planned around (user-directed); correctness established by running it rather than unit tests alone (user-directed); no test asserts a machine-specific or application-specific fact (user-directed). The plan's carries_target_text classification of LocalizedControlType/AriaRole as provider vocabulary (not withheld on secure fields) is the settled decision the reviewer flag on that point was reconciled against; the AriaRole author-text verbatim sub-question is recorded as a WebView2-capable-environment follow-up in probe A16-6.

Known Residuals

Recorded from code review, accepted as low-risk with rationale:

  1. cell refinement on WPF grid cells is unsettled (P2, design). WPF DataGrid cells carry GridItem/TableItem but present ControlType.Custom (resolves unknown). Whether Custom + those patterns should refine to cell is a decision recorded in the dogfood report for the reader; not changed here because it involves the role-map contract, not the read path.
  2. AriaRole/LocalizedControlType are not withheld on secure fields (P3, design, reconciled). The plan classifies them as provider vocabulary (carries_target_text=false, backed by A16-6's measurement that author tokens did not pass through), so they are not in VALUE_BEARING. A hypothetical hostile provider placing text in AriaRole would bypass the secure gate; the plan's settled classification stands and this is documented.
  3. Resolver sibling-cap silent truncation (P3). The resolver stops searching a sibling list at the 10k cap with no incompleteness marker, yielding STALE_REF for a bounded search. Fails closed (never a wrong target); low probability (10k siblings on one parent). Left as-is for the 2.4/2.5 boundary.
  4. list_apps/list_windows identity corroboration tightened but differs from macOS's full two-source merge (P3). Windows now fails the inventory when the window token disagrees with a fresh process read; macOS additionally merges a full app inventory. Windows 2.4's shape is documented as the shipped contract.

Post-Deploy Monitoring & Validation

No production/runtime impact — agent-desktop is a stateless CLI/adapter library; there is no server, daemon, or always-on service. The observable runtime surface (CLI JSON output) is covered by the unit/live tests and the committed dogfood report. No additional operational monitoring required; the macOS CI lane on this PR is the only always-running gate and it verifies the golden fixtures remain byte-identical.

lahfir added 14 commits August 1, 2026 23:50
Ten units grounded in the probe corpus, the shipped walker, and the macOS
reference implementation, with every load-bearing claim carrying a file or
ledger citation. Research disproved five statements in docs/phases.md §2.4
- the observe_tree seam, the nonexistent HtmlClass property, the display
scale field name, and two macOS-pattern claims - and the plan carries their
corrections as its own unit rather than planning around them.

A five-persona document review ran before commit and its findings are folded
in: the Windows interaction-lease override core's activation loop requires,
the descriptor group rehomed inside NodePresentation because both host
structs sit at the seven-field cap, window-title corroboration against
same-process HWND reuse, a committed home for the force-activation flag on
an at-cap request struct, and three new probe questions (AriaRole
passthrough, settle latency, the integrity boundary).
The round hunted what the first round's fixes missed and what they broke.
Real defects closed: the surface-advertisement gap that made U6's own
verification unpassable (supported_surfaces defaults empty and the crate
pins it); the title corroboration rescoped to the macOS split after the
adversarial pass showed a hard title check breaks drill-down on any
legitimately retitled window; the still-thin guidance restructured so it
can actually escape core's activation loop instead of decaying to a bare
timeout; the settle-latency branch given a real lever (--timeout-ms)
instead of a recommendation with no knob; activation gated to
Window-rooted walks; trace redaction extended to the two descriptor keys
no existing token covers; and four edit-introduced contradictions
reconciled (split-seam staleness twice, the U2 cap attribution, the
wrapper value pin, the corrections count).
@greptile-apps

greptile-apps Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR implements the Windows observation read path, including live inventories, strict ref resolution, Chromium settling, surface discovery, and optional descriptor evidence.

  • Adds Windows UI Automation tree observation and drill-down support.
  • Adds live application, window, display, focus, and process-generation discovery.
  • Threads snapshot timeout and renderer-accessibility options through the CLI and core.
  • Extends node output with optional subrole, role description, placeholder, and DOM class fields.

Confidence Score: 4/5

The PR is not yet safe to merge because stored references can resolve unintended elements or recycled windows, and Chromium activation state can leak across FFI snapshots.

Evidence-free refs still accept a sole unrelated candidate, stored HWND resolution does not verify the handle's current owner, and the adapter-wide activation flag causes a reused FFI adapter to treat a new Chromium target as already settled.

Files Needing Attention: crates/windows/src/tree/resolve.rs, crates/windows/src/tree/resolve_match.rs, crates/windows/src/system/window_identity.rs, crates/windows/src/adapter.rs, crates/windows/src/tree/observe.rs

Important Files Changed

Filename Overview
crates/windows/src/tree/resolve.rs Implements stored-ref root verification, bounded traversal, candidate selection, and strict-resolution error handling.
crates/windows/src/tree/resolve_match.rs Defines candidate identity matching and stale or ambiguous resolution errors.
crates/windows/src/adapter.rs Connects Windows observation and inventory operations while retaining renderer-activation state on the adapter instance.
crates/windows/src/tree/observe.rs Builds Windows observed trees and coordinates Chromium shell detection, settle behavior, and root re-verification.
crates/windows/src/system/window_identity.rs Introduces strict and stored window identity verification using HWND, PID, process-generation, and title evidence.
crates/core/src/commands/snapshot.rs Threads timeout and renderer-accessibility options into fresh snapshots and ref-based drill-down.
crates/core/src/node_descriptor.rs Adds optional serializable descriptor fields while preserving the legacy output shape when absent.

Sequence Diagram

sequenceDiagram
    participant CLI
    participant Core
    participant Win as WindowsAdapter
    participant UIA
    CLI->>Core: snapshot options
    Core->>Win: observe_tree(root, request)
    Win->>UIA: enumerate and read properties
    alt Chromium shell requires activation
        Win-->>Core: activation required
        Core->>Win: activate renderer accessibility
        Core->>Win: retry observation
        Win->>UIA: settle and walk tree
    end
    Win-->>Core: ObservedTree
    Core-->>CLI: reffed snapshot JSON
Loading

Reviews (3): Last reviewed commit: "fix: strip remaining plan ids and versio..." | Re-trigger Greptile

Comment thread crates/windows/src/tree/resolve.rs Resolved
Comment thread crates/windows/src/adapter.rs Resolved
Comment on lines +135 to +139
if !crate::system::process_identity::matches_instance(entry.process.pid, instance)? {
return Err(stale_ref_error(entry));
}
} else {
return Err(stale_ref_error(entry));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Recycled HWND crosses window identity

When a source window closes and Windows recycles its HWND to another window in the same still-running process, this check accepts the unchanged process-generation token and resolves the current UIA root for the recycled handle, causing drill-down or a later action to target a matching element in the wrong window instead of returning STALE_REF.

Context Used: CLAUDE.md (source)

lahfir added 2 commits August 2, 2026 18:36
Twelve findings survived independent validation out of fourteen raised by a
nine-reviewer pass; every one is closed here with a test that fails when its
fix is reverted.

The Chromium settle is deadline-bounded through core's retry loop instead of
dying after one 50ms attempt, so --timeout-ms actually extends the cold
settle A16-11 measured; activation state is keyed per target process instead
of one adapter-global flag; the force path and Element-rooted walks pass the
same liveness re-verification every other complete claim does.

ObservationRequest returns to seven fields with skeleton folded into
ObservationMode, and the two six-parameter snapshot entry points merge into
five-parameter forms over small target structs.

The boundary children_count read gets its own 25ms budget mirroring the
macOS child-read budget; the resolver's ambiguity tie-break is extracted
pure and pinned (two identical candidates stay ambiguous, a sole hash match
resolves); name corroboration is gated on core's mutable-value-role rule;
the ToolHelp process lookup is one shared helper; EnumWindows failures and
failed DPI reads are errors instead of fabricated defaults.

The CI probe build failure that silently uploaded no observation captures
is fixed and now fails loudly under the ci label, and the A16-7 row records
honestly that the flat WALK_SET decision awaits its second environment.
The phase-reference gate learns bare unit tokens and backtick-quoted
literals, with both added to its committed self-test; docs/phases.md gains
the four deferral write-backs review found missing.
@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown

Too many files changed for review. (101 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

lahfir added 2 commits August 2, 2026 21:13
…ond environment

The rebuilt binary was driven against all five real targets again after the
review fixes. The settle knob was proven live with a 50ms-versus-30s contrast
at the same cold instant, the force path failed closed on a dead window in
198ms, the boundary-count budget stamped counts on real Explorer and WPF
boundaries, and the killed-fixture drill-down returned STALE_REF in 35ms
with no application text. Depth-50 Chromium richness improved 57 to 166
refs. The fixed CI probe build uploaded the missing captures, so the A16-7
flat-set decision now rests on two environments (1.14x Win32, 1.24x WPF on
the hosted runner).
…fixture line

The must_pass fixture deliberately carries a backtick-quoted literal to prove
the gate does not false-fire on one; the backticks inside a single-quoted
shell string trip the same informational lint the two awk programs already
suppress, and for the same reason: the quoting is load-bearing.
@lahfir
lahfir merged commit bbad772 into feat/windows-adapter Aug 3, 2026
20 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Issues and PR Aug 3, 2026
lahfir added a commit that referenced this pull request Sep 23, 2026
… settle, drill-down (#119)

Lands the full Windows read path: observe_tree wired to the shipped walker
with honest completeness (partial trees, liveness-verified complete claims,
budgeted boundary counts), HWND-first window identity corroborated by a
process-generation token, list-windows/list-apps/list-displays inventories,
focused-window composition, the four P2-O8 descriptor fields as
absent-by-default cross-platform schema, Chromium detection with a
deadline-bounded activation settle (--timeout-ms, --force-electron-a11y),
the provenance-gated web-wrapper depth-skip, and fail-closed drill-down
resolution.

Verified by a nine-reviewer code review (twelve validated findings fixed
with revert-sensitive tests, two rejected with evidence), a two-wave
post-fix dogfood against real applications including cold-start
Chromium, and a two-environment cost corroboration (A16 ledger).
lahfir added a commit that referenced this pull request Sep 23, 2026
… settle, drill-down (#119)

Lands the full Windows read path: observe_tree wired to the shipped walker
with honest completeness (partial trees, liveness-verified complete claims,
budgeted boundary counts), HWND-first window identity corroborated by a
process-generation token, list-windows/list-apps/list-displays inventories,
focused-window composition, the four P2-O8 descriptor fields as
absent-by-default cross-platform schema, Chromium detection with a
deadline-bounded activation settle (--timeout-ms, --force-electron-a11y),
the provenance-gated web-wrapper depth-skip, and fail-closed drill-down
resolution.

Verified by a nine-reviewer code review (twelve validated findings fixed
with revert-sensitive tests, two rejected with evidence), a two-wave
post-fix dogfood against real applications including cold-start
Chromium, and a two-environment cost corroboration (A16 ledger).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant