← Unix and AI
LinuxToaster · on graph frameworks

The graph follows execution.

LangGraph, CrewAI, AutoGen, Flowise, Dify, n8n. Different products, one assumption: draw the graph first, and the runtime will execute it. Which works when somebody already knows the shape of the work — and is exactly the case autonomous systems are not.

Workflow automation begins with a graph. Autonomous intelligence begins by discovering one.

Unix already has a graph

Every process is a node. Every pipe is an edge. Every socket is another edge. Every machine on the network extends it.

The scheduler already runs it. The kernel already routes between the nodes. The filesystem already stores the state. The network stack already reaches the other machines. None of this needed inventing, and none of it needs a runtime on top.

So LinuxToaster does not add a graph engine. It adds four small programs that know how to be nodes in the one that already exists:

toast reasons. jam runs things, and talks to the other machines. ito keeps a durable record and can undo. squawk carries the conversation — a socket locally, multicast on the LAN, ssh from anywhere.

The graph emerges

Start one process.

$ toast "find every security issue in this repository"

It decides the job splits four ways, so it calls itself four times — toast in .tools means toast can invoke toast. One of those finds another machine on the bus. That machine starts two workers of its own. One checkpoints with ito log. Another asks a third machine for a second opinion.

What is the graph?

Nobody knows. Not because the runtime is hiding it. Because it does not exist yet.

It is being created by the computation, as the computation runs. When everything finishes you can reconstruct it exactly — from the process tree, the messages on the bus, and the ito history, which recorded not only what each step changed but why.

The graph is the trace. It was never the program.

Nobody writes a call graph

Here is a graph. Nodes, edges, control transferring between them:

main
  parse_config
    malloc
    read
    free
  connect
    socket
    malloc
  serve
    malloc
    write
    free

Nobody proposes writing that as the source. Not because it is uninformative — it is one of the most informative things you can look at when something is wrong — but because everyone understands what it is. It is produced by running the program. It is a view of the execution, and the execution came first.

An agent graph is the same shape. Nodes, edges, control transferring between them. The only difference is that somebody decided it should be the input.

There is a question underneath that, and it is the one worth carrying away, because it applies to anything:

Could this have been reconstructed from a record of a run that already happened?

A process tree answers yes. A call graph answers yes. A flame graph answers yes. An approval gate answers no — and not merely because it has to exist before the thing it gates. What a gate encodes is what it would do across inputs, including the ones that never arrived. A run takes one path. A trace is a single sample and a policy is a mapping, and you do not recover a mapping from one evaluation of it.

Note what kind of answer that is. It is a property of the information, not a preference available to whoever is designing the system.

Flame graphs: generated after execution. Process trees: generated after execution. Packet captures: generated after execution. Dependency graphs: generated after execution.

Every one of these is useful precisely because it describes what happened rather than what somebody expected to happen. Turn any of them into a language you write in beforehand and you have lost the property that made it worth having.

The graph is a safety blanket

It is worth being honest about why these frameworks exist, because it is not a bad reason.

A graph is not really a way of expressing a computation. It is a way of bounding one. The sentence underneath most of them is:

I do not know exactly what the model will do, so I will constrain where it can go.

That is a legitimate engineering decision, and at some model capability it was the correct one. But it is not a scaling strategy, because the constraint has to be authored, in advance, by somebody who anticipated the situation.

Every improvement in the model is met with more hand-written orchestration, not less. You now trust it to attempt more. Each new thing it might attempt needs a box drawn around it.

Expert systems failed this way. Not because the rules were wrong — because the rules had to keep being written, and the writing never finished.

Every node is a trust deficit

Watch what a graph framework grows as it matures:

router      planner     retry       judge       validator
critic      memory      approval    fallback    exception handler

Now watch the order they arrive in. Nobody designs a validator on day one because validators are elegant. The model produced nonsense, so a validator went in. It took the wrong branch, so a router went in. It gave up early, so a retry. It failed to notice its own mistake, so a judge. It did something expensive, so an approval gate.

Every new node marks a place where the designer stopped trusting the computation and started trusting themselves.

That is not a complaint, and it is not a matter of taste. It is a countable property of the architecture, and it only ever moves in one direction.

Add them up and you have an inference engine surrounded by hand-written rules about when to believe it. That is an expert system. The novelty is that the inference engine is a language model.

Which is not an insult. Expert systems worked, for a while, in domains where the rules could be enumerated. The question is whether the domain you are in is one of those.

Whose decomposition is it?

Conway's Law: organizations ship systems that mirror their own communication structure. A hand-drawn agent graph is the same effect at a smaller scale — it mirrors how one person, on one afternoon, understood the problem.

Usually Conway's Law is an accident, and something teams work to design around. Normally an organization mirrors itself without meaning to. Here an individual mirrors themselves deliberately, and ships the result as the program.

