Building agent-native Dialup applications

Copy Markdown View Source

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:

  1. mount/2 creates the session state.
  2. handle_event/3 is the single implementation of state-changing operations.
  3. <.dialup_action> / declare_action/1 declare operations for humans and agents.
  4. <.dialup_region> / declare_region/1 declare stable semantic areas and projected data.
  5. agent_state/1, agent_message/1, and agent_grant/1 define 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
  }
end

Capability 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_scene returns state, regions, actions, and version (subject to grant projections).
  • Mutations require _version unless the grant sets require_version: false.
  • Stale mutations return an isError tool result whose structuredContent.currentVersion is the latest.
  • confirm: :human is not executable via HTTP MCP (it returns an isError tool result).
  • read_audit_log exposes 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/3 implementation.
  • [ ] Every action has desc, params, effects, risk, and a verifiable success condition.
  • [ ] Browser and agent availability use the same __available__/2 predicate.
  • [ ] agent_state/1 and region data contain no secrets.
  • [ ] agent_message/1 is understandable without source-code context.
  • [ ] tools/list matches the declared actions on the page.
  • [ ] Stale-version, grant expiry, and revocation paths are tested.

Use dialup-framework.org/agent_demo as a reference.