From 9fa3b8800c33ea2705c3f16d5b2574e67830e360 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 28 Feb 2026 13:31:38 +0000 Subject: [PATCH] Add sexp-as-wire-format rationale for AI-driven systems Documents why s-expressions on the wire are a natural fit for LLM agents: fewer tokens, no closing-tag errors, components as tool calls, mutations as agent actions, content-addressed caching. Co-Authored-By: Claude Opus 4.6 --- docs/sexpr-ai-integration.md | 110 +++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 docs/sexpr-ai-integration.md diff --git a/docs/sexpr-ai-integration.md b/docs/sexpr-ai-integration.md new file mode 100644 index 0000000..e7c55cb --- /dev/null +++ b/docs/sexpr-ai-integration.md @@ -0,0 +1,110 @@ +# S-expressions and AI-Driven Systems + +**Why sexp on the wire is a natural fit for AI agents and LLM-driven interfaces.** + +--- + +## LLMs Are Better at Sexp Than HTML + +An LLM generating UI output produces fewer tokens, makes fewer syntax errors, and hallucinates less structure with s-expressions than with HTML: + +```html +
+

Weekend Markets

+

Three new vendors joining.

+ View Details +
+``` + +```scheme +(div :class "card" + (h2 "Weekend Markets") + (p "Three new vendors joining.") + (a :href "/markets/saturday/" :class "btn btn-primary" "View Details")) +``` + +Half the tokens. No closing tags to get wrong. No attribute quoting rules to mess up. The structure is unambiguous — every open paren has exactly one close paren. LLMs already handle Lisp-family syntax well because it's regular and context-free. + +--- + +## Trivially Parseable by Both Machines and Models + +An AI agent receiving a server response as sexp can parse it, reason about it, modify it, and send it back — all without an HTML parser. The AST *is* the wire format. An agent can: + +- **Read a page**: parse the sexp, extract structured data directly from the tree +- **Modify a page**: splice nodes, change attributes, insert components — all tree operations +- **Generate a page**: produce valid output with minimal syntax overhead +- **Diff two pages**: structural comparison on the AST, not string diffing + +With HTML, the agent has to generate a string, hope it's valid, and the receiver has to parse it back into a tree. With sexp, the tree *is* the string. + +--- + +## Components as Tool Calls + +An LLM generating `(use "product-card" :name "Sourdough" :price "3.50")` is structurally identical to an LLM making a tool call. The component registry *is* a tool registry. The parameters *are* tool parameters. + +You could expose your component library to an AI agent as tools and it would produce valid UI as naturally as it calls functions. The agent doesn't need to know HTML structure — it just needs to know the component name and its parameters. + +--- + +## Mutations as Agent Actions + +```scheme +(swap! "#cart" :append (use "cart-item" :name "Bread")) +``` + +This is an action with a target, an operation, and a payload. An AI agent orchestrating a UI isn't generating HTML blobs — it's issuing commands in a format it already understands. + +The `request!`, `swap!`, `batch!` primitives from the sexpr.js runtime plan are already shaped like agent tool calls: + +```scheme +(batch! + (swap! "#notifications" :append + (div :class "toast" "Order confirmed")) + (class! "#order-btn" :remove "loading") + (swap! "#cart-count" :inner "0")) +``` + +An agent producing this is doing exactly what it does when it calls tools — specifying actions with structured parameters. The only difference is that the actions target a DOM instead of an API. + +--- + +## Content-Addressed Components Enable AI Caching + +If an agent has seen `(use "product-card" ...)` before and knows its hash hasn't changed, it doesn't need to re-interpret the component definition. It knows the schema — name, price, image — and can generate invocations without seeing the template. + +This is analogous to how tool definitions are cached in an agent's context. The component registry becomes a stable vocabulary that the agent learns once and reuses across sessions. + +--- + +## Practical Example: AI Page Builder on Rose-Ash + +Imagine an AI assistant that helps users build pages on rose-ash. Today it would need to generate HTML or manipulate a Lexical JSON document. With sexp content, it just produces: + +```scheme +(section + (h2 "Our Markets") + (each markets (lambda (m) + (use "vendor-card" :name (get m "name") :stall (get m "stall")))) + (use "calendar-widget" :calendar-id 42)) +``` + +That's a page with embedded components, data binding, and iteration — and the AI produced it with minimal tokens, no closing tag errors, and using the actual component library as its vocabulary. The server evaluates it exactly as written. The client caches and renders it. The same string works for SSR, client rendering, AI generation, and API responses. + +--- + +## The Deeper Point + +HTML was designed for documents viewed by humans in browsers. S-expressions are designed for trees manipulated by programs. As more of the web becomes program-to-program (APIs, agents, server-driven UI, real-time mutations), the wire format should match. + +Rose-ash already builds its server-side rendering on sexp. Extending that to the wire completes the picture — one format that serves: + +- **Server rendering** (sexp → HTML for SEO/first paint) +- **Client rendering** (sexp → DOM via sexpr.js) +- **API responses** (sexp as structured data) +- **AI generation** (agents produce sexp as tool output) +- **Content storage** (posts, components, layouts all sexp) +- **Real-time mutations** (sexp commands over WebSocket) + +One syntax. Every boundary.