Execution flow and policy boundaries
An agent harness coordinates execution: state, model calls, tool calls, retries, branching, and termination. Guardrails check and constrain what may enter, execute, or leave that flow.
| Perspective | Central question | Examples |
|---|---|---|
| Harness | What should happen next, and how does execution continue? | Tool routing, state updates, retry handling |
| Guardrail | Is this input, action, or output permitted? | Argument validation, access control, output checks |
The harness invokes guardrails and handles their outcomes. The boundary is a useful design distinction, although teams and products may use these terms differently. A step limit, for example, can be both termination control and a cost boundary.
Follow one request
User request
↓
Validate input ── rejected → End with an explanation
↓
Select an action
↓
Check tool policy ── denied → Stop or choose another action
↓
Execute tool
↓
Validate evidence and update state
↓
Continue, request review, or return an answer
The full sequence belongs to the execution structure. The checks define boundaries at particular points in that sequence.
Retrieved documents are data. Instructions embedded inside them should not gain the authority to change tool permissions or application policy.
A small illustrative harness
This example uses a fixed tool selection so the execution boundary is easy to inspect. It is an orchestration sketch, not a production security implementation or a complete model-driven agent.
from collections.abc import Callable
def search_documents(query: str) -> str:
return f"Evidence about: {query}"
TOOLS: dict[str, Callable[[str], str]] = {
"search_documents": search_documents,
}
ALLOWED_TOOLS = frozenset({"search_documents"})
def execute_tool(name: str, argument: str) -> str:
if name not in ALLOWED_TOOLS:
raise PermissionError("Tool is not permitted")
if not argument.strip() or len(argument) > 500:
raise ValueError("Invalid search query")
return TOOLS[name](argument)
def run_agent(message: str, max_steps: int = 3) -> str:
query = message
for _ in range(max_steps):
evidence = execute_tool("search_documents", query)
if evidence:
return f"Answer grounded in: {evidence}"
query = f"Overview: {message}"
return "Stopped: no evidence within the execution budget"
run_agent connects action selection, execution, evidence handling, and stopping. execute_tool enforces a small tool policy before invoking the function. The caller must handle validation failures and permission errors.
In a real system, query selection may come from a model or router, and evidence needs meaningful evaluation. The fixed response in this example does not establish factual grounding.
Enforce controls before side effects
Listing permitted tools in a prompt is different from checking permissions at the execution boundary. Important restrictions should be enforced where the underlying tool or service can reject the action.
A permitted tool name also does not authorize every argument. Validate the target resource, user identity, tenant boundary, and requested change as appropriate.
Output validation cannot undo a tool action that has already occurred. A policy that requires approval must pause execution before the action, associate approval with the specific action and arguments, and resume only when that approval remains valid.
Evaluate both refusal and recovery
For guardrails, check that disallowed actions are blocked and legitimate requests still work. Confirm that rejected actions did not produce side effects through an alternate path.
For the harness, test tool errors, timeouts, empty evidence, execution limits, cancellation, and restart behavior. Logs help explain a decision, but a log entry alone does not establish that a boundary was enforced.
A web server delivers requests to the agent workflow. A container supplies its runtime environment. Neither automatically provides the orchestration or policy controls described here.