Metadata-Version: 2.4
Name: tenet-ai
Version: 0.6.0
Summary: Python SDK for Tenet AI - Audit trail, replay, and drift detection for AI agents
Home-page: https://github.com/vinayb21-work/tenetai
Author: Vinay Badhan
Author-email: vinay.badhan21.work@gmail.com
Project-URL: Bug Tracker, https://github.com/vinayb21-work/tenetai/issues
Project-URL: Documentation, https://tenetai.dev/docs
Keywords: ai agents audit logging llm observability replay drift-detection tenet
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Logging
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.26.0
Requires-Dist: pydantic>=2.5.0
Provides-Extra: google-adk
Requires-Dist: google-adk>=1.0.0; extra == "google-adk"
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
Provides-Extra: crewai
Requires-Dist: crewai>=0.1.0; extra == "crewai"
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == "openai"
Provides-Extra: autogen
Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
Provides-Extra: all
Requires-Dist: google-adk>=1.0.0; extra == "all"
Requires-Dist: langchain-core>=0.1.0; extra == "all"
Requires-Dist: crewai>=0.1.0; extra == "all"
Requires-Dist: openai>=1.0.0; extra == "all"
Requires-Dist: pyautogen>=0.2.0; extra == "all"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license-file
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Tenet AI SDK

**Git for AI Agent Decisions** - Complete audit trail, replay, and divergence detection for enterprise AI agents.

