C+
Systems · View as Markdown

Agent surface

C+ applications can expose a live native interface to an agent through the same semantic surface on every supported UI backend. The agent can describe the interface, act on exposed controls, and observe semantic events. The application chooses which nodes exist in that surface and supplies the grant that bounds each caller.

This is an optional application capability. A Facet application that never imports facet_agent links none of the agent stack.

Stack

Package Role
agent_core Stable node identity, exposure, roles, grants, sensitivity tiers, outcomes, events, and the backend vtable.
agent_appkit Native AppKit surface for macOS.
agent_uikit Native UIKit surface for iOS.
agent_android Android surface over the live Facet node tree.
agent_gtk Native GTK 4 surface for Linux.
agent_win32 Native Win32 surface for Windows.
agent_mcp External JSON-RPC 2.0 / MCP transport over the platform-neutral backend.
agent_inapp Typed, transport-free access for an assistant embedded in the application.
facet_agent Optional Facet integration that selects and installs the platform surface.

agent_core owns policy and protocol-neutral behavior. A platform backend walks its live native or Facet tree and implements the backend vtable. agent_mcp serializes that vtable for an external caller; agent_inapp calls the same vtable directly.

inspector is separate. It is a developer tool for inspecting and editing the Facet tree; ordinary agent actions drive the interface a person can use and do not imply structural editing power.

Describe, act, observe

  • Describe. describe_ui returns the curated, exposed tree by default. The opt-in full mode includes structural nodes, class names, and frames for diagnostics, but does not bypass exposure or value authorization. Backends re-walk the live interface before operations, so newly mounted controls can appear in the next snapshot. Developer keys and list-item keys provide stable identities across rebuilds.
  • Act. Backend verbs include click, text entry, navigation or scrolling, hit testing, caret movement, and platform-supported menu actions. Every verb is checked against exposure, the widget's affordance ceiling, and the caller's grant before native I/O occurs. Text writes carry a base version; a stale write returns VersionConflict instead of replacing newer text.
  • Observe. Semantic events use curated verbs and optional node, verb, and role filters. Subscriber queues are bounded; UiChanged coalesces and an overflow drops the oldest event rather than blocking the emitter.

Capability grants

Authorization is a capability set, not one allow/deny bit. An external policy has the shape fn(Request) -> Grant; an in-process session carries a Grant directly. The seven capabilities are:

Capability Permits
cap_read See structure and read ordinary values.
cap_act Click, type, or invoke on ordinary nodes.
cap_read_protected Read a value declared Protected, in addition to cap_read.
cap_act_protected Act on a Protected node, in addition to cap_act.
cap_read_private Read a value declared Private, in addition to cap_read.
cap_act_private Act on a Private node, in addition to cap_act.
cap_edit_tree Change UI structure; this is separate from operating the existing UI.

The convenience grants are nothing(), reader(), operator(), protected_reader(), and protected_operator(). None includes either Private bit or cap_edit_tree; those must be added deliberately.

A Request identifies the InApp or External channel and carries the MCP client name, method, and an opaque token. The client name is useful for a legible consent prompt but is not a credential. Token verification and grant construction belong to application policy; agent_jwt provides the JWT-shaped middleware layer.

Sensitivity and exposure

Sensitivity is declared on content and inherited by descendants, with the strictest tier winning:

Declaration Tree visibility Value or action result without the required grant
Open Visible Ordinary read / act rules apply.
Protected Visible and named NeedsGrant: approval may mint a wider grant, then retry.
Private Visible and named Forbidden: the application deliberately does not mint the needed bit.
Excluded Absent NotExposed.

Tiers keep a node's existence and developer-authored name visible while withholding its value. Exclusion removes a subtree from the agent view. These checks are separate from whether a control is enabled, observe-only, wired to a handler, or otherwise actionable.

Facet integration

Enable the optional integration before App::run, and register the MCP endpoint before startup:

import "facet_agent/agent" as agent;
import "facet_runtime/runtime" as runtime;

fn run() -> i32 {
    agent::enable();
    runtime::agent_mcp("myapp");

    let app = runtime::App::new("myapp");
    app.window("main", Home::boxed);
    let _run = app.run("main");
    return 0;
}

facet_agent/consent supplies the common non-blocking policy: an unknown MCP client's first request is refused while the application presents a prompt; the client retries after the user decides. Applications can instead install any fn(Request) -> Grant policy with facet_agent::set_policy.

The runtime derives the listening address from the application id and process id. macOS and Linux expose a user-only Unix socket plus loopback HTTP; Windows, iOS, and Android use loopback HTTP. The application prints the selected endpoint and records its discovery information for launchers.