Metadata-Version: 2.4
Name: scopeblind-llamaindex
Version: 0.1.0
Summary: Ed25519 signed receipts for LlamaIndex agent tool calls
Project-URL: Homepage, https://veritasacta.com
Project-URL: Repository, https://github.com/ScopeBlind/scopeblind-gateway
Author-email: Tom Farley <tommy@scopeblind.com>
License: MIT
License-File: LICENSE
Keywords: agent,audit,ed25519,governance,llamaindex,receipts
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.10
Requires-Dist: pynacl>=1.5.0
Provides-Extra: dev
Requires-Dist: llama-index-core>=0.11.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.11.0; extra == 'llamaindex'
Description-Content-Type: text/markdown

# scopeblind-llamaindex

[![PyPI](https://img.shields.io/pypi/v/scopeblind-llamaindex)](https://pypi.org/project/scopeblind-llamaindex/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)

Ed25519 signed receipts for [LlamaIndex](https://github.com/run-llama/llama_index) agent tool calls. Every `FUNCTION_CALL` event produces a tamper-evident, hash-chained receipt verifiable offline.

## Install

```bash
pip install scopeblind-llamaindex
```

## Quick start

```python
from llama_index.core.callbacks import CallbackManager
from scopeblind_llamaindex import ScopeBlindHandler, ReceiptSigner

signer = ReceiptSigner.generate()
handler = ScopeBlindHandler(signer=signer, agent_name="research-agent")
callback_manager = CallbackManager([handler])

# Attach to any LlamaIndex component
query_engine = index.as_query_engine(callback_manager=callback_manager)
# or
agent = OpenAIAgent.from_tools(tools, callback_manager=callback_manager)

# After execution
print(f"{handler.receipt_count} tool calls signed")
handler.export_receipts("receipts.jsonl")
```

Verify:

```bash
npx @veritasacta/verify receipts.jsonl --key $(python3 -c "from scopeblind_llamaindex import ReceiptSigner; s=ReceiptSigner.generate(); print(s.public_key_hex)")
```

## How it works

The handler listens for `CBEventType.FUNCTION_CALL` events via LlamaIndex's `CallbackManager`. On each tool call:

1. `on_event_start`: captures tool name + args, evaluates policy
2. `on_event_end`: signs the decision (allow/deny) with Ed25519, chains to previous receipt
3. Receipt is appended to the chain with `tool_input_hash` and `output_hash`

Compatible with all LlamaIndex callback integrations (Langfuse, Arize, W&B, etc.) since handlers compose in the CallbackManager.

## With a policy

```python
def no_web_scraping(tool_name, args):
    if tool_name == "web_scraper" and "competitor" in str(args):
        return (False, "scraping competitor sites not permitted")
    return (True, None)

handler = ScopeBlindHandler(signer=signer, policy=no_web_scraping)
```

## Standards

- **Ed25519** (RFC 8032), **JCS** (RFC 8785), **IETF draft-farley-acta-signed-receipts**

## License

MIT
