agent (EXPERIMENTAL)

enableAgentInterface() turns a tosijs app's existing records — the state registry, the binding wiring, the event handlers — into a described, path-addressed surface for non-human users: AI agents, test harnesses, automation. Nothing is recorded that tosijs doesn't already know; describe() assembles the picture on demand.

import { enableAgentInterface } from 'tosijs'

const agent = enableAgentInterface() // READ-ONLY introspection (the default)

agent.describe()          // roots, wiring (elements ↔ paths ↔ handlers), actions
agent.read('app.filter')  // serializable value
agent.observe('app.cart', (path) => { ... }) // push; returns un-observe
agent.changes(cursor)     // turn-based drain: final value per changed path
await agent.when('app.order.status', (s) => s === 'confirmed') // await a condition
agent.log()               // the audit trail

The verbs that CHANGE things need consent, so they are not in that list — write() and call() refuse on the default surface and say how to enable them. Declare a manifest (below), or expose: 'all' while developing:

const dev = enableAgentInterface({ expose: 'all' })
dev.write('app.filter', 'milk') // through the same observers as any write
dev.call('app.addItem', 'buy milk')          // invoke an action by path

In production, expose only what you declare:

enableAgentInterface({
  expose: {
    roots: ['app.cart', 'app.filter'],
    actions: ['app.addItem', 'app.checkout'],
    write: true, // omit for scoped reads with no writes
  },
})

A manifest scopes sight, not reach: roots says what may be seen, write: true is a separate grant to change it, and declared actions stay callable either way. describe().writable reports which you have.

One call is the whole story: where the browser provides a WebMCP host (document.modelContext), enableAgentInterface() also registers the generated tool set automatically — agent.webmcp is the receipt, and webmcp: false opts out. No host, no-op.

🚧 CONTRACTS ARE IN FLUX. ComponentMap / static contract / expose.contract will change shape without a deprecation cycle while the layering questions settle (tosijs#29, #30) — how contract.attributes and initAttributes divide the work, and whether an integrator's overlay may embellish a component's own declaration rather than replace it. Changes land in patch and minor releases and are called out in the CHANGELOG. For stable attribute declaration today, use static initAttributes; since 1.8.1 it is described to agents identically.

EXPERIMENTAL. Shapes and names may change. The surface is deliberately protocol-neutral — MCP / WebMCP adapters sit on top of it; the WebMCP auto-registration is a convenience over that adapter, not a dependency.