lahfir/agent-desktop/agent-desktop-ffi
- community
Checking that the SKILL.md file is still in the repository.
C-ABI bindings over agent-desktop's PlatformAdapter. Consumers (Python ctypes, Swift, Node ffi-napi, Go cgo, C++, Ruby fiddle) link libagent_desktop_ffi.{dylib,so,dll} and call `ad_*` functions directly instead of spawning the CLI binary per call. The canonical observe-act workflow is: ad_init → ad_adapter_create[_with_session] → ad_snapshot → read qualified refs (@s8f3k2p9:e5) → ad_execute_by_ref → ad_free_string → ad_adapter_destroy.
Install
npx vskill@latest install lahfir/agent-desktop/agent-desktop-ffiSKILL.md
agent-desktop-ffi
Direct C-ABI access to PlatformAdapter operations. Build the cdylib with the release-ffi profile:
The output is target/release-ffi/libagent_desktop_ffi.dylib (.so on Linux, agent_desktop_ffi.dll on Windows) plus a committed C header at crates/ffi/include/agent_desktop.h. The default release profile uses panic = "abort": a Rust panic inside an extern "C" function would SIGABRT the host. release-ffi keeps panic = "unwind", which the panic traps need.
Four reference topics, loaded as needed:
- ownership.md: who allocates and who frees every
pointer the FFI hands back.
- error-handling.md: errno-style last-error
contract, error codes, enum validation, panic boundary.
- threading.md: host-thread contract, mutation
ordering across threads and processes, adapter-bound native handles, AXIsProcessTrusted inheritance.
- build-and-link.md: prebuilt archives, ABI
handshake, struct size validation, C and Python examples.
Observe-act workflow (canonical path)
ad_snapshot returns the CLI envelope {version, ok, command, data}. data.tree carries snapshot-qualified refs for interactive elements. Read data.complete before trusting a tree: false means the observation budget ran out and the tree is partial, not an error. ad_snapshot always targets the focused window of app; window targeting, skeleton mode and --root drill-down are not exposed over the ABI. A bare @eN ref returns AD_RESULT_ERR_INVALID_ARGS.
Core constraints
- ABI handshake. Call
ad_init(AD_ABI_VERSION_MAJOR)once after loading the
library. A mismatch returns AD_RESULT_ERR_INVALID_ARGS: abort. ad_abi_version() returns the library's major for display. The major increments on a removed symbol or a layout change; new ad_* symbols and error codes are additive. Before 1.0, pin the exact libagent_desktop_ffi version you link against.
- Session adapters.
ad_adapter_create_with_session("session-id")scopes
refmap persistence to a session namespace, like CLI --session <id>. IDs are 1 to 64 ASCII alphanumeric, -, or _ characters. A null ID behaves like ad_adapter_create(). An invalid ID returns null (read ad_last_error_*).
- Ref actions.
ad_execute_by_refis the primary action entrypoint. It loads
the RefStore, resolves the ref strictly (STALE_REF when the snapshot has no such ref or the element changed, AMBIGUOUS_TARGET on several candidates), runs the live actionability preflight (visible, stable, enabled, supported action, policy, editable), then dispatches. A disabled or unsupported target fails before any platform call; on AD_RESULT_ERR_ACTION_FAILED the check report is JSON in ad_last_error_details(). It auto-waits 5000 ms before the preflight; ad_execute_by_ref_timeout takes timeout_ms (-1 = 5000 ms default, 0 = single-shot). PressKey defaults to focus_fallback; every other action, including TypeText, defaults to headless. Pass AD_POLICY_KIND_FOCUS_FALLBACK (1) to allow focus without cursor movement, or AD_POLICY_KIND_HEADED (2) to allow physical cursor and keyboard delivery. ad_execute_ref_action_exact_with_policy runs the same preflight from an AdExactRefEntry.
- Generation-safe direct APIs. Every direct targeting function takes
process-generation evidence: AdExactRefEntry, AdExactWindowInfo, ad_list_windows_exact, ad_launch_app_exact, and the other *_exact symbols. AdRefEntry, AdWindowInfo, and AdSurfaceInfo exist only as fields embedded in the exact structs. ad_list_surfaces_exact keeps the surface id. From AD_EXACT_SURFACE_INFO_VERSION 2, AdExactSurfaceInfo.unclassified names the surface kinds its probe could not read (for example sheet,menu), or is null. A missing sheet or menu next to a listed kind is unknown, not absent. Only the Windows adapter fills it; macOS and Linux return null.
- Native handles.
ad_resolve_element_exactandad_find_exactreturn an
AdNativeHandle. ad_execute_action (headless, no preflight) and ad_execute_action_with_policy act on it directly, bypassing the ref-action pipeline. Release every handle with ad_free_handle(adapter, &handle) on the same adapter and thread, before ad_adapter_destroy, on every platform. It zeroes handle.ptr, so a second call is a no-op.
- Action results.
AdActionResult.stepsmirrors the CLIstepsarray for
activation-chain actions. Each AdActionStep has label and outcome strings, mechanism (AD_STEP_MECHANISM_SEMANTIC_API = 1 or AD_STEP_MECHANISM_PHYSICAL_SYNTHETIC = 2, valid only when has_mechanism), and verified (valid only when has_verified). AdActionResult.disposition carries the same delivery/retry pair as ad_last_error_delivery_semantics. Release the result with ad_free_action_result(&out).
- Display discovery. Call
ad_list_displaysbefore using
AdScreenshotTarget.screen_index. List order is the screenshot index order. Each AdDisplayInfo carries a stable display ID, bounds, primary flag, and scale. Release the list with ad_display_list_free.
- Errors. A failing call sets thread-local last-error state; read it with the
ad_last_error_* accessors before the next failing call. ad_last_error_details() returns structured JSON (the actionability report on ACTION_FAILED, candidate summaries on AMBIGUOUS_TARGET) that can contain element names, values, and window titles from the user's screen: keep it out of shared logs. ad_last_error_delivery_semantics reports whether the failed action was delivered and whether a retry is safe. Invalid enum discriminants return AD_RESULT_ERR_INVALID_ARGS. See error-handling.md.
- Threading. Entrypoints may be called from any host thread. Native handles
stay on the adapter and thread that created them; pass qualified refs across threads. Desktop mutations take the same cross-process interaction lease as the CLI; reads take no lease and carry finite deadlines. See threading.md for the read/read, read/mutation, and mutation/mutation matrix.
- Tracing. File tracing (same JSONL contract as CLI
--trace; segments carry
event, ts_ms, seq, and redacted fields) is active only when the session has a manifest with trace: on, created by session start (or an equivalent manifest on disk) before the adapter is created. ad_adapter_create_with_session alone writes nothing. Each OS process appends to one segment, ~/.agent-desktop/sessions/<id>/trace/<pid>-<procTs>.jsonl, and a long-lived host reuses it. ad_trace_show (JSON envelope) and ad_trace_export (self-contained HTML file) read the adapter session's merged timeline; limit -1 = CLI default.
- Log callback.
ad_set_log_callback(cb)registers one process-wide callback
for diagnostics regardless of session manifest. It receives, as JSON, the tracing events emitted inside later ad_* calls on the same thread; the FFI never installs or replaces the host process's global tracing subscriber. cb receives an int32_t level (1=ERROR ... 5=TRACE) and a const char *msg valid only for the duration of the call. Pass NULL to unregister. Sensitive field values (password, token, text, ...) are replaced with {"redacted":true}. The callback must not unwind across the C ABI: catch C++ exceptions and Rust panics inside it, because a violation may abort the host. It may fire from several host threads at once and briefly after a NULL unregister: keep the callback and any data it captures valid for the process lifetime.
- Wait.
ad_wait(adapter, args, &out)runs the CLIwaitcommand. Set
mode.pause (AdOptionalU64, ms), mode.element, mode.window (waits for a window with that title), mode.text, or one of mode.surfaces.menu, .menu_closed, .notification; add the predicate group (predicate, value, action, count) and scope.timeout_ms / scope.app. The CLI event wait (--event, --window-id) is not exposed. Zero-initialize AdWaitArgs and validate the struct size against AD_WAIT_ARGS_SIZE / ad_wait_args_size() before calling. The output is a {version, ok, command, data} envelope freed with ad_free_string. ad_wait blocks the calling thread up to scope.timeout_ms ms. A concurrent ad_adapter_destroy does not invalidate an in-flight wait.
- Text input privacy. On macOS, focus-fallback or headed text insertion may
briefly use the clipboard for non-ASCII text. For sensitive text, prefer AD_ACTION_KIND_SET_VALUE with AD_POLICY_KIND_HEADLESS when the target supports settable values.
- `ad_get_tree_exact` vs `ad_snapshot`.
ad_get_tree_exactreturns a raw flat
breadth-first tree: no refs, no refmap persistence, no JSON envelope (free it with ad_free_tree). Use it for custom traversal or UI inspection. For observe-act agents that drive actions via ad_execute_by_ref, start with ad_snapshot.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/agent-desktop-ffi/SKILL.md
Popularity
- Repository stars
- 921
- Repository forks
- 54
- 7-day trend score
- 34
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 17 May 2026, 5 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/lahfir/agent-desktop/agent-desktop-ffi)