;; essay · 2026-07-11
Lisp vs TypeScript
for AI coding agents
(compare 'lisp 'typescript :for "agentic-workflows")
Almost every AI coding assistant you can name today is a Node process. TypeScript is a fine language for shipping a product — but the choice of runtime under an agent is a different question than the choice of language for a web app. An agent has to observe its own execution, recover from partial failure, extend itself, and — if it's honest about learning — modify its own behavior between sessions. The runtime either helps with that or it fights you.
This is the argument for building the runtime in Common Lisp instead of TypeScript. It's not nostalgia. Four language-level properties map directly onto things an evolving coding assistant needs to do.
the live image
A Node agent is a process. When you change its code you kill the process, reload from disk, and rebuild whatever in-memory state it had. Sessions drop. Anything the agent learned in RAM is gone. In practice this is why "learning" in a TS agent almost always means one of two things: appending to a vector store, or writing a longer prompt. The runtime can't hold new behavior; only the model can.
SBCL — the Common Lisp implementation tasa runs on — is a live image. You redefine a function and the definition is replaced in place. Callers pick up the new definition on their next call. The session doesn't drop. State survives the change. New behavior can be compiled into the running agent without restarting it. That is the raw material for a coding assistant that actually accumulates skills instead of re-prompting them every time.
conditions and restarts
JavaScript has exceptions. An exception unwinds the stack. By the time your handler sees the error, the site that raised it — the open file, the half-parsed response, the pending edit — is gone. Recovery means guessing what to do from the outside.
Common Lisp has conditions and restarts. When a call fails, the failure site stays live and offers named recovery points: retry, use-value, abort, and any custom restart the code exposes. A handler higher up the stack picks one. Nothing has been unwound. For a coding agent this is what makes "the tool failed — what should I do?" tractable: the model gets to decide from the actual failure context, and its decision is a first-class program value the agent can log, replay, and eventually learn a policy from.
code is data
An agent that improves has to read and write its own tools. In TypeScript that means shelling out to the compiler API, walking an AST, and re-emitting text — a round-trip that loses structure every time. In Lisp, source code and in-memory data are the same shape (s-expressions). A file that describes a tool is a tool. The agent can read it as data, transform it as data, and write it back as data. That is why learning in tasa can mean new tools, not just new prompts.
numbered generations
The consequence of a live image plus code-as-data is that behavior changes at runtime, safely. Every version of the agent's behavior is a numbered generation. A candidate generation is evaluated in a child SBCL process with an explicit capability manifest; if it wins on the Pareto fitness (with hard safety gates), it's swapped into the active image at a safe point. Roll back with one command and the session keeps going.
You can build a version of this on top of Node — spawn child processes, restart on promotion, replay sessions from logs. It costs a lot to build, and every session-drop becomes a user problem. In Lisp it's the shape of the runtime.
side by side
- RuntimeNode process; restart to reload code. State lives in memory the agent can't reach after crash.Live SBCL image. Redefine functions in place; state survives the change.
- ErrorsExceptions unwind the stack. The failure site is gone by the time you get the message.Conditions + restarts. The agent decides what to do at the failure site — retry, substitute, abort — without losing context.
- Extending behaviorNew skill = new prompt or new npm package. Ship a build; hope nothing regresses.New skill = a file that IS a tool. Same shape whether a human, an extension, or the agent writes it.
- IntrospectionAST via TS compiler API, then serialize to strings for the model. Round-trips lose structure.Code IS data. The agent reads, transforms, and rewrites its own building blocks without a serializer.
- Rollbackgit revert + redeploy + restart. Live sessions drop.Numbered generations with atomic swap at safe points. Roll back with one command; the session keeps going.
when typescript is the right call
If you are building a chat wrapper — call model, render markdown, ship — TypeScript is the right call. The ecosystem is enormous, the hiring pool is huge, and none of the runtime traits above matter because the product isn't accumulating anything. Most "AI coding assistants" today are, structurally, chat wrappers with tools.
The trade flips the moment the product is supposed to get better between sessions in a way you can inspect and roll back. At that point the runtime under the agent is the interesting engineering, and TypeScript is fighting it.
tl;dr
A frozen agent is a program. An agent that compounds needs a runtime that lets it be redefined, recover at the failure site, and reshape its own tools without a redeploy. Common Lisp gives you three of those out of the box. TypeScript makes you build them.
;; that is why tasa is written in Lisp — not for the parens.
tasa / blog