This is for Everyone, Even AI Agents

I recently read Sir Tim Berners-Lee‘s (@timbl) memoir This is for Everyone: The Unfinished Story of the World Wide Web. This is a wonderful history of Berners-Lee and his creation of the world wide web, as well as its continued impact on society. One of the most beautiful elements is his continued pursuit of making data, information, publishing, etc. available and accessible for every human. Many might be surprised by his optimism towards LLMs and their many uses to help humans continue to flourish. If you have not read the book, I recommend you purchase a copy. Stephen Fry provided the audio narration. As a fan of both Fry and audiobooks, I specifically recommend that format.

I was a bit surprised at Tim’s praise and optimism around LLMs. For some reviewers, his human-centric philosophy appears to be at odds with his optimism towards LLMs. I think he may see something others are missing. LLMs can indeed offer greater access to capabilities previously available only to those with the requisite skill or education. I think he sees LLMs providing a bridge to more fully realizing the message he shared at the 2012 London Olympics Opening Ceremonies: “This is for everyone”.

In the last post I built tic-tac-toe the Frank way: a hypermedia app where the server is the single source of truth, every screen is server-rendered HTML, and useOpenApi provides a means of self-description. A person plays it by reading the page and following links. Can a software agent successfully play a hypermedia game? Does this work better than MCP or other RPC approaches? And if so, what can it tell us about enabling the web for both humans and agents?

The easy answer is yes, and it is the wrong answer. Point an LLM at the root URL and tell it to play, and it will eventually finish a game by guessing URLs, getting rejected, and guessing again. “Can it” almost always resolves to “eventually, with enough retries.” How many retries, how much context is burned re-reading errors and re-deriving what to do next, and how often does a session give up before the game ends?

Continue reading

Tic-Tac-Toe the Frank Way

Tic-tac-toe is a game a child can play. So why build it as a multiplayer, real-time, statecharts-driven web app with authentication and an OpenAPI document? Because it is the smallest thing I could build that exercises everything Frank 7.2.0 added at once. It is also a hat tip to Scott Wlaschin, whose tic-tac-toe I used as inspiration.

The app lives at frank-fs/tic-tac-toe and brings the whole stack together:

  • Two players, X and O, from separate browsers, with multiple concurrent games on one page.
  • Real-time updates: every move streams HTML patches to all connected clients over SSE.
  • Cookie-based authentication and per-game X/O assignment, declared with Frank.Auth.
  • The game’s behavior modeled as a statechart, with parallel regions for play and player identity.
  • Zero client JavaScript and zero-copy rendering straight to Kestrel’s response pipe.
  • An OpenAPI document, published by useOpenApi.
Continue reading

Frank.Datastar

Frontend development has become a monstrosity, and Frank.Datastar is my response. It’s a Frank-native Datastar integration that lets the server send HTML over Server-Sent Events instead of shipping a state machine to the browser.

Frank.Datastar, shipped with Frank 7.2.0, provides:

  • A single datastar operation on a resource that manages the SSE stream for you.
  • Static Datastar.* helpers (patchElements, patchSignals, and friends) that read the HttpContext directly, with no service to register.
  • Stream-based overloads (streamPatchElements and friends) that render straight to the response for the F# view engines.
  • Full Datastar SDK ADR compliance, targeting net8.0 through net10.0.
Continue reading

Frank.Analyzers

An HTTP resource has one handler per method: one GET, one POST, and so on. That’s how Frank models a resource, and the F# type system won’t stop you from breaking the rule, but Frank.Analyzers will flag this class of bug and fail at compile time. Like the rest of Frank, it’s opt-in: a recommended, not required, package that makes an app more predictable by catching mistakes the compiler can’t.

Frank.Analyzers, shipped with Frank 7.2.0, provides:

  • A compile-time check that flags duplicate HTTP handler registrations within a resource.
  • Coverage of all nine HTTP method operations.
  • The same rule in your editor (Ionide, Visual Studio, Rider) and on the command line for CI.
  • Built on the FSharp.Analyzers.SDK, targeting net8.0, net9.0, and net10.0.
Continue reading

Frank.Auth

Authorization in Frank should read like part of the resource, not ceremony bolted onto the side. Frank.Auth, shipped with Frank 7.2.0, puts the access rules in the resource definition.

