Enforcement (Tool Boundaries)
A runbook for safely intercepting inputs and outputs at the tool boundary.
Enforcement is the core of Njira's safety guarantees. This guide explains how to properly instrument tool boundaries so that blocks and modifications apply successfully.
Integration Paths
Before writing code, decide how you are integrating NjiraAI into your agent architecture:
- Transparent Gateway (Proxy)
- No code changes required. Point your LangChain, OpenAI, or Anthropic client
baseURLto the Njira Gateway. - The Gateway natively enforces tool calls requested by the LLM.
- No code changes required. Point your LangChain, OpenAI, or Anthropic client
- Deep SDK Integration
- Use this path for custom frameworks, internal state tracking, or non-LLM bound tools.
- You must explicitly define the "pre" (input) and "post" (output) boundaries in your code.
(This guide details the Deep SDK Integration path. If using the Gateway proxy, boundaries are handled automatically.)
The Tool Boundary Pattern
The safest and most deterministic place to enforce policy is immediately before and after a capability is executed.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ enforcePre │ ───▶ │ Tool Call │ ───▶ │ enforcePost │
│ (input) │ │ (execute) │ │ (output) │
└─────────────┘ └─────────────┘ └─────────────┘
Handling Verdict States
Every boundary check (Pre or Post) returns an operational state. You must explicitly handle modify and block payloads.
| Verdict | Operator Action |
|---|---|
allow |
Safe. Pass the original payload to the next step. |
block |
Unsafe. Halt execution immediately. Throw an error or return a fallback mechanism back to the agent. |
modify |
Safe, but patched. Crucial: You must extract the modifiedInput (or modifiedOutput) and pass it downstream instead of the original payload. |
Implementation Reference (TypeScript)
When wrapping a tool, follow this rigid sequence to ensure modified payloads are not accidentally discarded.
import { NjiraAI } from "@njiraai/sdk";
// Initialize in your desired mode. Use mode: "active" for active enforcement.
const njira = new NjiraAI({ apiKey: "...", projectId: "...", mode: "active" });
async function safeToolWrap(toolName: string, originalInput: any) {
// ==========================================
// 1) PRE-EXECUTION (Input Evaluation)
// ==========================================
const pre = await njira.enforcePre({
input: originalInput,
metadata: { tool: toolName }
});
if (pre.verdict === "block") {
// Stop the workflow
throw new Error(`Execution Blocked: ${pre.reasons[0]?.message}`);
}
// CRITICAL: Extract the patched payload if one exists
const effectiveInput = pre.verdict === "modify"
? pre.modifiedInput
: originalInput;
// ==========================================
// 2) ACTUAL EXECUTION
// ==========================================
// You must use effectiveInput, not originalInput!
const rawOutput = await executeTool(toolName, effectiveInput);
// ==========================================
// 3) POST-EXECUTION (Output Evaluation)
// ==========================================
const post = await njira.enforcePost({
output: rawOutput,
metadata: { tool: toolName }
});
if (post.verdict === "block") {
// Stop the workflow from returning bad data
throw new Error(`Output Blocked: ${post.reasons[0]?.message}`);
}
// CRITICAL: Apply the final patch before returning to the agent
return post.verdict === "modify"
? post.modifiedOutput
: rawOutput;
}
Implementation Reference (Python)
from njiraai import NjiraAI
njira = NjiraAI(api_key="...", project_id="...", mode="active")
async def safe_tool_wrap(tool_name: str, original_input: dict):
# 1) PRE-EXECUTION
pre = await njira.enforce_pre(
input_data=original_input,
metadata={"tool": tool_name}
)
if pre["verdict"] == "block":
raise RuntimeError(f"Execution Blocked: {pre['reasons'][0]['message']}")
# Extract patched payload
effective_input = pre.get("modifiedInput", original_input) if pre["verdict"] == "modify" else original_input
# 2) ACTUAL EXECUTION
raw_output = await execute_tool(tool_name, effective_input)
# 3) POST-EXECUTION
post = await njira.enforce_post(
output=raw_output,
metadata={"tool": tool_name}
)
if post["verdict"] == "block":
raise RuntimeError(f"Output Blocked: {post['reasons'][0]['message']}")
return post.get("modifiedOutput", raw_output) if post["verdict"] == "modify" else raw_output
Adding Audit Metadata
Raw tool scopes are sometimes not enough for effective triage. Inject structured metadata into every enforcement call so you can pivot your Trace searches later in the Console.
// Example rich context payload
metadata: {
tool: "sql_runner", // Target capability (Required for good scoping)
workflow: "data_analyst", // The parent agent/workflow ID
tenant_id: "org_987", // Multi-tenant architecture support
user_id: "user_456" // End-user debugging
}
Shadow Mode Behavior
When your SDK instance is configured for Shadow mode:
enforcePreandenforcePostwill still calculate and log verdicts.- However, the SDK will automatically return
"allow"as theverdictto your runtime. - You do not need to rewrite your application logic to support Shadow mode; the SDK transparently prevents
blockormodifypayloads from impacting theeffectiveInput.