{"slug": "show-hn-surface-skill-for-html-pages-a-coding-agent-watches-and-reacts-to", "title": "Show HN: Surface skill for HTML pages a coding agent watches and reacts to", "summary": "A developer released 'surface', a pattern and skill that lets coding agents build ephemeral web pages for structured user interaction, such as drag-to-rank boards or approval buttons, and autonomously process submissions without requiring users to return to chat. The tool, installable in Claude Code and Codex, generates a URL for a task-specific UI and drains submissions automatically, addressing the limitations of chat-based interfaces for complex inputs.", "body_md": "Chat is a narrow channel: a column of text, with no real way to lay out a comparison, drag a list into order, mark up an image, or hand back a dense table you can act on row by row. `surface`\n\nis your agent's escape hatch — it builds a web page shaped to the task at hand, hands you a link, and reacts to whatever you do on it. The page can be as plain as a single approval button or as rich as a drag-to-rank board, an annotated floor plan, or a refereed two-player game; either way it's built for one task and thrown away when that task is done — no app to stand up, no form to maintain.\n\nMore precisely: `surface`\n\nis a **pattern + skill** for an agent to generate an ephemeral, structured UI at a URL, deliver that URL through any channel it has, and **drain submissions autonomously** — reacting on its own, without you coming back to chat to say \"I clicked it.\" The agent decides what every control on the page means, so submissions arrive in a known shape. It's also a strong way to *show* you information — tables, grouped lists, flagged rows — that a wall of chat text can't.\n\nYou don't run surface; you install the skill, then ask your agent. In **Claude Code** (CLI or Desktop):\n\n```\n/plugin marketplace add aac/surface\n/plugin install surface@surface\n```\n\n(On Codex, or to install by hand, see [Installing](#installing) below.) Then ask your agent to build the canonical demo:\n\n```\nUse the surface skill to build me a tic-tac-toe game I can play in my browser:\nI'll click squares to play X, and you drain each of my moves off the wire and\nplay O back onto the same board, until someone wins.\n```\n\nSoon you have a browser tab with a real board: your clicks land as X, and O appears a second or two later — played by your agent, drawn onto the *same* surface. (The first build takes a little longer — the agent is writing the server — but once it's up, your moves come back live.) That one prompt exercises the whole pattern.\n\n**Prerequisites:** none beyond the skill. For a local browser demo the agent writes and runs whatever small server it needs — you don't pre-install Go, Node, or Python (the reference servers in this repo are there to *read*, not to install). The local demo runs on loopback and needs no setup. One caveat: the tic-tac-toe board pulls its drawing library (tldraw) from a CDN, so that browser tab needs internet.\n\nSame pattern, wildly different shapes — each is a single ask:\n\n- \"Show me these 8 thumbnails and let me click the one to ship.\"\n- \"Give me a drag-to-rank surface for these 10 priorities, then tell me the order I chose.\"\n- \"Render these 30 flagged transactions as a table with approve / reject per row, and act on my picks.\"\n- \"Build an approval gate for this deploy, text me the link, and proceed only once I approve.\"\n\nThe agent shapes the UI to the task and throws it away when the task is done.\n\nA handful of terms recur — here's the whole vocabulary:\n\n**Surface**— the throwaway page itself, living at a URL.** Affordance**— a control on the surface (a button, a cell, an upload field). Each carries an** opaque ID**the agent mints; the agent keeps a private map from ID → what that control*means*, so a submission can't be twisted into meaning something it shouldn't.**Drain**— the agent consuming submissions as they arrive and reacting on its own. This is the non-negotiable part: a surface you have to return to chat to act on has failed the pattern.**Substrate / the wire**—*how*the surface is served and submissions travel: a local HTTP server, a hosted Cloudflare Worker, a Slack message, raw sockets. The pattern is fixed; the substrate is a choice.**The pattern**— five invariants every implementation preserves: mint opaque IDs, persist the ID→intent map, render the surface, drain autonomously, stay ephemeral. It's the contract; everything else is illustration. Full statement in`skills/surface/references/pattern.md`\n\n.\n\nAgents already have three ways to collect structured input, each with a gap: a **chat reply** (unstructured, and only if the user is in chat), an **inline chat-client widget** (structured, but trapped inside a supported chat surface), or a **purpose-built app or form** (full UI, but real build-and-maintain cost). surface fills the space between them — task-shaped UI the agent generates for the moment and discards.\n\nThe honest objection is \"isn't this just a web form?\" The answer is no, and the reason is the shift in *who builds it and how disposable it is*:\n\n**The cost of bespoke collapsed to the cost of asking.** A form builder gives you fixed fields and one respondent. surface lets an agent generate a UI*shaped to the task*— a drag-to-rank, a floor-plan annotator, a refereed two-player game, a flagged-transactions review with per-row decisions — in the time it takes to describe it, then throw it away. When making a custom interactive surface gets that cheap, the calculus flips: interactions that were never worth building a UI for (too one-off, too oddly-shaped, too ephemeral) become worth a surface, because nobody has to build and own anything.**The URL carries the whole interaction.** Because the response surface lives at the URL,*any*outbound channel — email, SMS, push, a paging system — becomes a reply path, not just a notification. That reframes \"the user isn't in chat\" from a dead end into a delivery choice.**Reactions are code, so monitoring is cheap.** The agent encodes the drain-and-react logic and lets it run; it only re-engages for submissions that genuinely need judgment. Watching a live surface is not an LLM-call-per-interaction tax — the mechanical reactions cost nothing once written.**Ephemeral by default.** Today surface aims at the moment, not forever: it fills the gap*below*the threshold where standing up and maintaining a durable tool makes sense, and for durable, recurring needs a real app or form tool is still the right call.\n\nThe plugin (Quick start above) is the easiest path. The full picture:\n\n**Claude Code (CLI or Desktop):**`/plugin marketplace add aac/surface`\n\n, then`/plugin install surface@surface`\n\n. The skill auto-loads from`skills/surface/`\n\n.**Cowork / Claude Desktop:** Customize → plugins → Add marketplace, and point it at`aac/surface`\n\n.**Codex:** clone the repo and symlink the skill —`ln -s \"$PWD/skills/surface\" ~/.codex/skills/surface`\n\n— or point your agent at this repo and let it install. (A first-class Codex plugin-marketplace path is on the way once the CLI's plugin flow settles.)**Or just point your agent at this repo**(`github.com/aac/surface`\n\n) and let it install whatever way fits — the skill bundle is harness-neutral under`skills/surface/`\n\n, and there's an`install.sh`\n\nthat symlinks it for you.\n\nEach harness loads `skills/surface/SKILL.md`\n\nand the references and examples it points to.\n\nThe first time you use surface interactively, the agent runs a quick setup pass: it surveys what your environment can do — local loopback, any tunnel CLIs, any hosted substrate you've configured — and records the findings, plus *where* the credentials for non-local delivery live (the locations, not the secrets), to `~/.surface/environment.md`\n\n. This is a discovery step the skill defines (`SKILL.md`\n\n§7), not a command you run. Later sessions read that file instead of re-probing, and an agent running autonomously (cron, a scheduled job) reads it instead of asking you.\n\nFor a local browser demo you need none of this — loopback works out of the box, so \"ask the agent for a tic-tac-toe game\" just works. Setup earns its keep the moment you want surface to deliver a URL through another channel (email, SMS, a hosted endpoint): that's what the environment file is for.\n\nsurface deliberately ships narrow and grows on real-use signal. Currently out of scope:\n\n**A bundled/installable server binary.** v0 is skill-only — the reference servers in`skills/surface/examples/`\n\nexist to be read and re-implemented, not installed. A canonical`surface-serve`\n\nis a v1 question.**Templating / surface-authoring helpers.** The agent writes the HTML/JS directly; a helper layer waits on friction signal.**Substantive prompt-injection mitigation patterns.**`references/security.md`\n\nnames the caution; deeper sanitization guidance accrues as real untrusted-input use does.**Persistent surfaces, link expiration, one-time-use semantics.** Surfaces are ephemeral; agents handle lifetime in their own state if they need it.\n\n(Hosted deployment and a push/WebSocket transport were once on this list — both are now documented as reference substrates, in `references/hosted-example.md`\n\nand `references/websocket-example.md`\n\n. These ship as *contracts to build against*, not runnable servers: unlike the local wire's committed `examples/server.go`\n\n, the hosted and WebSocket substrates have no reference implementation in the tree — the agent builds one for its environment from the reference. See \"What's in this repo\" below.)\n\nsurface depends on nothing else and knows about no other tool — but two sibling tools compose with it naturally, and an agent that has them gets more leverage:\n\n— an agent-to-human request inbox. A surface is a good way to`ask`\n\n*present*the thing an ask is about (a decision laid out as a board, a batch to review), while the ask itself is the durable record that a human action is pending. Surfaces are ephemeral; an ask survives the session.— delivers a payload through whatever channel a recipient prefers (SMS, email, push). When a surface's URL needs to travel to someone who isn't in chat, reach is the natural way to send it.`reach`\n\nNeither is required; surface works standalone. The composition lives in the agent's hands, not in surface's code.\n\n**Shipped as the skill** (loaded at runtime, all under `skills/surface/`\n\n):\n\n| Path | Purpose |\n|---|---|\n`skills/surface/SKILL.md` |\nSkill entry point — what surface is, when to use it, links into the references. |\n`skills/surface/references/pattern.md` |\nThe substrate-agnostic pattern. The contract every implementation must preserve. |\n`skills/surface/references/wire-example.md` |\nOne concrete wire (HTTP + JSON over localhost). Illustrative, not normative. |\n`skills/surface/references/lifecycle.md` |\nThe mechanism space for autonomous draining (Monitor, polling, fs watch, push webhook). |\n`skills/surface/references/security.md` |\nTrust boundary, deployment posture, free-field content as injection vector. CSRF + URL-unguessability notes for non-loopback deployments. |\n`skills/surface/references/hosted-example.md` |\nCloudflare Worker + KV wire walkthrough — sibling to `wire-example.md` , for the hosted substrate. |\n`skills/surface/examples/server.go` |\nGo reference server implementing the wire example. Supports either stdout (`SUBMIT` lines) or filesystem-drop drain via `--drain-mode={stdout,fs}` . Read it for orientation, re-implement in whatever fits. |\n`skills/surface/examples/server_test.go` |\nTests for the Go reference. |\n`skills/surface/examples/server.py` |\nPython stdlib reference, independently derived from the references (not Go-mirrored). Diverges from the Go sibling on operational details (port 8000, no parent-death watchdog, hard 32 MiB multipart cap) — same wire contract. |\n`skills/surface/examples/server.mjs` , `server.test.mjs` |\nNode stdlib reference (`node:http` ), independently derived from the references; `node:test` suite (21 cases). |\n`skills/surface/examples/rust/` |\nRust reference server (zero-dependency `std::net` ), independently derived from the references. Cargo project — `cargo run` / `cargo test` . |\n`skills/surface/examples/tic-tac-toe.html` , `tic-tac-toe.md` |\nThe showcase demo — a tldraw tic-tac-toe surface (you play, the agent drains moves and replies on the board), with the `.md` explaining how it maps onto the pattern and how to drive it by hand. |\n\nThe Python, Node, and Rust references were each built without their author reading the other siblings — derived from `skills/surface/references/`\n\nalone. The operational divergences (different ports, watchdog choices, error-status policies) are the validation: the pattern survives independent re-derivation.\n\n**Packaging** (harness-specific plugin wrappers, not loaded as part of the skill):\n\n| Path | Purpose |\n|---|---|\n`.claude-plugin/plugin.json` |\nClaude plugin manifest. Makes the bundle installable as a Claude Desktop / Cowork plugin; skills under `skills/` are auto-discovered. |\n`.codex-plugin/plugin.json` |\nCodex plugin manifest. Sibling to the Claude manifest with the same `skills/` pointer and lockstep `version` . Harness-neutral skill content stays under `skills/surface/` . |\n\n**For humans** (not loaded by the skill):\n\n| Path | Purpose |\n|---|---|\n`README.md` |\nThis file. |\n`LICENSE` |\nApache 2.0. |\n`AGENTS.md` |\nConventions for agents and contributors working on `surface` itself (load-bearing design principles, branch policy, halt conditions). `CLAUDE.md` is a thin shim that imports it so Claude Code auto-loads it. |\n`skills/surface/go.mod` |\nGo module declaration for the reference server. |\n\nThe drain mechanism (how the agent learns of submissions) is harness-neutral as a *category*, but maps to different primitives:\n\n| Category | Claude Code | Codex |\n|---|---|---|\n| Push-stream on subprocess stdout | `Bash(run_in_background)` + `Monitor` |\nLong-running `exec_command` session + `write_stdin` /output polling |\n| Scheduled wake-ups for cadence | `ScheduleWakeup` , `/loop` |\nHeartbeat automations |\n| FS drop-directory watch | `fswatch` /`inotifywait` /polling |\nSame — OS-level primitives are harness-neutral |\n| Hosted poll | `WebFetch` / HTTP |\n`WebFetch` / HTTP — same |\n| Tear-down | `KillShell` |\nCodex session/process-group teardown |\n\nA surface that requires the user to come back to chat and say \"I clicked it\" has failed the pattern. Any adaptation must include a real drain path (long-running stdout polling, drop-directory polling, heartbeat-driven re-check, hosted poll, or webhook where available).\n\n+`skills/surface/SKILL.md`\n\n— the canonical design: the pattern, the wire example, lifecycle mechanisms, security stance. Start here to understand the shape of the thing.`references/`\n\n— the load-bearing principles (trust the agent, pattern is the contract, autonomous draining is foundational). Read before changing anything in the skill bundle. (`AGENTS.md`\n\n`CLAUDE.md`\n\njust imports this for Claude Code.)\n\nThis skill has no phone-home. It emits no telemetry and collects no data. Any outbound network activity happens only through hosted-substrate references the user explicitly opts into (e.g., the Cloudflare Worker reference), and is entirely controlled by the operator.\n\nLicensed under the Apache License, Version 2.0. See [LICENSE](/aac/surface/blob/main/LICENSE) for details.", "url": "https://wpnews.pro/news/show-hn-surface-skill-for-html-pages-a-coding-agent-watches-and-reacts-to", "canonical_source": "https://github.com/aac/surface", "published_at": "2026-06-19 18:28:52+00:00", "updated_at": "2026-06-19 19:07:30.927864+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["Claude Code", "Codex", "tldraw", "Cloudflare Worker", "Slack"], "alternates": {"html": "https://wpnews.pro/news/show-hn-surface-skill-for-html-pages-a-coding-agent-watches-and-reacts-to", "markdown": "https://wpnews.pro/news/show-hn-surface-skill-for-html-pages-a-coding-agent-watches-and-reacts-to.md", "text": "https://wpnews.pro/news/show-hn-surface-skill-for-html-pages-a-coding-agent-watches-and-reacts-to.txt", "jsonld": "https://wpnews.pro/news/show-hn-surface-skill-for-html-pages-a-coding-agent-watches-and-reacts-to.jsonld"}}