PluginsHooks
Guard
Explain guard blocking semantics, execution order, and why it should not be used as a value transformer
Guard
The core semantic of guard is simple:
- if the flow is allowed, return nothing
- if the flow is not allowed, throw
Its job is not to transform values
Many people instinctively start modifying the input inside a guard.
But the clean responsibility of a guard is only:
- decide whether the current flow may continue
So if your main goal is "rewrite the input a bit," that belongs in pipeline.
Minimal example
await agent.plugins.guard("review.require_checked", {
reviewed: true,
});A plugin can define that point like this:
hooks: {
guard: {
"review.require_checked": [
async ({ value }) => {
const body = value as { reviewed?: unknown };
if (body.reviewed !== true) {
throw new Error("review is required before continuing");
}
},
],
},
}How multiple guard handlers run
If several guards attach to the same point:
- they run in registration order
- once one throws, the rest do not continue
So it behaves like a sequence of checkpoints.
Common good use cases
permission checks
For example:
- does the user have a role
- does the request have permission
preconditions
For example:
- is a field required
- is some prior state already ready
safety boundaries
For example:
- must this request be explicitly confirmed first
- must this input be reviewed before proceeding
Poor use cases
"continue, but also log something"
Split the logging into effect.
"conditionally add default values"
That belongs in pipeline.
"provide the final decision answer"
That belongs in resolve.
What happens when a plugin is disabled
If one guard plugin is currently disabled:
- its handler is skipped
- the other active guards still run
So the runtime meaning is:
- active guards participate
- disabled guards do not
Stable practices
- make the thrown error explain clearly why the flow was blocked
- avoid hiding lots of side effects inside guards
- keep value transformation and blocking logic separate