Appearance
Apps as conversation artifacts
A user asks for a trip bill splitter. The assistant returns a link. Ten minutes later the user asks for a separate drinks total. That second request exposes the real design problem. If the app existed only as source text in an old message, the assistant must recover the right files, infer which deployment the user means, and rebuild context before it can change one field.
An app made in conversation should become a first-class artifact. The chat is where intent arrives, but the artifact has its own identity, files, revision, checks, deployments, and public address. Replies refer to that state. They do not contain the only copy of it.
Tiny game engines, shared worlds showed why shared state needs explicit authority and protocol rules. Generated apps need the same discipline. The difference is that their source, revisions, and deployments also become state the conversation must recover later.
Concept map
text
conversation
-> app identity
-> mutable draft
-> checks
-> immutable deployment record
-> stable public URLThe key separation is between the conversation and the app. A conversation explains why a change exists. The app record says what exists now.
Terms worth fixing early
Artifact means a durable object produced through chat. It has an identity beyond one message.
Draft means the current editable file set. It may be incomplete or invalid.
Published snapshot means the exact files last sent to the hosting service.
Deployment means one hosting event and its provider-specific identifier.
Stable alias means the human-facing URL that keeps its address when a new deployment replaces the old one.
Operation means one recorded tool call, including its idempotency key and compact result.
These names stop a common muddle. "The app" might otherwise mean the request, generated source, live site, or latest deployment.
Give the artifact a home
The minimum useful app record is small:
sql
create table apps (
slug text primary key,
conversation_id text,
draft_files json not null,
published_files json,
draft_revision integer not null,
public_url text not null,
updated_at bigint not null
);The schema can vary, but the ownership rules should not. The draft belongs to the app. The conversation points to the app. A deployment copies a checked draft into a published snapshot. The assistant's prose is never authoritative.
A conversation may own several apps, though one app per clear user goal is easier to operate. An app may survive deletion of its source conversation if the product promises persistent public links. Decide that retention rule before launch. A private chat does not make a public app private. Public hosting changes the privacy boundary, even if the request started in an ephemeral thread.
The public identity should also be explicit. A branch alias, deployment slot, or named route can provide one URL per app. Redeploying updates the target without changing the address. Users can bookmark it, and follow-up requests can refer to it.
Put artifact facts back into the turn
Durable storage solves only half the problem. The model still needs the right facts when the next request arrives.
Inject a compact app index into the conversation context:
text
Apps in this conversation
- trip-split: https://trip-split.example.app
draft r12, published r11
files: index.html 84 lines, app.js 213 lines, styles.css 147 lines
previous change: app.js lines 90-118This summary prevents several expensive mistakes. The assistant does not need to list apps, dump files, or guess the slug. It can tell that unpublished work exists. It can inspect the previous change before reading a whole file.
The summary must come from storage, not old chat text. Conversation compaction will eventually remove details. Users also rename threads, retry failed turns, and continue work days later. A current artifact index is more reliable than a prose summary written before the last edit.
Keep the index bounded. Include file outlines or line counts, not source bodies. Mention the latest relevant change, not every historical revision. The goal is enough context to choose the next tool.
Stable identity matters more than clever generation
Slugs make useful app identifiers because they work in storage keys, URLs, logs, and user-visible labels. Validate them at the boundary. Reject traversal characters, reserved hostnames, and provider-specific limits before a deployment begins.
Do not let the model invent a second app when the user clearly means the first one. A compacted conversation may lose the original slug, so attach known app identities to every relevant turn. If the user does ask for a fork, make that an explicit copy operation. Accidental forks leave two public links and no clear source of truth.
Hosting should return both a stable alias and a deployment-specific URL. The stable alias is for users. The deployment URL is for diagnosis and audit. Store both.
Decisions, including the rejected ones
Keep files in an app record. The alternative is reconstructing files from messages or provider storage. Messages are incomplete after edits, and provider APIs are poor editing databases. Provider state may also disappear during cleanup.
Use a server-only artifact table. Syncing every generated file into the chat client's normal data model can send megabytes to every replica. The UI usually needs a title, status, revision, and URL. It can get those from compact operation results or a small projection.
Use one stable alias per app. A fresh URL on every publish breaks bookmarks and makes follow-up requests ambiguous. Immutable deployment URLs remain valuable as internal evidence.
Keep static apps static. Allowing generated server code under the host account increases the security boundary at once. A shared, capability-limited service can provide data when needed. Arbitrary generated backend code should be a separate product with separate review.
Record private-thread apps. Forgetting the chat does not remove a public deployment. Pretending otherwise gives users a false privacy guarantee.
Failure modes
- The app exists only in transcript text. Follow-up edits require large reads or accidental regeneration.
- A retry deploys twice. Make deployment records unique by operation id and return the prior result after a network timeout.
- The alias and stored files disagree. Write deployment records only after the provider succeeds, and keep the previous published snapshot on failure.
- A deleted conversation takes down a public app. Decide whether deletion cascades before implementing foreign keys. Public artifacts often need
SET NULL. - The model loses the slug after context compaction. Inject current app identities into every app-related turn.
- Public files contain secrets. Reject recognizable credentials before upload, keep provider tokens out of arguments and logs, and state that every app file is public.
- Cleanup removes provider state but not local state. A delete operation should update both, even when the provider reports that no deployments remain.
Field checklist
- Does each app have a stable identifier and public alias?
- Are draft files separate from published files?
- Can a retry return the existing deployment by operation id?
- Does conversation context list current apps without dumping source?
- Is public hosting called out even inside private conversations?
- Can the UI show revision, check state, and link from compact data?
- Does deletion define what happens to provider deployments and local records?
- Are generated server handlers prohibited or handled by a separate trust boundary?
- Can the system explain which exact files produced the live version?
An app record turns chat output into something an agent can find and change. The next question is how the agent should inspect that object without reading everything. The file tree is an API treats the file tree as a bounded tool interface.