On this page

This checkout supplies native bridge and Haskell embedding interfaces. An exported interface is not a promise that a complete macOS, Windows, iOS, Android or web client is shipped here. Installation and screen-by-screen guidance must come from the distribution providing that client. Do not infer a stable cross-version ABI from the presence of an exported function.

Choose the integration boundary

NeedInterface
HTTP clients and remote automationREST/SSE server; Haskell consumers can use agent-server-client.
Local durable subprocess schedulingUnix-socket daemon.
Native host with direct engine callbacksDarwin agent-native-bridge and its C header.
Distribution-owned service integrationsagent-integration-api, compiled into the distribution.

Build against the exact bridge contract

nix build .#agent-native-bridge

The native foreign-library output is Darwin-only. Read packages/agent-native-bridge/include/HaskellAgentBridge.h from the same revision as the library. It defines callback types, buffer lengths, ownership, return codes and operation-specific JSON schemas. Do not mix a newer header with an older library. The agent-native-bridge-library output is the Haskell package and is not interchangeable with a ready-made GUI app.

Read the complete C ABI reference in the source repository. Select the revision matching your library. It includes every declaration and contract comment, including callback schemas, result capacities and operation-specific return values. The documentation links to the source rather than maintaining a copy. Compare it with your installed distribution before compiling a consumer.

  1. Initialize the runtime, create an engine with a retained event callback, and preserve callback context for the lifetime required by the header.
  2. Stage options and attachments against the intended turn identifier; stage native voice only after turn options. Discard abandoned staging.
  3. Submit a typed request and distinguish admission from eventual task completion. Observe events rather than treating an accepted submission as a finished turn.
  4. Resolve only live interaction requests and the exact advertised choice. Keep arbitrary model text out of approval decisions.
  5. Cancel operations when their owner closes, await the specified completion boundary, destroy owned operation handles, then destroy the engine and runtime.

Callbacks can arrive outside a GUI main thread. Follow each function's contract rather than guessing lifetime or freeing a borrowed buffer. Copy data that must outlive its callback. A cancellation request does not roll back a completed commit, push, external message or other remote effect.

Create an engine and inspect the protocol

The following C program sends one read-only ping, waits at most five seconds for its callback, and destroys the engine before releasing callback storage. It does not submit a model turn or require a provider account. Save as native-protocol.c and type-check it in the documentation development shell with cc -std=c11 -Wall -Wextra -fsyntax-only -I packages/agent-native-bridge/include native-protocol.c. Syntax checking does not link or execute the native library.

#include "HaskellAgentBridge.h"
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
struct response_state {
    pthread_mutex_t mutex;
    pthread_cond_t condition;
    int received;
    size_t length;
    uint8_t bytes[4096];
};
static void receive_event(void *context, const uint8_t *bytes, size_t length) {
    struct response_state *state = context;
    pthread_mutex_lock(&state->mutex);
    if (!state->received) {
        state->length = length <= sizeof(state->bytes) ? length : 0;
        if (state->length != 0) memcpy(state->bytes, bytes, state->length);
        state->received = 1;
        pthread_cond_signal(&state->condition);
    }
    pthread_mutex_unlock(&state->mutex);
}
int main(void) {
    struct response_state state = {
        .mutex = PTHREAD_MUTEX_INITIALIZER,
        .condition = PTHREAD_COND_INITIALIZER
    };
    const uint8_t request[] = "{\"id\":\"probe\",\"method\":\"ping\"}";
    if (ha_runtime_init() != 0) return 1;
    void *engine = ha_engine_create(receive_event, &state);
    if (engine == NULL) { ha_runtime_exit(); return 1; }
    int32_t status = ha_engine_send_json(engine, request, sizeof(request) - 1);
    pthread_mutex_lock(&state.mutex);
    struct timespec deadline;
    int clock_status = clock_gettime(CLOCK_REALTIME, &deadline);
    if (clock_status == 0) deadline.tv_sec += 5;
    while (status == 0 && clock_status == 0 && !state.received) {
        if (pthread_cond_timedwait(&state.condition, &state.mutex, &deadline) != 0) break;
    }
    int complete = state.received && state.length != 0;
    if (complete) { fwrite(state.bytes, 1, state.length, stdout); putchar('\n'); }
    pthread_mutex_unlock(&state.mutex);
    ha_engine_destroy(engine);
    ha_runtime_exit();
    pthread_cond_destroy(&state.condition);
    pthread_mutex_destroy(&state.mutex);
    return complete ? 0 : 1;
}

