langchain-neuraltrust makes a
LangChain 1.x agent a TrustGuard collector. It is an
AgentMiddleware, so it hooks the agent loop itself rather than the HTTP path: prompts are
evaluated before the model is called, responses after, and tool calls can be gated before they
execute. Nothing about your model provider changes.
This suits an agent you own the code of. If you want coverage without touching application code,
put a gateway in front instead — that covers every
client at once, but cannot see individual tool calls.
Before you start
Keep the policy in Report mode for the first rollout. Report downgrades every rule to
report, so findings appear in Activity without breaking traffic. Switch to Enforce once the
finding volume looks right — see Policies.1. Install
2. Add the middleware
Leaving
session_id unset is usually right: it picks up the LangGraph thread_id, which is
already the conversation identity, so Activity groups turns the way your app does.
3. Choose what gets evaluated
Four independent flags, each mapping to a hook in the agent loop:check_tool_results is skipped when check_input is also on, because the conversation payload
already contains those tool messages — you would be paying twice to evaluate the same text.
check_tool_calls is the one worth adding deliberately. It is the only hook that sees a tool call
before it executes, so it is what stops a hijacked agent from actually running the destructive
call rather than reporting on it afterwards. It costs one round trip per tool call.
Both sync (invoke) and async (ainvoke) paths are implemented for every hook.
4. Know how each verdict lands
The middleware never changes the meaning of a verdict, only how it is applied to agent state:
Preserving the id is what makes
transform usable in an agent: a redacted message must occupy the
same slot in the thread, or the model sees both the original and the redaction.
exit_behavior decides what a block does:
SystemMessages are never removed by any of these. Under end and replace the middleware also
keeps the thread structurally valid — a blocked AIMessage has its tool_calls cleared, and a
blocked ToolMessage is converted so it cannot be left orphaning a tool response. An agent thread
with a tool_call that never got an answer will fail on the next model call, which would turn a
security block into a crash.
On the tool path a block returns a ToolMessage(status="error") and the tool is never called.
5. Choose fail-open or fail-closed
unreachable_fallback decides what happens when TrustGuard genuinely cannot be reached, and it is
deliberately narrow. It applies only to plain connect errors, timeouts, HTTP 502/504, and HTTP
429 after retries are exhausted. Those are retried with backoff, honouring Retry-After, before the
fallback applies.
Everything else fails closed even when you asked for
fail_open:
- HTTP 401/403, and 503 entitlement failures
- any other 4xx/5xx
- a non-JSON
200, or an unknown verdict - a
transformed_payloadthat cannot be applied safely - TLS failures — an expired or untrusted certificate is a connect error, but it is treated as a
configuration fault rather than an outage, so
fail_opendoes not cover it. A corporate MITM proxy without a trusted CA will therefore block every turn even withfail_openset.
6. Verify
AIMessage, the model is
never called, and on_violation prints the trace_id. That trace_id is the same identifier the
finding carries in Activity, so use it to reconcile a run with what the console shows.
on_violation is synchronous and fires from both invoke and ainvoke. Exceptions from it
propagate untouched — it is a hook for your own alerting, not a place where a bug should look like
a TrustGuard failure.
Call close() after invoke, or await aclose() after ainvoke, when the middleware owns its
HTTP clients.
Streaming responses
after_model runs on the assembled AIMessage, once the model call has finished.
For preventive enforcement on the response, either disable streaming on the routes that need it,
or buffer the stream until a verdict is available — at the cost of the time-to-first-token that
streaming exists to provide.
Limits to keep in mind
- Only this agent is inspected. Another service calling the provider directly bypasses TrustGuard, so use a gateway when the guarantee has to be network-wide.
- Each guarded stage costs a round trip, and the hooks run again on every pass through the agent
loop. With
check_input,check_outputandcheck_tool_callsall on, a turn that calls two tools in parallel and then answers is six evaluate calls, not four: input and output on each of the two model passes, plus one per tool call. Sequential tool calls cost more. Keeptimeouttight enough that a stalled call cannot hold a run open. - Detectors evaluate text and tool calls. Non-text content parts are not stripped from the
payload — a message whose content is a list of blocks is forwarded to
/v1/evaluateas-is, so image data an agent sees is transmitted. What is limited is the write path: a transform that tries to alter a non-text part, or to swap a block’s type, is refused and fails closed.
NeuralTrust/langchain-neuraltrust.