Overview
Your account summary and quick stats
Top Triggered Guardrails
Most active policy checks
Loading...
Tool Activity
Tool calls and block rates
Loading...
Recent Activity
Latest agent interactions
Loading...
Active Guardrails
Policies currently protecting your workloads
Tool Policies
This is where teams manage tool-level protections: data policy, approvals, parameter checks, and runtime safety defaults.
Tool Controls Live Here
Keep tool safety and tool permissions in one place.
Use this page for per-tool data policy, approval requirements, parameter-level limits, circuit-breaker thresholds, and any temporary execution exceptions your operators need to understand.
You have unsaved tool policy changes
Loading tool policies...
Loading data policies...
Loading roles...
Agent Registry
Register AI agents, define allowed tools, and set delegation or budget defaults without changing prompt guardrails.
Agent Controls Live Here
Use agents for who can act, not for what data should be exposed.
Keep tool access, agent-to-agent delegation, and workflow-level execution limits here. Use Tool Policies for data handling and I/O Guardrails for prompt and response safety.
Loading agents...
I/O Guardrails
Manage your input and output guardrail policies — each guardrail shows its live configuration JSON
You have unsaved changes
▸Full Policy JSON
Complete
input_guardrails + output_guardrails config — click to expandInput Guardrails
Applied to incoming user prompts
Custom Input Policies
Natural language policies evaluated by LLM
No custom input policies configured.
Output Guardrails
Applied to LLM responses before returning to users
Custom Output Policies
Natural language policies evaluated by LLM
No custom output policies configured.
▸Advanced — Raw JSON
For power users: edit the full policy config directly
Create Custom Policy
0/2000 characters
50%
100%
Current: 80%
1
High Priority
1000
Current Priority: Standard
Usage
Quota consumption over time
Activity
Recent changes to your tenant configuration
Loading…
Telemetry
Inspect agent messages, tool calls, RBAC outcomes, and guardrail stages.
Messages
0
logged agent-chat requests
Blocked
0
input or output hard blocks
Warnings
0
messages with blocked tools
Tool Calls
0
0 blocked
APIs & SDKs
Integrate Votal Shield into your applications. All endpoints require
X-API-Key header.
🔑
Base URL & Auth
Header: X-API-Key: <your-key>
🚀 Votal AI Platform Access
Use X-Votal-Tenant-Key header for tenant-specific policies
X-Votal-Tenant-Key Header
All requests to the Votal AI platform must include your tenant key for proper policy application and routing.
your-x-votal-tenant-key-here
API Endpoint
https://your-domain.com/v1/
Legacy API Key (Deprecated)
—
Use this key in the
X-API-Key header for all requests.2. Integration Flow
Typical integration architecture
1
Register Agents
Define agents with tools and role-based permissions
POST /v1/agents/registry
2
Configure Guardrails
Set input/output guardrail policies (adversarial, toxicity, PII, etc.)
PUT /v1/tenant/me/policies
3
Set Tool Policies
Data sanitization patterns, role restrictions, LLM validation per tool
PUT /v1/agents/tools/policies
4
Run Guardrails (Runtime)
LiteLLM handles input/output guardrails automatically. App calls Shield only for tool RBAC + data policies.
POST /v1/shield/tool/check · /output
5
Monitor & Audit
Track usage, review audit logs, monitor guardrail decisions
GET /v1/tenant/me/usage · /audit
3. All Available Endpoints
Complete endpoint map organized by category
Production Architecture: LiteLLM + Shield
Your app talks to two services — LiteLLM for LLM calls (guardrails automatic), Shield only for tool RBAC + data policies
Your App
│
├─▶ LiteLLM Proxy (https://litellm.your-company.com)
│ ├── Input guardrails run automatically (adversarial, PII, toxicity)
│ ├── LLM call → returns tool_calls
│ └── Output guardrails run automatically (PII leakage, bias)
│
├─▶ Shield (https://shield.votal.ai) /v1/shield/tool/check
│ ├── RBAC: can this role use this tool?
│ ├── Data policy: do input rules allow these args?
│ └── allowed → execute tool locally
│
└─▶ Shield (https://shield.votal.ai) /v1/shield/tool/output
├── allow → output unchanged
├── mask → partial masking (j***@****.com)
├── redact → full replacement ([REDACTED])
└── block → output rejected entirely
│
├─▶ LiteLLM Proxy (https://litellm.your-company.com)
│ ├── Input guardrails run automatically (adversarial, PII, toxicity)
│ ├── LLM call → returns tool_calls
│ └── Output guardrails run automatically (PII leakage, bias)
│
├─▶ Shield (https://shield.votal.ai) /v1/shield/tool/check
│ ├── RBAC: can this role use this tool?
│ ├── Data policy: do input rules allow these args?
│ └── allowed → execute tool locally
│
└─▶ Shield (https://shield.votal.ai) /v1/shield/tool/output
├── allow → output unchanged
├── mask → partial masking (j***@****.com)
├── redact → full replacement ([REDACTED])
└── block → output rejected entirely
Your app never calls
/guardrails/input or /guardrails/output directly.
LiteLLM handles those via Shield middleware. Your app only adds ~10 lines around the tool execution loop.
If tools/agents are not registered in the portal, they are blocked by default and tracked as shadow agents.
LangChain + LLM Shield
Protect LangChain agents with tool RBAC, data policies, and shadow discovery
Guardrails via LiteLLM
Tool RBAC
Data Policy (mask/redact/block)
Shadow Discovery
1. Install
pip install langchain langchain-openai openai requests
2. Complete Integration
LangChain agent with LiteLLM guardrails + Shield tool RBAC
langchain_shield_agent.py
"""
LangChain + LLM Shield (Production Architecture)
LiteLLM proxy handles input/output guardrails automatically.
App only calls Shield for tool RBAC + data policy enforcement.
"""
import os, json, requests
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
# LiteLLM proxy — your LLM gateway (guardrails run here automatically)
LITELLM_URL = os.getenv("LITELLM_URL", "https://litellm.your-company.com")
# Shield — tool RBAC + data policies (separate service)
SHIELD_URL = os.getenv("LLM_SHIELD_URL", "https://shield.votal.ai")
API_KEY = os.getenv("API_KEY")
AGENT_ID = os.getenv("AGENT_ID", "my-langchain-agent")
USER_ROLE = os.getenv("USER_ROLE", "user")
shield = requests.Session()
shield.headers.update({
"X-API-Key": API_KEY,
"X-Agent-Key": AGENT_ID,
"X-User-Role": USER_ROLE,
"Content-Type": "application/json",
})
# ── 1. Register Agent (do this once) ──────────────────────────────
def register_agent():
"""Register the agent so Shield knows about it.
If you skip this, the agent appears as a SHADOW AGENT."""
resp = shield.post(f"{SHIELD_URL}/v1/agents/registry", json={
"agent_id": AGENT_ID,
"name": "LangChain Support Agent",
"tools": ["search_faq", "create_ticket", "check_order_status"],
"role_permissions": {
"user": ["search_faq", "check_order_status"],
"support": ["search_faq", "create_ticket", "check_order_status"],
"admin": ["search_faq", "create_ticket", "check_order_status"],
},
})
print("Registered:", resp.json())
# ── 2. Define LangChain tools ─────────────────────────────────────
@tool
def search_faq(query: str) -> str:
"""Search the FAQ knowledge base for answers."""
return f"FAQ result for '{query}': Check our help center at /help."
@tool
def create_ticket(subject: str, description: str) -> str:
"""Create a support ticket for the customer."""
return f"Ticket created: {subject} — ID: TKT-{hash(subject) % 10000}"
@tool
def check_order_status(order_id: str) -> str:
"""Check the current status of a customer order."""
return f"Order {order_id}: Shipped, arriving in 2 days."
lc_tools = [search_faq, create_ticket, check_order_status]
# ── 3. Shielded LangChain agent ──────────────────────────────────
def run_agent(user_message: str):
# LangChain LLM pointed at LiteLLM proxy
# Input/output guardrails are applied automatically by LiteLLM
llm = ChatOpenAI(
model="default",
base_url=f"{LITELLM_URL}/v1",
api_key=os.getenv("OPENAI_API_KEY", "not-needed"),
default_headers={
"X-API-Key": API_KEY,
"X-Agent-Key": AGENT_ID,
"X-User-Role": USER_ROLE,
},
)
llm_with_tools = llm.bind_tools(lc_tools)
# Step 1: LLM call via LiteLLM (guardrails automatic)
messages = [HumanMessage(content=user_message)]
try:
response = llm_with_tools.invoke(messages)
except Exception as e:
return f"Blocked by guardrails: {e}"
if not response.tool_calls:
return response.content
# Step 2: Process tool calls with Shield RBAC + data policies
messages.append(response)
tool_map = {t.name: t for t in lc_tools}
output_parts = []
for tc in response.tool_calls:
name, args = tc["name"], tc["args"]
# 2a. Shield RBAC + data policy check on tool args
check = shield.post(f"{SHIELD_URL}/v1/shield/tool/check", json={
"tool_name": name,
"tool_input": args,
"agent_key": AGENT_ID,
"user_role": USER_ROLE,
}).json()
if not check.get("allowed", True):
reason = check.get("reason", "denied by RBAC")
messages.append(ToolMessage(content=f"DENIED: {reason}",
tool_call_id=tc["id"]))
output_parts.append(f"BLOCKED: {name} — {reason}")
continue
# 2b. Execute the tool locally
raw_output = tool_map[name].invoke(args)
# 2c. Sanitize tool output (mask/redact/block per data policy)
sanitized = shield.post(f"{SHIELD_URL}/v1/shield/tool/output", json={
"tool_name": name,
"tool_output": str(raw_output),
"agent_key": AGENT_ID,
}).json()
if sanitized.get("action") == "block":
messages.append(ToolMessage(
content="Tool output blocked by data policy.",
tool_call_id=tc["id"]))
else:
clean = sanitized.get("sanitized_output", str(raw_output))
messages.append(ToolMessage(content=clean, tool_call_id=tc["id"]))
output_parts.append(f"[{name}] {clean}")
# Step 3: Final LLM call (output guardrails automatic via LiteLLM)
try:
final = llm_with_tools.invoke(messages)
return final.content
except Exception:
return "\n".join(output_parts)
if __name__ == "__main__":
# register_agent() # Uncomment on first run
print(run_agent("What is your return policy?"))
print(run_agent("Check status of order ORD-555"))
print(run_agent("Create a ticket: billing issue"))
3. Shadow Discovery
What happens when a LangChain agent is deployed without registering
If
1. Every API call with that
2. Any tools the LLM calls are flagged as shadow tools
3. The tenant portal Agents tab shows a "Shadow Discovery" panel
4. Metadata captured: call count, endpoints hit, roles used, first/last seen
5. Admin can dismiss or register the agent from the UI
AGENT_ID is not registered:1. Every API call with that
X-Agent-Key is tracked by the middleware2. Any tools the LLM calls are flagged as shadow tools
3. The tenant portal Agents tab shows a "Shadow Discovery" panel
4. Metadata captured: call count, endpoints hit, roles used, first/last seen
5. Admin can dismiss or register the agent from the UI
CrewAI + LLM Shield
Multi-agent crews with per-agent tool RBAC, data policies, and shadow discovery
Guardrails via LiteLLM
Per-Agent Tool RBAC
Data Policy (mask/redact/block)
Shadow Discovery
1. Install
pip install crewai crewai-tools openai requests
2. Complete Integration
Each CrewAI agent registers with Shield separately, enabling per-agent RBAC and shadow detection
crewai_shield_agent.py
"""
CrewAI + LLM Shield (Production Architecture)
LiteLLM proxy handles input/output guardrails automatically.
App only calls Shield for per-agent tool RBAC + data policies.
"""
import os, json, requests
from crewai import Agent, Task, Crew, Process
from crewai.tools import BaseTool
from typing import Type
from pydantic import BaseModel, Field
# LiteLLM proxy — your LLM gateway (guardrails run here automatically)
LITELLM_URL = os.getenv("LITELLM_URL", "https://litellm.your-company.com")
# Shield — tool RBAC + data policies (separate service)
SHIELD_URL = os.getenv("LLM_SHIELD_URL", "https://shield.votal.ai")
API_KEY = os.getenv("API_KEY")
shield = requests.Session()
shield.headers.update({
"X-API-Key": API_KEY,
"Content-Type": "application/json",
})
# ── 1. Register all crew agents (do once) ─────────────────────────
def register_crew():
agents = [
{
"agent_id": "research-agent",
"name": "Research Analyst",
"tools": ["web_search", "document_search"],
"role_permissions": {
"analyst": ["web_search", "document_search"],
"viewer": ["web_search"],
},
},
{
"agent_id": "writer-agent",
"name": "Report Writer",
"tools": ["generate_report", "send_email"],
"role_permissions": {
"analyst": ["generate_report", "send_email"],
"viewer": ["generate_report"],
},
},
]
for a in agents:
r = shield.post(f"{SHIELD_URL}/v1/agents/registry", json=a)
print(f"Register {a['agent_id']}: {r.status_code}")
# ── 2. Shield-aware tool wrapper ──────────────────────────────────
class ShieldTool(BaseTool):
"""Wraps tool calls with Shield RBAC + data policy checks.
Input/output guardrails are handled by LiteLLM automatically."""
agent_key: str = ""
user_role: str = "analyst"
def _run(self, query: str) -> str:
# PRE-EXECUTION: RBAC + data policy check on tool args
tool_check = shield.post(f"{SHIELD_URL}/v1/shield/tool/check", json={
"tool_name": self.name,
"tool_input": {"query": query},
"agent_key": self.agent_key,
"user_role": self.user_role,
}).json()
if not tool_check.get("allowed", True):
return f"BLOCKED: {self.name} — {tool_check.get('reason', 'denied')}"
# Execute tool (your actual implementation here)
raw_output = f"[{self.name}] Results for: {query}"
# POST-EXECUTION: Sanitize tool output (mask/redact/block)
sanitized = shield.post(f"{SHIELD_URL}/v1/shield/tool/output", json={
"tool_name": self.name,
"tool_output": raw_output,
"agent_key": self.agent_key,
}).json()
if sanitized.get("action") == "block":
return f"BLOCKED: {self.name} output violated data policy"
return sanitized.get("sanitized_output", raw_output)
# ── 3. Build the crew ─────────────────────────────────────────────
class WebSearchInput(BaseModel):
query: str = Field(description="Search query")
class WebSearchTool(ShieldTool):
name: str = "web_search"
description: str = "Search the web for information"
args_schema: Type[BaseModel] = WebSearchInput
agent_key: str = "research-agent"
class GenerateReportInput(BaseModel):
query: str = Field(description="Report topic and data")
class GenerateReportTool(ShieldTool):
name: str = "generate_report"
description: str = "Generate a formatted report"
args_schema: Type[BaseModel] = GenerateReportInput
agent_key: str = "writer-agent"
def run_crew(topic: str, user_role: str = "analyst"):
# CrewAI uses LiteLLM as its LLM backend
# Input/output guardrails are applied automatically by LiteLLM
researcher = Agent(
role="Research Analyst",
goal=f"Research: {topic}",
backstory="Senior analyst with deep domain expertise",
tools=[WebSearchTool(user_role=user_role)],
llm=f"litellm/{LITELLM_URL}/v1/default",
verbose=True,
)
writer = Agent(
role="Report Writer",
goal="Write a clear, actionable report",
backstory="Technical writer specializing in research reports",
tools=[GenerateReportTool(user_role=user_role)],
llm=f"litellm/{LITELLM_URL}/v1/default",
verbose=True,
)
crew = Crew(
agents=[researcher, writer],
tasks=[
Task(description=f"Research {topic}", expected_output="Findings", agent=researcher),
Task(description="Write report", expected_output="Report", agent=writer),
],
process=Process.sequential,
verbose=True,
)
return str(crew.kickoff())
if __name__ == "__main__":
# register_crew() # Uncomment on first run
print(run_crew("Analyze Q1 2026 market trends in AI"))
3. Shadow Discovery for Crews
Detect crew members deployed without proper registration
Multi-agent shadow detection:
1. Each CrewAI agent maps to a separate
2. If any crew member is not registered, it is tracked as a shadow agent
3. Tools used by unregistered agents are tracked as shadow tools
4.
5. Admin can register missing agents or dismiss false positives
1. Each CrewAI agent maps to a separate
agent_id in Shield2. If any crew member is not registered, it is tracked as a shadow agent
3. Tools used by unregistered agents are tracked as shadow tools
4.
GET /v1/agents/unregistered returns all shadow items5. Admin can register missing agents or dismiss false positives
OpenAI Agents SDK + LLM Shield
OpenAI function-calling agents with tool RBAC, data policies via Shield
Guardrails via LiteLLM
Tool RBAC
Data Policy (mask/redact/block)
Shadow Discovery
1. Install
pip install openai requests
2. Complete Integration
ShieldedAgent wraps OpenAI function calling with pre/post guardrails and RBAC enforcement
openai_shield_agent.py
"""
OpenAI Agents SDK + LLM Shield (Production Architecture)
LiteLLM proxy handles input/output guardrails automatically.
App only calls Shield for tool RBAC + data policy enforcement.
"""
import os, json, requests
from openai import OpenAI
# LiteLLM proxy — your LLM gateway (guardrails run here automatically)
LITELLM_URL = os.getenv("LITELLM_URL", "https://litellm.your-company.com")
# Shield — tool RBAC + data policies (separate service)
SHIELD_URL = os.getenv("LLM_SHIELD_URL", "https://shield.votal.ai")
API_KEY = os.getenv("API_KEY")
AGENT_ID = os.getenv("AGENT_ID", "my-openai-agent")
USER_ROLE = os.getenv("USER_ROLE", "user")
# OpenAI client pointed at LiteLLM (guardrails automatic)
client = OpenAI(base_url=f"{LITELLM_URL}/v1",
api_key=os.getenv("OPENAI_API_KEY", "not-needed"),
default_headers={"X-API-Key": API_KEY,
"X-Agent-Key": AGENT_ID,
"X-User-Role": USER_ROLE})
shield = requests.Session()
shield.headers.update({
"X-API-Key": API_KEY,
"X-Agent-Key": AGENT_ID,
"X-User-Role": USER_ROLE,
"Content-Type": "application/json",
})
# ── 1. Register Agent (do this once) ──────────────────────────────
def register_agent():
shield.post(f"{SHIELD_URL}/v1/agents/registry", json={
"agent_id": AGENT_ID,
"name": "My OpenAI Agent",
"tools": ["lookup_order", "cancel_order", "get_refund_status"],
"role_permissions": {
"user": ["lookup_order", "get_refund_status"],
"admin": ["lookup_order", "cancel_order", "get_refund_status"],
},
})
# ── 2. Define tools (OpenAI function calling format) ──────────────
tools = [
{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up order details by order ID",
"strict": True,
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
"additionalProperties": False,
},
}},
{"type": "function", "function": {
"name": "cancel_order",
"description": "Cancel an existing order",
"strict": True,
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}, "reason": {"type": "string"}},
"required": ["order_id", "reason"],
"additionalProperties": False,
},
}},
]
# ── 3. Shielded agent loop ────────────────────────────────────────
def run_agent(user_message: str):
# Step 1: LLM call via LiteLLM (input guardrails automatic)
messages = [
{"role": "system", "content": "You are a helpful support agent."},
{"role": "user", "content": user_message},
]
response = client.chat.completions.create(
model="default", messages=messages,
tools=tools, tool_choice="auto",
)
msg = response.choices[0].message
if not msg.tool_calls:
return msg.content # Output guardrails already applied by LiteLLM
# Step 2: Process tool calls with Shield RBAC + data policies
for tc in msg.tool_calls:
name = tc.function.name
args = json.loads(tc.function.arguments)
# 2a. Shield RBAC + data policy check on tool args
check = shield.post(f"{SHIELD_URL}/v1/shield/tool/check", json={
"tool_name": name,
"tool_input": args,
"agent_key": AGENT_ID,
"user_role": USER_ROLE,
}).json()
if not check.get("allowed", True):
messages.append({"role": "tool", "tool_call_id": tc.id,
"content": f"DENIED: {check.get('reason', 'RBAC')}"})
continue
# 2b. Execute tool (your implementation)
raw_output = execute_tool(name, args)
# 2c. Sanitize output (mask/redact/block per data policy)
sanitized = shield.post(f"{SHIELD_URL}/v1/shield/tool/output", json={
"tool_name": name,
"tool_output": str(raw_output),
"agent_key": AGENT_ID,
}).json()
if sanitized.get("action") == "block":
messages.append({"role": "tool", "tool_call_id": tc.id,
"content": "Output blocked by data policy."})
else:
clean = sanitized.get("sanitized_output", str(raw_output))
messages.append({"role": "tool", "tool_call_id": tc.id,
"content": clean})
# Step 3: Final LLM call (output guardrails automatic via LiteLLM)
final = client.chat.completions.create(model="default", messages=messages)
return final.choices[0].message.content
if __name__ == "__main__":
# register_agent() # Uncomment on first run
print(run_agent("What's the status of order ORD-12345?"))
print(run_agent("Cancel order ORD-12345, I changed my mind"))
3. Shadow Discovery
What happens when a developer deploys an agent WITHOUT registering it
If
1. Every API call with that
2. Any tools the LLM calls are flagged as shadow tools
3. The tenant portal Agents tab shows a "Shadow Discovery" panel
4. Metadata captured: call count, endpoints hit, roles used, first/last seen
5. Admin can dismiss or register the agent from the UI
AGENT_ID is not registered:1. Every API call with that
X-Agent-Key is tracked by the middleware2. Any tools the LLM calls are flagged as shadow tools
3. The tenant portal Agents tab shows a "Shadow Discovery" panel
4. Metadata captured: call count, endpoints hit, roles used, first/last seen
5. Admin can dismiss or register the agent from the UI
Anthropic Claude + LLM Shield
Claude tool-use agents with tool RBAC, data policies via Shield
Guardrails via LiteLLM
Claude Tool Use
Data Policy (mask/redact/block)
Shadow Discovery
1. Install
pip install anthropic requests
2. Complete Integration
Anthropic Claude with tool use, wrapped by Shield guardrails and RBAC
anthropic_shield_agent.py
"""
Anthropic Claude + LLM Shield (Production Architecture)
LiteLLM proxy handles input/output guardrails automatically.
App only calls Shield for tool RBAC + data policy enforcement.
"""
import os, json, requests
import anthropic
# LiteLLM proxy — your LLM gateway (guardrails run here automatically)
LITELLM_URL = os.getenv("LITELLM_URL", "https://litellm.your-company.com")
# Shield — tool RBAC + data policies (separate service)
SHIELD_URL = os.getenv("LLM_SHIELD_URL", "https://shield.votal.ai")
API_KEY = os.getenv("API_KEY")
AGENT_ID = os.getenv("AGENT_ID", "my-claude-agent")
USER_ROLE = os.getenv("USER_ROLE", "user")
# Claude client via LiteLLM (guardrails automatic)
claude = anthropic.Anthropic(base_url=f"{LITELLM_URL}/v1",
api_key=os.getenv("ANTHROPIC_API_KEY", "not-needed"))
shield = requests.Session()
shield.headers.update({
"X-API-Key": API_KEY,
"X-Agent-Key": AGENT_ID,
"X-User-Role": USER_ROLE,
"Content-Type": "application/json",
})
# ── 1. Register Agent ─────────────────────────────────────────────
def register_agent():
shield.post(f"{SHIELD_URL}/v1/agents/registry", json={
"agent_id": AGENT_ID,
"name": "Claude Support Agent",
"tools": ["search_knowledge_base", "create_ticket", "escalate_to_human"],
"role_permissions": {
"user": ["search_knowledge_base"],
"support": ["search_knowledge_base", "create_ticket"],
"admin": ["search_knowledge_base", "create_ticket", "escalate_to_human"],
},
})
# ── 2. Define tools (Anthropic format) ────────────────────────────
claude_tools = [
{"name": "search_knowledge_base",
"description": "Search the support knowledge base",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
}},
{"name": "create_ticket",
"description": "Create a support ticket",
"input_schema": {
"type": "object",
"properties": {
"subject": {"type": "string"},
"description": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
},
"required": ["subject", "description", "priority"],
}},
]
# ── 3. Shield-wrapped Claude agent ────────────────────────────────
def run_claude_agent(user_message: str):
# Step 1: Call Claude via LiteLLM (input guardrails automatic)
response = claude.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system="You are a helpful support agent. Use tools when needed.",
messages=[{"role": "user", "content": user_message}],
tools=claude_tools,
)
# Step 2: Process response blocks
results = []
tool_results = []
for block in response.content:
if block.type == "text":
results.append(block.text)
elif block.type == "tool_use":
name = block.name
# 2a. Shield RBAC + data policy check on tool args
check = shield.post(f"{SHIELD_URL}/v1/shield/tool/check", json={
"tool_name": name,
"tool_input": block.input,
"agent_key": AGENT_ID,
"user_role": USER_ROLE,
}).json()
if not check.get("allowed", True):
tool_results.append({"type": "tool_result", "tool_use_id": block.id,
"content": f"DENIED: {check.get('reason', 'RBAC')}"})
continue
# 2b. Execute tool (your implementation)
raw_output = execute_tool(name, block.input)
# 2c. Sanitize output (mask/redact/block per data policy)
sanitized = shield.post(f"{SHIELD_URL}/v1/shield/tool/output", json={
"tool_name": name,
"tool_output": str(raw_output),
"agent_key": AGENT_ID,
}).json()
if sanitized.get("action") == "block":
tool_results.append({"type": "tool_result", "tool_use_id": block.id,
"content": "Output blocked by data policy."})
else:
clean = sanitized.get("sanitized_output", str(raw_output))
tool_results.append({"type": "tool_result", "tool_use_id": block.id,
"content": clean})
# Step 3: If tools were called, send results back to Claude
if tool_results:
final = claude.messages.create(
model="claude-sonnet-4-20250514", max_tokens=1024,
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results},
],
tools=claude_tools,
)
return final.content[0].text
return "\n".join(results)
if __name__ == "__main__":
# register_agent() # Uncomment on first run
print(run_claude_agent("Search for how to reset my password"))
print(run_claude_agent("Create an urgent ticket: billing error"))
Guardrails Playground
Test your input and output guardrails in real-time against your Shield endpoint
Endpoint Configuration
Configure the Shield API endpoint to test against
The base URL of your LLM Shield deployment (no trailing slash)
Pre-filled from your login session. Override if testing a different key.
Test Configuration
Choose guardrail direction, optional context, and the message to test
🛡️ RBAC Tool Testing
Test tool permissions for your tenant's agents and roles
POLICY STATUS
Click "Load Policy" to fetch your tenant's RBAC configuration
EXPECTED RESULT
Load your tenant policy to see expected RBAC results
AVAILABLE TOOLS
RBAC Test Result
Raw Response
Response
Raw Response
System Health
Monitor Votal AI platform services and infrastructure status
API Gateway
🟢 Online
LLM Proxy
🟢 Online
Guardrail Model
🟢 Online
Redis Cache
🟢 Online
GPU Utilization
45%
Memory Usage
32 GB
Service Status
All platform microservices
API Gateway
Entry point routing
LLM Proxy (LiteLLM)
Model routing & guardrails
Votal Guardrail Model
Pre/post call inspection
Main LLM Service
Qwen, GLM, KIWI models
Redis Stack
Policy cache & tenant data
Recent Alerts
System notifications
No recent alerts
Performance Metrics
Real-time performance monitoring for tenant operations
Policy Lookup Time
0.8ms
Target: <1ms
Guardrail Inspection
187ms
Target: <250ms
API Throughput
156 req/s
Target: >100 req/s
Redis Ops/Sec
68,432
Target: >50k
Cache Hit Rate
94.2%
Optimal: >90%
Error Rate
0.02%
Target: <0.1%
Performance Benchmarks
Key performance indicators for your tenant
Policy Lookup Latency
0.8ms / 1ms target
Guardrail Inspection Speed
187ms / 250ms target
API Gateway Throughput
156 req/s / 100 target
Votal AI Architecture
Platform architecture overview and service topology
Request Flow
How your requests flow through the Votal AI platform
Client
Agentic App
→
API
Gateway
→
LLM
Proxy
→
VGM
Votal Guardrail
X-Votal-Tenant-Key Header
All requests must include your tenant key for policy application and routing
Infrastructure
Current deployment resources
CPU Cores
32+
Memory
64GB
GPU VRAM
80GB
Storage
500GB SSD
Features Enabled
Platform capabilities
✓
Fast Policy Lookups (<1ms)
✓
Red Team Attack Synthesis
✓
Multi-Tenant Isolation
✓
Agent & Tool Governance
✓
Behavioral Monitoring
✓
185+ Attack Strategies
Tool Kill Switch
All registered tools in one view. Disable instantly, re-enable when safe.
| Tool | Agents | Status | Reason | Action |
|---|---|---|---|---|
| Loading tools... | ||||
Runtime Decision Audit
Every guardrail enforcement decision — who was blocked, by which policy, and when.
Query Decisions
Click Search to query decisions
Webhook Notifications
Push events to Slack, PagerDuty, or any HTTP endpoint when guardrails fire.
Create Webhook
Events:
Registered Webhooks
Loading...
Operations Center
Monitor active operations and handle immediate issues
Pending Approvals
Loading...
Circuit Breakers
Loading...
🟡 Pending Approvals
0
Need review
🔴 Circuit Breakers
0
Tools blocked
⏱️ Active Grants
0
Temporary access
✅ All Systems
Normal
Operational
⚠️ Needs Attention
Issues requiring immediate action
No urgent issues
📈 Recent Activity
Last 24 hours
No recent activity
Approval Queue
Read the current approval state for risky tool requests. Configure approval requirements in Tool Registry.
Execution Grants
See active short-lived exceptions and who they apply to. Grant issuance remains an operator action outside this runtime dashboard.
Checkpoints & Resume
Inspect saved workflow state and whether runs have resumed. Checkpoint creation stays in the workflow runtime or API.
Circuit Breakers
Live breaker state with manual reset for quarantined tools.
SIEM Integration
Send guardrail events to Splunk, Azure Sentinel, or any SIEM endpoint.
Add SIEM Endpoint
Guardrail Effectiveness
Track pass/block rates, latency, and trends for every guardrail.
All Guardrails (Last 30 Days)
Loading metrics...
Board & Compliance Report
Executive-level metrics for board presentations and compliance audits.
Daily Trend (30 Days)
Top Threats
Recent Incidents
Guardrail Coverage
Agent Identity & Certificates
Register certificate fingerprints for strong agent authentication. Cert-based agents get high trust, string-key agents get medium.
Register Certificate
Query Agent Trust
How It Works
Trust Levels: Cert-based agents get high trust. String API key agents get medium trust. Higher trust = more permissive guardrails.
Step 1: Generate a certificate for your agent
openssl req -x509 -newkey rsa:4096 -keyout agent.key -out agent.crt -days 365 -nodes openssl x509 -in agent.crt -fingerprint -sha256 -noout # Output: SHA256 Fingerprint=AB:CD:12:34:...
Step 2: Register the fingerprint above using the form, or via API:
curl -X POST ${BASE_URL}/v1/shield/agent/identity/register \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_key": "customer-service-agent",
"fingerprint": "AB:CD:12:34:...",
"tenant_id": "YOUR_TENANT_ID"
}'
Step 3: Agent sends fingerprint with every API call:
curl -X POST ${BASE_URL}/v1/shield/chat/agent \
-H "X-API-Key: YOUR_KEY" \
-H "X-Client-Cert-Fingerprint: AB:CD:12:34:..." \
-H "Content-Type: application/json" \
-d '{"agent_key": "customer-service-agent", "messages": [...]}'
Step 4: Query trust level for any agent:
curl ${BASE_URL}/v1/shield/agent/identity/customer-service-agent?tenant_id=YOUR_TENANT_ID \
-H "X-API-Key: YOUR_KEY"
# Response:
# {"agent_key": "customer-service-agent", "trust": {"trust_level": "high", "identity_method": "cert"}}
Step 5: Revoke if compromised — click Revoke Cert above or call POST /v1/shield/agent/identity/revoke. Agent immediately drops to medium trust.
Agent AuthN / AuthZ
Signed agent tokens prove who is calling. One-shot capability tokens decide what they can do — per action, ≤60s, replay-detected. Read the architecture →
Activity
Agent tokens issued
—
Capabilities minted
—
AuthZ denials
—
Capabilities verified
—
Replays caught
—
Non-zero = someone tried to reuse a one-shot token.
Invalid tokens
—
Revocations
—
Recent decisions
Auto-refreshes every 10s · last 50 events
| Time | Event | Agent | User | Tool | Resource | Reason |
|---|---|---|---|---|---|---|
| Loading… | ||||||
How to integrate
Three calls. Copy-paste ready.
1. Mint an agent token (control plane, admin-gated)
curl -X POST $BASE/v1/shield/auth/agent-token \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_sub": "alice",
"agent_id": "billing-bot",
"agent_instance_id": "inst-1",
"tenant_id": "",
"build_hash": "sha256:<agent-build>",
"model_version": "claude-opus-4.7",
"session_id": "s1",
"ttl_seconds": 600
}'
2. Before every tool call, mint a capability
curl -X POST $BASE/v1/shield/cap/mint \
-H "X-Agent-Token: $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tool":"send_email","resource":"alice/inbox","ttl_seconds":30}'
Returns a single-use cap. If RBAC denies, you get
403 with reasons — visible in the table above.3. Tool/MCP server verifies the cap before executing
curl -X POST $BASE/v1/shield/cap/verify \
-H "Content-Type: application/json" \
-d '{"cap_token":"$CAP","expected_tool":"send_email"}'
Verifies signature + expiry + tool binding + burns the nonce (one-shot). A replay attempt shows up in the table as
cap_replay in red.
Quick reality check:
- Agent tokens ≤ 15 minutes. Capability tokens ≤ 60 seconds.
- Caps are single-use (nonce burned at verify). Replay returns
valid: false. - Verify uses a local public key — no round-trip to Shield needed at the tool.
- If a build hash isn't on the allowlist, the token is rejected at the middleware.
- Revoke an instance / user / token via
POST /v1/shield/auth/revoke(admin-gated).