This guide is written for coding agents and developers implementing applications on Dialup.
The central rule is:
Build one server-side actor, then derive a human HTML projection and an HTTP MCP tool catalog from the same state and event handlers.
Do not build a separate automation API that duplicates page logic. Declare semantic actions and regions in the page that owns the state.
The application contract
An agent-enabled page has five parts:
mount/2creates the session state.handle_event/3is the single implementation of state-changing operations.<.dialup_action>/declare_action/1declare operations for humans and agents.<.dialup_region>/declare_region/1declare stable semantic areas and projected data.agent_state/1,agent_message/1, andagent_grant/1define the machine projection, operating instructions, and authority boundary.
Human browser events and agent tools/call requests are serialized through the same
UserSessionProcess. Every mutation advances a state version. Agents must read the scene,
pass _version, and recover from stale state instead of retrying blindly.
For the HTTP API surface, read HTTP MCP API.
Implementation workflow
When asked to build an agent-native Dialup page, follow this order.
1. Model the shared state
Keep state needed by both operators in page assigns or layout session state. Do not create a second agent-only state store.
2. Identify state-changing actions
For every operation, define a stable name, description, input schema, effects, risk, reversibility, examples, success criteria, and whether human confirmation is required.
Put metadata on <.dialup_action> or hoist with declare_action/1. The implementation lives
only in handle_event/3.
3. Define live availability
Define __available__/2 with the same predicate as the HTML available={...} attribute.
Agent calls are checked server-side; they do not click buttons.
4. Add regions where meaning must survive layout changes
Use regions for domain objects an agent should refer to by stable name. Include data when the
agent needs structured state not fully present in the DOM.
5. Project only necessary state
agent_state/1 is an allowlist, not a dump of assigns. Exclude secrets and unrelated personal
data.
6. Explain the application to an unfamiliar agent
agent_message/1 should describe the business concept, goals, recommended flow, and safety
constraints without assuming repository access.
7. Grant least authority
def agent_grant(_assigns) do
%{
capabilities: [:add_item, :read_audit_log],
projections: [:state, :regions, :actions],
expires_in: :timer.minutes(10),
require_version: true
}
endCapability URLs are bearer credentials. Mark irreversible operations with confirm={:human} —
they return an isError tool result over HTTP MCP and must be performed in the human UI.
Runtime behavior an agent must understand
read_scenereturnsstate,regions,actions, andversion(subject to grant projections).- Mutations require
_versionunless the grant setsrequire_version: false. - Stale mutations return an isError tool result whose
structuredContent.currentVersionis the latest. confirm: :humanis not executable via HTTP MCP (it returns an isError tool result).read_audit_logexposes the ordered human/agent activity log when granted.- Agents use HTTP only — there is no agent WebSocket transport.
Verification checklist
- [ ] Every exposed mutation has exactly one
handle_event/3implementation. - [ ] Every action has
desc,params, effects, risk, and a verifiable success condition. - [ ] Browser and agent availability use the same
__available__/2predicate. - [ ]
agent_state/1and region data contain no secrets. - [ ]
agent_message/1is understandable without source-code context. - [ ]
tools/listmatches the declared actions on the page. - [ ] Stale-version, grant expiry, and revocation paths are tested.
Use dialup-framework.org/agent_demo as a reference.