Frank.Auth provides:

  • Resource-level requirements declared in the resource CE: requireAuth, requireClaim, requireRole, and requirePolicy.
  • Host-level wiring on WebHostBuilder: useAuthentication, useAuthorization, and authorizationPolicy.
  • AND semantics, so stacked requirements all must pass.
  • A thin layer over ASP.NET Core’s own authentication and authorization, not a replacement.
Continue reading

Frank.OpenApi

OpenAPI support in Frank has been a long time coming. I opened an issue asking for it in January 2019. It’s here now, and it’s declarative: you describe what an endpoint produces and accepts, and the document falls out of that.

Frank.OpenApi shipped with Frank 7.2.0 and provides:

  • A handler computation expression that pairs a handler with its OpenAPI metadata: name, summary, tags, and the types it accepts and produces.
  • F# type schemas (records, DUs, options, collections) generated through FSharp.Data.JsonSchema.OpenApi.
  • useOpenApi to wire the services and middleware, with Scalar UI as a browser client in the box.
  • Targets for net9.0 and net10.0.
Continue reading

Frank Extensibility

Frank 7.2.0 adds a small extensibility point that lets companion libraries attach behavior to an endpoint without changing, or even knowing about, the core. This version also provides greater control for ordering middleware around routing.

Frank 7.2.0 makes two additions to the core:

  • Metadata on ResourceSpec — a list of (EndpointBuilder -> unit) convention functions applied as each endpoint is built, letting companion libraries (Auth, OpenApi, Datastar) attach endpoint metadata without changes to core Frank. Binary-breaking, but source-compatible with the empty default.
  • plugBeforeRouting — with plugBeforeRoutingWhen and plugBeforeRoutingWhenNot, for controlling middleware ordering around UseRouting().
Continue reading

FSharp.Data.JsonSchema v3.0.1

FSharp.Data.JsonSchema is three packages instead of one now, the old name still works, and a pile of bugs that had been open for years are finally fixed. Most of it shipped in 3.0.0, with a follow-up in 3.0.1, the current release.

  • FSharp.Data.JsonSchema.Core: Core JSON Schema representation types that relies on only FSharp.SystemTextJson. This library can be uses to parse a JSON Schema into F# types and F# types into a JSON Schema. However, it no longer has a specific target.
  • FSharp.Data.JsonSchema.NJsonSchema: This is equivalent to the previous version with a dependency on NJsonSchema as the target. This should be backward compatible with previous versions of the library.
  • FSharp.Data.JsonSchema.OpenApi: The new target depends on the Microsoft.OpenApi library introduced with the net9.0 framework target. This target is intended for use with generating Open API documents from ASP.NET Core applications.
Continue reading

AI DevOps Podcast

I recently had the pleasure of joining Jeffrey Palermo on the AI DevOps Podcast. You can find the episode here. We talked about several of my recent posts, including why I have delayed writing more. If you are curious, you can listen to the podcast. While you are at it, check out some of the other episodes. Jeffrey has and continues to produce excellent content.

If you want a tease, it reduces to this: after some early success using Claude to help build the Frank.OpenApi and Frank.Datastar extensions, I boldly explored some more challenging extensions and discovered the agents silently deferring, lying through type and test names, and falsely reporting success. I finally restarted the work recently after learning some techniques to avoid, identify, and otherwise resolve such issues. Some of those I’ve learned from others; others I’ve developed myself. Those will be the subject of their own, future posts.

Nothing New Under the Spec

I recently stumbled across Cameron Sjo‘s spec-compare project while working on a similar comparison of my own. The project is outstanding. Compiling, testing, and documenting this many tools and frameworks takes considerable effort, and the findings are immediately useful if you’re evaluating spec-driven approaches for your team. I’m grateful someone else did this work and shared it openly. If you haven’t explored the project yet, it’s well worth your time.

Then I read the Critical Analysis.

The practical observations about AI adherence, review burden, and especially the MDD parallel are solid work. Where it lost me was the historical framing. The analysis builds its central tension around whether SDD is “waterfall dressed in AI clothing.” This is built on several layers of popular misconception, and like a house of cards at a toddler’s birthday party, it doesn’t survive contact with much scrutiny.

Continue reading