The graph is frozen cognition: one person's decomposition of the problem, captured at the moment they understood it least — before any of it had run — and then executed unchanged for as long as the system lives.

The trouble is that the more autonomous a system becomes, the less its execution shape can be predicted. Ask something to design a satellite, build a compiler, audit a million lines of code:

How many nodes should the graph have? Unknown. How many retries? Unknown. How many machines? Unknown. How many subtasks will be invented on the way? Unknown.

Any graph drawn in advance is either too simple to solve the problem, or so general that it has quietly become a programming language. And it has frozen one person's decomposition of the work at the moment they had least information about it — before any of it ran.

A graph freezes today's understanding of the problem. The interesting case is when the machine finds a decomposition nobody had thought of.

Ask for something with a known shape — extract these fields, run those three checks, notify that channel — and a graph is a perfectly good way to say it. The decomposition already happened, in somebody's head, before anyone opened an editor. The graph is the write-up.

Now ask for this:

$ toast "audit this kernel for security bugs"

There is no correct decomposition. By subsystem? By bug class? By file age, by contributor, by the way memory moves through it? Nobody knows — and the reason nobody knows is that finding out is most of the problem.

Graph frameworks ask the programmer to finish that search before the computation starts. That is the part they get wrong. Not the graph — the ordering.

The decomposition is not the setup. It is the work, and it is discovered by doing the work. A system that cannot restructure its own approach halfway through is a system that can only solve problems somebody had already solved on paper.

Workflow automation begins with a graph. Autonomous intelligence begins by discovering one.

Complexity does not disappear. It moves.

Graph frameworks

Complexity moves into the orchestration.

LinuxToaster

Complexity moves into the processes.

Here each process decides for itself whether to decompose the task, whether to ask someone else, whether to checkpoint, whether to retry, whether to stop. The orchestration is what those local decisions add up to. That is a distributed system, not a workflow engine — which is a harder thing to reason about in the small and a much easier thing to grow.

State is just data

Persistent state is advertised as a headline feature of graph runtimes. But state is not special. It is data, and durable data was solved decades ago by files, logs, and object stores.

So state here belongs to you rather than to a runtime. It is a file. You can read it with cat, search it with grep, keep it in version control, and delete it. Nothing has to be exported from anywhere, because it was never imported into anything.

The other answer to not trusting the machine

Constraint is one answer to an untrusted worker. There is a second, and it is a great deal older.

A company is full of people who are wrong quite often, and it gets dependable work out of them without anyone predicting in advance what any of them will do. It reviews. It keeps a record of why. It can undo.

Several readers who fail in different places, so an error must survive all of them. A record of the reasoning, not only the change. And the ability to put it back. None of that requires anticipating the work.

The two do not cost the same as the models get better, and that is the whole argument.

To constrain, you have to enumerate: every branch the thing might take, every way it might fail, every case worth stopping for. That list grows with what you are prepared to let it attempt — and what you are prepared to let it attempt is precisely what improves. Capability does not make a model fail more often. It makes you hand it more scope, and the scope is combinatorial.

To review, you look at what came out. One result, however it was arrived at.

constraint  ∝  the space of things it might do
review      ∝  the things it did

One of those is a search space. The other is a single sample from it. They do not grow at the same rate, and the gap widens in the direction everything is moving.

Which is also why review is the older answer. Nobody has ever managed people by enumerating what they might do. The longer version of that argument →

Start with the hardest case, which is not certification. A process tree is ephemeral. Kill the machine halfway through and the kernel's graph dies with it — there is nothing left to tell you what had finished, what was in flight, or where to resume. Anything that must survive a crash and pick up where it stopped has to be written down somewhere durable in advance, and that is a real thing frameworks give you that a shell does not.

Which sharpens the criticism rather than softening it. A framework bundles three things: durable execution, which has to be authored; policy, which has to be authored; and the execution graph, which does not. Two of those are genuine work. The third is a transcription of something the runtime already knows. The mistake was never building the first two — it is shipping all three in one artifact, so the part that could have been observed gets hand-written alongside the parts that could not.

Beyond that, a graph is an excellent representation of a workflow whose structure is already known. ETL. CI/CD. A business process somebody signed off. It is a shared object a team can argue over, diff and version, and that is worth a great deal.

It is also the only right answer when you must prove in advance what a system can do rather than inspect afterwards what it did. Aviation, medical devices, anything certified before it runs. "Run it and see" is not an acceptable answer in those rooms, and no quality of trace makes it one.

So the claim here is not that graphs are wrong, which would be both broader than the argument supports and easy to disprove. It is narrower and much harder to argue with: a graph is the wrong primary abstraction for a system whose decomposition is part of the computation. That is a mismatch between an abstraction and a class of problem, not a defect in the abstraction. Where the structure is known in advance, the graph is the right tool and always was.

One sentence

Graph frameworks say: execution follows the graph.
LinuxToaster says: the graph follows execution.

