Why AI Agents Keep Reinventing the Same Runtime and What Algebraic Effects Have to Do With It
Retries, tools, approvals, memory, tracing, replay, fallbacks and parallel agents look like separate problems. A 20-year-old idea from programming-language research suggests they may be different versions of the same deeper problem.
By Ghulam Murtaza
There is a slightly funny pattern happening in AI engineering right now.
Build a simple agent and the code feels almost suspiciously small:
question → model → answer
Give it a tool and it is still manageable.
question → model → tool → model → answer
Then somebody says the tool can fail.
So you add retries.
Then the model should fall back to another provider. Some tools need human approval. Search and retrieval should run in parallel. The agent needs memory. Every action needs tracing. Expensive runs need budgets. A cancelled task should stop its children. Tests should not call the real APIs. And if the agent makes a bad decision halfway through a twenty-minute run, ideally we should not replay twenty minutes of expensive work just to try another branch.
At some point the innocent little agent loop has turned into something that looks suspiciously like a runtime.
How the tiny agent loop grows up
Retries
Fallbacks
Tool policy
Memory
Approvals
Tracing
Budgets
Replay
↓
“Simple” Agent Runtime
Model + tools + increasingly serious control flow
What is interesting is not that we need these features. Of course we do.
What is interesting is how often we keep building the same features in different places.
Open one agent SDK and you find tool middleware. Open another and you find guardrails. Another has interceptors. Another has approval gates. Another has checkpoints and replay. Another has model fallback middleware. Another has an execution policy around tools.
Different APIs. Different names.
But there is a shape hiding underneath them.
I ended up following that shape down a fairly deep rabbit hole, and unexpectedly, it led back more than twenty years — to programming-language researchers thinking about exceptions, state, nondeterminism and something called algebraic effects.
The weirdly relevant 20-year-old idea
Before talking about AI, it is worth getting one thing straight: algebraic effects were not invented for agents, LLMs or even JavaScript.
The roots go back to work on how programming languages should give a clean mathematical meaning to things that are not just “take an input, calculate a value, return it.”
Programs do messy things.
They read state. They write state. They throw errors. They perform I/O. They make choices. They start concurrent work.
Eugenio Moggi's earlier work gave computational effects a common monadic semantics. Gordon Plotkin and John Power then developed an algebraic account of effect operations. Their 2003 paper, Algebraic Operations and Generic Effects, showed a deep correspondence between algebraic operations and what they called generic effects.
Then, in 2009, Plotkin and Matija Pretnar developed the general idea of handlers of algebraic effects.
The theory kept moving.
A very compressed family tree
1989–1991
Moggi
Computational effects through monads
→
2001–2003
Plotkin + Power
Algebraic operations & generic effects
→
2009
Plotkin + Pretnar
General effect handlers
→
2015–2017
Eff + Koka work
Handlers, effect types, structured async
→
2022+
OCaml 5
Production effect handlers with one-shot continuations
→
2025–2026
LLM / Agent research
Effects meet models, tools, traces, replay and policy
Daan Leijen later showed how asynchronous programming, cancellation and timeouts could be expressed through effect handlers. Eff explored effects as first-class programming constructs. OCaml 5 brought effect handlers into a mainstream production language runtime.
And recently, the same ideas have begun turning up explicitly in LLM research.
But before getting there, we need a mental model that does not require a category-theory textbook.
Algebraic effects without the math headache
Imagine you walk into a restaurant and say:
“One coffee, please.”
You have specified what you want.
You did not specify which espresso machine should be used, which barista should make it, whether the beans come from grinder A or grinder B, or what should happen if the coffee machine is broken.
Your request is separate from its interpretation.
That is roughly the useful intuition behind an effect operation.
Generate(prompt)
Search(query)
ReadMemory(key)
UseTool(name, args)
AskForApproval(action)
Those operations describe things the computation wants to happen.
A handler decides what they actually mean in the current environment.
Maybe Generate calls one model in production.
In testing, the same operation returns a recorded fixture.
In an evaluation run, it generates sixteen candidates and selects the strongest one.
In a private environment, it routes to a local model.
The program asked for “Generate.”
The handler decided what “Generate” meant here.
The basic effect-handler loop
1 · AGENT LOGIC
“I need a model response.”
→
2 · OPERATION
Generate(prompt)
→
3 · HANDLER
Choose provider, retry policy, budget, tracing…
→
4 · WORLD
Model / API / DB / Tool
The result comes back → the paused computation continues
And then there is the continuation
This is the piece that makes effect handlers more interesting than ordinary middleware.
When an effect is performed, think of the runtime putting a bookmark into the computation.
The bookmark means:
“Here is everything that still has to happen after this operation finishes.”
That “rest of the computation” is the continuation.
The handler can potentially resume it with a result, abandon it, transform what happens next, or — in systems with multi-shot continuations — explore it more than once.
An exception handler usually gets control after the stack has been unwound.
An algebraic effect handler can get control together with a representation of what should happen next.
That distinction sounds theoretical until you think about an agent waiting for a human to approve a payment.
Suddenly “pause here and continue later” feels very practical.
A small technical footnote that actually matters
People often use “algebraic effects” casually to mean “anything involving side effects and handlers.” The original theory is more precise than that.
An algebraic operation has specific compositional properties. Handlers are interpreters of those operations; they are not merely another operation. And continuations themselves have historically sat at an interesting boundary because unrestricted continuation behavior does not fit every algebraic theory cleanly.
For our purposes, the important engineering idea is still straightforward:
Describe the operation separately from the policy that decides how the operation reaches the world.
Now look at an AI agent through that lens
A modern agent is basically surrounded by effects.
What the agent wants
Conceptual operation
What the environment may decide
Generate something
Generate
Provider, model, sampling, fallback, budget
Search
Search
Backend, cache, ranking, parallelism
Call a tool
UseTool
Validation, permission, sandbox, execution
Remember something
PutMemory
Session store, DB, retention rules
Read memory
GetMemory
Source, permissions, consistency
Start parallel work
Fork
Scheduler, limits, ownership
Wait for workers
Join
Failure semantics, cancellation
Request risky action
Approve
Auto-allow, deny, human review
Report an expected failure
Fail
Retry, fallback, repair, escalate
Emit execution information
Trace
Logs, spans, audit, cost accounting
Now the interesting question becomes less:
“How do I add retries to this agent?”
and more:
“What does the surrounding environment do when this agent performs an operation?”
That sounds like a tiny wording change.
Architecturally, it is not.
The same agent, three completely different worlds
Suppose the business logic contains this:
plan = Generate(planningPrompt)
docs = Retrieve(plan)
result = UseTool("analyze", docs)
answer = Generate(finalPrompt(result))
Notice what is missing.
No provider names.
No retry loop.
No test mocks.
No tracing calls.
No cache implementation.
No approval UI.
Now imagine running that exact logical workflow under three different interpretations.
SAME AGENT PROGRAM
Generate → Retrieve → Tool → Generate
↙ ↓ ↘
Production
Real model
Live retrieval
Real tool
Retry + fallback
Tracing + budgets
Test
Deterministic model fixture
Local documents
Fake tool result
Injected failures
Zero external side effects
Replay / Evaluation
Recorded model output
Recorded tool result
Alternate policy
Different evaluator
Compare another future
The point is not that this is impossible with ordinary functions. Obviously it is possible.
The point is that effect handling tries to make this separation a first-class programming model rather than a convention every framework reinvents slightly differently.
And this is where the recent AI research gets genuinely interesting
This is no longer just a thought experiment.
1. LLM calls themselves are starting to be treated as effects
In 2025, Di Wang presented Composable Effect Handling for Programming LLM-Integrated Scripts at the ACM SIGPLAN LMPL workshop.
The idea was direct: separate the workflow from operations such as LLM calls, I/O and concurrency, then interpret those operations through composable handlers.
The interesting result was not just cleaner code.
In a Tree-of-Thoughts case study, the paper reports roughly a 10× speedup by changing how operations were handled and parallelized without giving up the modular workflow structure.
That is a case-study result, not “algebraic effects make every agent ten times faster.”
But it demonstrates something important: separating intent from execution can expose optimization opportunities that are awkward when concurrency is welded directly into the workflow.
2. Pangolin asks an even stranger question
A 2025 workshop paper called Programming Large Language Models with Algebraic Effect Handlers and the Selection Monad sketches Pangolin.
Its core move is fascinating: treat an LLM interaction itself as a first-class algebraic effect.
The program might ask for an LM operation.
One handler calls a model once.
Another handler samples many candidates in parallel.
Another can try different model providers.
The program's logical structure can remain the same while the interpretation changes.
Pangolin then mixes this with the selection monad so different futures can be scored by what happens after the generated value.
This is where the idea becomes more interesting than ordinary provider abstraction.
It is not just:
OpenAI.generate(...)
vs
Anthropic.generate(...)
It can become:
“At this point in the program, explore several possible model outputs,
continue the downstream computation for each,
score what those futures become,
then choose.”
That starts to look a lot like test-time search expressed at the programming-language level.
3. A 2026 paper gives prompt pipelines actual algebraic semantics
J. J. Segura's 2026 paper in Computing is useful because it is much less hand-wavy.
It models a bounded class of prompt pipelines using algebraic effects, with primitive operations for retrieval, state, exceptions, LLM invocation and typed tool invocation.
And this part matters: the same source computation can be interpreted as live execution, logging, replay, sandboxing or testing.
The paper also does something responsible that many architecture posts do not: it clearly states its boundary.
Cyclic orchestration is handled through finite-depth unrolling. Unbounded recursion and probabilistic LLM behavior are left for extensions.
In other words, formal semantics help precisely because they force you to say what you have not solved.
4. Shepherd turns agent execution into something you can manipulate
Shepherd, a 2026 research system for meta-agents, moves in another interesting direction.
Model actions, tool calls and environment changes are represented as typed effects in execution scopes. Execution becomes a Git-like trace that a meta-agent can inspect, fork and replay.
Imagine debugging an agent the way you use Git:
good state
|
+--- bad decision --- tool call --- failure
|
+--- alternative decision --- different suffix
Instead of treating the run as a pile of chat messages that happened in the past, the execution itself becomes an object another program can work with.
That is a very natural idea once effects are explicit.
If “what touched the outside world” is represented structurally, we can observe it, record it, reinterpret it, and — where the underlying action is actually reversible — undo or branch around it.
5. ETAS asks what happens when effects become permissions
Another 2026 preprint, ETAS, pushes the idea toward agent authority.
Instead of treating tools, approvals, memory and execution traces as loose framework conventions, it makes them part of the language's semantic model.
A computation carries information about what effects may escape and also about the actions it attempted to request.
That second part is subtle.
Suppose an email-send operation is intercepted by a dry-run handler. No email is actually sent.
For ordinary control flow, the effect may be considered “handled.”
For security and auditing, however, the fact that the agent asked to send an email may still matter.
That distinction — handled does not mean forgotten — feels extremely relevant to agent systems.
Now comes the part I did not expect
You can look at current agent platforms and see many of these ideas being rediscovered independently.
To be very clear: this does not mean OpenAI, Anthropic, LangChain, Microsoft or Google secretly implemented Plotkin–Pretnar algebraic effect handlers.
They did not announce that.
What is interesting is the architectural convergence.
Not algebraic effects — but notice the shape
Modern agent stacks keep creating interception boundaries
OpenAI Agents SDK
Tool guardrails · approvals · resumable RunState · tracing spans
Anthropic
Structured tool requests · application-side execution · allow / ask / deny policies
LangChain / LangGraph
Retry middleware · fallback · HITL · tool interception · checkpoint forks
Microsoft Agent Framework
Middleware · policies · exception handling · HITL · checkpoint/resume
Google ADK
Before/after agent, model and tool callbacks · sandboxed execution
Effect 4
Typed errors · structured concurrency · retries · services · AI Tool + Toolkit handlers
OpenAI's current Agents SDK has guardrails around individual tool invocations. A tool can be rejected before it executes. Human approval can pause a run and later resume from serialized state. Tracing treats generations, tool calls, handoffs and guardrails as distinct structured events.
Anthropic describes tool use as a contract: the model requests an operation; the model itself does not magically execute your application code. Managed Agents add policies that can allow, deny or pause tool execution for approval.
LangChain exposes middleware around model and tool calls. Its current middleware catalog includes tool retry, model fallback, human approval, model-call limits, tool-call limits, context editing and tool emulation. LangGraph adds checkpoints, replay and branching from earlier states.
Microsoft's Agent Framework describes middleware almost exactly the way you would expect a cross-cutting interpreter layer to be described: wrap the execution pipeline, inspect or modify requests and responses, centralize logging, guardrails, rate limits, retries and telemetry without modifying the agent's core logic.
Google's ADK exposes callbacks before and after agents, model calls and tool calls, while its runtime work increasingly separates agent logic from sandboxed execution environments.
And Effect 4 is particularly relevant on the TypeScript side. Its current AI APIs explicitly separate tool definitions from handlers through Toolkits, while its broader runtime provides typed errors, retry schedules, structured concurrency, dependency injection and observability.
Again: these systems are not the same thing.
But they are all discovering a useful truth:
Once agents can act, you need places where “the agent wants to do X” can be separated from “X is actually allowed and executed this way.”
Retry is a perfect example of why this matters
Imagine a researcher agent asks for a generation.
report = Generate(prompt)
Now the provider returns a rate-limit error.
Should the research logic know that this provider should be retried three times with exponential backoff?
What if next month the infrastructure team changes the fallback order?
What if low-priority requests should wait while premium requests immediately switch models?
What if tests should fail instantly rather than sleeping for backoff?
This is exactly the sort of behavior that feels more natural as an interpretation of Generate than as part of the researcher's reasoning.
Generate(prompt)
↓
RetryHandler
↓
BudgetHandler
↓
FallbackHandler
↓
ProviderHandler
↓
actual model
There is a caveat, though.
Handler order can matter.
Does the budget wrap the retry policy, so every attempt counts?
Or does the retry policy sit inside some higher-level transaction?
This is exactly why the “algebraic” part matters. The dream is not merely a pile of middleware. The dream is to understand which operations and handlers obey laws that make composition predictable.
Tool calls are where this becomes more than elegant code
Consider:
UseTool("send_invoice", {
customer: "Acme",
amount: 18000
})
The LLM can decide that sending an invoice is useful.
It should not necessarily decide that the invoice is allowed to leave the building.
Between intent and reality we may need:
schema validation
↓
authentication
↓
authorization
↓
risk classification
↓
human approval
↓
idempotency check
↓
audit record
↓
actual execution
That boundary is extremely important for MCP-based systems too.
MCP can standardize how a model-facing application discovers a capability and communicates with it.
But “the model can describe this tool” is not the same as “the model has unconditional authority to materialize this side effect.”
One is a protocol problem.
The other is an execution-semantics and policy problem.
Algebraic-effect thinking is interesting precisely at that second boundary.
Human approval makes continuations stop feeling academic
Suppose an agent reaches this step:
result = Approve({
action: "send_payment",
amount: 18000
})
sendReceipt(result)
The rest of the computation — sendReceipt and everything after it — cannot run yet.
Put a bookmark there.
A continuation is basically a bookmark in the running computation
Prepare payment
→
perform Approve
execution suspended here
→
Handler asks human
Approved
Resume the bookmarked continuation → send receipt → continue
Rejected
Do not resume that path → return denial / choose another route
This is conceptually close to what modern agent SDKs already expose as interruption and resume.
But we should not confuse two things.
A language continuation is a control-flow abstraction.
A serialized agent checkpoint is a durability mechanism.
If your approval arrives three days later after two server deployments, you still need durable storage, versioning and recovery.
Algebraic effects do not make persistence disappear.
Parallel agents have a parenting problem
Agent demos love parallelism.
Spawn three researchers. Ask four models. Run five tools.
Starting work is easy.
Owning it is harder.
Suppose the parent research task is cancelled while four children are still running.
Do the children stop?
Can they still write memory?
Can they still call a paid API?
What happens when one child fails after the parent has already returned?
This is where structured concurrency enters the story.
A useful analogy is a family leaving an airport.
The parent task should not happily get into a taxi while three child tasks are still wandering around Terminal 2 buying API tokens.
The scope that created them should own their lifetime.
This is not a fringe connection. Daan Leijen's 2017 work used algebraic effects to structure asynchronous computation, including cancellation and timeouts. And a POPL 2026 Distinguished Paper gives algebraic semantics to dynamic threads built around operations such as fork and wait.
For multi-agent orchestration, that matters because concurrency is not merely “run promises in parallel.”
It is a lifetime and ownership model.
One-shot vs multi-shot: the difference between a bookmark and a photocopier
This distinction deserves an analogy because it is easy to get wrong.
A one-shot continuation is a bookmark you may use once.
You pause here. Later you resume from here. The bookmark is consumed.
That is the discipline used by current OCaml effect handlers. The current WebAssembly stack-switching proposal is also designed around one-shot continuations.
A multi-shot continuation is more like photocopying the bookmark before you use it.
Now you can explore several futures from the same point:
same earlier computation
|
+── resume with strategy A
|
+── resume with strategy B
|
+── resume with strategy C
That is powerful for backtracking, probabilistic programming, search and some forms of speculative computation.
It also becomes much harder when the world contains mutable state, files, sockets, payments and other things you cannot casually duplicate.
This is why “multi-shot would be great for Tree of Thoughts” is true but incomplete.
Branching a pure reasoning continuation is one thing.
Branching a continuation after one branch has already emailed the customer is another.
That brings us to the annoying thing called reality
Effects inside a mathematical model are beautifully composable.
The real world is less cooperative.
If an agent writes a temporary file, we can probably roll it back.
If it modifies a database, maybe we can compensate.
If it charges a payment method, perhaps we can issue a refund.
If it sends an email, there is no unsendToTheEntireInternet() operation we can rely on.
This is why recent agent research is also exploring transactions and effect staging.
Cordon, for example, argues that tool-using agents need a task-level containment boundary that can stage externally visible actions before commit rather than treating every tool call as an isolated RPC.
This complements the effect-handler story rather than replacing it.
An effect handler can give us a clean place to intercept an action.
Transaction, idempotency and compensation mechanisms decide whether that action can be safely committed or recovered.
Replay is also more subtle than “just run it again”
Say you checkpoint an agent after step five.
You replay from there tomorrow.
The same LLM call may return a different answer.
The search index may have changed.
The API may return different data.
A tool may not be idempotent.
LangGraph's current time-travel model makes this distinction clear: nodes after a checkpoint are re-executed, including LLM calls and API requests, so they may produce different results.
Shepherd explores a different model in which effects and environment state are recorded more directly so a meta-agent can manipulate execution history as a first-class object.
Neither approach is universally “right.”
They answer different questions.
But making effects explicit helps us ask the question clearly:
When I replay this computation, which effects am I replaying from history, which am I re-executing, and which must never happen twice?
What about memory?
Memory often starts life as one object:
state = {
messages,
facts,
plan,
toolResults,
userProfile,
checkpoints,
...
}
Then every component receives it, mutates it or returns a slightly different version of it.
Sometimes that is exactly the right architecture.
But algebraic-effect thinking gives us another mental model:
user = GetMemory("user")
PutMemory("plan", plan)
history = Recall("research_context")
The business logic says what state capability it needs.
A handler decides whether that means an in-memory map, database transaction, persistent session, read-only snapshot or test fixture.
This idea connects back to the much older theory of state as a computational effect. Your fancy agent memory system and a decades-old programming-language paper are, unexpectedly, distant relatives.
Observability also becomes less invasive
Today we often write instrumentation around everything:
startSpan()
callModel()
recordTokens()
recordCost()
endSpan()
startSpan()
callTool()
recordResult()
endSpan()
But if Generate, UseTool, Approve, Fork and WriteMemory are already explicit semantic boundaries, those boundaries are natural observation points.
That is roughly why modern agent SDKs already trace model generations, tool calls, handoffs and guardrails separately.
Once operations are structured, logging stops being random print statements and starts becoming an interpretation of execution.
Testing is probably the least glamorous argument — and one of the strongest
Suppose you are testing an agent that normally:
calls a model
searches the web
reads Salesforce
sends an email
writes memory
You do not want that unit test accidentally sending “hello from CI” to a real customer.
If the external interactions are represented as operations, the test environment can provide different handlers.
Generate → deterministic fixture
Search → local documents
ReadCRM → fake record
SendEmail → capture intent, do not send
PutMemory → in-memory store
The test now asks:
“Given these interpretations of the outside world, what does the agent logic do?”
That is a much cleaner question than mocking random SDK methods scattered across the application.
So are algebraic effects the replacement for LangGraph, Temporal and MCP?
No.
And treating this as a framework-versus-framework argument misses the point.
Different questions, different abstractions
Graph / Workflow
Where can control go next? What depends on what?
Effect / Handler
What operation is being requested, and what does it mean here?
Durable Workflow
How does execution survive crashes, restarts and long waits?
MCP / Tool Protocol
How are capabilities described and communicated across systems?
LangGraph gives you explicit graph structure, checkpoints and workflow state.
Temporal gives you durable execution across failures and long periods of time.
MCP standardizes communication with external capabilities.
Effect handlers address a different concern: separating the request for an operation from the interpretation of that operation.
Those things can stack.
An Approve effect might be handled by a durable workflow that waits three days.
A UseTool effect might ultimately call an MCP tool.
An effectful program could live inside a graph node.
A durable runtime could record the results of effectful activities rather than execute them twice during replay.
The interesting architecture is not necessarily “effects instead of graphs.”
It may be graphs for topology, effects for semantics, durability for time, and protocols for integration.
There is also a JavaScript-shaped elephant in the room
If this is so useful, why don't we simply write:
perform Generate(prompt)
in JavaScript today?
Because JavaScript does not currently have native general algebraic effect handlers.
Libraries can emulate important parts of the model using generators, explicit effect values, fibers, services and runtimes.
But true transparent continuation capture requires runtime support that ordinary JavaScript does not expose.
OCaml 5 does expose effect handlers, but its continuations are deliberately one-shot.
That is not an accident. One-shot continuations are cheaper and interact more safely with linear resources such as sockets and file descriptors.
There is also interesting movement below JavaScript.
The current WebAssembly Stack Switching proposal is in the implementation phase and introduces typed continuations for non-local control flow such as coroutines, async/await, generators and effect handlers.
Its continuations are also one-shot.
So the platform story is moving — just not in the simplistic direction of “native multi-shot effects are coming to JavaScript next month.”
And Effect 4 deserves a careful mention
The TypeScript ecosystem already has a library called Effect, and the name makes this discussion easy to muddle.
Effect 4 is not “JavaScript suddenly gained native Plotkin–Pretnar resumable handlers.”
But it demonstrates many of the engineering benefits that make effect-oriented programming attractive:
typed failures, dependency injection through services, retry schedules, resource safety, structured concurrency, observability, provider-independent AI services and tool handlers.
Its AI Toolkit explicitly groups tool definitions with handler functions, and its LanguageModel service hides provider-specific model implementations behind a shared interface.
That makes Effect relevant evidence for the broader design direction without pretending it is identical to native algebraic effect handlers.
What algebraic effects absolutely do not solve
They do not make hallucinations disappear.
A well-structured wrong answer is still wrong.
They do not make a weak model smarter.
They do not replace durable persistence.
They do not automatically provide exactly-once execution for external APIs.
They do not magically make email reversible.
They do not automatically make a tool safe simply because it passes through a handler.
They do not remove the need for authentication, authorization, sandboxing, rate limits, idempotency, transactions, evaluations or ordinary software engineering.
And a poorly designed stack of handlers can become its own version of middleware spaghetti.
The promise is narrower — and, in my view, more useful:
Give side effects an explicit place in the programming model so that execution policy does not quietly infect every piece of agent logic.
The part that feels most important for agents
For normal application code, separating effects from logic is elegant.
For agents, it may become something more important: a boundary of authority.
Traditional software mostly executes operations chosen by programmers.
Agents choose operations at runtime.
That changes the security question.
A model should be able to say:
“I would like to send this email.”
without that sentence meaning:
“Therefore the email has already been sent.”
Between those two events lives almost everything that matters in serious agent infrastructure:
intent
↓
typed request
↓
policy
↓
approval
↓
budget
↓
sandbox / transaction
↓
execution
↓
audit
↓
result
↓
resume reasoning
That is why the old effect-handler idea suddenly feels very contemporary.
Maybe we don't actually need the perform keyword to steal the best idea
I do not know whether mainstream agent development will converge on literal algebraic effects.
Maybe it will.
Maybe effect handlers will remain mostly inside languages such as OCaml, Koka and research systems.
Maybe JavaScript and Python agent frameworks will keep expressing the same idea through middleware, typed requests, callbacks, workflow nodes and execution policies.
That part is genuinely open.
But the research points to a useful design principle we can apply right now:
Do not make the agent own every detail of how its intentions reach the world.
Let the reasoning layer ask for capabilities.
Let another layer interpret those requests.
Make retries a policy instead of personality.
Make approval a boundary instead of another prompt.
Make external actions observable before they become irreversible.
Make child work belong to a parent.
Make testing, replay and alternate execution strategies possible without rewriting the agent's actual job.
Twenty years ago, this was largely a programming-language question about computational effects.
Today we are building software that can decide, at runtime, which tools to call, which data to mutate, which agents to delegate to and which parts of the outside world to touch.
So perhaps it is not surprising that the old question has come back.
We just gave it an LLM and a credit card.
Sources & References
- Gordon Plotkin & John Power — Algebraic Operations and Generic Effects (2003) Research Paper
- Gordon Plotkin & Matija Pretnar — Handlers of Algebraic Effects (2009) Research Paper
- Andrej Bauer & Matija Pretnar — Programming with Algebraic Effects and Handlers (2015) Research Paper
- Daan Leijen — Structured Asynchrony with Algebraic Effects (2017) Research Paper
- Danel Ahman & Matija Pretnar — Higher-Order Asynchronous Effects (2024) Research Paper
- Cong Ma et al. — Zero-Overhead Lexical Effect Handlers (OOPSLA 2025) Research Paper
- Kammar et al. — An Equational Axiomatization of Dynamic Threads via Algebraic Effects (POPL 2026) Research Paper
- Di Wang — Composable Effect Handling for Programming LLM-Integrated Scripts (LMPL 2025) Research Paper
- Programming Large Language Models with Algebraic Effect Handlers and the Selection Monad — Pangolin (2025) Research Paper
- J. J. Segura — Algebraic Effects for Bounded Prompt Pipelines (Computing, 2026) Research Paper
- Shepherd — Enabling Programmable Meta-Agents via Reversible Agentic Execution Traces (2026) Research Paper
- ETAS — An Effect-Typed Language for Agent Systems (2026) Research Paper
- OCaml 5.5 Effect handlers Official Documentation
- WebAssembly Stack Switching proposal Other
