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_uireturns the curated, exposed tree by default. The opt-infullmode 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
VersionConflictinstead of replacing newer text. - Observe. Semantic events use curated verbs and optional node, verb, and
role filters. Subscriber queues are bounded;
UiChangedcoalesces 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.