Skip to main content

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() to tools.
  • Pydantic AI: VitnaCapability(look_tool=True).
  • Agent Framework: add vitna_look_tool() from vitna.agent_framework to tools. The middleware answers it.
  • Google ADK: add vitna_look_tool() from vitna.google_adk to an agent's tools. The plugin answers it.
  • CrewAI: add vitna_look_tool() from vitna.crewai to an agent's tools. 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 without before_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:

  • langchain 1.4.3 with langgraph 1.2.14 (create_agent, sync and async)
  • openai-agents 0.23.1 (Runner.run)
  • pydantic-ai-slim 2.54.0 (Agent.run)
  • agent-framework-core 1.20.0 (Agent.run)
  • google-adk 2.11.0 (InMemoryRunner.run_async)
  • crewai 1.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)

Source distribution for vitna 0.1.0
File Size Uploaded
vitna-0.1.0.tar.gz 27.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for vitna 0.1.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page