A zero send status means admission, not completion. This example accepts the first callback only because it submits exactly one ping to a fresh engine; a real host must parse JSON and match the response id. Expected output has ok: true and result containing runtime: haskell and protocol: 4. Nonzero send statuses are 1 null engine, 2 invalid buffer, 3 internal failure, or 4 invalid request envelope. Copy callback buffers before returning; they need not be NUL-terminated.

Runtime initialization is process-global and reference-counted. Balance each successful initialization with exit only after all engines and independently owned operation handles are closed. ha_cli_main is a different, process-owning entry point: call it once on the initial thread with writable, NUL-terminated argv before any runtime initialization. It normally terminates the process and is not a command to call from an AppKit window. Rejected arguments return 64; an already initialized runtime returns 70. Do not mix the two entry paths.

Submit a turn with explicit context

Engine requests have string id, string method, and an optional params object. Responses carry the same id, boolean ok, and either result or an error string. The id correlates a request; turnId identifies execution and staging. Keep both unique within the host's active requests.

{"id":"request-1","method":"turn.start","params":{"turnId":"turn-1","prompt":"Explain this project without changing files.","cwd":"/absolute/project","worktree":false,"computerUse":false}}
  1. Select the absolute project directory. For a new session omit sessionId; to resume, supply its observed ID. worktree: true is accepted only for a new session. Supply provider and model together or omit both; an optional effort must be a recognized effort value.
  2. Before sending, call ha_engine_stage_turn_options with that turn ID. Interaction modes are ASK=0, PLAN=1, YOLO=2; shell modes are NONE=0, BASH=1, GHCI=2, BOTH=3. Unstaged turns default to ASK and BASH. Staging replaces earlier options for the same ID.
  3. Stage images with ha_engine_stage_turn_images: each entry has a nonempty MIME string and nonempty encoded bytes. This copies the ordered batch; an empty batch clears it. Turn IDs must be valid nonempty UTF-8, at most 1024 bytes.
  4. After options, optionally stage context with ha_engine_stage_turn_context. At most 32 integration attachments supply connection ID, server name and display name, not credentials. Each context string is at most 4096 UTF-8 bytes and contains no NUL. Token 0 means no window and requires empty window strings. A nonzero window token requires an application name and a host-resolvable window until turn completion; an expired attachment must fail rather than open unrestricted computer access.
  5. Serialize staging and turn.start for the same ID. Matching valid admission consumes staging. Rejected input discards staging; explicitly call ha_engine_discard_turn_staging when abandoning a draft, including staged voice. This is idempotent. Context staging without options returns 5 and leaves existing context unchanged.

For a running turn, ha_engine_set_turn_interaction_mode affects future authorization only. It neither approves pending input nor reverses authorized actions. Status 5 means the turn is not initialized or no longer running; do not display the new mode as applied. All engine calls must be serialized against destruction.

Read-only request methods

MethodParameters and result
pingNo parameters; runtime name and protocol version.
sessions.listNo parameters; visible session summaries with archive/runtime status, filtered to the current gateway boundary.
sessions.showRequired session id; optional integer before and limit. Default limit 50, clamped to 1–200; returns a history page after checking the same authority boundary.
models.listRequired cwd, optional sessionId; catalog is resolved under the current credential and gateway identity.
turn.agentsOptional turnId, required while multiple turns run. Returns the selected running agent snapshot: path, status, model and steps (state/title/detail). With no selected turn and none running, returns an empty array. An explicitly inactive ID fails. This is a snapshot, not a subscription.

An unknown method produces an asynchronous ok: false response. Do not infer supported methods from CLI slash commands. The source decoders are NativeRequest.hs and NativeRequestHandler.hs under packages/agent-native-bridge/ffi/Agent/CLI/MacOS/.

Observe admission, execution and cancellation

After submitting, consume turn events by turn ID. Use ha_engine_list_tasks to reconcile pending UI rows: callback status 0 supplies task ID, nullable session ID and state (0 queued, 1 running); status 1 completes the list and -1 reports failure. A new task can have no session ID yet. ha_engine_set_task_limit(engine, 2), for example, limits future cross-session scheduling to two tasks; allowed limits are 1–32, default 3. Lowering it does not cancel existing tasks.

