Thomas Gazagnaire

Thomas Gazagnaire

Building Functional Systems from Cloud to Orbit. thomas@gazagnaire.org

Thought Experiment #1

2026-10-16

Yesterday evening I was at the first Thought Experiment, a new series of informal evenings on how "AI is changing the way we code, find vulnerabilities, and prove theorems". It was held at the Paris School of AI, which turns out to be a few hundred metres from the Tarides office, and it was cheerfully organised by Caterina Urban and Karthik Bhargavan, with whom we are sending formally verified post-quantum cryptography to space.

Message sequence charts for agents

Benedikt Bollig (CNRS, LMF) asked whether AI agents can be made to coordinate safely. I read his papers on message sequence charts (MSCs) during my PhD, when I was working on scenario languages built on the same formalism, so I was curious to see where he would take them twenty years later. The answer is agentic workflows. An MSC draws each participant as a lifeline and each message as an arrow between two of them, and that is a good fit for a system of agents: the message-passing structure around the LLM calls is where deadlocks and type-mismatched messages live, and that part can be checked. His paper with Matthias Függer and Thomas Nowak (ISoLA 2026) turns this into a small language in which every choice and every loop is decided by one named participant, who tells the others which branch was taken. A projection then generates each agent's local program from the global chart, deadlock-free by construction whatever the LLM calls return. There is a Python implementation called ZipperGen.

Writing an exchange down once and cutting out each participant's part has a long history, from the realisability of MSCs studied by Alur, Etessami and Yannakakis to the multiparty session types of Honda, Yoshida and Carbone; I spent a few years myself generalising these techniques to more abstract partial-order automata. They stayed in papers because a global specification is long and precise, the proof that an implementation follows it is longer still, and few projects could afford to write either by hand. Over the past year that cost has collapsed, because models now write the verbose parts. So the same ideas are resurfacing twenty years later, with concrete applications, and I find that fascinating.

I have also tried to revive those ideas recently, in a new framework I called ocaml-fsm (it now handles symbolic state machines too, so I should think of another name before a proper release :p). It is early days and very much a prototype, limited to Mazurkiewicz traces for now, with full pomset automata cooking. Used as an oracle, though, it has already found bugs in reviewed and tested code, such as a security association in our CCSDS stack that could be neither created nor deleted. Compared with Benedikt's agents, it works one level down, on the finite control of protocols such as HTTP/2, SSH or the CCSDS security layer, and at the same level in mq, my merge queue, where landing a branch is one description of the flow between the caller, the hook that admits the entry, the run that holds the lock and the resolver that takes its conflicts. The rule is one exhaustive OCaml function. The tool evaluates it over the finite domains of states and inputs, and reports the states the code can never reach and the arrows it can never take, each with the shortest sequence of messages that shows it. Fsm.Global.project does the projection, cutting each party's machine out of one description of the whole exchange, on the boxed-pomset theory Loïc Hélouët and I worked out in 2007 (so the talk felt like familiar ground). A keepalive protocol is written once for both parties, one arrow per message, and the sender of a message the standard leaves optional is the one who decides:

let keepalive ping =
  Fsm.Global.v ~name:"keepalive" ~initial:Fresh ~roles:[ Client; Server ]
    ~pp_role ~controls:[ Fresh; Live; Over ] ~pp_control:pp_phase
    ~arrows:
      [
        Fsm.Global.arrow ~sender:Client ~receiver:Server Hello;
        Fsm.Global.arrow ~sender:Client ~receiver:Server Ping;
        Fsm.Global.arrow ~sender:Client ~receiver:Server Bye;
      ]
    ~pp_message ~error:pp_error ~finals:[ Over ]
    (fun phase (arrow : (role, message) Fsm.Global.arrow) ->
      match (phase, arrow.message) with
      | Fresh, Hello -> Fsm.goto Live ~effects:[]
      | Fresh, (Ping | Bye) -> Fsm.refuse Not_open ~effects:[]
      | Live, Hello -> Fsm.refuse Busy ~effects:[]
      | Live, Ping -> ping ()
      | Live, Bye -> Fsm.goto Over ~effects:[]
      | Over, (Hello | Ping | Bye) -> Fsm.refuse Finished ~effects:[])

