Agent Client Protocol

Agent Client Protocol

Foundry’s Agent Client Protocol support lets an application expose a project to agent subprocesses while keeping the policy, file access, terminal access, and user interface in the application. Agents are discovered through plugins and presented as FoundryAcpAgent objects from the FoundryAcpManager service.

The ACP API is part of libfoundry and is available when the library is built with feature-acp.

#include <foundry.h>

#ifdef FOUNDRY_FEATURE_ACP
  /* ACP-specific code */
#endif

Application Integration

Applications start from their FoundryContext. The context owns a FoundryAcpManager, and the manager tracks loaded FoundryAcpProvider plugins and their available agents.

g_autoptr(FoundryAcpManager) acp_manager = NULL;
g_autoptr(GListModel) agents = NULL;

acp_manager = foundry_context_dup_acp_manager (context);
agents = foundry_acp_manager_list_agents (acp_manager);

agents is a GListModel of FoundryAcpAgent instances. A GTK application can bind that model directly to a drop-down or list view; a headless application can call foundry_acp_manager_find_agent() with a stable agent identifier.

Opening a session requires an application-provided FoundryAcpClient. The client is the boundary where the agent calls back into the application for updates, permission prompts, file operations, and terminal operations.

g_autoptr(FoundryAcpAgent) agent = NULL;
g_autoptr(FoundryAcpSession) session = NULL;
g_autoptr(GError) error = NULL;

agent = dex_await_object (foundry_acp_manager_find_agent (acp_manager, "codex"), &error);

if (agent != NULL)
  session = dex_await_object (foundry_acp_agent_open_session (agent,
                                                             FOUNDRY_ACP_CLIENT (client),
                                                             pipeline),
                              &error);

The FoundryBuildPipeline gives the agent plugin the same build and runtime environment that the application would use for project commands. For example, the bundled Codex ACP plugin prepares the pipeline for execution and starts codex-acp in the project directory.

Once a session is open, send prompts as FoundryAcpContentBlock arrays. The returned future resolves to FoundryAcpPromptResult.

g_autoptr(FoundryAcpContentBlock) text = NULL;
g_autoptr(FoundryAcpPromptResult) result = NULL;
FoundryAcpContentBlock *content[2];

text = foundry_acp_content_block_new_text ("Summarize the pending changes");
content[0] = text;
content[1] = NULL;

result = dex_await_boxed (foundry_acp_session_prompt (session, content, 1), &error);

Applications should keep the session object around while the conversation is visible. The session exposes list models for event history, active terminals, and changed files:

These models are updated as ACP notifications are received and as the application refreshes changed-file state.

Implementing an AcpClient

FoundryAcpClient is implemented by the application or by an adapter owned by the application. The interface methods all return DexFuture so UI prompts, file operations, and terminal operations can finish asynchronously.

At minimum, most interactive applications implement:

  • session_update to append agent messages, tool calls, progress, file changes, and errors to the conversation UI
  • request_permission to show a permission dialog and resolve to FoundryAcpPermissionResponse
  • read_text_file and write_text_file if the agent may read or edit project files
  • the terminal methods if the agent may run commands
  • refresh_changed_files so the session’s changed-file model matches the VCS state after prompts or terminal commands

For common project-scoped file and terminal behavior, delegate to FoundryAcpProjectClient. It provides a reusable implementation backed by a FoundryContext or project directory, including a headless FoundryAcpPermissionPolicy.

static DexFuture *
my_acp_client_read_text_file (FoundryAcpClient  *client,
                              FoundryAcpSession *session,
                              const char        *path,
                              guint              line,
                              guint              limit)
{
  MyAcpClient *self = (MyAcpClient *)client;

  return foundry_acp_client_read_text_file (FOUNDRY_ACP_CLIENT (self->project_client),
                                            session,
                                            path,
                                            line,
                                            limit);
}

This split is useful because the application can keep control of UI-facing methods such as session_update and request_permission, while using Foundry’s default project client for low-level file and terminal work.

Permission Model

ACP agents must not assume unrestricted access to the project. Permission requests arrive through foundry_acp_client_request_permission() with a FoundryAcpPermissionRequest. The application chooses an option and returns FoundryAcpPermissionResponse.

