Appearance
Static apps at the edge
A user asks an agent for a bill-splitting tool and sends the resulting link to five friends. The chat system is private, but the generated app is now public. That change in audience is an infrastructure event, even if the app contains only HTML and JavaScript.
Static edge hosting gives the app a stable public address without letting generated code execute inside the private cell.
Publication model
text
private agent cell
|
+-- validates text files
+-- writes temporary directory
+-- uploads static assets
v
edge hosting project
|
+-- branch alias per app
+-- immutable deployment URL per revision
+-- optional custom domain for launched app
visitor browser runs the appThe upload process is privileged. The uploaded app is not.
Terms
A branch alias is a stable edge URL that points to the newest deployment for one branch name.
A deployment URL identifies one immutable upload and remains useful for diagnosis or rollback.
A static-only boundary allows inert files such as HTML, CSS, JavaScript, images, and manifests, but rejects platform server functions.
A derived file is generated by the platform from declared launch configuration and cannot be edited directly by the model.
A canonical domain is the preferred public URL used in metadata, sitemaps, and sharing.
One project, one branch per app
Cloudflare Pages Direct Upload is the reference choice. One Pages project can hold many apps, each on a branch named for its slug. The branch alias stays stable while each upload also receives a unique deployment URL. Set the production branch to a reserved unused name so the bare project hostname serves nothing.
This choice is transferable. Any static host works if it offers immutable revisions, stable aliases, bounded credentials, and deletion. A home-grown object-store server can work too, but it creates another public service, cache policy, routing layer, and abuse path.
Use the vendor-supported upload tool instead of reverse-engineering an internal API. The reference worker runs Wrangler with a strict timeout and a temporary configuration directory. That adds a production dependency, but it follows the documented upload path.
Validate before writing
Generated files are untrusted input. Require index.html. Normalize paths and reject absolute paths, .., NUL bytes, and reserved internal directories. Cap file count and decoded byte size. Accept text by default and explicitly handle generated binary assets.
Most important, reject files that enable edge execution. In Cloudflare Pages that includes _worker.js, Functions directories, and routing files that could invoke server code. The account token may have upload permission, but generated content must not inherit the ability to run with account bindings.
A generic validator should enforce the boundary before creating a temporary directory:
ts
for (const file of files) {
const path = normalizeRelativePath(file.path)
if (isExecutablePlatformPath(path)) throw new Error("static files only")
if (decodedSize(file) > perFileLimit) throw new Error("file too large")
}Use a fresh temporary directory for each upload, remove it on every exit path, and serialize deployments if the vendor CLI shares local state. Apply an account-wide rate limit so an agent retry loop cannot consume deployment quotas.
Public means public
The app URL needs no chat login. Do not put personal data, API keys, capability URLs meant to remain private, or provider credentials in generated files. A private conversation does not make its deployed artifact private.
Store the current file snapshot and deployment record server-side. Use an operation ID with a uniqueness constraint so an agent turn retried after a crash does not create ambiguous state. Re-uploading the same static content is harmless, but the record still needs one logical operation.
Keep revision history so a follow-up can patch the current app without asking the user to paste it again. Bound retained revisions. Large generated histories are operational data, not an excuse for unlimited database growth.
Launch mode is a separate choice
A prototype needs a URL. A launched app needs stable identity and disclosures. Make launch mode opt-in.
The platform can generate the repetitive files from one launch declaration:
- web manifest and icons
- service worker
- canonical and social metadata
robots.txt, sitemap, andllms.txt- About and Privacy pages
- structured data and launch notes
Mark these files as generated and overwrite them deterministically. If the model edits a privacy page by hand, its claims can drift from actual storage, analytics, push, or third-party hosts.
Drafts should carry noindex. Launch publication can remove it, attach <slug>.<apps-domain>, submit indexing, and expose the canonical URL. Custom-domain automation requires a narrowly scoped DNS and Pages token. Indexing requests are best effort, not a promise that a search engine will list the app.
Browser capabilities need origin rules
A static app may use a shared-data or push service hosted by the private cell. That does not make the app trusted. Require capability keys for data access and exact origin matching for browser requests.
Accept the branch alias and, when configured, one slug below the custom apps domain. Reject HTTP, the bare parent, nested labels, cross-app origins, and lookalike suffixes. Compare parsed hostnames, not substring matches.
Origin checks prevent browser mistakes. They do not authenticate a script or curl client that forges an Origin header. Capability keys, quotas, and data validation remain necessary.
A service worker adds another lifetime. Use generated, versioned code with a known cache list. Draft updates can appear stale until old tabs reload or the installed app relaunches. Document that before diagnosing the edge upload.
Decisions and rejected alternatives
Choose static hosting instead of generated server functions. Browser JavaScript is already risky enough; server execution would expose account bindings, network access, and billing.
Choose one hosting project with branch-per-app aliases. Project-per-app isolation appears cleaner, but provider project limits and token administration become the scaling constraint.
Keep the private chat UI on its own origin. Public mini apps belong at the edge, but moving the authenticated chat UI there would disrupt its cookie and WebSocket model.
Generate launch files instead of asking the agent to maintain them. Determinism is more valuable than creative variation for icons, privacy claims, and service-worker behavior.
Do not use redirects or shorteners for ordinary campaign links. Canonical URLs with query parameters preserve ownership and remove another public dependency.
Failure modes
- A permissive path check lets
../escape the upload directory. - A generated function file turns static content into account-level execution.
- A reused slug overwrites an unrelated app.
- An alias returns 404 for a short propagation window and the agent retries until rate limits trigger.
- A custom domain works for HTML but fails for sync because CORS still allows only the Pages alias.
- A service worker serves a previous revision.
- Deleting the database row without deleting edge deployments leaves public copies reachable.
Deletion must address current aliases, immutable deployments where the provider permits it, stored snapshots, custom domains, and related capability state.
Edge publication checklist
Previous: Chapter 36, "Secrets that never enter Git".
Next chapter
Chapter 38, "Observability for agents", measures both the cell and the work an agent performs inside it.