[![PyPI version](https://badge.fury.io/py/tenet-ai.svg)](https://pypi.org/project/tenet-ai/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Why Tenet AI?

When your AI agent approves a $10,000 refund or denies a loan application, can you answer:

- **Why** did the agent make that decision?
- **What context** did it have at the time?
- **Would it decide the same way** if we ran it again?
- **Who overrode** the agent's recommendation?

Tenet AI captures every decision your agent makes with full context, enabling **audit trails**, **replay verification**, **divergence detection**, and **guardrails enforcement**.

---

## Installation

```bash
pip install tenet-ai
```

With framework integrations:
```bash
pip install tenet-ai[langchain]     # LangChain
pip install tenet-ai[google-adk]    # Google ADK
pip install tenet-ai[crewai]        # CrewAI
pip install tenet-ai[openai]        # OpenAI Assistants
pip install tenet-ai[all]           # All integrations
```

---

## 30-Second Quickstart

```python
from tenet import TenetClient, ActionOption, ResultType

tenet = TenetClient(api_key="tnt_your_key")

with tenet.intent("Process refund request", agent_id="support-agent") as intent:
    # 1. Capture what the agent saw
    intent.snapshot_context({
        "customer_tier": "gold",
        "order_amount": 149.99,
        "days_since_delivery": 5,
    })

    # 2. Record the decision with options considered
    intent.decide(
        options=[
            ActionOption(action="approve_refund", score=0.95, reason="Gold customer, within policy"),
            ActionOption(action="deny_refund", score=0.05, reason="N/A"),
        ],
        chosen_action="approve_refund",
        confidence=0.95,
        reasoning="Customer eligible per 30-day return policy"
    )

    # 3. Record execution
    intent.execute(action="approve_refund", target={"order_id": "ORD-123"}, result=ResultType.SUCCESS)
```

Now query any decision: **"Why did we approve this refund?"** - Full context, reasoning, and alternatives considered.

---

## Key Features

### 1. Decision Replay with Divergence Detection

Store the prompt and re-run decisions to verify consistency:

```python
from tenet import TenetClient, ActionOption, ReplayConfig

tenet = TenetClient(api_key="tnt_your_key")

# Record decision WITH replay data
with tenet.intent("Handle support ticket") as intent:
    intent.snapshot_context({"ticket_id": "T-123", "priority": "high"})

    intent.decide(
        options=[ActionOption(action="escalate", score=0.9)],
        chosen_action="escalate",
        confidence=0.9,
        reasoning="High priority ticket requires escalation",
        # Enable replay
        replay_prompt="You are a support agent. Ticket: high priority. Action?",
        replay_config=ReplayConfig(model="gpt-4o-mini", temperature=0.0),
    )

    exec_id = intent.execute(action="escalate", target={"ticket_id": "T-123"}, result=ResultType.SUCCESS)

# Later: Verify the decision is still consistent
replay_result = tenet.replay(exec_id, force_llm_call=True)

if replay_result["diverged"]:
    print(f"WARNING: Model behavior changed!")
    print(f"Original: {replay_result['original_decision']['chosen_action']}")
    print(f"Replayed: {replay_result['replayed_decision']['chosen_action']}")
    print(f"Reason: {replay_result['divergence_reason']}")
else:
    print("Decision verified - model behavior consistent")
```

### 2. Multi-Agent Tracking (Parent-Child Intents)

Track complex workflows with multiple agents or MCP tool calls:

```python
with tenet.intent("Process customer request", agent_id="orchestrator") as parent:
    parent.snapshot_context({"request": "refund for order 123"})

    # Spawn child intent for sub-agent
    with parent.child_intent("Search order history", mcp_server="database") as child:
        child.snapshot_context({"query": "order 123"})
        child.decide(
            options=[ActionOption(action="query_db", score=1.0)],
            chosen_action="query_db",
            confidence=1.0
        )
        child.execute(action="query_db", target={"order_id": "123"}, result=ResultType.SUCCESS)

    # Parent continues with child's result
    parent.decide(...)
    parent.execute(...)
```

### 3. Human Override Tracking

When humans override agent decisions:

```python
intent.execute(
    action="deny_refund",  # Human chose differently
    target={"order_id": "123"},
    result=ResultType.SUCCESS,
    actor=ActorType.HUMAN,
    override_reason="Customer has history of fraud"
)
```

### 4. Guardrails — Block, Warn, or Log Agent Actions

Define rules that are evaluated server-side before decisions are recorded. Rules that trigger with `block` enforcement reject the decision with HTTP 403.

```python
from tenet import TenetClient

tenet = TenetClient(api_key="tnt_your_key")

# Create a guardrail rule (stored server-side)
tenet.create_guardrail({
    "name": "Block dangerous actions",
    "rule_type": "action_blocklist",
    "enforcement_level": "block",          # block | warn | log
    "rule_config": {"blocked_actions": ["delete_account", "drop_table"]},
})

# Supported rule types:
#   action_allowlist  — only allow specific actions
#   action_blocklist  — block specific actions
#   confidence_threshold — require minimum confidence
#   context_field_check — check a context input field (e.g. amount > 10000)
#   regex_match       — regex on action or field
#   json_path_check   — dot-path check into context
#   value_range       — min/max range on any numeric field
```

#### SDK-Side Pre-Flight Evaluation

Run the same rules locally before calling the server:

```python
from tenet import GuardrailEvaluator, GuardrailRule, EnforcementLevel, GuardrailType

evaluator = GuardrailEvaluator()

# Option A: Sync rules from server
evaluator.sync_from_server(tenet)

# Option B: Add rules manually
evaluator.add_rule(GuardrailRule(
    name="Min confidence",
    rule_type=GuardrailType.CONFIDENCE_THRESHOLD,
    enforcement_level=EnforcementLevel.BLOCK,
    rule_config={"min_confidence": 0.7},
))

# Evaluate locally
result = evaluator.evaluate(
    chosen_action="approve",
    confidence=0.5,
    context_inputs={"amount": 500},
)

if result.blocked:
    print(f"Blocked by: {result.blocking_rule}")
elif result.warnings:
    print(f"Warnings: {result.warnings}")
else:
    print(f"All clear — {result.triggered_count} rules triggered")
```

### 5. Ghost SDK — Zero-Impact Observability

The Ghost SDK ensures Tenet never slows down your agent. All write operations (intent, context, decision, execution) are pushed to a background queue and sent asynchronously. Your LLM agent never waits for Tenet.

**The Ghost SDK Promise:**
- **Main-thread overhead: <5ms** per full decision lifecycle (4 writes)
- **Silent failure:** SDK errors never propagate to your host process
- **Zero network dependency** on the write path — your agent runs at full speed
- **100% uptime guarantee** for your primary process

**How it works:** The SDK generates UUIDs client-side and enqueues JSON payloads onto a thread-safe background queue. A single daemon thread drains the queue and sends HTTP requests. If the network is down, payloads are retried up to `max_retries` times then dropped — your agent is never affected.

```python
import tenet

# Ghost SDK is enabled by default — no code changes needed
tenet.init(api_key="tnt_your_key")

with tenet.trace(agent_id="finance-bot-v1") as decision:
    response = agent.run(prompt)
    # All writes happen in the background — zero blocking

# In tests, call flush() to wait for writes to land
tenet.flush()

# For graceful shutdown
tenet.shutdown()
```

**Configuration:**

| Parameter | Default | Description |
|-----------|---------|-------------|
| `async_writes` | `True` | Set to `False` for synchronous mode (tests, debugging) |
| `on_error` | `None` | Callback `(exception, payload)` for observability |
| `max_retries` | `3` | Retry count before silent drop |

**Testing with `flush()`:**

```python
# flush() blocks until all queued writes are sent
tenet.init(api_key="tnt_your_key")

with tenet.trace(agent_id="test-agent") as decision:
    decision.decide(chosen_action="approve", confidence=0.9)
    decision.execute(action="approve", target={"order": "123"})

tenet.flush()  # Wait for writes — then assert on read-back

# Or disable async entirely for simpler test setup:
tenet.init(api_key="tnt_your_key", async_writes=False)
```

### 6. Capture Health Monitoring

Check whether data is actually reaching the Tenet backend — useful for integration health checks and alerting:

```python
stats = tenet.get_stats()
# {
#   'sent': 42,
#   'failed': 0,
#   'dropped': 0,
#   'last_success_at': '2026-03-15T12:00:00+00:00'
# }

if stats['failed'] > 0:
    print(f"WARNING: {stats['failed']} captures did not reach Tenet")
if stats['dropped'] > 0:
    print(f"WARNING: {stats['dropped']} captures were dropped (queue full or retries exhausted)")
```

- **`sent`** — total successful writes to the backend
- **`failed`** — writes that exhausted all retries without success (data NOT in Tenet)
- **`dropped`** — writes dropped because the background queue was full
- **`last_success_at`** — ISO timestamp of the most recent successful write

Failed and dropped captures also emit `WARNING`-level log messages via `logging.getLogger("tenet.transport")` so they surface in your production logs automatically.

---

## Getting Started

### 1. Create an Account

Go to [tenetai.dev](https://tenetai.dev) and sign in with Google.

### 2. Create a Workspace

After signing in, create a workspace (e.g., "Production Agents").

### 3. Generate an API Key

Navigate to **API Keys** > **Create API Key** > Copy your key (`tnt_xxxx...`).

### 4. Use the SDK

```python
from tenet import TenetClient

tenet = TenetClient(api_key="tnt_your_key")
```

---

## Framework Integrations

### LangChain

```python
from langchain_openai import ChatOpenAI
from tenet import TenetClient, ActionOption, ResultType

tenet = TenetClient(api_key="tnt_xxx")
llm = ChatOpenAI(model="gpt-4o-mini")

def run_agent(query: str):
    with tenet.intent(query, agent_id="langchain-agent") as intent:
        intent.snapshot_context({"query": query})

        response = llm.invoke(query)

        intent.decide(
            options=[ActionOption(action="respond", score=0.9)],
            chosen_action="respond",
            confidence=0.9,
            reasoning=response.content[:100]
        )
        intent.execute(action="respond", result=ResultType.SUCCESS)
        return response.content
```

### Google ADK

```python
from google.adk.agents import Agent
from tenet import TenetClient
from tenet.integrations.google_adk import TenetTracker

tenet = TenetClient(api_key="tnt_xxx")
tracker = TenetTracker(tenet, agent_id="support-agent")

agent = Agent(name="support", model="gemini-2.0-flash")

with tracker.track("Handle customer request") as t:
    t.context({"customer_id": "123"})
    # ... run agent ...
    t.decision(chosen="resolve", confidence=0.92, options=[...])
    t.execute(action="resolve", result="success")
```

### CrewAI

```python
from crewai import Agent, Task, Crew
from tenet import TenetClient
from tenet.integrations.crewai import TenetCrewTracker

tenet = TenetClient(api_key="tnt_xxx")
tracker = TenetCrewTracker(tenet)

crew = Crew(agents=[...], tasks=[...])

with tracker.track_crew("Content pipeline") as t:
    result = crew.kickoff()
    t.record_result(result, agents=[...], tasks=[...])
```

---

## API Reference

### TenetClient

```python
tenet = TenetClient(
    api_key="tnt_xxx",                                # Required
    endpoint="https://ael-backend.onrender.com",    # Optional (default: production)
    timeout=30.0                                       # Optional
)
```

### Core Methods

| Method | Description |
|--------|-------------|
| `tenet.intent(goal, agent_id)` | Context manager for tracking an intent |
| `intent.snapshot_context(inputs)` | Capture what the agent sees |
| `intent.decide(options, chosen_action, confidence)` | Record a decision |
| `intent.execute(action, target, result)` | Record execution |
| `tenet.replay(execution_id, force_llm_call)` | Replay and verify a decision |
| `tenet.get_execution(id)` | Get execution details |
| `tenet.get_session_timeline(session_id)` | Get full session timeline |
| `tenet.get_stats()` | Return `{sent, failed, dropped, last_success_at}` capture counters |

### Replay Methods

| Method | Description |
|--------|-------------|
| `tenet.replay(exec_id)` | Compare against original (no LLM call) |
| `tenet.replay(exec_id, force_llm_call=True)` | Re-run LLM and check for divergence |

### Multi-Agent Methods

| Method | Description |
|--------|-------------|
| `intent.child_intent(goal, mcp_server)` | Create child intent |
| `tenet.get_intent_hierarchy(id)` | Get parent chain (breadcrumbs) |
| `tenet.get_intent_tree(id)` | Get full descendant tree |

### Guardrail Methods

| Method | Description |
|--------|-------------|
| `tenet.create_guardrail(data)` | Create a guardrail rule |
| `tenet.list_guardrails(is_active?)` | List guardrails |
| `tenet.get_guardrail(id)` | Get guardrail by ID |
| `tenet.update_guardrail(id, data)` | Update a guardrail |
| `tenet.delete_guardrail(id)` | Delete a guardrail |

### Guardrail Evaluator (Local Pre-Flight)

| Method | Description |
|--------|-------------|
| `GuardrailEvaluator()` | Create a local evaluator |
| `evaluator.add_rule(rule)` | Add a rule for local evaluation |
| `evaluator.sync_from_server(client)` | Pull active rules from server |
| `evaluator.evaluate(action, confidence, ...)` | Run all rules locally |
| `evaluator.clear_rules()` | Remove all local rules |

---

## Dashboard

View your agent's decisions at [tenetai.dev](https://tenetai.dev):

- **Execution Timeline**: See all decisions chronologically
- **Session View**: Group related decisions by session
- **Decision Details**: Full context, options considered, and reasoning
- **Hierarchy View**: Navigate parent-child intent relationships

---

## Use Cases

| Industry | Use Case |
|----------|----------|
| **FinTech** | Audit loan decisions, fraud detection reasoning |
| **Healthcare** | Track triage recommendations, document clinical AI decisions |
| **E-commerce** | Refund approvals, pricing decisions, inventory management |
| **Legal** | Contract analysis decisions, compliance checking |
| **HR** | Resume screening, candidate ranking explanations |

---

## License

MIT License - see [LICENSE](LICENSE) for details.

---

## Links

- **Dashboard**: [tenetai.dev](https://tenetai.dev)
- **PyPI**: [pypi.org/project/tenet-ai](https://pypi.org/project/tenet-ai/)
- **Node.js SDK**: [npmjs.com/package/@tenet-ai/sdk](https://www.npmjs.com/package/@tenet-ai/sdk)
