vitna
VITNA for agent code. Every tool call your agent makes is checked by VITNA before it runs:
- Allowed: the tool runs.
- Blocked: the tool does not run, and the agent gets VITNA's reason as the tool's result.
- Held: calls to tools you mark as risky wait for a person to approve or deny them in VITNA. If nobody answers in time, the call is blocked.
- Any error blocks: with no key, VITNA unreachable or no decision, the call is not run.
Decisions come only from VITNA. On a claimed account, VITNA signs a record of each decision. The core uses only the standard library.
pip install vitna # the core
pip install "vitna[langchain]" # with LangChain / LangGraph
pip install "vitna[openai-agents]" # with the OpenAI Agents SDK
pip install "vitna[pydantic-ai]" # with Pydantic AI
pip install "vitna[agent-framework]" # with Microsoft Agent Framework
pip install "vitna[google-adk]" # with Google ADK
pip install "vitna[crewai]" # with CrewAI (Python 3.10 to 3.13)
Set VITNA_API_KEY (an agent's key, from your VITNA dashboard) and VITNA_OWNER_ID, or pass api_key and owner_id to vitna.Vitna.
Your framework
LangChain and LangGraph (langchain 1.x)
from langchain.agents import create_agent
from vitna.langchain import VitnaMiddleware
agent = create_agent(model, tools, middleware=[*your_middleware, VitnaMiddleware()])
Put VitnaMiddleware() last. The last middleware wraps the tool call innermost, so no other middleware can change the arguments after VITNA checked them. Sync and async agents both work.
OpenAI Agents SDK (Python)
from agents import Agent, function_tool
from vitna.openai_agents import with_vitna, vitna_guardrail
agent = Agent(name="assistant", tools=with_vitna([read_file, delete_file]))
@function_tool(tool_input_guardrails=[vitna_guardrail()])
def send_email(to: str, subject: str) -> str: ...
with_vitna returns copies of your function tools with VITNA's tool input guardrail added. It does not change the tools you pass in.
Pydantic AI (pydantic-ai 2.x)
from pydantic_ai import Agent
from vitna.pydantic_ai import VitnaCapability
agent = Agent(model, tools=[read_file, delete_file], capabilities=[VitnaCapability()])
The capability checks each call in before_tool_execute and asks to be the innermost capability, so it sees the arguments that run even when another capability changes them. A blocked call is skipped and its result is VITNA's reason.
Microsoft Agent Framework (agent-framework 1.x)
from agent_framework import Agent
from vitna.agent_framework import VitnaMiddleware
agent = Agent(client=chat_client, tools=[read_file, delete_file], middleware=[*your_middleware, VitnaMiddleware()])
After VITNA allows a call, the middleware registers the arguments it checked with Agent Framework's own argument-change guard, the same one the framework's security middleware uses. If any middleware after it changes them, including run-level and context-provider middleware, the framework refuses the call. Put argument-repair middleware before VITNA. The guard's names are private to agent-framework; this was tested with 1.20.0. A version without them logs a warning, and middleware after VITNA could then change arguments unchecked.
Google ADK (google-adk 2.x)
from google.adk.runners import Runner
from vitna.google_adk import VitnaPlugin, vitna_before_tool_callback
runner = Runner(agent=root_agent, app_name="app", session_service=sessions, plugins=[VitnaPlugin()])
The plugin covers every agent the runner runs. ADK runs plugins first and then each agent's own before_tool_callback list on the same arguments. If an agent's callbacks change arguments, also put vitna_before_tool_callback() last in that agent's list.
CrewAI (crewai 1.x)
from vitna.crewai import install_vitna
uninstall = install_vitna() # after registering your own tool hooks
crew.kickoff()
CrewAI swallows any exception from a hook except HookAborted and then runs the tool. So VITNA's hook turns every failure into HookAborted, and the call is blocked. CrewAI runs global hooks in the order they were registered, then crew-scoped hooks. Install VITNA after your own global hooks. A crew-scoped hook that changes tool_input runs after VITNA's check.
Your own code
import vitna
@vitna.guard
def delete_file(path: str) -> str: ...
d = vitna.check("send_email", {"to": to, "subject": subject})
if not d.allowed:
return d.message
@vitna.guard works on sync and async functions. For async code, use await vitna.acheck(...).
Options (vitna.Vitna(...))
| Option | Default | |
|---|---|---|
api_key, owner_id |
VITNA_API_KEY, VITNA_OWNER_ID |
|
base_url |
VITNA_BASE_URL, else https://vitna.costrinity.xyz |
|
policy |
every tool allowed, none held | allowed_actions, hold_actions, hold_window_seconds, honeytools |
hold_wait_seconds |
600 | The longest a held call waits. If your framework or server limits how long a tool call may take, set this below that limit. |
unwrapped |
[] |
Tool surfaces your agent can reach that do not go through VITNA, for the session record |
builtin_tools |
not declared | "yes" or "no": whether the agent has tools the provider runs itself |
A held call that is still open when the wait ends is blocked and answered "held, not run". If a person approves it later, the same call retried within 10 minutes runs once.
The aquarium look tool
vitna_choose_look is an optional tool. The agent uses it to pick one emoji and one of 12 colours for how it appears in its owner's VITNA aquarium. It is cosmetic only, never held, and the agent cannot set its name.
- LangChain:
VitnaMiddleware(look_tool=True). - OpenAI Agents: add
vitna_look_tool()totools. - Pydantic AI:
VitnaCapability(look_tool=True). - Agent Framework: add
vitna_look_tool()fromvitna.agent_frameworktotools. The middleware answers it. - Google ADK: add
vitna_look_tool()fromvitna.google_adkto an agent'stools. The plugin answers it. - CrewAI: add
vitna_look_tool()fromvitna.crewaito an agent'stools. The hook answers it.
No other tool with that name can skip VITNA. The LangChain, Pydantic AI, Agent Framework, ADK and CrewAI adapters answer every call of that name themselves, so another tool with that name never runs. In the OpenAI Agents SDK, only VITNA's own look tool passes unguarded. Another tool with that name is checked like any other.
Limits
- Hosted tools skip these hooks. Tools the model provider runs itself are not seen by VITNA. That includes OpenAI's web search, file search, code interpreter and computer use, and the built-in shell tools.
- A developer can remove the wrapper. VITNA checks the calls that go through it.
- Shell tools can route around wrapped functions. A shell or code-execution tool can do what a wrapped function does without calling it. Hold or block such tools in your policy.
- Pydantic AI output functions (
output_type=[fn]) run withoutbefore_tool_execute, so VITNA does not see them. - CrewAI holds block a thread. CrewAI hooks are synchronous. A held call waits in the hook, which blocks the thread or event loop that runs it until the person answers or the wait ends.
Tested
On 7 October 2026, the tests in tests/ ran each adapter inside its framework's own agent loop, with a scripted model and a stand-in VITNA server, on Python 3.14:
langchain1.4.3 withlanggraph1.2.14 (create_agent, sync and async)openai-agents0.23.1 (Runner.run)pydantic-ai-slim2.54.0 (Agent.run)agent-framework-core1.20.0 (Agent.run)google-adk2.11.0 (InMemoryRunner.run_async)crewai1.15.23 (Crew.kickoff, native tool calling, on Python 3.12.15: CrewAI does not support 3.14)
They show that allowed calls run, blocked calls never run and the model receives VITNA's reason, held calls wait, and errors block. No run against a live model or the production VITNA service is recorded here yet.
cd tests && python -m unittest discover -s .
License
MIT
Metadata
Release files for vitna 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vitna-0.1.0.tar.gz | 27.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vitna-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.8 kB
Release files / vitna-0.1.0.tar.gz
| Download URL | vitna-0.1.0.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
39c4304557ebb984533d4f23a122ca204dc2810b3814725331b575af2bde439a
|
|
BLAKE2b-256 checksum How to use checksums |
4f5b1dcc06c9bb734a82a0375d335ca371f67b1f1e3bd86fd51750c4f52adc8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / vitna-0.1.0-py3-none-any.whl
| Download URL | vitna-0.1.0-py3-none-any.whl |
|---|---|
| Size | 21.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2c9718f750c3ae0dd455ba032fdbe6a6cfb1dc2a6db0b24ddc1c912e66b636fa
|
|
BLAKE2b-256 checksum How to use checksums |
7465d1fe224d325497879ea2fd9e6950877dddca05f68891aea7143c3513c827
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|