Pass the copied task ID to ha_engine_cancel_task. Return 0 only accepts the cancellation command, even if the task has already ended. Keep observing terminal events and inspect completed effects before retrying work. List callbacks run on the engine command worker; do not synchronously destroy the engine from them.

Find, inspect and transfer a conversation

  1. Call ha_engine_search_conversations with a query and limit, clamped to 1–100. Active and archived conversations are searched; deleted ones are excluded. Collect status 0 rows until terminal 1 or failure -1.
  2. Use the chosen session ID, not its display title, for ha_engine_session_rename, ha_engine_session_archive or ha_engine_session_delete. Present destructive deletion separately from reversible archive. A zero immediate return accepts the request; update the UI only after the result callback succeeds.
  3. For an anchored transcript, call ha_session_load_around with session ID, center turn index and radius (clamped to 500). Status 0 carries a turn, status 1 completes the page, -1 fails. Only terminal success supplies meaningful has_older and has_newer. Usage -1 means unreported, not zero. Preserve transcript effects and extensible response item JSON rather than reducing everything to assistant text.
  4. Fork with ha_session_fork through the inclusive durable turn index and use the new session ID returned by the transfer result. Export with ha_session_export: concatenate status 0 chunks into one version-1 haskell-agent.session-transfer JSON document only after terminal 1. Discard partial files on -1.
  5. Import that complete document through ha_session_import and use its returned ID. Treat exports as sensitive conversation data, not an executable script. Import does not authorize replaying tool calls.

Observe a CLI-owned session without taking ownership

Start ha_session_observation_start with the session ID, callback and output-handle slot. Return 0 owns a nonnull handle; 1 is invalid input and 2 initialization failure, with no callback. Callbacks may arrive before start returns. Copy each batch into a temporary UI projection: RESET replaces the live turn, events update it, and READY atomically publishes it. Every event in the batch shares a sequence; sequences can skip but never decrease within an owner instance. A changed owner identifies a different CLI process.

generation_start identifies compaction and durable_turn_count the saved boundary. PERSISTED advances that boundary only after a write; do not duplicate live content into saved history. TOOL_OUTPUT replaces provisional output and TOOL_RETRACTED removes its card. RESPONSE_DISCARDED removes display items since RESPONSE_RESTARTED or RESET. RESET/READY low flag bits indicate running, waiting, completed or interrupted; bit 8 means truncated catch-up. Ignore unknown bits.

UNAVAILABLE and DISCONNECTED are reconnecting, nonterminal states. CANCELLED and FAILURE are terminal. Cancel stops observation, not the CLI. Destroy the handle exactly once outside its callback; destruction joins it and guarantees no later callback. This read-only interface provides neither steering nor approval authority over the observed process.

Implement questions and plan decisions

Install ha_engine_set_interaction_callback before submitting a turn. Copy the turn ID, interaction ID, prompt and all option labels, then dispatch UI work asynchronously. The callback must return promptly; returning does not answer the question. Kind 1 is plan entry (enter/stay), 2 plan exit (approve/request changes/cancel), and 3 a question.

For example, a free-text question is resolved with the copied IDs, selected_index = -1 and nonempty answer bytes; -1 with empty text cancels. Ordinary choices use zero-based indices. Plan-exit option 1 carries change-request notes in custom text. Status 4 means absent, resolved or out-of-range: dismiss/refetch instead of submitting another approval. Replacing/clearing the callback waits for in-flight invocation and cancels unanswered interactions; never replace it reentrantly.

Tool approvals use a separate JSON path. An approval.requested event identifies the turn and an approval object with id, callId, name, summary, arguments, argumentsEncrypted, async, truncated and onceOnly. Show the exact request, respecting encrypted/truncated fields, and send approval.resolve with approvalId and decision allow_once, allow_tool or deny. A once-only request accepts only allow_once or deny; allow_once must also carry onceOnly: true to confirm client support. A stale or duplicate resolution fails instead of applying to another request.

First host call: syntax highlighting without an engine

This small C consumer exercises a synchronous, read-only interface before adding engine lifetime and asynchronous callbacks. It emits byte ranges, not substrings. Save it as highlight.c and check its types against the pinned header:

cc -fsyntax-only -I packages/agent-native-bridge/include highlight.c
#include "HaskellAgentBridge.h"
#include <stdio.h>
static void span(void *context, size_t offset, size_t length, int32_t kind) {
    (void)context;
    printf("%zu %zu %d\n", offset, length, (int)kind);
}
int main(void) {
    const uint8_t language[] = "haskell";
    const uint8_t source[] = "main = putStrLn \"hello\"\n";
    if (ha_runtime_init() != 0) return 1;
    int32_t status = ha_syntax_highlight(language, sizeof(language) - 1,
        source, sizeof(source) - 1, span, NULL);
    ha_runtime_exit();
    return status == 0 ? 0 : 1;
}

The command only checks the consumer's C declarations; it does not link or run the bridge. Your native build must link the matching Darwin foreign-library artifact. Set AGENT_SYNTAX_DIR to the distribution's syntax definitions before initialization. Highlighting returns 0 for spans, 1 for plain-text fallback, 2 for invalid pointers/UTF-8 and 3 for internal failure. Discard all collected spans for any nonzero status. Language input is limited to 4096 bytes and source to 256 KiB or 5000 lines. Callback offsets and lengths are UTF-8 bytes, not character indices; ranges omit line-feed separators and never split a Unicode scalar. The example prints spans immediately for inspection; a real editor should collect them first and commit them only after a zero return code.

Enable charts only after implementing the renderer

Chart presentation is disabled by default. With a live engine, call ha_engine_set_chart_rendering_enabled(engine, 1) only after your host can decode the versioned chart document. Pass 0 to disable it for subsequently starting turns. This is independent of the operating system and does not alter a turn already running. Return values are 0 success, 1 null engine, 2 an enabled value other than 0 or 1, and 3 runtime failure. Serialize this synchronous, thread-safe call against engine destruction; it retains no callbacks or buffers.

After enabling, process tool-finish events by call ID. If HAEV kind 5 has flag bit 3, its third length-prefixed field is the chart JSON. Copy the entire field before returning from the callback, validate its version before rendering, and retain the ordinary tool output as a readable fallback. Reject unsupported documents without losing the tool result. A missing chart field is not an error: the tool may have produced only text. Do not infer chart support from successful syntax highlighting; those are separate capabilities.

Run and cancel an argv-based repository check

  1. Obtain the repository snapshot for the path being reviewed and retain its snapshot ID. Prepare ha_utf8_string arguments, a retained callback context and an output handle slot before calling ha_repository_check_start.
  2. For a check such as git diff --check, pass executable git and two separate arguments diff and --check, not a shell command string. Review the executable and arguments before starting: this API executes a program; it is not inherently read-only.
  3. On return status 0, own the opaque handle. Stream callback 1 is stdout and 2 is stderr. Copy callback-scoped bytes if retaining them; preserve order within each stream but do not assume a total order across both streams.
  4. The exit callback reports the process exit code, or -1 plus error text when launch fails. Distinguish its cancellation flag from test success. No callback failure is retried; output pipes are still drained.
  5. To stop, call ha_repository_check_cancel outside its callbacks. It targets the process group, including descendants, and joins teardown with a short termination-escalation grace period. Cancellation is not rollback.
  6. Call ha_repository_check_destroy exactly once from an owner thread. It waits for readers/process completion and guarantees no callbacks after return. Only then release callback context.

Callbacks may begin before start returns; the output handle is stored first. Keep callbacks prompt and schedule UI updates rather than blocking teardown. Calling cancel or destroy reentrantly from that check's own callback is a no-op; schedule it on another thread after the callback returns. Every executable and argument must be nonempty UTF-8. Limits are 4096 arguments, 1 MiB per argument and 8 MiB total argument bytes. A null argument array is allowed only with count zero. An accepted start is not a passed check: wait for the exit callback.

Decode engine events and own callbacks

ha_event_callback receives opaque context, borrowed byte pointer and explicit length. Copy bytes before returning. JSON is not the only event format: native loop events use binary HAEV, followed by version (one byte), kind (one byte), flags (unsigned 16-bit big-endian), then unsigned 32-bit big-endian length-prefixed UTF-8 turn ID and kind-specific fields.

KindMeaning
1 / 2 / 3Reasoning text / assistant text / status.
4Tool start and argument updates. Replace the card identified by call ID. Flags bit 0 encrypted arguments, bit 1 truncated, bit 2 asynchronous.
5Tool finish. Bits 1/2 indicate truncated/asynchronous; bit 3 adds a third field after call ID and output: complete chart JSON, at most 256 KiB. Chart JSON itself is never truncated.
6 / 7One provider response's usage / aggregate user-turn usage. Decimal UTF-8 input/output/cached counts, then optional provider USD cost. Missing cost is not zero or a local estimate. Kind 7 is terminal and precedes turn.completed/failed when an outcome is available.

