IT Philosophy ■ a thesis, with the strongest case against it
On 10 August 2024 somebody filed bug 1078208 against the Debian package of fail2ban. The title was mild, as bug titles go: the SSH filter did not work with OpenSSH 9.8. Nothing needed to crash for that to be true. OpenSSH had moved part of its daemon into a new process called sshd-session, the name attached to its log lines had changed with it, and the patterns fail2ban uses to recognise a failed login stopped recognising anything. The fix came to two lines, and the reporter added, with the patience of somebody who had tried the obvious thing first, that the first line alone did not solve the issue.
For as long as an affected machine ran that way, its protection against brute force was a daemon watching the authentication log with great attention and finding nothing in it worth acting upon. Its status page, presumably, looked splendid.
That is a small story about a small tool, and I would leave it there if the arrangement behind it were not currently being rebuilt at a scale fail2ban never contemplated, by people with rather larger budgets.
The thesis of this piece is that text remains the universal interface between people and machines, exactly as it has since 1978. What the AI industry is doing now is wiring machines to machines through that same text, and in the process it is removing the one thing that made the text trustworthy, which is somebody reading it.
Why the text worked
Doug McIlroy set the rules down in the foreword to a Bell System Technical Journal issue in the summer of 1978, and one of them reads, word for word:
Expect the output of every program to become the input to another, as yet unknown, program.
In Peter Salus's history of Unix, published in 1994, the idea appears in the form everybody quotes: programs should handle text streams, because that is a universal interface.
He was right, and he still is. service sshd status on FreeBSD answers with a sentence, grep pulls the process number out of it, and a pipeline put together that way in the eighties still runs today, because text has outlived every library that ever promised to replace it.
The reason it works deserves stating precisely. Whoever types that pipeline is present when it runs. If the sentence changes, no number appears, and the person at the keyboard has adjusted the pattern before the kettle has boiled. Text between a person and a machine is forgiving because one side of the conversation can see when the other has stopped making sense.
What fail2ban already knew
fail2ban took the person out of that arrangement a long time ago, and for thoroughly sensible reasons. Nobody wishes to read an authentication log at three in the morning.
So the daemon writes a sentence composed for an administrator, and a second program, written by different people in a different decade, reads it in the administrator's place. On FreeBSD 15 the filter for this one job carries 27 regular expressions, each an attempt to anticipate how OpenSSH might word a failure. They work until OpenSSH words it differently. At that moment nothing reports an error, since from the reader's side a sentence it cannot recognise is indistinguishable from a sentence that was never written.
That is the whole mechanism, and it helps to have it in one place before going further. A writer composing for a person, a reader that is no person at all, no agreement between the two about what the words mean, and a failure that makes no sound.
The same arrangement, with a GPU bill
Now consider what an agent pipeline is. One model receives a task and writes its answer in prose. A second model, or a function wrapped around one, reads that prose and decides what happens next. Several of these get chained together and left to run with tools in hand.
Every element of fail2ban is present. The writer composes for a reader it has never met. The reader is no person. Nobody agreed on the words. And the text itself has taken a different form from McIlroy's, because the writer phrases the same answer differently each time it is asked, and the reader interprets what it receives, much as a person would. That is a considerable improvement on a regular expression, and it also means that when something goes wrong, it goes wrong plausibly.
This has been measured, and by researchers inside the field. A team at UC Berkeley annotated 1,642 execution traces from seven multi-agent frameworks for a paper presented at NeurIPS 2025, and sorted what went wrong into fourteen failure modes. Across the open-source systems they examined, between 41 and 86.7 per cent of tasks failed. The category they call inter-agent misalignment, agents misreading or talking past one another, accounted for 32.3 per cent of those failures. Put plainly, about a third of the failures happened between the agents.
The most instructive figure comes from a vendor. When OpenAI introduced structured outputs in August 2024, it published its own evaluation of how reliably a model followed a complex JSON schema when simply asked to. The previous generation, gpt-4-0613, managed less than 40 per cent. With the schema enforced by the platform, the new model reached 100. A company that builds some of the most fluent writers of prose in existence measured its own model against a format and responded by adding a mechanism that constrains what the model may write.
The strongest objection
The case for prose between agents deserves its best form, because it is a good one.
Natural language is the reason these systems compose at all. Nobody convened a standards body or negotiated a schema, and a model built by one company takes instructions from a model built by another on the first attempt. That is remarkable, and it does for machines in 2026 roughly what McIlroy's text streams did for programs half a century earlier. An interpreting reader is also a different animal from a regular expression. It survives a changed phrasing that would have blinded fail2ban for weeks, and it can ask what was meant.
Nor is the industry naive. Structured outputs and the Model Context Protocol both exist, and many teams running agents in production use them, which is where the objection lands its best blow: if the answer is a schema, this piece is arguing with people who already agree.
Some of them do. The thesis concerns the default, and the default is still a prompt that says "respond in the following format" followed by a parser that hopes. The Berkeley traces came from popular open frameworks built by capable people, and a third of their failures sat in the exchange between agents. The schema is available in the same sense that the fail2ban filter update was available, and it helps the teams that go looking for it.
What the base system did about it
The FreeBSD base system met a version of this problem earlier, on a smaller stage, and the answer it adopted is worth studying for its shape.
Phil Shafer at Juniper Networks wrote a library for a situation his manual describes in plain terms: output had to reach other systems in structured form, and it had to reach people at the command line as text, without separate code paths that "can't help but get out of sync". The library is called libxo. Its manual page records that it first appeared in FreeBSD 11.0, and on the machine I measured, 25 programs in the base system link against it.
The shape matters more than the count. netstat -rn prints the routing table as text for whoever sits at the keyboard, exactly as McIlroy would have it. netstat --libxo json -rn prints the same table from the same code as JSON, for the program that was always going to read it. Nobody at the keyboard has lost anything, and the program reading the JSON receives a form that cannot drift away from the text, because both come out of one source.
Across the fence the same need produced jc, a thoroughly useful piece of work by Kelly Brazil that converts the output of ordinary command-line tools into JSON after the fact. Installed on FreeBSD 15 it brings 220 parsers in 57,683 lines of Python. Its author wrote in 2019 that having to parse unstructured, human-readable output "has made life much much harder than it needs to be", and argued that tools should offer JSON themselves. Most still do not, which is how a converter comes to need 220 parsers.
The limit
The position has edges, and the first of them is at home.
Twenty-five programs out of 873 is a beginning, and a modest one. ifconfig, the network tool people reach for first, does not speak libxo at all, and the parser jc ships for it runs to 728 lines with 31 regular expressions. It reads FreeBSD's ifconfig correctly, which is to say that my own base system needs precisely the after-the-fact reader this piece describes.
A model reading prose also fails less often than a regular expression, and less predictably. Those two properties do not cancel out, and I cannot say which weighs more for a given pipeline. Nobody can yet, and that alone is a reason for caution.
And people still need the words. A pipeline of agents that speaks only in schemas is one no person can follow, and when it goes wrong, somebody has to. That is why the libxo arrangement, both forms from one source, is the right one: it gives the machine something to hold to without taking the text away from the person who will eventually have to read it.
The point
McIlroy asked for programs whose output could become the input to another, as yet unknown, program. Text has made that possible for half a century, and it will go on doing so wherever a person sits at the end of the pipe and notices when the numbers stop arriving.
The agent builders kept the text and let the person go home. What they have built is the arrangement fail2ban has run for twenty years, with a bigger vocabulary and a GPU bill: two programs exchanging sentences at three in the morning, both remarkably articulate, and neither of them obliged to understand the other.