Instrument an agent
An integration has two jobs: observe how messages are produced, processed and passed, and check actions at every dispatch site before they execute. Observability supplies the graph a policy reasons over. The reference monitor (RM) decides whether a proposed action may proceed. Recording an action after it happens does not enforce a policy.
The core contract
Section titled “The core contract”Treat framework methods as computations from input messages to output messages. Record the actual messages and connect each input to the output that used it: input → output. A dependency means “this computation consumed that input,” not simply “these events happened next to each other.”
| Framework operation | Inputs | Output |
|---|---|---|
| LLM call | The list of messages sent to the model, including supplied context | The model’s response, with any structured tool-call specifications |
| Tool-call handler | The message containing the selected tool call, plus any additional message inputs the handler consumes | A message containing the tool’s actual return value and its originating tool specification |
| Parser, formatter or summarizer | The messages being processed | The derived message |
| Agent delegation or message forwarding | The messages passed to the receiving agent | The receiving message or subsequent result, retaining its input dependencies |
Observe all methods that produce, process or pass these messages, including application-owned wrappers around a framework. Preserve an existing message ID when passing the same observation; give a newly derived message a new ID. Keep parallel calls and subagents connected to their actual inputs. If a method consumes additional context, include that context rather than linking only the most recent message.
A tool-call handler
Section titled “A tool-call handler”This framework-neutral pseudocode shows the order of operations. observe and require_allow stand for your adapter’s SDK calls; they are not SDK function names. Inputs have already been recorded in the active session.
def handle_tool_call(input_message, tool_call): inputs = [input_message.id] require_allow(tool_action(tool_call), input_ids=inputs) value = dispatch_tool(tool_call) output = tool_result_message(value, derived_from=tool_call) observe(output, input_ids=inputs) return outputThe checked name and arguments must describe the call that is actually dispatched. Place the check where execution occurs, including alternate dispatch paths, retries and delegated calls. Only an allow decision may reach dispatch_tool. A denial, an approval request, or an unavailable engine must stop this path; an approval suggestion is not itself authorization. Apply required transforms through a supported adapter before dispatch, or stop if the adapter cannot apply them.
Record a tool exception as an error outcome, not a successful result. If the call is denied, report the denial to the framework instead of manufacturing a tool return value.
An LLM call
Section titled “An LLM call”The model consumes the whole supplied message list, so its reply depends on every message in that list. A reply that proposes a tool call is still a message; it is not permission to execute the call.
def call_model(messages): inputs = [message.id for message in messages] require_allow(model_request(messages), input_ids=inputs) response = dispatch_model_request(messages) observe(response, input_ids=inputs) return responseRepresent the outgoing model request using the action vocabulary your policy and adapter agree on, such as its HTTP request. The check belongs at that outgoing dispatch site. If a framework adapter already mediates it, preserve that mediation. The later tool dispatch needs its own check against the selected tool and arguments.
Connect the contract to the SDK
Section titled “Connect the contract to the SDK”In Python, sasy.observability.record_events_with_dependencies(events, edges) records messages and edges atomically. Event.tools carries tool-call specifications in an input message; Event.derived_from identifies the tool behind a result. An Edge(source=input_id, destination=output_id) records the dependency. The authenticated writer is stamped by the server; message roles and caller-supplied actor labels are not authentication credentials.
For tool actions, sasy.check_tool_call(name, args_json, input_node_ids=[...]) returns the decision. The TypeScript SDK exposes registerEventsWithDependencies and checkToolCall; its session wrapper supplies the session scope. Bind the policy and keep recording and checking inside the same session; register input observations before checking an action that depends on them. HTTP forwarding uses the reference-monitor proxy API. See configuration for connection and session setup and policy language for the request facts these operations produce.
Python adapters are provided for supported Langroid and Tau2 paths, plus HTTP instrumentation. Installing an SDK does not automatically instrument an arbitrary framework or every application-owned dispatch path.
Validate an adapter with a small conversation: inspect the stored input/output edges, exercise an allowed and denied dispatch, and check a tool error, concurrent calls and engine unavailability. The protected call must never run because an observation or authorization RPC failed.
Captured content
Section titled “Captured content”Python and TypeScript capture APIs remove recognized transport credentials from supported telemetry fields by default. They preserve the outgoing request and direct authorization arguments. This is not general secret redaction: graph content can still be sensitive, and policies comparing recorded text must account for the sanitized copy. Capture-budget failures stop before the observation RPC; do not proceed with a protected action whose required observations were not recorded. See current limits.