Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.
Every tool call in Hexgate already passes one check: decide, the policy verdict
that answers allow / deny / needs-approval. Guards are the open extension point
next to it — small functions you attach before and after a tool call to do
everything a yes/no rule was never meant to express: strip a secret out of the
arguments, rate-limit a tool per user, watch a result before the model reads it,
add metrics.A tool call runs through four steps:
the agent wants to call a tool │ 1. before-guards look at the arguments; tweak them, or refuse │ 2. decide the allow / deny / needs-approval check (unchanged) │ 3. run the tool │ 4. after-guards look at the result; refuse to pass it back │ the model sees the result (or the refusal)
Observe. Just look — log it, count it, emit a metric. Change nothing.
Rewrite the arguments (before-guards only). Strip a credential so the tool
never receives it; the cleaned call is what runs.
Halt. Refuse the call and hand the model a short, safe reason instead.
A before-guard sees the ToolCall; an after-guard also
sees the ToolOutcome (the return value, or a raised
error). Result rewrite is not in v1 — after-guards observe or halt.
Before-guards run beforedecide, so decide always authorizes the exact
arguments that will execute. If a guard cleans the arguments, the policy checks the
cleaned version. A guard can only ever narrow a call — tweak it or refuse it — it
can never slip something past the policy, because decide still runs on whatever
the guard produced.
Write a guard with @before_tool or @after_tool, then hand the agent one flat
guards list — each guard already knows whether it runs before or after, so the
list stays flat.
from hexgate import before_tool, after_tool, create_agentfrom hexgate.guards import Halt, Proceed@before_tool # every tooldef block_secrets(call): if looks_like_a_secret(call.args): return Halt(reason="Refused: remove the credential and resend.") return None # None means "carry on"@before_tool(tool_names=["refund_order"]) # scoped to one tooldef cap_refund(call): if call.args.get("amount", 0) > 1000: return Halt(reason="Refunds over 1000 need a manager. Lower it or escalate.") return None@after_tool(observe=True) # fail-open watcherdef log_result(call, outcome): log(call.tool_name, ok=outcome.ok)agent, _ = create_agent(model="gpt-5.4", tools=[...], guards=[block_secrets, cap_refund, log_result])
The decorators are dual-form — bare (@before_tool) or called
(@before_tool(tool_names=..., observe=...)) — and double as inline wrappers
(guards=[before_tool(lambda call: ...)]). Position is the decorator, reach is
tool_names (a name or list; default every tool), and observe=True marks a
fail-open watcher.
ToolCall — tool_name, args (a read-only JSON mapping), agent_name,
context (the active HexgateContext), and scratch (a
per-call dict shared from a before-guard to an after-guard). ToolOutcome (after
only) — ok, value (the return), and error (the stringified exception when the
tool raised, so a watcher sees a failure the same way it sees a result).
Halt.reason is the only field the model sees. Name the rule and category,
never the offending input — a leaked value both exposes the secret and hands the
model a substring to obfuscate and resend in a loop. Halt.detail carries the
specifics on the operator/audit channel only, and the rendered refusal is marked
retryable: false. Pick guards whose reason names a fix (“remove the credential”)
over dead walls (“this is blocked”) so the model reworks the call in one step.
A guard that can rewrite or halt is a security control, so it fails closed: if
it raises, the call is denied — a crash inside the trust boundary must not become a
silent allow. An observe guard fails open: a raise is swallowed and logged,
and in exchange it may neither rewrite nor halt. Safe by default; loose only when
you say so.
The same guards attach wherever your agent lives — the guard itself never changes,
only the one call that installs it. Guards run through the same shared pipeline on
every framework, so behavior is identical; only how a refusal reaches the model
differs (a tool-result string on most, a ModelRetry on Pydantic AI), and Hexgate
handles that. See the adapters overview for each wrapper.
Surface
Install guards with
Native (create_agent, enforce_policy)
guards=[...], guard_observer=
LangChain (wrap_langchain_agent)
guards=[...], guard_observer=
Pydantic AI (wrap_pydantic_agent)
guards=[...], guard_observer=
OpenAI / Google (HexgateRunner)
HexgateRunner(guards=[...], guard_observer=)
guards= is always Hexgate’s argument. The only hooks= on the surface is the
OpenAI/Google HexgateRunner.run(hooks=...), which is the framework SDK’s own
run-lifecycle RunHooks — a different thing. Rule of thumb: if it says guards,
it’s Hexgate’s; if hooks, it’s the framework’s.
The code decides what a guard does. The policy can only switch it on or off, with
a top-level guards: block keyed by the guard’s name (its function’s
__name__):
guards: block_secrets: enabled: false # skipped on every call
A guard the policy doesn’t mention runs. enabled is required in each entry.
The block is agent-wide. A guards: under a tool entry is rejected, and every
role must set the same values, or hexgate policy validate fails.
With --manifest, validate also fails on a name the agent doesn’t declare
(unknown-guard) or one two of its guards share (ambiguous-guard).
For the policy (and the dashboard) to know an agent’s guards, its manifest has to
declare them. create_agent(guards=...) does that for you. For an agent you build
with a framework directly and then register or serve, stamp the list onto the
object with attach_guards:
from hexgate import attach_guardsagent = attach_guards(agent, [block_secrets, cap_refund])
The manifest then lists them, and the wrappers use the stamped list when you don’t
pass guards=.
hexgate.plugins ships ready-to-use guards built on one shared secret detector, so
you can drop credential protection in without writing the matcher yourself.
Plugin
Kind
What it does
secret_guard
before / halt
refuses a call whose arguments carry a credential
secret_redactor
before / rewrite
strips the credential from the arguments and lets the cleaned call run
secret_watch
after / observe
logs a warning when a credential leaks into a tool’s result, leaving it unchanged
secret_scrubber
after / rewrite
strips the credential from a tool’s result so the cleaned result flows on
secret_guard and secret_redactor are the two halves of the outbound case: pick
per tool by whether a secret’s presence means the call is wrong (guard) or merely
incidental and safe to strip (redactor) — don’t put both on the same tool, since
the redactor would clean the arguments before the guard ever saw them.secret_watch and secret_scrubber are the inbound pair, for a credential that
comes back in a tool’s result: watch when you only want the leak flagged on the
operator channel, scrub when the result must be made safe before it flows on.The refusal and the redaction record name the credential’s category and field,
never the value:
[guard_denied] Tool 'send_email' is denied by the agent policy: Refused: acredential (aws_access_key) was found in the tool arguments (`auth.token`) and wasnot sent. Remove it and resend without the secret. The tool was not executed.
The detector is prefix-only in v1: high-confidence provider patterns (AWS,
GitHub, OpenAI/Anthropic, Slack, Google, Stripe, Hexgate fty_, and PEM private
keys). It does not guess at unrecognized secrets by entropy — a random-looking
value is as likely to be a content hash or opaque ID as a secret, and a false
positive on the fail-closed secret_guard would block a real call. That tradeoff
(precision over recall for v1) is recorded in ADR R-GUARD-005.The detector primitives are exported too — scan_secrets, redact_secrets,
safe_reason, SecretHit — for building a custom, scoped guard when you need one.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"hexgate","colors":{"primary":"#3B82F6","light":"#60A5FA","dark":"#2563EB"},"logo":{"light":"https://mintcdn.com/hexamind/Sz1fI7dVJWhG1jGp/logo/light.svg?fit=max\u0026auto=format\u0026n=Sz1fI7dVJWhG1jGp\u0026q=85\u0026s=d7f949cb2f7b21bccfd23b147449f5be","dark":"https://mintcdn.com/hexamind/Sz1fI7dVJWhG1jGp/logo/dark.svg?fit=max\u0026auto=format\u0026n=Sz1fI7dVJWhG1jGp\u0026q=85\u0026s=70d4bcb78fb97c51abfd772727ad7754","href":"https://hexgate.ai"},"favicon":"https://mintcdn.com/hexamind/Sz1fI7dVJWhG1jGp/favicon.svg?fit=max\u0026auto=format\u0026n=Sz1fI7dVJWhG1jGp\u0026q=85\u0026s=48420d1d8ce58db050d2be4216c69c36","navbar":{"links":[{"href":"https://github.com/HexamindOrganisation/hexgate","label":"GitHub"}],"primary":{"type":"button","label":"Get started","href":"/quickstart"}},"footer":{"socials":{"github":"https://github.com/HexamindOrganisation/hexgate"}},"styling":{"latex":false},"navigation":{"global":{"anchors":[{"anchor":"PyPI","icon":"cube","href":"https://pypi.org/project/hexgate/"},{"anchor":"Discussions","icon":"github","href":"https://github.com/HexamindOrganisation/hexgate/discussions"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"hexamind","hasAudioNarration":false,"actualSubdomain":"hexamind","customDomains":["docs.hexgate.ai"],"gitSource":{"type":"github","owner":"hexamindorganisation","repo":"hexgate","deployBranch":"main","contentDirectory":"docs","isPrivate":false,"mountPath":"","label":""},"chroma":{"collectionId":"d9b65f34-06b4-4288-b97d-6d969298c000","liveDeploymentHistoryId":"6adb625eadb57cc3ba9018fb","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6adb625eadb57cc3ba9018fb"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6adb625eadb57cc3ba9018fb","clientVersion":"0.0.3965","searchFilterCounts":[{"language":"default","tag":"Using Hexgate","count":33},{"language":"default","tag":"Concepts","count":13},{"language":"default","tag":"Platform","count":12},{"language":"default","tag":"Policy","count":7},{"language":"default","tag":"Framework adapters","count":5},{"language":"default","tag":"CLI","count":4},{"language":"default","tag":"Get started","count":4},{"language":"default","tag":"Self-hosting","count":4},{"language":"default","tag":"Reference","count":4},{"language":"default","tag":"Internals","count":3}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"concepts/guards","slug":"/concepts/guards","pageMetadata":{"title":"Guards","description":"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.","href":"/concepts/guards"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Note,Table}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Note)_missingMdxReference(`Note`,true);if(!Table)_missingMdxReference(`Table`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`Every tool call in Hexgate already passes one check: `,_jsx(_components.code,{children:`decide`}),`, the policy verdict\nthat answers allow / deny / needs-approval. `,_jsx(_components.strong,{children:`Guards`}),` are the open extension point\nnext to it — small functions you attach before and after a tool call to do\neverything a yes/no rule was never meant to express: strip a secret out of the\narguments, rate-limit a tool per user, watch a result before the model reads it,\nadd metrics.`]}),`\n`,_jsx(_components.p,{children:`A tool call runs through four steps:`}),`\n`,_jsx(CodeBlock,{numberOfLines:`12`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`12`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` the agent wants to call a tool`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` 1. before-guards look at the arguments; tweak them, or refuse`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` 2. decide the allow / deny / needs-approval check (unchanged)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` 3. run the tool`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` 4. after-guards look at the result; refuse to pass it back`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` the model sees the result (or the refusal)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`2`,id:`what-a-guard-can-do`,children:`What a guard can do`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Observe.`}),` Just look — log it, count it, emit a metric. Change nothing.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Rewrite the arguments`}),` (before-guards only). Strip a credential so the tool\nnever receives it; the cleaned call is what runs.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Halt.`}),` Refuse the call and hand the model a short, safe reason instead.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`A before-guard sees the `,_jsx(_components.a,{href:`#what-a-guard-receives`,children:_jsx(_components.code,{children:`ToolCall`})}),`; an after-guard also\nsees the `,_jsx(_components.a,{href:`#what-a-guard-receives`,children:_jsx(_components.code,{children:`ToolOutcome`})}),` (the return value, or a raised\nerror). Result `,_jsx(_components.strong,{children:`rewrite`}),` is not in v1 — after-guards observe or halt.`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-one-rule-that-is-a-security-property`,children:`The one rule that is a security property`}),`\n`,_jsxs(_components.p,{children:[`Before-guards run `,_jsx(_components.strong,{children:`before`}),` `,_jsx(_components.code,{children:`decide`}),`, so `,_jsx(_components.code,{children:`decide`}),` always authorizes the exact\narguments that will execute. If a guard cleans the arguments, the policy checks the\ncleaned version. A guard can only ever `,_jsx(_components.em,{children:`narrow`}),` a call — tweak it or refuse it — it\ncan never slip something past the policy, because `,_jsx(_components.code,{children:`decide`}),` still runs on whatever\nthe guard produced.`]}),`\n`,_jsx(Heading,{level:`2`,id:`authoring`,children:`Authoring`}),`\n`,_jsxs(_components.p,{children:[`Write a guard with `,_jsx(_components.code,{children:`@before_tool`}),` or `,_jsx(_components.code,{children:`@after_tool`}),`, then hand the agent one flat\n`,_jsx(_components.code,{children:`guards`}),` list — each guard already knows whether it runs before or after, so the\nlist stays flat.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`21`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`21`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` hexgate `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` before_tool, after_tool, create_agent`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` hexgate.guards `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` Halt, Proceed`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`@before_tool`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # every tool`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`def`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` block_secrets`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`call`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`):`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` if`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` looks_like_a_secret(call.args):`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` Halt(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`reason`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"Refused: remove the credential and resend.\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` None`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # None means \"carry on\"`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`@before_tool`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`tool_names`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`[`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"refund_order\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`]) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# scoped to one tool`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`def`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` cap_refund`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`call`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`):`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` if`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` call.args.get(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"amount\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`>`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:` 1000`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` Halt(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`reason`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"Refunds over 1000 need a manager. Lower it or escalate.\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` None`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`@after_tool`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`observe`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`True`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# fail-open watcher`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`def`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` log_result`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`call`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`outcome`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`):`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` log(call.tool_name, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`ok`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`outcome.ok)`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`agent, _ `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` create_agent(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`model`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"gpt-5.4\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`tools`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`...`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`],`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` guards`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`[block_secrets, cap_refund, log_result])`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The decorators are dual-form — bare (`,_jsx(_components.code,{children:`@before_tool`}),`) or called\n(`,_jsx(_components.code,{children:`@before_tool(tool_names=..., observe=...)`}),`) — and double as inline wrappers\n(`,_jsx(_components.code,{children:`guards=[before_tool(lambda call: ...)]`}),`). Position is the decorator, reach is\n`,_jsx(_components.code,{children:`tool_names`}),` (a name or list; default every tool), and `,_jsx(_components.code,{children:`observe=True`}),` marks a\nfail-open watcher.`]}),`\n`,_jsx(Heading,{level:`3`,id:`what-a-guard-returns`,children:`What a guard returns`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Return`}),_jsx(_components.th,{children:`Meaning`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[_jsx(_components.code,{children:`None`}),` or `,_jsx(_components.code,{children:`Proceed()`})]}),_jsx(_components.td,{children:`carry on unchanged`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`Proceed(args=new_args)`})}),_jsx(_components.td,{children:`continue with rewritten arguments (before-guards)`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`Halt(reason=...)`})}),_jsx(_components.td,{children:`refuse; the model sees a safe error instead of a result`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`Halt(reason=..., outcome=NEEDS_APPROVAL)`})}),_jsxs(_components.td,{children:[`consult the `,_jsx(_components.a,{href:`/concepts/approval-required`,children:`approval handler`}),`, exactly like a policy `,_jsx(_components.code,{children:`needs_approval`})]})]})]})]}),`\n`,_jsx(Heading,{level:`3`,id:`what-a-guard-receives`,children:`What a guard receives`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`ToolCall`}),` — `,_jsx(_components.code,{children:`tool_name`}),`, `,_jsx(_components.code,{children:`args`}),` (a read-only JSON mapping), `,_jsx(_components.code,{children:`agent_name`}),`,\n`,_jsx(_components.code,{children:`context`}),` (the active `,_jsx(_components.a,{href:`/concepts/user-scope`,children:_jsx(_components.code,{children:`HexgateContext`})}),`), and `,_jsx(_components.code,{children:`scratch`}),` (a\nper-call dict shared from a before-guard to an after-guard). `,_jsx(_components.code,{children:`ToolOutcome`}),` (after\nonly) — `,_jsx(_components.code,{children:`ok`}),`, `,_jsx(_components.code,{children:`value`}),` (the return), and `,_jsx(_components.code,{children:`error`}),` (the stringified exception when the\ntool raised, so a watcher sees a failure the same way it sees a result).`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-halt-message-safe-by-construction`,children:`The halt message: safe by construction`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`Halt.reason`}),` is the `,_jsx(_components.strong,{children:`only`}),` field the model sees. Name the rule and category,\nnever the offending input — a leaked value both exposes the secret and hands the\nmodel a substring to obfuscate and resend in a loop. `,_jsx(_components.code,{children:`Halt.detail`}),` carries the\nspecifics on the operator/audit channel only, and the rendered refusal is marked\n`,_jsx(_components.code,{children:`retryable: false`}),`. Pick guards whose reason names a fix (“remove the credential”)\nover dead walls (“this is blocked”) so the model reworks the call in one step.`]}),`\n`,_jsx(Heading,{level:`2`,id:`error-tiers-fail-closed-or-observe`,children:`Error tiers: fail-closed, or observe`}),`\n`,_jsxs(_components.p,{children:[`A guard that can rewrite or halt is a security control, so it `,_jsx(_components.strong,{children:`fails closed`}),`: if\nit raises, the call is denied — a crash inside the trust boundary must not become a\nsilent allow. An `,_jsx(_components.code,{children:`observe`}),` guard `,_jsx(_components.strong,{children:`fails open`}),`: a raise is swallowed and logged,\nand in exchange it may neither rewrite nor halt. Safe by default; loose only when\nyou say so.`]}),`\n`,_jsx(Heading,{level:`2`,id:`guards-across-frameworks`,children:`Guards across frameworks`}),`\n`,_jsxs(_components.p,{children:[`The same guards attach wherever your agent lives — the guard itself never changes,\nonly the one call that installs it. Guards run through the same shared pipeline on\nevery framework, so behavior is identical; only how a refusal reaches the model\ndiffers (a tool-result string on most, a `,_jsx(_components.code,{children:`ModelRetry`}),` on Pydantic AI), and Hexgate\nhandles that. See the `,_jsx(_components.a,{href:`/adapters/overview`,children:`adapters overview`}),` for each wrapper.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Surface`}),_jsx(_components.th,{children:`Install guards with`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`Native (`,_jsx(_components.code,{children:`create_agent`}),`, `,_jsx(_components.code,{children:`enforce_policy`}),`)`]}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`guards=[...]`}),`, `,_jsx(_components.code,{children:`guard_observer=`})]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`LangChain (`,_jsx(_components.code,{children:`wrap_langchain_agent`}),`)`]}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`guards=[...]`}),`, `,_jsx(_components.code,{children:`guard_observer=`})]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`Pydantic AI (`,_jsx(_components.code,{children:`wrap_pydantic_agent`}),`)`]}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`guards=[...]`}),`, `,_jsx(_components.code,{children:`guard_observer=`})]})]}),_jsxs(_components.tr,{children:[_jsxs(_components.td,{children:[`OpenAI / Google (`,_jsx(_components.code,{children:`HexgateRunner`}),`)`]}),_jsx(_components.td,{children:_jsx(_components.code,{children:`HexgateRunner(guards=[...], guard_observer=)`})})]})]})]}),`\n`,_jsx(Note,{children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`guards=`}),` is always Hexgate’s argument. The only `,_jsx(_components.code,{children:`hooks=`}),` on the surface is the\nOpenAI/Google `,_jsx(_components.code,{children:`HexgateRunner.run(hooks=...)`}),`, which is the framework SDK’s own\nrun-lifecycle `,_jsx(_components.code,{children:`RunHooks`}),` — a different thing. Rule of thumb: if it says `,_jsx(_components.code,{children:`guards`}),`,\nit’s Hexgate’s; if `,_jsx(_components.code,{children:`hooks`}),`, it’s the framework’s.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`turning-a-guard-off-from-the-policy`,children:`Turning a guard off from the policy`}),`\n`,_jsxs(_components.p,{children:[`The code decides what a guard does. The policy can only switch it on or off, with\na top-level `,_jsx(_components.code,{children:`guards:`}),` block keyed by the guard’s name (its function’s\n`,_jsx(_components.code,{children:`__name__`}),`):`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`guards`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` block_secrets`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:` enabled`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`false`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # skipped on every call`})]}),`\n`]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`A guard the policy doesn’t mention runs. `,_jsx(_components.code,{children:`enabled`}),` is required in each entry.`]}),`\n`,_jsxs(_components.li,{children:[`The block is agent-wide. A `,_jsx(_components.code,{children:`guards:`}),` under a tool entry is rejected, and every\nrole must set the same values, or `,_jsx(_components.code,{children:`hexgate policy validate`}),` fails.`]}),`\n`,_jsxs(_components.li,{children:[`With `,_jsx(_components.code,{children:`--manifest`}),`, `,_jsx(_components.code,{children:`validate`}),` also fails on a name the agent doesn’t declare\n(`,_jsx(_components.code,{children:`unknown-guard`}),`) or one two of its guards share (`,_jsx(_components.code,{children:`ambiguous-guard`}),`).`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`For the policy (and the dashboard) to know an agent’s guards, its manifest has to\ndeclare them. `,_jsx(_components.code,{children:`create_agent(guards=...)`}),` does that for you. For an agent you build\nwith a framework directly and then `,_jsx(_components.code,{children:`register`}),` or `,_jsx(_components.code,{children:`serve`}),`, stamp the list onto the\nobject with `,_jsx(_components.code,{children:`attach_guards`}),`:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` hexgate `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` attach_guards`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`agent `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` attach_guards(agent, [block_secrets, cap_refund])`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The manifest then lists them, and the wrappers use the stamped list when you don’t\npass `,_jsx(_components.code,{children:`guards=`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`official-plugins`,children:`Official plugins`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`hexgate.plugins`}),` ships ready-to-use guards built on one shared secret detector, so\nyou can drop credential protection in without writing the matcher yourself.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Plugin`}),_jsx(_components.th,{children:`Kind`}),_jsx(_components.th,{children:`What it does`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`secret_guard`})}),_jsx(_components.td,{children:`before / halt`}),_jsx(_components.td,{children:`refuses a call whose arguments carry a credential`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`secret_redactor`})}),_jsx(_components.td,{children:`before / rewrite`}),_jsx(_components.td,{children:`strips the credential from the arguments and lets the cleaned call run`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`secret_watch`})}),_jsx(_components.td,{children:`after / observe`}),_jsx(_components.td,{children:`logs a warning when a credential leaks into a tool’s result, leaving it unchanged`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`secret_scrubber`})}),_jsx(_components.td,{children:`after / rewrite`}),_jsx(_components.td,{children:`strips the credential from a tool’s result so the cleaned result flows on`})]})]})]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`5`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`5`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` hexgate `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` create_agent`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` hexgate.plugins `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` secret_redactor, secret_scrubber`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`agent, _ `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` create_agent(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`model`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"gpt-5.4\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`tools`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`[`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`...`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`],`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` guards`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`[secret_redactor, secret_scrubber])`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`secret_guard`}),` and `,_jsx(_components.code,{children:`secret_redactor`}),` are the two halves of the outbound case: pick\nper tool by whether a secret’s presence means the call is wrong (guard) or merely\nincidental and safe to strip (redactor) — don’t put both on the same tool, since\nthe redactor would clean the arguments before the guard ever saw them.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`secret_watch`}),` and `,_jsx(_components.code,{children:`secret_scrubber`}),` are the inbound pair, for a credential that\ncomes back in a tool’s result: watch when you only want the leak flagged on the\noperator channel, scrub when the result must be made safe before it flows on.`]}),`\n`,_jsxs(_components.p,{children:[`The refusal and the redaction record name the credential’s `,_jsx(_components.strong,{children:`category and field`}),`,\nnever the value:`]}),`\n`,_jsx(CodeBlock,{numberOfLines:`4`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`4`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`[guard_denied] Tool 'send_email' is denied by the agent policy: Refused: a`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:\"credential (aws_access_key) was found in the tool arguments (`auth.token`) and was\"})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`not sent. Remove it and resend without the secret. The tool was not executed.`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`3`,id:`what-the-detector-matches`,children:`What the detector matches`}),`\n`,_jsxs(_components.p,{children:[`The detector is `,_jsx(_components.strong,{children:`prefix-only`}),` in v1: high-confidence provider patterns (AWS,\nGitHub, OpenAI/Anthropic, Slack, Google, Stripe, Hexgate `,_jsx(_components.code,{children:`fty_`}),`, and PEM private\nkeys). It does not guess at unrecognized secrets by entropy — a random-looking\nvalue is as likely to be a content hash or opaque ID as a secret, and a false\npositive on the fail-closed `,_jsx(_components.code,{children:`secret_guard`}),` would block a real call. That tradeoff\n(precision over recall for v1) is recorded in ADR `,_jsx(_components.code,{children:`R-GUARD-005`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`The detector primitives are exported too — `,_jsx(_components.code,{children:`scan_secrets`}),`, `,_jsx(_components.code,{children:`redact_secrets`}),`,\n`,_jsx(_components.code,{children:`safe_reason`}),`, `,_jsx(_components.code,{children:`SecretHit`}),` — for building a custom, scoped guard when you need one.`]}),`\n`,_jsx(Heading,{level:`2`,id:`related`,children:`Related`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/approval-required`,children:`Approval-required`}),` — `,_jsx(_components.code,{children:`Halt(outcome=NEEDS_APPROVAL)`}),` reuses the same handler.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/audit-trail`,children:`Audit trail`}),` — a guard halt is recorded like a policy denial.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/adapters/overview`,children:`Adapters overview`}),` — how each framework installs guards.`]}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Guards","description":"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.","href":"/concepts/guards"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"What a guard can do","slug":"what-a-guard-can-do","depth":2,"children":[]},{"title":"The one rule that is a security property","slug":"the-one-rule-that-is-a-security-property","depth":2,"children":[]},{"title":"Authoring","slug":"authoring","depth":2,"children":[{"title":"What a guard returns","slug":"what-a-guard-returns","depth":3,"children":[]},{"title":"What a guard receives","slug":"what-a-guard-receives","depth":3,"children":[]}]},{"title":"The halt message: safe by construction","slug":"the-halt-message-safe-by-construction","depth":2,"children":[]},{"title":"Error tiers: fail-closed, or observe","slug":"error-tiers-fail-closed-or-observe","depth":2,"children":[]},{"title":"Guards across frameworks","slug":"guards-across-frameworks","depth":2,"children":[]},{"title":"Turning a guard off from the policy","slug":"turning-a-guard-off-from-the-policy","depth":2,"children":[]},{"title":"Official plugins","slug":"official-plugins","depth":2,"children":[{"title":"What the detector matches","slug":"what-the-detector-matches","depth":3,"children":[]}]},{"title":"Related","slug":"related","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"hexamind","siteName":"hexgate","title":"Guards - hexgate","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-19T07:55:17.641Z","originalFileLocation":"concepts/guards.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.\">\u003cmeta data-mint-head name=\"application-name\" content=\"hexgate\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Guards - hexgate\">\u003cmeta data-mint-head property=\"og:description\" content=\"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"hexgate\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.hexgate.ai/concepts/guards\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://hexamind.mintlify.app/_mintlify/api/og/v2?division=Concepts\u0026amp;title=Guards\u0026amp;description=Functions+that+run+before+and+after+a+tool+call+to+observe+it%2C+rewrite+its+arguments%2C+or+refuse+it+%E2%80%94+plus+the+official+secret-detection+plugins.\u0026amp;theme=4192cabb10185815ede9237a\u0026amp;signature=082d36f2c28193c954323f9b15548b1e69553949f4e4d0b2ed8ba3a88216d183\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Guards - hexgate\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://hexamind.mintlify.app/_mintlify/api/og/v2?division=Concepts\u0026amp;title=Guards\u0026amp;description=Functions+that+run+before+and+after+a+tool+call+to+observe+it%2C+rewrite+its+arguments%2C+or+refuse+it+%E2%80%94+plus+the+official+secret-detection+plugins.\u0026amp;theme=4192cabb10185815ede9237a\u0026amp;signature=082d36f2c28193c954323f9b15548b1e69553949f4e4d0b2ed8ba3a88216d183\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/hexamind/W3iZq8x_Zqjvq4qm/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"hexgate\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#3B82F6\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.hexgate.ai/concepts/guards\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/concepts/guards.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.hexgate.ai/#organization\",\"name\":\"hexgate\",\"url\":\"https://docs.hexgate.ai\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/hexamind/Sz1fI7dVJWhG1jGp/logo/light.svg?fit=max\u0026auto=format\u0026n=Sz1fI7dVJWhG1jGp\u0026q=85\u0026s=d7f949cb2f7b21bccfd23b147449f5be\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.hexgate.ai#website\",\"name\":\"hexgate\",\"url\":\"https://docs.hexgate.ai\",\"publisher\":{\"@id\":\"https://docs.hexgate.ai/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.hexgate.ai/concepts/guards#webpage\",\"url\":\"https://docs.hexgate.ai/concepts/guards\",\"name\":\"Guards\",\"description\":\"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.\",\"dateModified\":\"2026-10-19T07:55:17.641Z\",\"isPartOf\":{\"@id\":\"https://docs.hexgate.ai#website\"},\"breadcrumb\":{\"@id\":\"https://docs.hexgate.ai/concepts/guards#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.hexgate.ai/concepts/guards#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Concepts\",\"item\":\"https://docs.hexgate.ai/concepts/policy-decision\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Guards\",\"item\":\"https://docs.hexgate.ai/concepts/guards\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.hexgate.ai/concepts/guards#article\",\"headline\":\"Guards\",\"name\":\"Guards\",\"description\":\"Functions that run before and after a tool call to observe it, rewrite its arguments, or refuse it — plus the official secret-detection plugins.\",\"url\":\"https://docs.hexgate.ai/concepts/guards\",\"mainEntityOfPage\":{\"@id\":\"https://docs.hexgate.ai/concepts/guards#webpage\"},\"dateModified\":\"2026-10-19T07:55:17.641Z\",\"publisher\":{\"@id\":\"https://docs.hexgate.ai/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.hexgate.ai#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"hexgate\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"hexgate","nav":{"global":{"anchors":[{"anchor":"PyPI","icon":"cube","href":"https://pypi.org/project/hexgate/"},{"anchor":"Discussions","icon":"github","href":"https://github.com/HexamindOrganisation/hexgate/discussions"}]},"tabs":[{"tab":"Using Hexgate","groups":[{"group":"Get started","pages":[{"title":"Introduction","href":"/introduction"},{"title":"Quickstart","href":"/quickstart"},{"title":"Which path do I pick?","href":"/two-paths"},{"title":"Build an agent","href":"/guides/build-an-agent"}]},{"group":"Concepts","pages":[{"title":"Policy decision","description":"The Decision object — allow, deny, approval-required — at the heart of every tool call.","href":"/concepts/policy-decision"},{"title":"Guards","href":"/concepts/guards"},{"title":"Request context","description":"Per-request caller identity, role selection, attributes, biscuit attenuation.","href":"/concepts/user-scope"},{"title":"Agent-level enforcement","href":"/concepts/agent-level-enforcement"},{"title":"Agent skills","href":"/concepts/skills"},{"title":"Signed policy bundles","href":"/concepts/signed-bundles"},{"title":"Audit trail","href":"/concepts/audit-trail"},{"title":"MCP servers (proxy)","href":"/concepts/mcp"},{"title":"Approval-required","href":"/concepts/approval-required"},{"title":"Approval in other frameworks","href":"/concepts/approval-frameworks-compared"},{"title":"Bans","href":"/concepts/bans"},{"title":"Workspace sandbox","href":"/concepts/sandbox"},{"title":"Network egress","href":"/concepts/egress"}]},{"group":"Framework adapters","pages":[{"title":"Adapters overview","href":"/adapters/overview"},{"title":"OpenAI Agents SDK","href":"/adapters/openai"},{"title":"LangChain / LangGraph","href":"/adapters/langchain"},{"title":"Google ADK","href":"/adapters/google-adk"},{"title":"Pydantic AI","href":"/adapters/pydantic-ai"}]},{"group":"Policy","pages":[{"title":"Policy YAML shape","href":"/policy/yaml-shape"},{"title":"Writing conditions","href":"/policy/writing-conditions"},{"title":"Constraints reference","href":"/policy/constraints"},{"title":"Define policy in code","href":"/policy/in-code"},{"title":"WASM bundles","href":"/policy/wasm-bundles"},{"title":"Signing \u0026 verification","href":"/policy/signing"},{"title":"Local override (HEXGATE_LOCAL_POLICY)","href":"/policy/local-override"}]},{"group":"CLI","pages":[{"title":"hexgate chat","href":"/cli/chat"},{"title":"hexgate serve","href":"/cli/serve"},{"title":"hexgate register","href":"/cli/register"},{"title":"hexgate policy","href":"/cli/policy"}]}]},{"tab":"Platform","pages":[{"title":"Platform","href":"/platform/overview"}]},{"tab":"Reference","pages":[{"title":"Reference","href":"/sdk"}]},{"tab":"Internals","pages":[{"title":"Internals","href":"/internals/audit-pipeline"}]}]},"title":"Guards - hexgate","buildId":"dpl_849ArpqvBiCczQiT1Y1stNGpbqHT","deploymentId":"dpl_849ArpqvBiCczQiT1Y1stNGpbqHT","dynamics":[],"chunks":[]}