Fsm.Global.project then gives each party its own automaton. The client may send a ping or not; the server takes it whenever it comes.

# let () = show_party Client "send ping to server";;
live on send ping to server: goto live
live on send ping to server: reject quiet
latitude on send ping to server in live: Example 5.1, recommended false
# let () = show_party Server "receive ping from client";;
live on receive ping from client: goto live
no latitude

The realisability check runs when the description is built, so a test that constructs it is enough to catch it (the types do not, yet). If accepting the ping could lead to two different phases, the server could not tell from the message which one the client chose, so the constructor refuses the description. The message names the cell, the ping at live, and the two phases, live and over, that the receiver would have to choose between without being told.

keepalive (fun () ->
    Fsm.may ~clause:"Example 5.2"
      [ Fsm.goto Live ~effects:[]; Fsm.goto Over ~effects:[] ])

Proofs on machine code, and the oracle problem

Úlfar Erlingsson (Google Cloud Security) had the best title of the evening, "Petrifying the dungheap: Better software through bookkeeping". His starting point is that finding bugs has become so easy that the software we all build on stands revealed as a dungheap. We see it in the OCaml ecosystem too, from two directions. The frontier models now write OCaml well (a few of us wrote last year about what it would take), so the bug and security reports reaching the OCaml compiler and the libraries around it have become noticeably better, and a report now tends to come with a patch that compiles and a test that fails without it. And Scrutineer, Andrew Nesbitt's agentic scanner, finds plenty: by the end of September a dozen OCaml developers were triaging on Anil's instance and twenty-four fixes had been merged in ocaml-tls, ocaml-x509, mirage-crypto and others. Type safety definitely helps here, but there is still a surprising number of bugs lying around in our nice OCaml libraries.

Úlfar's bookkeeping is for the code that cannot be rewritten in a safer language, because its performance and memory footprint have to stay where they are, and because even its bugs are part of its contract: fix one and something that relied on it breaks. The idea is to prove pre- and post-conditions directly on the machine code, with neither the source nor the compiler in the trusted base, and to use the proofs to find memory-safety bugs in software as it ships. The proofs are generated, which raises the question every generated artefact raises: is the program the proof was built for the program that ships? His answer is the cheapest oracle there is, a byte-for-byte comparison of the generated binaries.

I met the same question porting Tailwind to OCaml, where models wrote much of the code. The first check was bytes against the reference compiler's output, then a CSS-aware diff, because the reference emits spellings that differ without mattering. But a model can change a test and the code it checks in the same commit, so the check that decides has to share no code with either tool. In my case it is Chrome rendering both stylesheets and comparing pixels. Úlfar's bytes and my pixels do the same job. Once a model generates the artefact, the oracle has to sit outside anything the model can touch, and the simpler it is, the easier it is to trust.

AI tools for democracy

Lê Nguyên Hoang (Télécom Paris) closed with "AI tools to democratically govern AIs". He started from the attacks: AI is being used against democracies everywhere, in disinformation and in the recommendation systems that amplify it. His point is that the same tools can make democracies harder to attack, with better recommender systems and tools for building consensus, and Taiwan's g0v civic-tech community was his running example. (He is also behind Tournesol, a participatory recommender.) I am halfway through Plurality by Glen Weyl and Audrey Tang, so the talk landed on prepared ground.

It also made me finish a small thing I had been playing with, a page on French public money that lays out the official figures, each number linking to the Eurostat or Insee cell it came from and the arithmetic in between. It is a visualisation tool and nothing more. I have a few other ideas for this kind of data (one of them is a map of every commune, below), and I will write about them if they become something.

Screenshot of the home page of the public-money page: a short description, then one card per question, with each figure underlined as a link to its source
The home page of the public-money page. Each underlined figure opens its source.
An animated dark map of France in which each commune is coloured by the resource it uses most per resident relative to the national average, playing year by year from 2006 to 2027
The 35,000 communes, each coloured by the indicator per resident furthest above the French average (energy, water, farmland, building materials, commuting wages, jobs, industrial CO₂), year by year from 2006 to 2027.

References

Related Posts