Ignore unknown flag bits. Preserve ordering within a task; different tasks may invoke callbacks concurrently on worker threads. Never block a worker waiting for the GUI thread to synchronously call back into a destroying engine. Validate version, frame length and every field boundary before decoding; the header does not make a partial frame safe to interpret.

Native operation families

The header is the parameter-level reference. This table identifies which workflow belongs to each family and the safety condition the host must preserve.

FamilyOperations and host responsibility
Lifecycle and stagingRuntime init/exit; engine create/destroy; turn context/images/options, interaction mode and staging discard. Never reuse staging for an unintended turn.
Requests and tasksJSON request, ping, session list/show, model list and turn agents; list/cancel tasks and set concurrency. Track asynchronous completion.
ConversationsSearch, rename, delete, archive; observe with owned start/cancel/destroy handle; load around a message; fork/export/import. Keep canonical context separate from display-only output.
Human interactionRegister callback and resolve interaction. Match request identity and preserve full prompt/options.
AccountsList, OAuth start/poll, API-key connection, enable and delete. Protect credential material.
Organization gatewayAccount/status, connect start/poll/exchange and disconnect. Preserve authority throughout each operation.
MCP connectionsList/icons/create/rename/enable/remove/authorize, cancel/destroy connection operation. Keep named connections distinct from raw servers.
MCP serversList/read/status/add/edit/enable/disable/remove/restart. Explain reconnect and tool availability to the user.
Distribution integrationsAdmin list/call and connections list/search/begin/submit/poll/cancel/disconnect. Availability depends on distribution and authority.
Host capabilitiesBrowser/computer callback registration, chart rendering switch, syntax highlighting. Do not imply permission or implementation from registration alone.
Data browserCatalog list and row loading. Preserve data scope, pagination and authorization.
SkillsInstalled list/read; learned list/read/create/update/archive/restore/rollback/history. Respect scope and revision conflicts.
Repository reviewSnapshot/diff, apply path/hunks, commit. Refresh stale repository state.
Repository deliveryRepository/session PR status, PR summary, delivery status, push/PR preview and confirm, cancel all. Confirm the exact preview.
ChecksStart/cancel/destroy repository check. Retain callback owner until safe teardown.
MobileSession open/close, runner register, pairings list/revoke/wake, relay open/send/receive. Preserve pairing authorization.
VoiceStage voice, submit audio and receive playback/reset/end callbacks. Distinct from composer transcription.

Connect and manage a provider account

Start with ha_accounts_list. Status 0 rows include provider, billing, selection ID, account ID, label/detail, managed ID, source, enabled and can_manage; status 1 ends the list and -1 fails it. Usage windows follow their account row and use its selection ID; reset timestamps are Unix seconds. Externally discovered accounts can be visible but not manageable. Never pass a display label where a managed ID is required.

  1. For OAuth, call ha_account_oauth_start with the provider. On challenge status 0 copy the verification URL, user code, device IDs, polling interval and expiry; open the URL without logging its secrets.
  2. Pass those challenge fields back to ha_account_oauth_poll. Result status 1 is pending, 0 success, -1 error. Preserve callback storage through each outstanding call; stopping the UI's polling must not free a callback still in flight.
  3. For API-key authentication, use ha_account_api_key_connect with protected input, not a conversation. After successful connection, reload the account list and verify the intended provider and identity.
  4. For a manageable row, use its managed ID with ha_account_set_enabled or ha_account_delete. Re-list after success. Removing a local credential is not revocation at the provider; use the provider's controls when revocation is required.

Connect an organization gateway

These process-global calls need no engine. First inspect ha_gateway_status: 0 connected with base URL, 1 disconnected, -1 error. ha_gateway_account separately provides organization ID/name, user name and optional PNG; copy and validate the image before use.

  1. Call ha_gateway_connect_start with the selected base URL and client name. Challenge status 0 is not connected yet. Display its verification URI/user code and preserve the device code privately.
  2. Poll with the same base URL/device code. Status 0 is authorized, 1 pending, 2 slow down, -1 failure. Honor a nonzero replacement retry interval and stop at expiry rather than continuously restarting login.
  3. Alternatively, a browser authorization flow uses ha_gateway_connect_exchange with client ID, authorization code, PKCE verifier and redirect URI. Successful exchange validates and persists the credential inside the runtime; no bearer token is returned.
  4. Re-read status/account before loading organization data. Disconnect with ha_gateway_disconnect, then discard organization-bound UI projections. Do not mutate gateway credentials from an integration callback: workers retain an authority boundary until cleanup.

