.config
The gem's configuration (origin-trial tokens, registration budget).
The agent-interop gem: the MCP server (stdio exe and the Rack HTTP transport), its bundled assembly, the WebMCP runtime's Ruby side, and the origin-trial middleware.
Generated from the gem's source documentation. Component classes are documented on their own gallery pages — this page carries the framework surface.
module · lib/poetry/agent.rb
poetry-agent: the agent-interop gem of the poetry family - the surfaces through which agents reach the component contract, built once (the registry) and projected many ways:
poetry-agent MCP server (discover:list_components / describe_component / check / compose / build_page / get_skill) for coding agents in editors.
(tool declarations in poetry-core) registered with the browser's document.modelContext for the user's own agent (operate), plus the declarative-form path and the origin-trial delivery.
an agent generates declarative UI against).
folded into chat frames and relayed as versioned Turbo Streams, with the component tools the browser executes advertised as the agent's frontend tools.
Both read the same committed registries; neither is a second source.
| Constant | Description |
|---|---|
| VERSION | The gem version (lockstep with the poetry family). |
The gem's configuration (origin-trial tokens, registration budget).
Yields the configuration for block-style setup.
Poetry::Agent.configure do |config|
config.origin_trial_tokens = [ENV["WEBMCP_ORIGIN_TRIAL_TOKEN"]].compact
end
Gem root (the directory containing lib/, exe/, app/).
class · lib/poetry/agent/mcp/server.rb
The server: constructed with the registry root (and the host's helper names, so check knows the group/provider helpers - though the registry's own "helpers" section now carries those boot-free). icon_names: the active icon set's names, so the check tool validates icon values by membership, not just shape. Everything read is the live committed registry.
Poetry::Agent::MCP::Server.from_registry("registry").serve
| Constant | Description |
|---|---|
| ARCHETYPE_MATCH | The brief-word -> page-architecture routing table. |
| STEPS | build_page: the guided workflow's five steps, the themes its probe/direct steps sniff for, and the plan step's match floor (one curated archetype keyword = 2, so 2 is the weakest real hit). |
| STOPWORDS | Tokens compose ignores when scoring a brief. |
| STRONG_MATCH | compose routing: the strong-match threshold, and the words too generic to signal a block - connectives, task verbs, and component-anatomy words (title/description/action) every brief uses regardless of scale. |
| THEMES | The theme roster build_page's direct step reads. |
skills: skill name => a zero-arg callable returning the skill's {relative path => content} file map (the get_skill tool). Lazy because the usage skill is generated from the registry on first fetch - server boot stays instant.
A JSON-RPC 2.0 request hash -> a response hash (or nil for a notification, which gets no reply).
request
(Hash)
— one parsed JSON-RPC 2.0 request
Returns (Hash, nil) — the response hash, or nil for a notification
app_root: the HOST app directory (where bundle exec poetry-agent runs, i.e. Dir.pwd), so build_page's probe/direct steps can read the app's config/theme. nil => the host is not inspected and those steps degrade gracefully; the registry read (root) is unaffected. recipes: registry-item SUMMARIES (content-free) from the owning gem's RecipeItems projection - the exe passes them so this class stays poetry-ui-free.
Returns (Server) — a new instance of Server
The thin stdio loop: newline-delimited JSON-RPC in, replies out. A malformed line yields a parse error, never a crashed server.
class · lib/poetry/agent/mcp/http.rb
The MCP server over HTTP: a Rack app answering JSON-RPC 2.0 POSTs (the Streamable HTTP transport's request/response half) with the same pure {Server#handle} the stdio exe uses - one surface, two transports. Mount it at the conventional same-origin /mcp, which is where in-page bridges (WebMCP site MCP server packs among them) look for a site's own MCP server.
Read-only by construction (every tool describes or verifies), so exposing it is a discovery surface, not a mutation surface. The Origin header is validated against the request's own host, so a cross-site page cannot drive it through a visitor's browser.
mount Poetry::Agent::MCP::HTTP.new => "/mcp"
| Constant | Description |
|---|---|
| JSON_TYPE | The response media type. |
The Rack entry point.
env
(Hash)
— the Rack environment
Returns (Array(Integer, Hash, Array<String>)) — status, headers, body
server
(Server, Proc, nil)
— the server, a lambda building it
on first request, or nil for the bundled assembly
Returns (HTTP) — a new instance of HTTP
module · lib/poetry/agent/mcp/bundled.rb
The server as the exe assembles it: the registry root defaults to the bundled poetry-ui gem, poetry-lucide's icon names power check's icon-membership tier, and poetry-ui's skills, helper names, and recipes ride along when the gem is bundled - each a soft require, so the same assembly serves a core-only host. ONE assembly for the exe and the HTTP mount, so no surface can lag another.
The lucide names, or nil for a host without poetry-lucide (check then validates icon-name shape only).
root
(String, nil)
— a registry root; defaults to poetry-ui
app_root
(String)
— the host app (build_page's probe/direct
steps read its config/theme)
module · lib/poetry/agent/webmcp.rb
The WebMCP runtime's Ruby side. The contract itself lives in poetry-core (the tool declarations, their registry projection, and the per-instance webmcp: payload a component root carries); this module owns what only the runtime gem knows: the Stimulus controller identifiers the JS registers, the controllers manifest that lets core's attribute builder validate them, and origin-trial delivery.
Nothing here exposes a tool by itself - a rendered instance opts in (webmcp: "country" on the helper call), and the registrar controller registers that instance's declared tools with document.modelContext on connect, aborting them on disconnect.
| Constant | Description |
|---|---|
| CONTROLLER | The registrar controller: reads a root's payload and registers its tools, dispatching each to the component's own controller. |
| FORM_CONTROLLER | The declarative-form controller: answers an agent-invoked submit
with the form's outcome through SubmitEvent.respondWith. |
The committed controllers manifest (the JS surface of the two controllers), merged into poetry-core's catalog by the engine so use_stimulus / the Builder validate poetry--agent--* names exactly like core's own.
class · lib/poetry/agent/webmcp/origin_trial.rb
Rack middleware serving the Origin-Trial response header on HTML responses, one token per browser trial (Chrome and Edge run separate trials and issue separate tokens). Tokens come from {Poetry::Agent::Config#origin_trial_tokens}; with none configured the middleware is a pass-through, so it is always safe to mount.
Local development needs no token: enable the API through the browser flag instead.
config.middleware.use Poetry::Agent::WebMCP::OriginTrial
| Constant | Description |
|---|---|
| HEADER | The response header the browsers read (Rack 3 lowercases names). |
The Rack entry point: appends the tokens to HTML responses.
env
(Hash)
— the Rack environment
Returns (Array(Integer, Hash, Object)) — the downstream response
Returns (OriginTrial) — a new instance of OriginTrial
class · lib/poetry/agent/config.rb
The gem's configuration. Every WebMCP surface is OFF until a rendered instance opts in; the settings here shape delivery and budgets, never exposure.
The process-wide configuration instance.
Returns (Config) — a new instance of Config
Origin-trial tokens the {OriginTrial} middleware serves in the Origin-Trial response header (one per browser trial - Chrome and Edge run separate trials). Empty by default: local development enables the API through the browser flag instead.
Origin-trial tokens the {OriginTrial} middleware serves in the Origin-Trial response header (one per browser trial - Chrome and Edge run separate trials). Empty by default: local development enables the API through the browser flag instead.
The per-page registration budget the registrar enforces: past this many registered tools on one document, further registrations are dropped with a console warning (each tool costs the agent context; overlap confuses tool choice). Stored on poetry-core's configuration, where the component contract reads it to put the budget on every opted-in root - this accessor writes through.
Sets the per-page registration budget.
count
(Integer)
—
module · lib/poetry/agent.rb
The poetry namespace.
module · lib/poetry/agent/a2ui.rb
The A2UI surface: Google's declarative generative-UI format, where an agent emits a flat component list against a client-owned catalog and the client renders it with its own components. Two halves ship:
document, so any A2UI agent generates against Poetry's vocabulary and a renderer validates what arrives against the same document.
createSurface,updateComponents, updateDataModel, deleteSurface) into {Surface}s, {Renderer} renders a surface through the host's view context with a catalog binding ({Catalogs::Basic} for the spec's basic catalog, {Catalogs::Native} for Poetry's own), {Streams} delivers changes as versioned Turbo Streams, and a submitted surface form becomes the spec's action message ({Session#action}). {Functions} holds the basic catalog's function set - the formatString grammar ({Expression}), the formatters, the validators behind checks ({Checks}) - which {Evaluator} runs for both catalogs.
| Constant | Description |
|---|---|
| COMMON_TYPES | The shared type definitions a catalog may reference. |
| PROTOCOL_VERSION | The protocol version the projection targets. |
The catalog bindings a {Session} starts with: the spec's basic catalog and Poetry's own, keyed by catalog id.
class · lib/poetry/agent/a2ui/catalog.rb
Projects the component registry into an A2UI v1.0 catalog: one JSON Schema component per registry entry (the discriminator component: { const: Name }, style axes as enums, options as typed properties, slots as child references, the content block as text or children, Button's action), the catalog's composition instructions, and the $defs the envelope schema references. The document obeys the v1.0 catalog rules: the allowed top-level keys only, $defs holding exactly anyComponent and anyFunction, external refs into common_types.json only.
catalog = Poetry::Agent::A2UI::Catalog.from_registry(Poetry::Ui.root)
catalog.to_h["components"].keys.first(3) # => ["Accordion", "ActionBar", "Alert"]
catalog.inline # => { "catalogId" => ..., "components" => {...} }
| Constant | Description |
|---|---|
| DEFAULT_ID | The default catalog id (a versioned, conventionally URI-shaped string identifier - it does not need to resolve). |
| DEFAULT_INSTRUCTIONS | The catalog-level guidance every generation reads. |
| DYNAMIC_BOOLEANS | Option names whose boolean values bind to the data model. |
| DYNAMIC_STRINGS | Option names whose values bind to the data model ({path}). |
| NAME_PATTERN | Component and property names must be UAX #31 identifiers. |
| SKIPPED_OPTIONS | Options an agent never sets: styling escape hatches, the envelope's
own id, and the universal wiring keywords. |
The catalog component name of a registry path (PascalCase of its last segment).
path
(String)
—
Builds the catalog from a registry root (the directory holding config/component_registry.yml), or from the bundled poetry-ui registry when no root is given.
Keyword arguments pass through to {#initialize} (catalog_id:, title:, description:, instructions:, exclude:).
root
(String, Pathname, nil)
—
Loads the registry entries under a root (the bundled poetry-ui registry when no root is given).
root
(String, Pathname, nil)
—
Returns (Hash{String => Hash}) — entries by registry path
name
(String)
— an option name
Returns (Boolean) — whether an agent never sets it
The A2UI component name of a registry path (poetry/ui/alert_dialog becomes AlertDialog).
path
(String)
—
The component schemas by name.
The functions the catalog declares (the basic catalog's set, which the renderer implements).
entries
(Hash{String => Hash})
— registry entries by path
catalog_id
(String)
—
title
(String)
—
description
(String)
—
instructions
(String)
— catalog-level guidance for the model
exclude
(Array<String>)
— registry paths to leave out
Returns (Catalog) — a new instance of Catalog
The inline form a transport ships to an agent or a middleware fetches at boot.
Returns (Hash)
— { "catalogId", "components" }
The catalog document (JSON Schema, string keys, components sorted by name).
Returns (String) — the document as JSON
module · lib/poetry/agent/a2ui/catalogs/basic.rb
The catalog bindings a {Surface} renders through: how a catalog's components reference their children, which of them are bound inputs, and how each one renders with Poetry.
class · lib/poetry/agent/a2ui/catalogs/basic.rb
The A2UI basic catalog (v1.0) rendered with Poetry: every one of its components maps onto a library component or a plain element, its enums onto the library's axes, its bound inputs onto named form controls, and its checks onto native constraint attributes where the browser can enforce them.
| Constant | Description |
|---|---|
| ALIGN | Cross-axis alignment classes by the catalog's align values. |
| BUTTON_VARIANTS | Poetry's Button variant by the catalog's Button variant values. |
| FIT | Object-fit classes by the Image fit values. |
| ICON_ALIASES | Lucide names for the Material-style icon names agents tend to emit; anything else converts camelCase to kebab-case as is. |
| ID | The catalog id agents name in createSurface. |
| IMAGE_VARIANTS | Sizing classes by the Image variant values. |
| INPUT_KINDS | The bound kinds of the input components. |
| JUSTIFY | Main-axis alignment classes by the catalog's justify values. |
Returns (Functions) — the basic catalog's function set
The bound input of a component, if it is one.
component
(Hash)
—
scope
(String, nil)
—
Returns (Array<Hash>)
— { path:, kind: }
The child references of a component (ids, id lists, templates).
component
(Hash)
—
component
(Hash)
—
scope
(String, nil)
—
renderer
(Renderer)
—
Returns (String) — HTML
class < Struct · lib/poetry/agent/a2ui/catalogs/basic.rb
A ChoicePicker's resolved options, binding, selection, and label.
Returns the value of attribute label
Returns (Object) — the current value of label
Sets the attribute label
value
(Object)
— the value to set the attribute label to.
Returns (Object) — the newly set value
Returns the value of attribute options
Returns (Object) — the current value of options
Sets the attribute options
value
(Object)
— the value to set the attribute options to.
Returns (Object) — the newly set value
Returns the value of attribute path
Returns (Object) — the current value of path
Sets the attribute path
value
(Object)
— the value to set the attribute path to.
Returns (Object) — the newly set value
Returns the value of attribute selected
Returns (Object) — the current value of selected
Sets the attribute selected
value
(Object)
— the value to set the attribute selected to.
Returns (Object) — the newly set value
class · lib/poetry/agent/a2ui/catalogs/native.rb
Poetry's own catalog (the {Catalog} projection) rendered back through the registry: a component name resolves to its registry entry, style axes and options become constructor keywords, slot properties drive the generated slot setters, text is the content block, children and child nest, and a bound value names the control for the surface form. The registry is the one source: whatever it declares renders, nothing is hand-mapped.
| Constant | Description |
|---|---|
| BOUND_OPTIONS | The bound options an input component may carry. |
| KINDS | Registry option types by bound kind. |
Returns (Hash{String => Array(String, Hash)})
— [path, entry] by component name
Returns (Hash{String => Hash}) — registry entries by path
Returns (Functions) — the function set Poetry's catalog declares (the basic one)
entries
(Hash{String => Hash}, nil)
— registry entries by path
(the poetry-ui registry when nil)
id
(String)
— the catalog id this binding answers to
Returns (Native) — a new instance of Native
component
(Hash)
—
scope
(String, nil)
—
Returns (Array<Hash>)
— { path:, kind: } for each bound option
The child references: child, children, and every slot property.
component
(Hash)
—
component
(Hash)
—
scope
(String, nil)
—
renderer
(Renderer)
—
Returns (String) — HTML
module · lib/poetry/agent/a2ui/checks.rb
Evaluates a component's checks: each rule's condition (a binding or a function call) yields a ValidationResult - { "valid", "code", "message", "severity" } - or a boolean; a failing rule of error severity is a failure, carrying the result's message or the rule's fallback.
| Constant | Description |
|---|---|
| DEFAULT_MESSAGE | The message when neither the result nor the rule carries one. |
component
(Hash)
—
evaluator
(Evaluator)
—
Returns (Array<Hash>)
— failures as { code:, message:, severity: }
class · lib/poetry/agent/a2ui/evaluator.rb
Resolves dynamic values in a scope: a { "path" } binding reads the data model, a { "call", "args" } invokes a registered function with its arguments resolved first (a string argument with ${} blocks interpolates, an array resolves item by item), and @index reads the collection index the scope carries. Problems (an unknown function, a bad argument, a malformed expression) resolve to nil and reach the on_error callback.
Evaluator.new(surface, "/items/2").resolve({ "call" => "@index", "args" => { "offset" => 1 } }) # => 3
Resolves a function argument: like {#resolve}, plus strings interpolate and arrays and plain objects resolve inside.
value
(Object)
—
Calls a function by name.
name
(String)
—
args
(Hash, nil)
— raw arguments (resolved here unless resolved:)
resolved
(Boolean)
— whether the arguments are already resolved
Evaluates a parsed expression node.
node
(Array)
—
Returns (Integer, nil) — the collection index the scope carries
surface
(Surface)
—
scope
(String, nil)
—
on_error
(#call, nil)
— receives each problem's message
Returns (Evaluator) — a new instance of Evaluator
Interpolates a formatString template.
text
(String)
—
Resolves a component property: bindings and calls resolve, everything else is a literal.
value
(Object)
—
Returns (String, nil) — the collection-item pointer in effect
The string a value displays as (the spec's conversion rules: nil is empty, containers are JSON, whole floats drop their fraction).
value
(Object)
—
module · lib/poetry/agent/a2ui/expression.rb
The formatString grammar: literal text with ${...} blocks, each block a data path, a literal, or a function call with named arguments whose values are expressions again (a bare argument is value); \${ is a literal ${. Parsing yields a plain tree the {Evaluator} walks:
[:template, nodes] the whole string [:text, "literal text"] [:path, "/absolute"] or a relative path [:literal, 12] / [:literal, "quoted"] / [:literal, true] [:call, "formatDate", { "value" => node, "format" => node }]
Expression.parse("Hi ${/user/name}, ${formatNumber(value: ${/n}, decimals: 1)}")
| Constant | Description |
|---|---|
| IDENT | Identifier: function names, argument names, relative path heads. |
| KEYWORDS | Keyword literals. |
| MAX_DEPTH | Nesting depth beyond which an expression is refused. |
| NUMBER | A number literal not followed by a path or identifier character. |
text
(String)
—
Returns (Boolean) — whether the text carries an interpolation block
text
(String)
—
Returns (Array)
— the [:template, nodes] tree
class · lib/poetry/agent/a2ui/expression.rb
The recursive-descent parser.
source
(String)
—
Returns (Parser) — a new instance of Parser
Returns (Array)
— the [:template, nodes] tree
class < StandardError · lib/poetry/agent/a2ui/expression.rb
A malformed expression.
class · lib/poetry/agent/a2ui/functions.rb
The renderer's function registry: named functions an agent may reference in a component's dynamic values and checks, each with the declaration a catalog document publishes (functions and $defs.anyFunction). {Functions.basic} holds the spec's basic catalog set - the validators, the formatters, the boolean combinators, openUrl - implemented from their descriptions; @index is the evaluator's own system function.
Functions.basic.call("pluralize", { "value" => 2, "one" => "item", "other" => "items" }, evaluator)
# => "items"
| Constant | Description |
|---|---|
| CALLERS | The spec's execution boundaries: who may invoke a function. |
| CURRENCIES | Currency symbols (and fraction digits) by ISO 4217 code; other codes render as a code prefix. |
| DATE_FIELDS | Unicode TR35 date-pattern fields to strftime. |
| The email shape the basic catalog names. |
The basic catalog's functions.
value
(Object)
—
Returns (Numeric, nil) — the value as a number, when it is one
The boolean reading of a value: a ValidationResult by its valid, strings by content, nil and false as false.
value
(Object)
—
name
(String)
—
Returns (Boolean)
— whether an agent may invoke the function through callRendererFunction
The catalog document's $defs.anyFunction.
Calls a function with resolved arguments.
name
(String)
—
args
(Hash{String => Object})
—
evaluator
(Evaluator)
—
name
(String)
—
Declares a function.
name
(String)
—
description
(String)
—
returns
(String)
— the spec's returnType
params
(Hash{String => Hash})
— argument schemas by name
required
(Array<String>)
— required argument names
activation
(Boolean)
— whether the call needs a user activation
callers
(String)
— the execution boundary (rendererOnly - the default - keeps the
function out of an agent's callRendererFunction; agentOnly / rendererOrAgent admit it)
Returns (Functions) — self
Returns (Functions) — a new instance of Functions
Returns (Array<String>) — the declared names
The catalog document's functions section.
module · lib/poetry/agent/a2ui/functions.rb
The basic catalog's set, from the spec's descriptions.
| Constant | Description |
|---|---|
| CHECKED | The argument every validator checks. |
| DYNAMIC | A parameter schema referencing one of the common dynamic types. |
| PLURAL_CATEGORIES | The CLDR plural categories pluralize selects among. |
registry
(Functions)
—
A boolean list argument (and, or).
class < Struct · lib/poetry/agent/a2ui/functions.rb
One declared function.
Returns the value of attribute activation
Returns (Object) — the current value of activation
Sets the attribute activation
value
(Object)
— the value to set the attribute activation to.
Returns (Object) — the newly set value
Returns the value of attribute callers
Returns (Object) — the current value of callers
Sets the attribute callers
value
(Object)
— the value to set the attribute callers to.
Returns (Object) — the newly set value
Returns the value of attribute description
Returns (Object) — the current value of description
Sets the attribute description
value
(Object)
— the value to set the attribute description to.
Returns (Object) — the newly set value
Returns the value of attribute impl
Returns (Object) — the current value of impl
Sets the attribute impl
value
(Object)
— the value to set the attribute impl to.
Returns (Object) — the newly set value
Returns the value of attribute name
Returns (Object) — the current value of name
Sets the attribute name
value
(Object)
— the value to set the attribute name to.
Returns (Object) — the newly set value
Returns the value of attribute params
Returns (Object) — the current value of params
Sets the attribute params
value
(Object)
— the value to set the attribute params to.
Returns (Object) — the newly set value
Returns the value of attribute required
Returns (Object) — the current value of required
Sets the attribute required
value
(Object)
— the value to set the attribute required to.
Returns (Object) — the newly set value
Returns the value of attribute returns
Returns (Object) — the current value of returns
Sets the attribute returns
value
(Object)
— the value to set the attribute returns to.
Returns (Object) — the newly set value
class < StandardError · lib/poetry/agent/a2ui/functions.rb
A missing function or a bad argument list.
module · lib/poetry/agent/a2ui/markdown.rb
The Markdown subset an A2UI Text component needs, rendered without a Markdown dependency: ATX headings, paragraphs, bullet lists, emphasis, strong, inline code, and links. Input is escaped first, so agent text never reaches the page as markup.
Markdown.render("## Login\nWelcome **back**") # => "<h2>Login</h2><p>Welcome <strong>back</strong></p>"
text
(String)
—
Returns (String)
— HTML (unmarked; wrap in html_safe at the render site)
Strips the same markers instead of rendering them - the fallback the basic catalog guide asks for when markup is unwanted.
text
(String)
—
Returns (String) — plain text
module · lib/poetry/agent/a2ui/pointer.rb
JSON Pointer (RFC 6901) over plain Ruby documents, with A2UI's two extensions: relative paths (no leading slash) resolve against a collection scope, and an upsert writes through missing objects.
Pointer.get({ "user" => { "name" => "Ada" } }, "/user/name") # => "Ada"
Pointer.absolute("name", "/users/1") # => "/users/1/name"
Resolves a path against a scope: absolute paths pass through, relative ones append to the scope (the root when no scope).
path
(String)
—
scope
(String, nil)
— the collection-item pointer in effect
Returns (String) — an absolute pointer
Joins tokens back into a pointer.
parts
(Array<String>)
—
Reads the value at a pointer; nil for any missing step.
document
(Object)
—
path
(String)
—
Splits a pointer into unescaped reference tokens; "" and "/" both name the whole document.
path
(String)
—
Writes a value at a pointer (A2UI upsert semantics): missing objects are created along the way, a nil value removes the key, and the whole-document pointer replaces the document.
document
(Hash, Array, nil)
—
path
(String)
—
value
(Object, nil)
—
Returns (Object) — the updated document
class · lib/poetry/agent/a2ui/renderer.rb
Renders one {Surface} to HTML through the host's view context, dispatching each component to the surface's catalog binding. The surface becomes a form when an action_url is given: bound inputs are named by their absolute data-model pointer and every agent action is a submit button, so a user action posts the surface's current inputs plus the source component - the spec's "inputs sync only on an action" contract, in Hotwire's native shape. The wrapper carries the surface's version for the versioned Turbo Stream replace.
Rendering never raises for an agent's mistake: an unknown component, a dangling reference, a component the library refuses to build, or an unsupported function renders nothing and lands in {#warnings}.
renderer = Renderer.new(surface, view: view_context, action_url: "/a2ui/action")
html = renderer.call
renderer.warnings # => []
| Constant | Description |
|---|---|
| ACTION_PARAM | The parameter carrying the source component of the action. |
| COMPONENT_ERRORS | The errors an agent-authored component may provoke in the library. |
| ELEMENT_PREFIX | The DOM id prefix of a rendered surface. |
| EVALUATE_ACTIONS | The form events that re-run the checks. |
| SURFACE_CONTROLLER | The Stimulus controller that runs a surface's checks as the user types. |
| SURFACE_PARAM | The parameter carrying the surface id. |
| VALUES_PARAM | The parameter carrying the bound values, keyed by absolute pointer. |
surface_or_id
(Surface, String)
—
Returns (String) — the DOM id of the surface's wrapper
component
(Hash)
—
scope
(String, nil)
—
Returns (String, nil) — the component's accessibility label
Returns (String) — an empty html_safe string
Returns (String) — the surface's HTML (html_safe)
Calls a catalog function; a problem warns and returns nil.
name
(String)
—
args
(Hash, nil)
—
scope
(String, nil)
—
Builds and renders a library component. Every instance gets a render-stable key: (the surface, the component, its scope, and a suffix for repeated instances), so Turbo morph pairs the same logical element across updates and local state survives.
klass
(Class)
— the component class
attributes
(Hash)
— constructor keywords
suffix
(String, nil)
— distinguishes several instances of one class for one component
keywords
(Hash)
— constructor keywords given keyword-style (merged into attributes)
component
(Hash)
—
scope
(String, nil)
—
Returns (String) — a DOM id for the component's control
Returns (String, nil)
— the key of the component being rendered (id, or id@scope)
component
(Hash)
—
scope
(String, nil)
—
Returns (String, nil) — the first check failure message for the component
Returns (Hash{String => Array<Hash>}) — check failures by component key (see {Surface#failures})
surface
(Surface)
—
view
(Object)
— an ActionView context (view_context)
action_url
(String, nil)
— where actions post; nil renders a plain container
html
(Hash)
— extra attributes for the wrapper (class: etc.)
errors
(Hash{String => Array<Hash>})
— check failures to show, by
component key (a rejected action's errors)
Returns (Renderer) — a new instance of Renderer
path
(String)
— a bound pointer
scope
(String, nil)
—
Returns (String) — the input's form name
text
(String)
—
Returns (String) — the Markdown subset rendered (html_safe)
Renders a child reference (an id, an id list, or a template).
reference
(String, Array<String>, Hash, nil)
—
scope
(String, nil)
—
component_id
(String)
—
scope
(String, nil)
—
Returns (String) — the component's HTML (empty when it cannot render)
value
(Object)
—
scope
(String, nil)
—
Returns (Object, nil) — the resolved dynamic value
suffix
(String, nil)
—
Returns (String) — the render-stable key of the component being rendered
The attributes that make a button an agent action.
component
(Hash)
—
scope
(String, nil)
—
The display string of a dynamic value; a function problem warns.
value
(Object)
—
scope
(String, nil)
—
Returns (Object) — the view context
Records a problem and renders nothing for it.
message
(String)
—
Returns (String) — an empty html_safe string
Returns (Array<String>) — what could not be rendered, in render order
class · lib/poetry/agent/a2ui/session.rb
The renderer-side consumer of the A2UI envelope: applies createSurface, updateComponents, updateDataModel, and deleteSurface to a set of {Surface}s, answers what it cannot honor with renderer-to-agent error messages, and turns a submitted form into the spec's action message.
session = Session.new
session.apply_all(messages) # => ["login"]
session.surfaces["login"].data
session.errors # => [] or renderer-to-agent error messages
| Constant | Description |
|---|---|
| MESSAGE_KEYS | Every message carries exactly one of these keys. |
Turns a submitted surface form into the agent's action message: bound input values are written to the data model first (two-way binding syncs on an action), then the source component's event context resolves against the updated model. Returns nil when the source has no agent event (a local action, or an unknown component), and an invalid action - no message, errors by component key - when a checks rule fails.
surface_id
(String)
—
source
(String)
— the submit button's value (id or id@scope)
values
(Hash{String => Object})
— submitted values by absolute pointer
timestamp
(Time)
—
Applies one envelope message. Returns the ids of the surfaces it changed (a deleted surface counts); problems are recorded in {#errors} and return no ids.
message
(Hash)
—
Applies the A2UI messages an AG-UI a2ui-surface activity carries (an a2ui_operations, messages, or operations list, or one bare message).
content
(Hash, Array)
—
Returns (Array<String>) — the changed surface ids
messages
(Array<Hash>)
—
Returns (Array<String>) — the changed surface ids, deduplicated
catalog_id
(String, nil)
—
Returns (Object) — the catalog binding for an id (the default when unknown)
Returns (Hash{String => Object}) — catalog bindings by catalog id
Returns (Array<String>) — ids of deleted surfaces, in order
Returns (Array<Hash>) — renderer-to-agent error messages, in order
catalogs
(Hash{String => Object})
— catalog bindings by id
default_catalog
(Object, nil)
— the binding for unknown catalog ids
(Poetry's own when nil)
Returns (Session) — a new instance of Session
Returns (Array<Hash>)
— renderer-to-agent rendererFunctionResponse messages, in order
surface_id
(String)
—
Returns (Hash{String => Surface}) — live surfaces by id
class < Struct · lib/poetry/agent/a2ui/session.rb
A user action, ready for the agent: the spec message plus the AG-UI placement (forwardedProps.a2uiAction.userAction).
Returns the value of attribute errors
Returns (Object) — the current value of errors
Sets the attribute errors
value
(Object)
— the value to set the attribute errors to.
Returns (Object) — the newly set value
Returns (Hash)
— the AG-UI forwardedProps carrying the action (and the
data model when the surface asked for it); empty when invalid
Returns the value of attribute message
Returns (Object) — the current value of message
Sets the attribute message
value
(Object)
— the value to set the attribute message to.
Returns (Object) — the newly set value
Returns the value of attribute surface
Returns (Object) — the current value of surface
Sets the attribute surface
value
(Object)
— the value to set the attribute surface to.
Returns (Object) — the newly set value
Returns (Hash, nil)
— the { "version", "action" } renderer-to-agent
message; nil when a check failed
Returns (Boolean) — whether every check passed and the message exists
class · lib/poetry/agent/a2ui/streams.rb
Delivers a {Session}'s surfaces as Turbo Streams: a surface's first appearance appends into the container (when one is given), every later change is a versioned replace of its wrapper (vreplace, from the AG-UI relay: a stale version never overwrites a newer one), and a deletion removes it. The host renders each surface through the render callable (typically a {Renderer}).
streams = Streams.new(session: session, container: "surfaces",
render: ->(surface) { Renderer.new(surface, view: view_context).call })
response.stream.write(AGUI::TurboStream.sse(streams.apply(message)))
Applies one message and returns the streams for what changed.
message
(Hash)
—
Returns (String) — Turbo Stream HTML (empty when nothing changed)
messages
(Array<Hash>)
—
Returns (String) — Turbo Stream HTML
session
(Session)
—
render
(#call)
— (surface) -> html
container
(String, nil)
— the DOM id new surfaces append into
morph
(Boolean)
— morph replaced surfaces (the default) so local state -
typed text, a selected tab, an open dialog - survives an update; false swaps
Returns (Streams) — a new instance of Streams
Marks surfaces as already on the page (rendered server-side), so their next change replaces instead of appending.
ids
(Array<String>)
—
id
(String)
—
Returns (String) — the stream for one surface (remove, append, or vreplace)
The streams for a set of surface ids.
ids
(Array<String>)
—
class · lib/poetry/agent/a2ui/surface.rb
One A2UI surface on the renderer side: its flat component list (an adjacency list keyed by id, root at the top), its data model, and a monotonic version the Turbo Stream delivery compares. A surface belongs to a catalog binding, which knows how each component references its children; the surface itself is catalog-agnostic beyond that.
surface = Surface.new(id: "card", catalog: Catalogs::Basic.new)
surface.update_components([{ "id" => "root", "component" => "Text", "text" => { "path" => "/name" } }])
surface.update_data("/name", "Ada")
surface.resolve({ "path" => "/name" }) # => "Ada"
| Constant | Description |
|---|---|
| NAME_PATTERN | Component names are UAX #31 identifiers. |
| RESERVED_COMPONENT | The reserved container the renderer instantiates on createSurface. |
value
(Object)
—
Returns (Boolean)
— whether the value is a { "path" => ... } data binding
Returns (Object) — the catalog binding (see {Catalogs::Basic})
Returns (String, nil) — the catalog id the agent named
component_id
(String)
—
Returns (Hash{String => Hash}) — components by id
Returns (Hash) — the data model
Expands a child reference into [id, scope] pairs: an id array keeps the scope, a template instantiates its component once per item of the bound array with the item's pointer as the scope.
reference
(Array<String>, Hash, String, nil)
—
scope
(String, nil)
—
Evaluates every rendered component's checks against the data model, keyed the way an action names its source (id, or id@scope inside a template).
on_error
(#call, nil)
— receives each function problem's message
Returns (Hash{String => Array<Hash>}) — failures by component key
value
(Object)
—
Returns (Boolean)
— whether the value is a { "call" => ... } function call
id
(String)
—
catalog
(Object)
— the catalog binding
catalog_id
(String, nil)
—
send_data_model
(Boolean)
—
data
(Hash, nil)
— the initial data model
components
(Array<Hash>)
— the initial component list
Returns (Surface) — a new instance of Surface
Bound input descriptors of the rendered tree, absolute paths only.
Returns (Array<Hash>)
— { path:, kind: } (kind: :string, :boolean, :number, :string_list)
What a client-side evaluator needs to run the checks as the user types: every checked component's rules with its bindings made absolute for its scope, the bound inputs by absolute path with their kinds, and the data model for paths no input carries.
Returns (Hash)
— { "checks" => { key => { "kind", "rules" } }, "inputs" => { path => kind }, "model" => data }
Reads a bound path in a scope.
path
(String)
—
scope
(String, nil)
—
Resolves a dynamic value in a scope: a { "path" => ... } binding reads the data model (relative paths against the scope), a { "call" => ... } function call runs through the catalog's functions (see {Evaluator}), anything else is a literal.
value
(Object)
—
scope
(String, nil)
— the collection-item pointer in effect
on_error
(#call, nil)
— receives each function problem's message
Returns (Hash, nil) — the top-level component
Returns (Boolean) — whether actions carry the whole data model
component
(Hash)
—
scope
(String, nil)
—
Returns (String)
— id, or id@scope inside a template
value
(Object)
—
Returns (Boolean)
— whether the value is a { "componentId", "path" } template
The string a resolved value displays as (the spec's conversion rules: nil is empty, containers are JSON).
value
(Object)
—
scope
(String, nil)
—
on_error
(#call, nil)
— receives each function problem's message
Returns (Hash) — a JSON-ready snapshot
Upserts components by id and validates the result. Returns the validation errors (each { code:, path:, message: }); a dangling child reference is not one - streaming delivers children later.
list
(Array<Hash>)
—
Applies an updateDataModel (upsert; nil removes; the root pointer replaces the whole model).
path
(String, nil)
—
value
(Object, nil)
—
Returns (Integer) — bumps on every applied change
Walks the rendered tree depth-first from the root, yielding each [component, scope] in render order (templates instantiate once per item; a cycle guard keeps the walk finite).
module · lib/poetry/agent/agui.rb
The AG-UI surface: a Rails-side CLIENT of the Agent-User Interaction protocol. An agent backend (any AG-UI integration, or a Ruby server) streams events - text deltas, tool calls, state, activities, run lifecycle, interrupts - and this module turns that stream into server-rendered chat frames a Hotwire page updates through Turbo Streams, the same pipeline the chat replay rig proves.
The pieces, each usable alone:
text/event-stream chunks into event hashes.RunAgentInput wire hash, and{.tool_descriptor} advertises a rendered component's declared tools as frontend-defined tools the browser executes.
shared state (JSON Patch), activities, the run status, pending client tools, and interrupts.
host-supplied row renderer, plus the client-tool bridge element the poetry--agent--agui-client-tool controller executes.
Nothing here calls a model: the agent is whatever the host points the client at.
| Constant | Description |
|---|---|
| EVENT_TYPES | The AG-UI event types this transcript understands (the wire strings; deprecated THINKING_* aliases included). |
Reads a wire field from an event or message that may arrive camelCased (the protocol) or snake_cased (a Ruby producer).
hash
(Hash)
—
name
(String)
— the camelCase name
The frontend-defined tool descriptor for one of a rendered component's declared tools: the MCP Tool shape the registry projects, renamed to AG-UI's parameters and prefixed with the instance name exactly as the WebMCP registrar registers it, so a call the agent makes is executable in the browser by name.
instance
(String)
— the webmcp: instance name
definition
(Hash)
— one entry of Component#webmcp_tools
Returns (Hash)
— { "name", "description", "parameters" }
Poetry::Agent::AGUI.tool_descriptor("sections", tabs.webmcp_tools.first)
# => { "name" => "poetry.sections.set_value", "description" => "...", "parameters" => {...} }
class · lib/poetry/agent/agui/client.rb
The HTTP client: POSTs a RunAgentInput to an AG-UI endpoint and yields the streamed events as they arrive (stdlib Net::HTTP, text/event-stream). One call is one run; the multi-run model (client tools, interrupts) is the caller's loop over {Transcript}.
url
(String)
— the agent's run endpoint
headers
(Hash{String => String})
— extra request headers (auth)
open_timeout
(Numeric)
— seconds
read_timeout
(Numeric)
— seconds between chunks
Returns (Client) — a new instance of Client
Runs the agent and yields every event.
input
(Hash)
— the wire hash ({RunInput.build})
Returns (SSE::Parser)
— the parser (its errors list any unreadable lines)
client.run(input) { |event| transcript.apply(event) }
class < Poetry::Core::Error · lib/poetry/agent/agui/client.rb
Raised for a non-success HTTP status.
Returns (Error) — a new instance of Error
module · lib/poetry/agent/agui/json_patch.rb
RFC 6902 JSON Patch over plain Ruby data (Hash / Array), with RFC 6901 JSON Pointer paths - what AG-UI's STATE_DELTA and ACTIVITY_DELTA carry. Applies atomically: the document is deep- copied first and the copy is returned, so a failing operation leaves the caller's document untouched.
Applies a patch and returns the patched copy.
document
(Hash, Array)
—
operations
(Array<Hash>)
— RFC 6902 operations (string or symbol keys)
Returns (Hash, Array) — the new document
JsonPatch.apply({ "a" => 1 }, [{ "op" => "replace", "path" => "/a", "value" => 2 }])
# => { "a" => 2 }
Reads the value at a JSON Pointer.
document
(Hash, Array)
—
path
(String)
— RFC 6901 pointer ("" is the whole document)
class < Poetry::Core::Error · lib/poetry/agent/agui/json_patch.rb
Raised for an operation the document cannot take (an unknown op, a missing path, a failed test).
class · lib/poetry/agent/agui/relay.rb
Turns transcript changes into Turbo Streams. The host supplies the row renderer (its own partial or component: a message and its version in, the row's HTML out - the row must carry data-version), the target id scheme, and the container new rows append to. The relay stays view-free.
Client tools ride the same channel: when a run ends with tool calls the browser must execute, {#client_tool_streams} appends one bridge element per call; the poetry--agent--agui-client-tool controller executes it through the registrar and POSTs the result to the continue URL, whose response streams the next run.
relay = Poetry::Agent::AGUI::Relay.new(transcript: transcript, container: "chat-messages",
render: ->(message, version) { render_row(message, version) })
client.run(input) { |event| relay.apply(event).each { |stream| write(TurboStream.sse(stream)) } }
| Constant | Description |
|---|---|
| CLIENT_TOOL_CONTROLLER | The bridge controller's identifier. |
Applies an event and answers the Turbo Streams it produced: an append for a message's first appearance (when a container is set), then the update action for every change.
event
(Hash)
—
Bridge elements for every pending client tool call.
continue_url
(String)
— where the browser POSTs { toolCallId, name, content, error }
container
(String)
— the element the bridge elements append to
transcript
(Transcript)
—
render
(#call)
— (message, version) -> String the row HTML
container
(String, nil)
— the id new rows append to (nil: replace only)
target
(#call)
— (message) -> String the row's element id
action
(String)
— the stream action for updates (vreplace by default)
append_render
(#call, nil)
— (message, version) -> String the HTML a
first appearance appends - the row inside its list wrapper (a scroller
item); defaults to render
Returns (Relay) — a new instance of Relay
Marks message ids the page already renders, so their next change is an update rather than an append (server-rendered history).
ids
(Array<String>)
—
The stream for one message id (nil when the message is unknown).
id
(String)
—
module · lib/poetry/agent/agui/run_input.rb
Builds the RunAgentInput wire hash an AG-UI agent accepts: camelCased keys, messages in the protocol's shapes, the frontend-defined tools, context entries, state, forwarded props, and the resume entries that answer interrupts.
thread_id
(String)
— the conversation thread
messages
(Array<Hash>)
— protocol messages (id, role, content, ...)
run_id
(String)
— defaults to a fresh UUID
tools
(Array<Hash>)
— frontend-defined tools ({AGUI.tool_descriptor})
context
(Array<Hash>)
— { "description", "value" } entries
state
(Hash)
— the shared state to send
forwarded_props
(Hash)
— integration-specific props
parent_run_id
(String, nil)
— the run this one branches from
resume
(Array<Hash>, nil)
— { "interruptId", "status", "payload" } answers
Returns (Hash) — the wire hash (string keys)
RunInput.build(thread_id: "t1", messages: [RunInput.user_message("hi")],
tools: [Poetry::Agent::AGUI.tool_descriptor("sections", definition)])
A resume entry answering an interrupt.
interrupt_id
(String)
—
status
(String, nil)
— e.g. "approved", "rejected"
payload
(Object, nil)
—
A tool-result message answering a tool call the browser ran.
tool_call_id
(String)
—
content
(String)
— the result as text (JSON for structured results)
error
(String, nil)
— set when the tool failed
id
(String)
—
A user message.
content
(String)
—
id
(String)
—
module · lib/poetry/agent/agui/sse.rb
A text/event-stream parser for AG-UI: every event is a JSON object on one or more data: lines, terminated by a blank line. Incremental (feed chunks as they arrive) and tolerant of comments, event: / id: / retry: fields, and CRLF.
Parses a complete stream (a String or anything responding to each with chunks) and yields every event.
source
(String, #each)
—
Returns (Array<Hash>) — every event, when no block is given
Poetry::Agent::AGUI::SSE.parse("data: {\"type\":\"RUN_STARTED\"}\n\n") # => [{ "type" => "RUN_STARTED" }]
class · lib/poetry/agent/agui/sse.rb
The incremental parser.
Lines that carried data the parser could not read as JSON.
Feeds a chunk and yields each completed event.
chunk
(String)
—
Flushes a trailing event that lacked its blank line.
Returns (Parser) — a new instance of Parser
class · lib/poetry/agent/agui/transcript.rb
Folds an AG-UI event stream into what a chat page renders: the messages in order, each assistant message as Chat-shaped parts ({kind: :text, text:}, {kind: :reasoning, text:}, {kind: :tool, name:, input:, output:, state:, tool_call_id:}), the shared state (snapshots and JSON Patch deltas), activities, the run's status, its interrupts, and the tool calls the browser must execute before the next run.
Every change bumps {#version}, and {#apply} answers the ids of the messages it touched, so a relay re-renders exactly those rows with a monotonic version the page's versioned replace honors.
Activities by message id: { "type" => ..., "content" => ... }.
Applies one event.
event
(Hash)
— an AG-UI event (camelCase or snake_case keys)
Returns (Array<String>) — the ids of the messages this event changed
Applies every event of a stream.
events
(#each)
— event hashes
The frontend-defined tool names the browser executes.
RAW and CUSTOM events, in order.
Returns (Boolean) — the run ended (finished, interrupted, or errored)
The run error, if any: { message:, code: }.
The render-ready frame of one message.
id
(String)
—
Returns (Hash)
— { parts:, version: }
client_tools
(Array<String>)
— names of tools the browser executes
Returns (Transcript) — a new instance of Transcript
The open interrupts (string-keyed hashes as on the wire).
id
(String)
—
The messages in arrival order.
The messages as the next run's RunAgentInput.messages: user and assistant messages (assistant tool calls in the protocol's toolCalls shape) and a tool message for every finished tool call; reasoning and activities stay client-side, as the protocol says.
Tool calls to client tools awaiting execution: { tool_call_id:, name:, input:, message_id: }.
Marks a client tool call as executed and records its result, so the next run's input carries the tool message.
tool_call_id
(String)
—
content
(Object)
— the result (a string, or data serialized as JSON)
error
(String, nil)
—
Returns (String, nil) — the id of the message that changed
The run: { thread_id:, run_id:, status:, interrupts:, error:, result: }; status is :idle, :running, :finished, :interrupted, or :error.
The shared state after the last snapshot / delta.
Event types this transcript did not understand.
A monotonic clock over every applied change.
class < Struct · lib/poetry/agent/agui/transcript.rb
One message. role is the protocol's ("user", "assistant", "tool", "activity", ...); parts is the render-ready list.
Returns the value of attribute id
Returns (Object) — the current value of id
Sets the attribute id
value
(Object)
— the value to set the attribute id to.
Returns (Object) — the newly set value
Returns the value of attribute parts
Returns (Object) — the current value of parts
Sets the attribute parts
value
(Object)
— the value to set the attribute parts to.
Returns (Object) — the newly set value
Returns the value of attribute role
Returns (Object) — the current value of role
Sets the attribute role
value
(Object)
— the value to set the attribute role to.
Returns (Object) — the newly set value
Returns the value of attribute version
Returns (Object) — the current value of version
Sets the attribute version
value
(Object)
— the value to set the attribute version to.
Returns (Object) — the newly set value
module · lib/poetry/agent/agui/turbo_stream.rb
Turbo Stream builders for the relay: plain strings, no view context needed. vreplace is the versioned replace the runtime installs on Turbo (registerPoetryAgent) - it applies a frame only when its data-version is newer than the row's, so an out-of-order delivery can never paint an older state over a newer one.
target
(String)
—
html
(String)
—
action
(String)
— a Turbo Stream action (append, replace, vreplace, remove, ...)
target
(String)
— the target element id
html
(String, nil)
— the template content (already rendered, trusted)
method
(String, nil)
— Turbo's method attribute ("morph" morphs instead of swapping)
target
(String)
—
target
(String)
—
html
(String)
—
One SSE frame carrying the streams (newlines folded, as Turbo's stream source expects one data: line).
html
(String)
—
target
(String)
—
html
(String)
—
morph
(Boolean)
— morph the target (Turbo's idiomorph) instead of swapping it, so
local state - typed text, a selected tab, an open dialog - survives the update
class < Rails::Engine · lib/poetry/agent/engine.rb
The Rails engine: merges the WebMCP controllers manifest into poetry-core's catalog (so webmcp: roots validate at render), serves the runtime JavaScript through the importmap-first channel, and mounts the origin-trial middleware. Loading the gem is the only integration step; the host imports @poetry/agent beside @poetry/controllers.
module · lib/poetry/agent/mcp/server.rb
The MCP server projecting the component contract over Model Context Protocol so an agent in Claude Code / Cursor queries the LIVE registry and runs the linter as a tool. Thin - it projects the surfaces already built (Registry + LlmsText + Check), never a second source. Read-only, progressive-disclosure (brief|detailed|full), and verdict-returning (check returns per-finding pass/fail).
Two transports, one server: newline-delimited JSON-RPC 2.0 over stdio (the poetry-agent exe; own the supply chain - no MCP SDK dependency) and POST JSON-RPC over HTTP ({HTTP}, for the same-origin /mcp mount in-page bridges read). {Server#handle} is a pure request->response function (testable without either transport); {Server#serve} is the stdio loop; {Bundled} is the one assembly both transports share.
v1 is the read/verify surface. The heavier roadmap - verify_screen running the eval gate array, component:// artifact resources, tag browsing, SSE streaming - is maturity-gated and NOT in this cut.
| Constant | Description |
|---|---|
| PROTOCOL_VERSION | The MCP protocol revision this server negotiates. |
| SERVER_INFO | The serverInfo payload returned by the initialize handshake. |
| TOOLS | The tool roster the server advertises (tools/list): MCP Tool-shaped definitions, read-only by construction. |