Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
An interactive chat UI for talking to a Claude Managed Agent. Each thread is one stateful agent session — persistent history plus a sandbox with bash, file editing, and web tools. Hono (backend) + React (frontend), with Val Town SQLite for storage.
Where
claude-managed-agents
is a one-shot script, this is a multi-turn, multi-thread UI.
- A thread = one session. The agent + environment are created once
(lazily on first use) and their ids are cached in a
configrow — sessions pin to an agent version, so recreating them per request would be wrong. Each thread makes one session and reuses it for follow-up turns. - On a new user message the server inserts the user row, ensures the thread has
a session, then opens the event stream, sends the message, and writes each
event to SQLite as it arrives (
agent.message→ text,agent.tool_use→ tool name,session.error→ error), returning when the session goes idle. - The frontend polls
GET /api/threads/:id/messages?after=<lastId>about once a second and renders new rows in order. No SSE to the browser. - The built-in toolset runs in Anthropic's sandbox with
permission_policy: always_allow, so tools just run — no approval prompts. - Files the agent creates (images, charts, PDFs) are surfaced after each
turn. When the session goes idle,
backend/files.tslists the session's files via the Managed Agents Files API (files.list({ scope_id })), downloads any new ones (files.download(id)), stashes the bytes instd/blob(keyfile_<file_id>), and writes amessagesrow for each —type: "image"forimage/*, elsetype: "file". The row'scontentis JSON{ id, name, mime }. The frontend renders images inline and other files as a⬇ namedownload link, both pointing atGET /api/files/:id, which streams the blob bytes with the right Content-Type.
Set ANTHROPIC_API_KEY (from https://platform.claude.com/settings/keys).
Everything else is created on the first message. Tweak the model or system
prompt in backend/agent.ts.
index.ts— Hono server + API routesbackend/db.ts— SQLite schema + helpers (threads,messages,config)backend/agent.ts— Managed Agents: create-once config, per-thread session, per-turn streamingbackend/files.ts— surface sandbox files: Files API →std/blob→ image/file message rowsfrontend/— React chat UI (App.tsx,Chat.tsx,api.ts)
- Stream reconnection. The turn request stays open for the whole agent turn.
If it dies before
session.status_idle, reopen the stream and replay events for thatsession_idfrom the thread'slast_cursorto resume writing to SQLite — session state is persisted server-side, so it's recoverable. - Long turns may exceed Val Town request limits → tied to the reconnection / background-execution work above.