Manage a named remote MCP connection

List with ha_mcp_connections_list or its icon variant and retain the latest revision. A connection ID is immutable and distinct from both its display name and endpoint; two accounts may share an endpoint. Status 0 is a row, 1 terminal success, -1 failure, -2 cancelled. State codes are configured=0, connecting=1, authorization-required=2, ready=3, failed=4. Only ready confirms initialization/tool discovery.

  1. Create using revision, display name and endpoint. An accepted operation returns an owned handle. Wait for its row and terminal callback, then destroy the handle off the UI thread.
  2. Authorize using the returned ID and fresh revision. Open the URLs delivered to the authorization callback without logging/persisting them. The runtime owns the loopback listener and timeout.
  3. Rename, set enabled, or remove using the latest revision. A conflict returns the current revision: reload and let the user review the new state, not blindly retry a mutation.
  4. Cancel is nonblocking; destroy cancels and joins. Do not destroy from an operation callback. Cancellation may follow a committed catalog change, so reload before offering retry.

Inputs are copied, nonempty UTF-8 without NUL, at most 1 MiB per text field. Immediate codes are 0 accepted, 1 missing callback/output, 2 invalid input, 3 start failure. Rejected calls do not callback and leave the output handle null. Icon metadata is negotiated locally, not fetched by listing; treat icon URLs and bytes as untrusted content.

Edit a raw MCP server catalog entry

This is distinct from the named HTTP-connection workflow. List with ha_mcp_servers_list; argument and environment-key callbacks arrive before their row. Environment values are never returned. Read/status emits exactly one row or failure, not an additional completion item. Status means configured for the next turn, not a live process probe.

  1. Read the row and revision. Prepare command, argv slices, working directory, environment key/value entries and startup/request timeouts.
  2. Add or edit with the observed revision. Text is limited to 1 MiB each; arrays to 4096 entries. Environment values are write-only secrets. Edit preserves enabled state; use enable/disable explicitly.
  3. After successful mutation, ask ha_engine_mcp_server_restart to discard the engine's warm fleet. It is rejected while a turn is active. Wait until idle, re-read revision, and request restart again; the next turn starts the catalog.

Do not report a successful catalog write as a successful server connection. A restart return 0 promises one result callback even during shutdown; return 3 after shutdown admission closes promises none.

Drive distribution-specific connection setup

ha_engine_integration_admin_list returns operation definitions; call ha_engine_integration_admin_call only for an advertised operation and its parameter schema. Its result is one JSON value (status 0) or error (-1), not conversational output. Keep marked sensitive fields out of logs. An empty distribution provider is a supported empty catalog, not permission to discover an alternative local credential.

For typed setup, list/search connections, call ha_engine_connection_begin with a selected provider/identifier, and render the returned snapshot. Phases are catalog=0, search=1, credentials=2, challenge=3, redirect=4, selection=5, connected=6, waiting=7. Copy its session ID, fields and items. Submit identifier/value answers only for the current setup session; poll after its advertised milliseconds. Cancel abandons setup, while disconnect removes an established connection.

Secret fields use kind 1 and are never echoed. The secure-store callback supplies exactly 32 key bytes for the opaque identity scope; do not derive the key from a user-facing title. Inputs are limited to 16 KiB per string and 64 answers. Return 0 accepts exactly one terminal callback; 1 is null engine, 2 invalid input, 3 closed engine. Keep both callback contexts until completion. Engine destruction cancels and joins accepted workers.

Provide browser and computer capabilities

Browser: install both request and cancellation callbacks with ha_engine_set_browser_callback before starting a turn. Both null disables the capability; mixed nullability is invalid. Validate struct_size, operation, scope/call IDs and buffer bounds. Copy the request before dispatching to a web-view thread. For each accepted command, call its completion exactly once, including cancellation; a cancel notification is not completion. Return a supported failure status when the host cannot perform the operation, rather than inventing success.