Interactive applications normally present the request title, description, and options to the user. Headless applications can use FoundryAcpPermissionPolicy directly or through FoundryAcpProjectClient. The project client confines file operations to the project directory and records changed files on the session.

Session Updates

Agents stream progress through foundry_acp_client_session_update(). Updates are represented by FoundryAcpSessionUpdate and include message chunks, tool calls, terminal events, file reads and writes, patches, progress, and errors.

Applications should treat session updates as an append-only event stream for the conversation, while using the session models for stateful views such as active terminals and changed files. Message chunks may arrive incrementally, so UIs should merge adjacent chunks into the currently displayed agent response when appropriate.

Adding an ACP Agent Plugin

ACP agents are provided by Foundry plugins. A plugin usually contains:

  • a FoundryAcpProvider subclass that decides when agents are available
  • one or more FoundryAcpAgent subclasses
  • optional build, runtime, or subprocess helpers used by the agent
  • a .plugin metadata file with X-Category=acp
  • a meson.build entry gated by feature-acp

Register the provider from the plugin entry point:

#include <foundry.h>

#include "plugin-example-acp-provider.h"

FOUNDRY_PLUGIN_DEFINE (_plugin_example_acp_register_types,
                       FOUNDRY_PLUGIN_REGISTER_TYPE (FOUNDRY_TYPE_ACP_PROVIDER,
                                                    PLUGIN_TYPE_EXAMPLE_ACP_PROVIDER))

The provider loads inside a FoundryContext. It can inspect the project or build pipeline, create an agent, and call foundry_acp_provider_agent_added() when that agent should appear in the manager.

static DexFuture *
plugin_example_acp_provider_load (FoundryAcpProvider *provider)
{
  PluginExampleAcpProvider *self = (PluginExampleAcpProvider *)provider;

  if (self->agent == NULL)
    self->agent = g_object_new (PLUGIN_TYPE_EXAMPLE_ACP_AGENT, NULL);

  foundry_acp_provider_agent_added (provider, self->agent);

  return dex_future_new_true ();
}

On unload, remove any agents previously added and clear resources:

static DexFuture *
plugin_example_acp_provider_unload (FoundryAcpProvider *provider)
{
  PluginExampleAcpProvider *self = (PluginExampleAcpProvider *)provider;

  if (self->agent != NULL)
    foundry_acp_provider_agent_removed (provider, self->agent);

  return dex_future_new_true ();
}

The agent subclass supplies an identifier, display name, capabilities, and the session-opening implementation. open_session normally starts or connects to the protocol peer, creates a FoundryAcpConnection, initializes it, and returns a FoundryAcpSession.

static char *
plugin_example_acp_agent_dup_id (FoundryAcpAgent *agent)
{
  return g_strdup ("example");
}

static char *
plugin_example_acp_agent_dup_name (FoundryAcpAgent *agent)
{
  return g_strdup ("Example Agent");
}

static DexFuture *
plugin_example_acp_agent_open_session (FoundryAcpAgent      *agent,
                                       FoundryAcpClient     *client,
                                       FoundryBuildPipeline *pipeline)
{
  /* Start the ACP peer, create a FoundryAcpConnection, initialize it,
   * and return a FoundryAcpSession.
   */
  return dex_future_new_reject (FOUNDRY_ACP_ERROR,
                                FOUNDRY_ACP_ERROR_UNSUPPORTED,
                                "Not implemented");
}

Plugin metadata should identify the plugin as ACP-related:

[Plugin]
Name=Example ACP
Description=Provides Example Agent through the Agent Client Protocol
Embedded=_plugin_example_acp_register_types
Module=example-acp
X-Category=acp

Add the plugin to plugins/meson.build with the same option gate used by the bundled ACP provider:

'example-acp': {'options': ['feature-acp']},

The bundled plugins/codex-acp/ provider is a complete example. It exposes a Codex agent only when codex is available in the prepared build pipeline, wraps the application client with a project client for file and terminal operations, starts codex-acp, and resets the ACP connection if the subprocess exits unexpectedly.

Testing

ACP behavior is covered by GLib tests under testsuite/:

  • test-acp-agent
  • test-acp-permission-policy
  • test-acp-project-client
  • test-acp-session-update

There is also a GTK test tool in testsuite/tools/test-acp-gtk.c that shows a minimal interactive client using FoundryAcpManager, a custom FoundryAcpClient, and the session list models.