A Position Piece · Agentic Middleware
Agent harnesses are stochastic by design. Governance is the discipline that decides which parts of them must not be stochastic.
The pitch, in one breath
Modern agent harnesses write, run, and check code with much autonomy. They give almost no guarantees. An agent can paraphrase a compliance check. It can skip a validation. It can go around a safeguard. Today those controls are in the same operational space that the agent can interpret again.
We propose that you govern determinism as a spectrum. Do not wish for it as a property. Skills, commands, plugins, and hooks are now standard, and they give us a set of control surfaces. At each surface you can change probabilistic behavior into a guarantee.
A surface has more value when it is farther outside the agent's discretion. We call the practice gradual determinism. Keep the agent dynamic where its dynamism has value. Make constant only the parts that must not change.
01 / THE PREMISE
The power of an autonomous coding agent is its freedom. The agent interprets intent, writes the code, runs it, and decides when the work is complete. That freedom is not a defect. But a control that the agent can rewrite is not a control. It is a suggestion.
A skill says “validate the license headers.” That skill depends on three decisions by the agent. The agent must decide to validate. It must decide to validate correctly. It must not decide that the step is unnecessary this time. When the rule and the actor are in the same room, the actor always wins.
The correct fix is not a limit on the intelligence of the agent. Move the rules into a room that the agent cannot enter. Do this only where a guarantee is worth its cost.
02 / THE FRAME
The mechanisms that we have align with the anatomy of the agent loop. Commands control its entry. Scripts control execution. Hooks control the exit. Loops control continuation. Wrappers own the whole loop.
From left to right, that order shows two things. It shows how much the surface guarantees. It also shows how far the control is outside the reach of the agent.
There are two dimensions: the breadth of the guarantee and its tamper-resistance. A skill is fully in-band. The agent can paraphrase the skill or skip it. The harness runtime enforces a hook, even if the agent does not cooperate. That difference separates a pattern from a control.
03 / THE SURFACES
For three sprints, the “run the compliance gate” skill never fired. Each time, the agent decided in good faith that it satisfied the intent of the rule. No person could point to the run that proved it. We made the gate available as /comply, a command that the reviewer types. Then we could answer the question “did the gate run?” for the first time.
The agent selects the skills. The user cannot make a skill fire. Commands reverse this. The operator invokes a command, so a command is a routing guarantee. It is a named entry point into a known procedure, and you can audit it.
You cannot make the agent choose the correct path. But you can give the operator a path that the agent cannot refuse.
The settlement-rounding step gave a result that changed by one cent between runs. The error was too small to fail a check, and too large to trust. The skill gave the calculation in prose and let the agent derive it again each time. We put the approved implementation into a script. We reduced the skill phase to one instruction: invoke the script. The cent did not move again.
A skill phase in natural language lets the agent interpret the phase and write the code at run time. That is the correct default for new work. But you can make a phase, review it, curate it, and approve it. Then you can package the same code as a script. You can reduce the phase description to an instruction that adapts or invokes that script. The non-determinism of regeneration becomes the determinism of a constant artifact under version control.
“Done” was what the agent thought was done. Sometimes the test suite failed and the diff had no license header. We put a gate at the end of the turn. Now “done” must satisfy a checker that does not negotiate.
The stop-hook fires when the agent tries to end its turn. At that hook point, deterministic code examines the output for structural and semantic properties. You can also drive the loop again from here. A stop-hook can return a block decision with a reason. The block prevents the agent from stopping, and the reason becomes its next instruction. The hook is a hard exit gate.
One guardrail is necessary. The harness sends a stop_hook_active flag. The flag is true when the agent is already in a forced continuation. The hook must obey the flag. If it does not, the hook loops forever.
#!/usr/bin/env python3 """Stop-hook gate: refuse to end the turn until the build is verifiably clean. The harness invokes this when the agent tries to stop. Emitting a "block" decision returns control to the agent with `reason` as its next instruction; exiting 0 silently lets it stop. """ import json, subprocess, sys payload = json.load(sys.stdin) # Honor the forced-continuation flag, or this gate loops forever. if payload.get("stop_hook_active"): sys.exit(0) checks = subprocess.run(["make", "verify"], capture_output=True, text=True) if checks.returncode != 0: print(json.dumps({ "decision": "block", "reason": f"Verification failed; fix before finishing:\n{checks.stdout}", })) sys.exit(0)
Some properties need judgement, not an exit code. An example is the question “is this complete?” For these, a prompt-type or agent-type hook runs a low-cost evaluator in the same slot. The structure is the same. Only the oracle is different.
The migration guide needed four passes before the style linter was quiet. We did those passes by hand. Each time we wrote the prompt again and read the report again. This was the least deterministic part of a pipeline that was otherwise clean. A /loop changed “continue until it is clean” from a habit into a construct.
A /loop construct runs a task again and again until the outcome is satisfactory. The loop is not deterministic on its own. But it increases the minimum result that we can expect. A loop is only as deterministic as its verifier. A loop with no oracle only uses turns.
So loops and hooks go together well. The hook is the oracle and the loop is the driver. Together they move toward a property that you can name.
The overnight batch was correct until one morning. On that morning, an agent applied a plan that no person had seen. We did not want a slower batch. We wanted a hard gate between plan and apply, owned by something that was not the agent. So we wrote the gate, and we called the harness from inside it.
A custom wrapper drives the harness through its API or its CLI. The wrapper is a more specific and more deterministic relative of /loop. The multi-turn question also has a clean answer. The first headless call returns a session id. Later calls resume the same session and keep the full context. This is the same thread-id pattern that graph-based orchestration SDKs use.
The control flow is now in code that the operator owns. The agent gives capability. The wrapper gives the guarantee that a person reviews each change before the harness applies it.
"""Deterministic envelope around an agent harness. The orchestrator — not the agent — owns the control flow: it opens a session, holds it open across turns by session id, and inserts a hard human-approval gate between *plan* and *apply*. """ import json, subprocess def run(prompt, session=None): """Invoke the harness for one turn; return its parsed JSON result. :param prompt: the operator message for this turn. :param session: an existing session id to resume, or None to start one. :returns: the result, including `session_id` for continuation. """ cmd = ["claude", "-p", prompt, "--output-format", "json", "--max-turns", "8"] if session: cmd += ["--resume", session] return json.loads(subprocess.run(cmd, capture_output=True, text=True).stdout) plan = run("Plan the migration. Do not modify any files yet.") sid = plan["session_id"] if input("Approve this plan? [y/N] ").strip().lower() == "y": run("Apply the approved plan exactly as described.", session=sid)
04 / COMPOSITION
These surfaces are not alternatives. They stack. A mature harness uses all of them. A command routes into a known procedure. A script does the step that must compute identically. A hook verifies the result and starts the work again after a failure.
A wrapper owns the approvals around the full exchange. The agent keeps its dynamism in the spaces between these surfaces. That is where dynamism has value.
We do not make the agent deterministic. Surface by surface, we decide which invariants the agent can no longer violate.
Be precise about the result. Generation is still stochastic. The model still improvises. We get guaranteed invariants at selected checkpoints. These are islands of determinism around a probabilistic core. That claim is more honest than “deterministic agents,” and it is more useful.
05 / WHY IT MATTERS
Compliance, security, and governance do not ask for cleverness. They ask one question. Can you show, after the event, that the rule held? Each surface on this spectrum is code: a command definition, a constant script, a hook, or an orchestration wrapper.
You can put code under version control. You can review it, sign it, and attest to it. The same change that makes a control resistant to the agent also makes it clear to an auditor.
That is the quiet result when you move controls out-of-band. We no longer hope that the agent behaved correctly. We can show the envelope that contained it. An autonomous system that touches regulated work needs this change from trust to evidence. Gradual determinism gives us that change, and we keep the autonomy that made the harness worth its adoption.