---
title: Build and link
description: Get libagent_desktop_ffi from a release or build it from source, find the header, link a C program against it, and run the ABI handshake.
url: "https://agent-browser.dev/desktop/ffi/build-and-link"
docs_index: /desktop/llms.txt
lastUpdated: 2026-10-30
navTitle: "Build and link"
---

> For an index of all documentation, see [/desktop/llms.txt](/desktop/llms.txt).

Use a prebuilt archive from a release, or build the library from source with the `release-ffi` profile.

## Prebuilt archives

Every GitHub release ships `agent-desktop-ffi-v<version>-<target>.tar.gz` (`.zip` on Windows) for macOS arm64 and x86_64, Linux x64 and arm64 (glibc), and Windows x64 and ARM64. Each archive holds:

<table>
  <thead>
    <tr>
      <th>Path</th>

      <th>Contents</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><code>lib/</code></td>

      <td>The library and the helper that must stay beside it: <code>agent-desktop-macos-helper</code> on macOS, <code>agent-desktop-linux-clipboard-owner</code> on Linux. On Windows, also <code>agent_desktop_ffi.dll.lib</code>, the import library for MSVC.</td>
    </tr>

    <tr>
      <td><code>include/agent_desktop.h</code></td>

      <td>The generated C header</td>
    </tr>
  </tbody>
</table>

`checksums.txt` in the release catches corruption; verify origin with `gh attestation verify <file> --repo vercel-labs/agent-desktop`.

## Build from source

```bash
cargo build --profile release-ffi -p agent-desktop-ffi    # writes target/release-ffi/libagent_desktop_ffi.so on Linux
```

The output is `libagent_desktop_ffi.dylib` on macOS, `libagent_desktop_ffi.so` on Linux, and `agent_desktop_ffi.dll` on Windows, in `target/release-ffi/`. The `release-ffi` profile keeps `panic = "unwind"`, so every entrypoint can catch a panic and report it as `AD_RESULT_ERR_INTERNAL`. The workspace `release` profile uses `panic = "abort"`, so `cargo build --release` produces a library whose panics abort the host process. Do not ship it.

A source build also needs the platform helper beside the library; see [Installation](/desktop/installation#build-from-source).

## The header

`crates/ffi/include/agent_desktop.h` is generated by cbindgen; do not edit it. When the Rust ABI or `crates/ffi/cbindgen.toml` changes, regenerate it with `scripts/update-ffi-header.sh`, and CI fails on drift. The header ends with C11 `_Static_assert` guards that pin the size and field offsets of every size-pinned struct; define `AGENT_DESKTOP_ABI_ASSERTS` to skip them.

## Link and run a program

This program checks the ABI, snapshots one app, and prints the envelope:

```c
#include <stdio.h>
#include "agent_desktop.h"

int main(int argc, char **argv) {
    const char *app = argc > 1 ? argv[1] : NULL;   /* NULL reads the focused window */

    if (ad_init(AD_ABI_VERSION_MAJOR) != AD_RESULT_OK) {
        fprintf(stderr, "ABI mismatch: %s\n", ad_last_error_message());
        return 1;
    }

    AdAdapter *adapter = ad_adapter_create();
    if (!adapter) {
        fprintf(stderr, "adapter_create failed: %s\n", ad_last_error_message());
        return 1;
    }

    char *json = NULL;
    AdResult rc = ad_snapshot(adapter, app, AD_SNAPSHOT_SURFACE_WINDOW,
                              3, true, true, &json);   /* max_depth, interactive_only, compact */
    if (json) {                      /* an ok:true or ok:false envelope: free either */
        printf("%s\n", json);
        ad_free_string(json);
    } else {
        fprintf(stderr, "snapshot failed (%d): %s\n", (int)rc, ad_last_error_message());
    }

    ad_adapter_destroy(adapter);
    return rc == AD_RESULT_OK ? 0 : 1;
}
```

Compile and run it from the repository root on Linux or macOS:

```bash
cc -std=c11 -I crates/ffi/include main.c \
   -L target/release-ffi -lagent_desktop_ffi \
   -Wl,-rpath,"$PWD/target/release-ffi" \
   -o snapshot_demo
./snapshot_demo gnome-calculator
```

On Linux against GNOME Calculator it printed this envelope, shown here with `tree` elided:

```json
{"version":"2.5","ok":true,"command":"snapshot","data":{"app":"gnome-calculator","complete":true,"ref_count":2,"snapshot_id":"s18ekeexjf5trb","tree":{"...":"..."}}}
```

The macOS install name is `@rpath/libagent_desktop_ffi.dylib`, so a program that links the library needs an rpath to its directory; `dlopen` and `ctypes` callers load by path and need nothing. On Windows, an MSVC program links `agent_desktop_ffi.dll.lib` and loads the DLL at run time. On Linux the program must run inside the user's graphical session to see apps; see [Linux](/desktop/platforms/linux).

## Validate struct sizes

A language whose struct layout can differ from C's, such as Python `ctypes` or Go `cgo`, must check each size-pinned struct before passing one to the library. Each has an `AD_*_SIZE` macro and a runtime getter that returns the size compiled on the Rust side: `AdAction` (`ad_action_size`), `AdActionResult`, `AdActionStep`, `AdDisplayInfo`, `AdDragParams`, `AdElementState`, `AdExactRefEntry`, `AdExactSurfaceInfo`, `AdExactWindowInfo`, `AdRefEntry`, and `AdWaitArgs`, each with a matching `ad_<name>_size()` getter.

Zero-initialize every caller-allocated struct before you set fields, and set the `version` and `size` fields of versioned structs to the header's constants.

## Smoke test

`tests/ffi-python/smoke.py` is the reference for non-C hosts and uses only the Python standard library. It checks the ABI version, the size getters, `ad_version`, and the create, snapshot, free, and destroy cycle:

```bash
python3 tests/ffi-python/smoke.py target/release-ffi/libagent_desktop_ffi.so crates/ffi/include/agent_desktop.h
```

On the Linux test desktop it ended with `All legs passed.`

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)