agent-desktop/crates/ffi
Lahfir 53d6800aca docs: align README and repo docs with session-first trace
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 06:58:07 -07:00
..
codegen_templates feat(ffi): Phase B and C — Python smoke harness, parity gates, build.rs codegen (#77) 2026-06-26 19:14:14 -07:00
examples feat(ffi): Phase B and C — Python smoke harness, parity gates, build.rs codegen (#77) 2026-06-26 19:14:14 -07:00
include feat: complete FFI C-ABI surface (Phase A) — handshake, pipeline entrypoints, log callback (#67) 2026-06-25 19:24:06 -07:00
src feat(macos,core): harden adapter and core foundation with caller-controllable guardrails (#82) 2026-06-29 11:32:27 -07:00
tests fix(session): harden session-first trace against review findings 2026-06-30 22:18:05 -07:00
build.rs feat(ffi): Phase B and C — Python smoke harness, parity gates, build.rs codegen (#77) 2026-06-26 19:14:14 -07:00
Cargo.toml feat(ffi): Phase B and C — Python smoke harness, parity gates, build.rs codegen (#77) 2026-06-26 19:14:14 -07:00
cbindgen.toml feat: complete FFI C-ABI surface (Phase A) — handshake, pipeline entrypoints, log callback (#67) 2026-06-25 19:24:06 -07:00
README.md docs: align README and repo docs with session-first trace 2026-07-01 06:58:07 -07:00

agent-desktop-ffi

C-ABI cdylib over PlatformAdapter. Exposes libagent_desktop_ffi.{dylib,so,dll} to Python (ctypes), Swift, Go (cgo), Node (ffi-napi), and C++ consumers without spawning a CLI subprocess per call.

Build

cargo build --profile release-ffi -p agent-desktop-ffi

The release-ffi profile keeps panic = "unwind", which is required for the catch_unwind traps that prevent panics from crossing the extern "C" boundary. Do not use --release — that profile sets panic = "abort", which silently defeats every trap.

For the CI / stub-adapter path (no AX permission required):

cargo build --profile release-ffi -p agent-desktop-ffi --features stub-adapter

--features stub-adapter replaces the real platform adapter with a no-op that returns PLATFORM_NOT_SUPPORTED for every adapter call.

ABI Surface

Group Key symbols
ABI handshake ad_abi_version(), ad_init(expected_major)
Adapter lifecycle ad_adapter_create(), ad_adapter_create_with_session(id), ad_adapter_destroy()
Command-backed JSON entrypoints ad_snapshot, ad_execute_by_ref, ad_wait, ad_version, ad_status
Log callback ad_set_log_callback(fn(level, msg)) — unstructured tracing to a callback
Structured file trace Same JSONL contract as CLI, gated by a trace: on session manifest (no ABI change); use session start before creating a session adapter
Errno last-error ad_last_error_code(), ad_last_error_message(), ad_last_error_suggestion(), ad_last_error_platform_detail()
Type accessors / size getters ad_*_size(), ad_*_list_{count,get,free}(), ad_image_buffer_*()
Free helpers ad_free_string(), ad_free_handle(), ad_free_tree(), ad_free_action_result()

The full declaration list is in include/agent_desktop.h. Consumers must call ad_init(AD_ABI_VERSION_MAJOR) after dlopen to verify the loaded dylib matches the header the consumer was built against.

Codegen

The 5 command-backed JSON entrypoints (ad_snapshot, ad_execute_by_ref, ad_wait, ad_version, ad_status) are generated by build.rs from the template files in codegen_templates/ into src/commands/generated.rs. Do not hand-edit generated.rs; edit the corresponding .rs.in template instead. The build panics if a template file exists in codegen_templates/ but is not registered in build.rs, so stale templates cannot silently accumulate.

Thread Safety (macOS)

Every adapter-touching entry point must be called from the process's main thread. The guard fires at runtime in all build profiles; a worker-thread call returns AD_RESULT_ERR_INTERNAL with a 'static diagnostic string. Operations safe from any thread: ad_abi_version, ad_init, adapter create/destroy, ad_last_error_* readers, and all accessor / free helpers.

Error Model

Every AdResult-returning function sets thread-local last-error state on failure. Read it with ad_last_error_code() / ad_last_error_message() / ad_last_error_suggestion(). The pointer returned by ad_last_error_message() stays valid until the next failing call on the same thread — successful calls leave it untouched (POSIX errno semantics).

Full Documentation

skills/agent-desktop-ffi/ — build-and-link guide, ownership model, threading rules, and error-handling patterns.