For example, NAVIGATE acts only in the designated browser scope and returns bounded text; SCREENSHOT returns PNG only on success. Text results are at most 256 KiB and PNG bytes at most 16 MiB; validate pixel limits from the header. Result buffers are copied during completion. Replacing registration waits for accepted requests to drain, so never replace/destroy from the host callback itself. Registration does not implement URL policy or user consent for the host.

Computer: register ha_engine_set_computer_callback and explicitly enable computer use on the turn. The version-3 host receives OPEN, LIST, BIND, OBSERVE_OR_ACT and CLOSE operations with bounded borrowed request/result buffers. Open supplies an owned host session token; subsequent operations must stay within that session's authorized target. Implement OPEN_ATTACHED by resolving the exact staged window token; if unavailable, fail without falling back to unrestricted OPEN. Optional capability queries must not capture pixels or change the target.

Unlike browser replacement, existing computer sessions retain the old callback/context generation until their final CLOSE returns. Keep that storage alive after installing a replacement. Engine destruction stops workers and closes remaining sessions before releasing registrations. OS capture/accessibility permission and the host's consent UI still need testing in the actual application.

Load a bounded custom-data preview

  1. Call ha_data_catalog_list with the workspace path. Each object precedes its column records; completion is 2 and failure -1. Copy scope, table/view kind, object name and column types/nullability.
  2. Select a returned object and call ha_data_rows_load with its scope (user=0, repository=1, checkout=2), offset 0 and, for example, limit 50. Limits are 1–500 and offsets must be nonnegative.
  3. Assemble value callbacks by zero-based row and column. Kinds are null=0, text=1, JSON number=2, boolean=3, encoded JSON=4. Null has no bytes; do not render it as the literal string “null”.
  4. On terminal success 1 use row count and has_more to offer the next offset. Failure -1 invalidates the incomplete preview. Arbitrary SQL and server-side filter expressions are not parameters of this ABI; do not manufacture a query interface from object names.

These operations are read-only and resolve objects through the custom-scope catalog. Immediate returns are 0 accepted, 1 missing callback, 2 invalid pointer/length, 3 invalid scope/name/offset/limit. Keep callback context until its single terminal event; schema metadata is not credential material.

Inspect installed skills and revise learned skills

Installed filesystem skills and learned skills are separate resources. ha_installed_skill_list discovers bundled, personal and project skills using the session's precedence rules. Save the emitted absolute SKILL.md identity and use it with ha_installed_skill_read; this is not an arbitrary file reader. Both workspace and identity paths must be absolute UTF-8 without NUL, at most 32768 bytes. Status 0 is an item, 2 a discovery warning, 1 completion, -1 failure, -2 missing identity. Display warnings rather than implying the list is complete.

  1. List learned skills with the current workspace, selected scope and bounded limit (1–1000). Scope -1 lists applicable scopes; 0/1/2 select user/repository/checkout. Read revision 0 to obtain the current content.
  2. Create a new lower-case hyphenated slug with title, description, instructions and activation (always=0, relevant=1, manual=2). Optional applicability text is not secret storage.
  3. For update, archive, restore or rollback, pass the exact positive observed revision. Status -3 is a conflict containing the current revision: reload, compare, and get confirmation again. Never reinterpret the token as “latest”. History lists revisions; rollback selects a historical revision through the explicit operation.

Learned read emits one item or error; list/history emit items then one terminal. Mutation status 0 succeeds; -1 fails, -2 is missing, -3 conflict, -4 already exists, -5 revision missing. Character bounds are slug 80, title 200, description 1000, applicability 2000, instructions 30000 and change summary 1000. These operations have no cancellation handle: closing a panel must not release its callback context before completion.

Repository review and delivery

Load a repository snapshot and diff before offering path/hunk application. Refresh after edits rather than applying stale selections. Commit, push and pull-request creation are separate mutations. Present the exact push/PR preview to the user and confirm that preview; if repository state changes, obtain a fresh preview instead of reusing stale confirmation data. Check remote status when a response is lost. Cancellation cannot establish that a remote server did not accept a push.

Stage selected changes and commit

  1. Call ha_repository_snapshot. It emits snapshot/root/HEAD fingerprints, changed-file rows, then one result. HEAD can be empty on an unborn branch; rename source paths can be absent.
  2. Call ha_repository_diff with the snapshot, exact changed path, and WORKTREE=0 or STAGED=1. Assemble ordered patch chunks (at most 64 KiB each) and number hunks in emitted order.
  3. After user review, call ha_repository_apply_path or ha_repository_apply_hunks with STAGE=0, UNSTAGE=1 or RESTORE=2. Pass hunk indices, not caller-authored patch text. Binary, deletion and rename diffs cannot use hunk mutation; restoring never deletes an untracked file.
  4. Reload the snapshot and staged diff before ha_repository_commit. Supply that snapshot and reviewed message. Callback status 0 succeeds; -1 fails, -2 means stale, -3 cancelled. A stale operation makes no repository modification.

