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.contractwill change shape without a deprecation cycle while the layering questions settle (tosijs#29, #30) — howcontract.attributesandinitAttributesdivide 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, usestatic 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.