# @poetry/agent API

The agent JavaScript surface: the WebMCP registrar and form companion, the AG-UI client-tool bridge, the A2UI surface checks, and the stream actions - generated from the source JSDoc and the controllers manifest.

## poetry--agent--webmcp

The registrar: one controller on an opted-in component root
(`webmcp: "country"` on the helper call renders it beside the
component's own controllers) registers that instance's declared tools
with document.modelContext on connect and aborts them on disconnect.
Components gain zero runtime code - each tool dispatches to the
component's OWN controller action (the `executes` descriptor the Ruby
contract validated at class load), passing the tool's parameters
positionally in declared order.

Correctness rules the spec makes load-bearing:
- Re-registration is skipped while the payload is unchanged (the spec
  documents an unregister/quick-re-register race where in-flight args
  for the old tool can hit the new tool's schema).
- Never register under Turbo's cache preview.
- Duplicate names are rejected by the browser; we warn and skip.
- A per-document budget caps registrations (each tool costs the agent
  context; overlap confuses tool choice).
- Errors come back as descriptive result strings (granular exceptions
  are still open spec issues; a string lets the agent self-correct):
  a missing or unknown parameter, a value of the wrong type or outside
  the enum, a missing action, a throwing action.
- A result is the action's return value when it is JSON-serializable
  (the contract's actions return their resulting state, so an answer
  says what happened rather than "done"); the done marker covers
  actions that return nothing.
- Parameters map positionally onto the action in declared order; the
  execute callback's {signal} is not forwarded (the actions are
  synchronous UI operations).

**Values**:
- `name` (String): The instance name the tools register under (poetry.<name>.<tool>).
- `tools` (Array): The tool definitions this instance registers, as the component declared them.
- `budget` (Number, default: 20): The most tools one page registers; past it, further registrations are dropped with a console warning.

**Events**: `poetry:webmcp:executed`, `poetry:webmcp:registered`, `poetry:webmcp:unregistered`

### #connect()

Registers this instance's tools and records it for the executor.

### #disconnect()

Forgets the instance and aborts its registrations.

### #nameValueChanged()

Re-registers under the new name once connected.

### #toolsValueChanged()

Re-registers the new tool list once connected.

### #register()

Registers every declared tool with the browser under the instance name,
within the page budget; skipped when unsupported or in a preview.

### #execute(name, args = {})

Runs one of this instance's tools by its registered or short name; an
unknown name resolves to an error message.

### #unregister()

Aborts every registration of this instance and announces it.

## poetry--agent--webmcp-form

The declarative-form companion: a form declared with poetry_webmcp_form
(toolname/tooldescription on the <form>) is registered by the BROWSER;
this controller only answers an agent-invoked submit with the outcome.
Chrome's SubmitEvent carries agentInvoked + respondWith(promise): we
submit the form ourselves (fetch, same method/action, Turbo-Stream
accepting) and respond with a short descriptive result instead of
navigating, so the agent learns whether the submission succeeded and
what the server said (validation errors included - it can self-correct).

The person's page then catches up with the answer (a beat after the
result is handed to the browser, so a navigation can never swallow
it): a GET answer is the page at that URL - a Turbo visit, or a plain
navigation without Turbo; a Turbo-Stream answer renders; a redirected
POST (redirect-after-create) visits where the redirect went. An HTML
re-render of a failed POST stays put - the agent already holds the
errors, and the person keeps their filled form.

Submits without agentInvoked (a person pressed Submit) pass through
untouched: the human path stays the human path.

**Events**: `poetry:webmcp:form-submitted`

### #connect()

Listens for the form's submit.

### #disconnect()

Stops listening for the form's submit.

## poetry--agent--a2ui-surface

The client side of an A2UI surface's checks: the server renders the
program (every checked component's rules with absolute bindings, the
bound inputs by path with their kinds, and the data model) and this
controller evaluates it as the user types - a button whose own checks
fail is disabled, a failing input is marked invalid and its error slot
carries the message. The five validators and the three combinators are
the checks vocabulary; anything else passes here and is judged by the
server, which re-runs every rule on the action.

**Values**:
- `program` (Object): The check program the server compiled: the checks, the bound inputs by path, and the data model.

### #connect()

Evaluates every check once at mount.

### #evaluate()

Runs every check in the program and applies its failures.

### #failure(rule)

A rule's failure message, or null when it passes or cannot be decided
here.

### #apply(key, kind, failures)

Reflects a check's failures on the elements bound to a key: a button
disables, an input turns invalid.

### #resolve(value)

A value with its bindings read and its calls run, recursively.

### #call(name, rawArgs)

Runs a catalog function by name with its arguments resolved; an unknown
name resolves to null.

### #read(path)

The current value of a bound path from the form's inputs, falling back to
the data model.

## poetry--agent--agui-client-tool

The AG-UI client-tool bridge: the relay appends one of these (hidden)
per tool call the agent made to a FRONTEND tool - a component tool the
page declared - and this controller executes it through the registrar
(the same dispatch a WebMCP call takes, so it works in every browser,
modelContext or not), then POSTs the result to the continue URL. The
server folds the tool message into the transcript and answers with the
next run's streams, which Turbo renders. One element, one execution:
the done flag makes a Turbo re-render inert.

**Values**:
- `call` (Object): The pending tool call: its id, name and arguments.
- `url` (String): Where the tool result is posted.
- `done` (Boolean): Whether the call has already been answered (a restored snapshot must not answer twice).

**Events**: `poetry:agui:client-tool-executed`

### #connect()

Runs the pending client tool once, posts its result, and marks the call
done.

## @poetry/agent

@poetry/agent - the WebMCP runtime. Register beside poetry's own
controllers:

  import { Application } from "@hotwired/stimulus"
  import { registerPoetryControllers } from "@poetry/controllers"
  import { registerPoetryAgent } from "@poetry/agent"
  const application = Application.start()
  registerPoetryControllers(application)
  registerPoetryAgent(application)

## @poetry/agent/adapter

The document.modelContext adapter - the ONE file that knows the WebMCP
surface's shape, so API churn (the spec has changed twice this summer,
and the shipping browser trails it) stays a one-file fix. Everything
else in the runtime talks to this.

Spec surface (rev 41d12f0): document.modelContext with
registerTool(tool, {signal, exposedTo}), getTools({fromOrigins}) resolving
RegisteredTool dictionaries whose inputSchema is an object,
executeTool(tool, inputObject, {signal}) resolving the stringified
result, and the toolchange event.

Shipping-browser deltas this file absorbs (Chrome 151, measured
2026-09-01; spec issue #278 tracks them): getTools() reports inputSchema
as a serialized JSON string, and executeTool() parses ONLY a JSON-string
argument - an object rejects with UnknownError("Failed to parse input
arguments"), not a TypeError.

## @poetry/agent/stream_actions

Local state a morph must not reset inside an A2UI surface: the
selection a tab strip holds, a dialog's open state, a popup's expanded
state, and a control the user has edited (its value or checked state
differs from what the server last rendered). The server does not know
this state, so its frame would carry the defaults; canceling Turbo's
before-morph-attribute event keeps the page's own.