Why We Intercept the LLM Call, Not Just the Tool Call
Tool-level interception catches the execution. LLM-level interception catches the intent. For governance, intent is what matters.
There are two natural places to instrument an agent for governance: the tool call, where it acts on the world, or the LLM call, where it decides what to do next.
Most governance tools default to the tool call. It's concrete - a function name, an argument list, an obvious action to evaluate and block. obsvr instruments both, but if we had to pick one, the LLM call is the one that actually matters more.
What's in the LLM call
The LLM call carries the prompt - the full context the model was working from when it made its decision. That's where intent actually lives.
By the time a tool call happens, the decision is already locked in. The model has decided to call delete_file with path: "/data/users.db". Catching that at the tool level tells you what it decided. Catching it earlier, at the LLM call, tells you what it was working with - closer to why, even if not a full explanation.
For compliance purposes, the why carries about as much weight as the what. "The agent deleted the database" is an incident report. "The agent deleted the database because the prompt contained something it read as a maintenance instruction" is an incident with a cause you can actually act on.
PII in prompts
The most immediately practical reason to sit at the LLM level is catching PII before it leaves. People paste things into prompts - emails, forms, whole documents - and that content regularly contains SSNs, card numbers, medical details, all headed to a third-party API with its own data residency and retention rules.
If you're only watching tool calls, that data is already gone by the time you'd notice anything. Sitting at the LLM level means you catch it first, with time to redact before the request goes out.
The full reasoning chain
In an agentic loop, the pattern is LLM call, tool call, LLM call, tool call, and so on - each LLM call produces a tool call, and the result feeds the next LLM call. If you only log tool calls, you end up with a list of actions and no thread connecting them. There's no way to reconstruct why the agent went from step A to step B.
Log both and you get the whole trace: what the model was given, what it decided, what happened, what came back, what it decided next. That's the version compliance teams and post-incident reviewers actually need to reconstruct a decision.
How obsvr.wrap() works, mechanically
obsvr.wrap(client) returns a Proxy of your LLM client that duck-types the call pattern for whichever provider you're using - chat.completions.create for OpenAI, generateContent for Gemini, messages.create for Anthropic - and intercepts it. The wrapped client keeps the exact same interface as the original one. Every call runs through the governance layer before it reaches the provider, and every response gets logged before it's handed back to your code.
You're not rewriting anything. You're inserting a control point into a path that didn't have one before, and it's a point that has to sit above the tool call to catch what matters.
The ordering problem, concretely
The clearest way to see why the interception point matters is to walk one request through both placements.
A support agent receives: "Customer 4417 says his card ending 8829 was double charged, his SSN is on file as 555-01-9987, can you check and refund him." The agent decides to call lookup_customer and then issue_refund.
Intercept at the tool call and you see two structured invocations with clean arguments: a customer ID and a refund amount. Both look entirely reasonable, and both pass any allowlist you would write. What you never see is that the SSN and the card number went to the model provider in the prompt, because by the time a tool call exists, the request has already been sent, the response has already come back, and the sensitive data left your process several hundred milliseconds ago. There is no control you can apply at the tool boundary that undoes it.
Intercept at the LLM call and the ordering inverts. The scan runs on the prompt before it goes anywhere. The SSN is redacted or the call is blocked, per policy, and the model never receives it. The tool calls that follow are still governed, because interception at the LLM call does not mean giving up interception at the tool call. You get both, in the order that makes the first one useful.
This is the whole argument in one example. Tool-level governance is control over what the agent does. LLM-level governance is control over what the agent sees and sends. Data leaves through the second one, and only the second one can stop it.
The half of the record that lives in the prompt
There is a second reason, and it becomes the important one months later.
A tool-call record tells you an action occurred. It cannot tell you why. When an incident review asks how the agent concluded it should issue a refund, the answer lives in the prompt, the retrieved context, and the model's reasoning, none of which exist in a record that starts at the tool boundary. You end up with a perfectly detailed account of what happened attached to no explanation of why, which is exactly the artifact that fails the transparency obligations under regulation and, more practically, fails the engineer trying to work out what went wrong.
Interception at the LLM call captures both halves: the decision and the context that produced it, joined in one signed record. That is what makes reconstruction possible rather than merely archaeology.
The cost of being early in the path
Sitting above the tool call means being on the hot path of every model call, which is a real obligation rather than a free win, and it constrains the implementation in three ways worth naming.
It has to be fast enough to be invisible, which is why the decision engine is deterministic rules rather than a second model. It has to be non-blocking on the delivery side, because an audit backend having a bad day must never become your application having a bad day, which is why event delivery is fire-and-forget on a bounded queue that drops and counts rather than waits. And it has to not break the host when the control layer itself fails, which is a harder problem than it sounds and took us a dedicated piece of work to close properly.
A control point placed early is more useful and more dangerous than one placed late. Being honest about the second part is what makes the first part safe to accept.