Metadata-Version: 2.4
Name: norviq
Version: 0.2.1
Summary: Runtime policy enforcement for LLM agent tool calls on Kubernetes.
Author: Norviq
License: Apache-2.0
Project-URL: Homepage, https://norviq.dev
Project-URL: Documentation, https://docs.norviq.dev
Project-URL: Repository, https://github.com/norviq-dev/norviq
Keywords: llm,agents,security,policy-enforcement,opa,kubernetes,ai-safety
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=50.0.0
Requires-Dist: aiohttp>=3.14.3
Requires-Dist: pyopenssl>=26.4.0
Requires-Dist: click>=8.1
Requires-Dist: pydantic>=2.0
Requires-Dist: pydantic-settings>=2.0
Requires-Dist: structlog>=23.0
Requires-Dist: httpx>=0.24
Requires-Dist: opentelemetry-api>=1.20
Requires-Dist: opentelemetry-sdk>=1.20
Requires-Dist: opentelemetry-exporter-otlp>=1.20
Requires-Dist: opentelemetry-exporter-prometheus>=0.41b0
Requires-Dist: prometheus-client>=0.20
Requires-Dist: redis[hiredis]>=5.0
Requires-Dist: sqlalchemy[asyncio]>=2.0
Requires-Dist: asyncpg>=0.29
Requires-Dist: networkx>=3.0
Requires-Dist: fastapi>=0.109
Requires-Dist: uvicorn>=0.27
Requires-Dist: websockets>=12
Requires-Dist: pyjwt[crypto]>=2.8
Requires-Dist: bcrypt>=4.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: httpx; extra == "dev"
Requires-Dist: ruff>=0.2; extra == "dev"
Provides-Extra: spiffe
Requires-Dist: spiffe>=0.3; extra == "spiffe"
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == "langchain"
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == "langgraph"
Provides-Extra: crewai
Requires-Dist: crewai[litellm]>=1.0; extra == "crewai"
Requires-Dist: mcp<2,>=1.28.1; extra == "crewai"
Provides-Extra: autogen
Requires-Dist: autogen-core>=0.4; extra == "autogen"
Requires-Dist: autogen-agentchat>=0.4; extra == "autogen"
Requires-Dist: autogen-ext[openai]>=0.4; extra == "autogen"
Provides-Extra: mcp
Requires-Dist: mcp<2,>=1.28.1; extra == "mcp"
Requires-Dist: starlette>=0.37; extra == "mcp"
Provides-Extra: semantic-kernel
Requires-Dist: semantic-kernel>=1.0; extra == "semantic-kernel"
Provides-Extra: frameworks
Requires-Dist: langchain-core>=0.2; extra == "frameworks"
Requires-Dist: langgraph>=0.2; extra == "frameworks"
Requires-Dist: crewai>=0.80; extra == "frameworks"
Requires-Dist: mcp<2,>=1.28.1; extra == "frameworks"
Requires-Dist: autogen-core>=0.4; extra == "frameworks"
Requires-Dist: autogen-agentchat>=0.4; extra == "frameworks"
Requires-Dist: autogen-ext[openai]>=0.4; extra == "frameworks"
Requires-Dist: semantic-kernel>=1.0; extra == "frameworks"
Dynamic: license-file

<!-- SPDX-License-Identifier: Apache-2.0 -->
<!--
  This is the PyPI long description, not the repo landing page (README.md).

  Two reasons it is separate. (1) PyPI cannot resolve relative links or images, so every README.md
  link would render dead on pypi.org — every URL below is absolute. (2) The audiences differ: someone
  on pypi.org came for the Python SDK, whereas README.md opens with `git clone` + `helm install`,
  which is the wrong first instruction for them.

  Keep it short and keep the links absolute.
-->

# Norviq

**Runtime policy enforcement for LLM agent tool calls.**

[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/norviq-dev/norviq/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-docs.norviq.dev-00a576)](https://docs.norviq.dev)

Norviq is a policy enforcement point (PEP) that sits between an agent's reasoning loop and the tools
it can call. Every tool call is evaluated against OPA/Rego policy and then **allowed, blocked,
escalated, or audited** — *before* the tool function runs. It turns "the model decided to call
`execute_sql`" from implicit trust into an enforced, auditable decision.

This package is the **Python SDK**: it wraps your existing tools so enforcement happens in-process.
The full platform (control plane, console, Kubernetes admission webhook, sidecar injection) is
installed with the Helm chart — see the [documentation](https://docs.norviq.dev).

## Install

```bash
pip install "norviq[langchain]"      # LangChain / LangGraph
pip install "norviq[crewai]"         # CrewAI
pip install "norviq[autogen]"        # AutoGen
pip install "norviq[semantic-kernel]" # Semantic Kernel
pip install "norviq[frameworks]"     # all of the above
```

## Use

```python
from norviq.sdk import PolicyEngineClient, ToolInterceptor
from norviq.sdk.langchain.adapter import protect

engine = PolicyEngineClient()                      # NRVQ_POLICY_ENGINE_URL + NRVQ_API_TOKEN
interceptor = ToolInterceptor(evaluator=engine)

# Every tool is wrapped: policy runs before the tool body, on every call the model makes.
tools = protect([search_kb, execute_sql, delete_record], interceptor, session_id="session-1")
```

A blocked call raises `NorviqBlockError`, which carries the full `PolicyDecision` — including the
`rule_id` that fired and a human-readable `reason` — so you can surface a refusal instead of
performing the action:

```python
from norviq.sdk import NorviqBlockError

try:
    result = agent.invoke({"messages": [("user", "delete all customer records")]})
except NorviqBlockError as exc:
    print(f"blocked by {exc.decision.rule_id}: {exc.decision.reason}")
```

Two things to know before your first run, because either one makes a correct setup look broken:

- **A scope with no policy is ALLOWED, not denied.** With nothing loaded for the namespace/agent-class
  you evaluate against, the decision is `allow` with `rule_id: default_allow` — so a typo in
  `agent_class`, or an agent pointed at a namespace you never wrote a policy for, looks like a clean
  pass rather than an error. If your first calls all come back allowed, check that the scope you are
  sending matches the scope you wrote the policy for.

  This is deliberate: Norviq sits in the request path of production agents, so an unconfigured scope
  must not take the customer's traffic down. Set `config.noPolicyDecision: deny` (chart) or
  `NRVQ_NO_POLICY_DECISION=deny` to lock a namespace down once you have written its policies —
  deny-by-default is then an explicit choice you make per deployment, not a surprise on day one.
- **`POST /api/v1/evaluate` requires a bearer token.** Without one the client fails closed.

## Links

- **Documentation** — https://docs.norviq.dev
- **Integration guide** (all supported frameworks) —
  https://github.com/norviq-dev/norviq/blob/main/docs/guides/integrating-agents.md
- **Runnable examples** — https://github.com/norviq-dev/norviq/blob/main/examples/README.md
- **Source** — https://github.com/norviq-dev/norviq
- **Security policy** — https://github.com/norviq-dev/norviq/blob/main/SECURITY.md

Apache 2.0 licensed.
