Public
Remixed
A tiny LLM agent in ~30 lines, inspired by fly.io
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.

roll-your-own-agent

A complete LLM agent in ~30 lines of TypeScript. An LLM in a loop, with one tool. That's it. That's an agent.

Inspired by Thomas Ptacek's You Should Write An Agent on the Fly.io blog — go read it, it's great.

Run it

Open main.ts and hit Run. The agent will fetch the Hacker News front page and tell you the top story:

tool: fetch_url({"url":"https://news.ycombinator.com/"})

>>> The top story on Hacker News right now is titled "..."

You didn't write a loop to fetch URLs. You gave the model permission to fetch, and it figured out the rest.

How it works

Rendering mermaid diagram...

The whole thing:

  1. Load prior messages from SQLite (or start fresh on first run).
  2. Append a new user message and call the LLM with the messages and a list of tools.
  3. If the response has tool_calls, run them, save results to SQLite, loop.
  4. Otherwise, print the answer and exit.

The LLM is stateless. The conversation is an illusion we cast on ourselves by replaying the whole transcript every turn. SQLite makes the illusion persist across runs.

Memory (memory.ts)

Messages are scoped to conversations — isolated threads stored in SQLite.

  • createConversation(title?) — starts a new conversation, returns its id.
  • getLatestConversation() — returns the most recently updated conversation id (or null).
  • listConversations() — lists all conversations, most recent first.
  • loadMessages(conversationId) — reads a conversation's message history.
  • saveMessages(conversationId, msgs) — appends messages to a conversation.
  • clearMessages(conversationId) — deletes a conversation and its messages.

Toggle START_NEW in main.ts to start a fresh conversation or resume the latest one.

Make it yours

  • Add a tool. Append another entry to the tools array and a branch in the loop. Try read_sqlite, send_email, roll_dice, whatever.
  • Make it interactive. Wrap the loop in a for await over Deno.stdin lines (or turn it into an HTTP val with a chat UI).
  • Reset memory. Call clearMessages() from memory.ts to start a fresh conversation.
  • Sub-agents. Spawn another messages array with different tools. Have them talk.
  • Swap the model. Change gpt-4o-mini to anything else std/openai supports.

Your wackiest idea will probably (1) work and (2) take 30 minutes to code. — tqbf

Credits

All credit for the framing and most of the prose-worth ideas goes to Thomas Ptacek (@tqbf) and the Fly.io blog: https://fly.io/blog/everyone-write-an-agent/. This val is just a Val Town / TypeScript port of the spirit of that post.