Paths must exactly match a changed path and be normalized repository-relative literals, not globs/pathspecs. Text bounds are 8 MiB each, 16 MiB combined, and at most 4096 hunks. Locks coordinate cooperating runtimes but do not protect against arbitrary direct file writes by another process. Refresh after external edits rather than promising that the UI snapshot is locked.

Confirm an exact push or pull request

Read delivery status for the snapshot to show branch, HEAD, upstream and ahead/behind counts. Request ha_repository_push_preview, display its commit/destination, then pass its confirmation token to ha_repository_push_confirm only after approval. Tokens are random, one-use, in-memory and expire after ten minutes; they bind repository, snapshot/HEAD, upstream configuration and remote OID. The runtime rechecks them, uses a server-side lease and refuses history rewrites.

For PR creation, preview with snapshot/base/title/body, show the exact content, and confirm its separate token. Bounds are base 1 KiB, title 512 characters, body 1 MiB. Status -2 requires a fresh snapshot/preview; -4 means invalid, expired or used token. Neither is authority to silently generate a new approval. After an uncertain network response inspect remote state before retrying.

ha_repository_pr_status returns found=0, no PR=1, unavailable=-1 or cancelled=-3. State codes are open=1, draft=2, merged=3, closed=4; CI is unknown=0, none=1, pending=2, passed=3, failed=4. ha_session_pr_status lists at most 20 associated PRs and then terminal 1; an item state 0 retains a validated link whose current state is unavailable. ha_pull_request_summary accepts a canonical GitHub PR URL and returns review metadata, not a diff or credential.

ha_repository_cancel_all joins repository workers and stops new admission during its barrier; it must run outside repository callbacks. Accepted work gets one terminal result, with -3 if cancelled before terminal completion. It does not own repository-check handles and cannot undo a completed remote effect.

Accounts, gateway and integration authority

Native account OAuth has separate start and poll stages; secret entry belongs in the host's protected interface, not a model prompt. Gateway connection is a separate organization identity. Retain its authenticated authority through the complete operation, not only setup. Changing or closing that authority must retire its owned resources instead of silently continuing with local credentials.

IntegrationProvider is a compiled, distribution-supplied implementation, not a dynamic untrusted plugin loader. The ordinary public distribution's empty provider deliberately supplies no local integrations. Local, remote, combined and explicit local-overlay endpoints can be supplied by a distribution. Organization-local execution requires explicit opt-in via OrganizationIntegrationProvider; an organization acquisition does not evaluate the ordinary local provider.

If an integration exists in another distribution but is unavailable here, first establish which distribution owns it. Do not substitute another account or weaken authorization. On closure, integrations must unsubscribe their MCP host, cancel and join owned workers, and reject callbacks after cleanup.

Voice, mobile and host capabilities

Native conversational voice is distinct from dictation. The host provides capture/playback callbacks and supplies PCM audio only while the live call accepts it. WebRTC is a provider-independent media layer with offer/answer, connection and PCM stream operations; it owns no microphone, credentials, HTTP client or tool execution. Its media format is PCM16, 24 kHz, mono. Do not advertise a calling UI solely because this library is present.

Mobile bridge operations register a runner, list/revoke/wake pairings and exchange relay messages. Pairing and credential ownership are security boundaries, not a substitute for a phone application's setup instructions. Test revocation and disconnected runner behavior in the actual consuming client. Browser/computer/chart callbacks likewise require a capable host and the relevant consent.

Integration acceptance checklist

  • Match header/library revisions and exercise creation/destruction without leaked workers.
  • Test stale callbacks, closed owners, cancellation, unavailable capabilities and queue limits.
  • Test account/organization switching without credential or transcript leakage.
  • Test approval denial, stale repository previews and uncertain remote outcomes.
  • Verify screenshots and controls in the actual application; bridge tests do not certify its UI.

This is an interface guide derived from source. It does not certify every exported callback schema or an external application's behavior. The full header and native tests remain required reading for an embedder.