It sounds like a small reversal. It produces different systems. One asks the developer to predict the shape of a computation before it starts. The other lets the computation discover its own shape and leaves a record of what it did.

Every abstraction that worked went below the line

There is a pattern here, and it has nothing to do with AI.

Assembly programmers allocated registers by hand.
Network programmers implemented retransmission by hand.
Distributed programmers implemented leader election by hand.
AI programmers draw graphs by hand.

Each of those was, at the time, the serious craft of the field. Reputations were made on doing it well. And each ended the same way — not abolished, absorbed. Register allocation went into the compiler. Retransmission went into the kernel. Leader election went into a library, then into a service you rent by the month.

Every one of them went through the same four steps:

1.  we do it by hand
2.  the patterns become obvious
3.  the compiler or the kernel takes ownership
4.  we stop thinking about it

Nobody writes page tables now, and not because page tables stopped mattering. Nobody writes register allocators because registers went away. The work is still being done, several thousand times a second, on the machine you are reading this on. It simply has an owner.

We will stop programming graphs for the same reason we stopped programming page tables. Not because they stopped mattering — because something underneath took ownership.

Which raises the obvious question, and this is where it gets strange.

Something already did. Fifty years ago.

The kernel has maintained a live execution graph since the seventies. The process table is the node list. The parent-child tree is the topology. File descriptors are the edges, and sockets are the edges that reach other machines. pstree renders it. lsof lists the edges. It is scheduled, routed and persisted by code that every program ever written has helped test.

Which means a graph framework is not a young abstraction waiting its turn. It is a substrate concern that was dragged back up into application code — and once you notice that, you notice something else. It did not come back up alone.

There are two graphs

Put a framework's node types on a table and sort them into two piles.

One pile: nodes, scheduling, message passing, process lifetime, state, networking, parent and child. Operating system concerns, every one, and they have been since the seventies.

The other pile: router, planner, retry, judge, validator, critic, approval gate. Those are not infrastructure. Those are admissions.

One graph moves bytes. The other moves confidence.

There are two questions you can ask of any node in an agent framework, and between them they account for very nearly all of them:

Does this exist because the operating system does not provide something? Does this exist because I do not trust the model?

Systems design named that boundary in 1975, when the Hydra papers separated mechanism from policy. The substrate provides mechanism; the application decides policy. It became a rule because the two change at completely different rates, and mixing them means the slow thing inherits the churn of the fast one.

scheduling  is mechanism.     retry       is policy.
sockets     are mechanism.    validation  is policy.
processes   are mechanism.    judges      are policy.

A graph framework draws both with the same boxes and the same arrows, in the same file, at the same level. That is the error, and it is not a new one. It is an error the field named half a century ago and then forgot the moment the boxes started containing prompts.

The first graph already has an owner. The second has an expiration date.

Which is the shortest explanation of why graph programming manages to feel over-engineered and fragile at once. One abstraction is being asked to carry two responsibilities that have nothing to do with each other.

A prediction

So the two halves do not even share a future.

The mechanical graph disappears downward, because it went down half a century ago. It is at step four and has been since the seventies. A framework that reimplements it has not reached step two — it has walked a solved problem back to step one, in a slower language, without the testing.

The semantic graph never reaches step three either, but not because it drains away. That is the tempting inference and it does not survive contact with the argument. Each node is temporary: a validator written because a 2024 model invented citations has a removal condition, and a model that stops inventing them removes it. The class is not. Rising capability gets spent on wider and more consequential scope — the model you trusted to draft a paragraph needed a style check; the model you trust to move money needs an approval gate. The old node retires and a costlier one is written in its place.

Unstable membership. Stable existence. Churn, not disappearance.

And churn is exactly what makes it the wrong thing to freeze into a language. Hydra's argument was never that policy is unimportant. It was that policy changes at a different rate from mechanism, so embedding one in the other propagates the fast thing's churn into the slow one. A framework that writes judge and socket as the same kind of box, in the same file, has arranged for a validator's obsolescence to become a change to the program's structure.

One half is absorbed. The other never settles. Neither is a good thing to write a language around.

What survives is the picture. Today's frameworks make excellent debugging tools — they will draw what an autonomous system actually did, the way a flame graph draws what a CPU actually executed, and most of that work transfers directly. The graph does not disappear.

It moves from the input side of the system to the output side.

You should not have to think about the shape of your agent graph. You should think about it roughly as often as you think about the process table — which is to say, when something has gone wrong and you want to see what happened.

Graphs are observational artifacts, not computational primitives.

The first section of this essay was not an opinion. It was the premise. Unix already had the graph. We simply mistook an observation for a programming model.

The long version, with the classification applied to a framework's documented primitives and predictions that can be checked against release histories: The Graph Follows Execution (PDF).

Where this lands in practice: the six levels of using AI, and why an arrangement can be dependable when its parts are not.