C+
Systems · View as Markdown
v0.0.27 is a macOS / AppKit release. That is the supported path. Other platforms are not recommended; wait for a later version.

Agent surface

C+ apps can be built to be driven by an agent, not just by a person. An app exposes a controllable surface; an external agent can then describe it (what is here?), act on it (click this, set that text), and observe it (what changed?) — all through a consent gate the app owns. Nothing is reachable that the app did not deliberately expose.

This is the systems counterpart to the rest of C+: where the manual is written so a model can read the code, the agent surface lets a model operate the running app, under explicit authorization.

For a concrete proof recipe, see AppKit agent surface: the checked docs/examples/recipes/appkit_agent recipe from the C+ source tree. It exposes a native AppKit window through agent_appkit and agent_mcp, with stable agent ids, curated describe_ui output, authorized actions, stale text rejection, and consent refusal.

The three pieces

Package Role
agent_core The framework-agnostic authorization brain. Headless and fully tested.
agent_appkit The macOS GUI backend: turns a live AppKit window into a controllable surface.
agent_gtk The GTK backend.
agent_win32 The Win32 backend.
agent_mcp The bridge: exposes the surface to an external agent over JSON-RPC 2.0 / MCP.
agent_inapp The same verbs in-process: no transport, no authorization.

agent_core holds the rules and never touches a UI framework. Each GUI backend binds those rules to a live native tree. agent_mcp carries them over the wire. inspector is a separate capability: it walks the facet tree for a developer, not the permissioned agent surface.

describe → act → observe

  • Describe. open(window) walks the live native tree into a Surface. describe_ui takes an optional params.mode:
    • omitted or "exposed" (the default) — only developer-exposed / keyed nodes, lean fields (id, role, name, actionable), re-parented to the nearest exposed ancestor.
    • "full" — the whole walked tree, including auto-keyed structural nodes. Diagnostic. The surface is a snapshot. Every MCP operation re-walks the window before acting, on the main thread, so describe / click / set_text see the tree as it is now. A view not yet mounted is simply absent. Each exposed node carries a stable agent id, so an agent can refer to the same button across snapshots. agent_inapp::Session::describe_ui(full: bool = false) is the same split.
  • Act. Authorized click / set_text / scroll_to run through the agent_core authorization brain. Text edits use optimistic-concurrency versioning, so a stale edit is rejected rather than clobbering a newer value.
  • Observe. App notifications are translated into verbs and delivered as bubbling events; an agent subscribes by {node, verb, role}.

Consent, not capability

Authorization is all-or-none and app-controlled: an AuthGate consent check guards every request, and an affordance ceiling bounds what an exposed node will ever permit, so exposure can never escalate past what the app intended. agent_mcp speaks JSON-RPC 2.0 (describe_ui / actions / events) over Unix-domain sockets (serve_uds / serve_fd), with that gate in front of every call.

The gate itself stays a pure predicate; deciding what it should say is a separate, swappable policy. The reference agent_consent middleware shows the pattern: decide(rules_dir, mode, agent_id, prompt) resolves an agent in three steps — a remembered per-agent rule (persisted to disk), then a standing mode (allow-all / deny-all), and otherwise it prompts the user and remembers the answer — and maps the result onto a real AuthGate. So "ask the user once, then remember per agent" is a policy you compose, not something baked into the gate.

The appkit_agent recipe in the compiler repo shows the whole flow end to end.