Metadata-Version: 2.4
Name: vitna
Version: 0.1.0
Summary: VITNA for agent code: check every tool call before it runs. Adapters for LangChain/LangGraph, the OpenAI Agents SDK, Pydantic AI, Microsoft Agent Framework, Google ADK and CrewAI. Calls you mark as risky wait for a person. Fails closed.
Author: COSTRINITY INC.
License-Expression: MIT
Project-URL: Homepage, https://vitna.costrinity.xyz/connect
Keywords: ai-agents,agent-containment,human-in-the-loop,langchain,langgraph,openai-agents,pydantic-ai,agent-framework,google-adk,crewai,tool-calling,vitna
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: langchain
Requires-Dist: langchain>=1.0; extra == "langchain"
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.23; extra == "openai-agents"
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai-slim>=2.54; extra == "pydantic-ai"
Provides-Extra: agent-framework
Requires-Dist: agent-framework-core>=1.20; extra == "agent-framework"
Provides-Extra: google-adk
Requires-Dist: google-adk>=2.11; extra == "google-adk"
Provides-Extra: crewai
Requires-Dist: crewai>=1.15; extra == "crewai"

# 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.

```bash
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)

```python
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)

```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)

```python
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)

```python
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)

```python
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)

```python
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

```python
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.

```bash
cd tests && python -m unittest discover -s .
```

## License

MIT
