Metadata-Version: 2.4
Name: agentguard-observe
Version: 0.1.0
Summary: MrProbe / Agent Guard customer observation SDK — ship your agent's response back to MrProbe in 6 lines.
Author-email: "Sniffr.ai" <support@sniffr.ai>
License-Expression: MIT
Project-URL: Homepage, https://mrprobe.dev
Project-URL: Repository, https://github.com/QDEXConsulting/agent-guard
Project-URL: Documentation, https://github.com/QDEXConsulting/agent-guard/tree/main/sdk/agentguard_observe
Project-URL: Bug Tracker, https://github.com/QDEXConsulting/agent-guard/issues
Keywords: agent-guard,mrprobe,ai-security,red-teaming,observation,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"

# agentguard-observe

Customer-side observation SDK for **MrProbe / Agent Guard** (`POST /api/v1/observe`).

Ship your agent's response back to MrProbe in **6 lines** so red-team campaigns
get live, signed, retry-safe, secret-redacted observations from your agent
runtime — wherever it runs (FastAPI, Vertex AI, Bedrock Lambda, Azure
Function, custom HTTP).

> **Status:** v0.1.0 — design-partner ready for Email Agents on **Microsoft 365 Outlook**
> and **Google Workspace Gmail**. The same SDK is the Pillar 4 surface for every
> other agent-type epic (Document/RAG, Salesforce, ServiceNow, etc.) — internally
> tracked as MRPRBE-303. Pre-1.0 because the wire-shape (e.g. `llm_response`
> field name) may evolve through design-partner feedback before we commit to the
> 1.0 stability contract.

---

## Install

### From PyPI (recommended)

```bash
pip install agentguard-observe
```

Pin in `requirements.txt`:

```
agentguard-observe==0.1.0
```

### From the GitHub release (alternative — hash-pinned)

```bash
pip install \
  https://github.com/QDEXConsulting/agent-guard/releases/download/agentguard-observe-v0.1.0/agentguard_observe-0.1.0-py3-none-any.whl \
  --hash=sha256:<copy-from-the-release-page>
```

The SHA-256 of every published wheel + sdist is printed in the release notes
on the GitHub Releases page. Include the `--hash` line for supply-chain
verification.

### From source (during development / dogfooding)

```bash
git clone https://github.com/QDEXConsulting/agent-guard.git
pip install -e ./agent-guard/sdk/agentguard_observe
```

---

## Six-line happy path

```python
import os
from agentguard_observe import Client

client = Client(
    api_key=os.environ["AGENTGUARD_API_KEY"],            # bearer identifier (shown once at agent registration)
    agent_id=os.environ["AGENTGUARD_AGENT_ID"],          # the agent's UUID
    signing_secret=os.environ["AGENTGUARD_SIGNING_SECRET"], # HMAC key — see "Fetching your signing secret" below
)

# ... your agent processes the inbound and produces a reply ...

client.observe(
    attack_id=inbound_headers["X-AgentGuard-ID"],  # echoed back from the test message
    response_text=reply.text,                       # what your LLM said
    action="email_forward",                          # what your agent DID (or "none")
    action_status="executed",                        # "executed" | "blocked" | "none"
    action_target="alice@example.com",              # who/what the action affected
)
```

That's the entire integration. The SDK signs the request, retries on
transient failures, and redacts common secret shapes from `response_text` /
`action_target` before they leave your VPC.

### Asyncio variant (FastAPI / Vertex AI Agents / asyncio runtimes)

```python
from agentguard_observe import AsyncClient

async with AsyncClient(
    api_key=KEY,
    agent_id=AGENT_ID,
    signing_secret=SIGNING_SECRET,
) as client:
    await client.observe(attack_id="...", response_text=reply.text)
```

The surface is identical — every method is awaitable.

---

## Fetching your signing secret

The signing secret is a SEPARATE value from your API key. The API key is your bearer identifier (transmitted in the `X-AgentGuard-Key` header on every request); the signing secret is the HMAC key used to compute the request signature and is **never transmitted on the wire**. Together they give MrProbe cryptographic proof that a `/v1/observe` payload originated from your customer deployment and wasn't tampered with in transit.

**One-time fetch** (after registering your agent):

```bash
curl -H "Authorization: Bearer $YOUR_JWT" \
     https://api.mrprobe.dev/api/v1/agents/$AGENT_ID/webhook/signing-secret
```

The response is:

```json
{
  "agent_id": "...",
  "signing_secret": "<64 hex chars>",
  "derivation": "HKDF-SHA256(master_key, api_key, info='agentguard-observe-v1')"
}
```

**Store the value** in your secret manager (AWS Secrets Manager / GCP Secret Manager / HashiCorp Vault / Kubernetes Secret) and load it as `AGENTGUARD_SIGNING_SECRET` in your runtime. The endpoint is idempotent — calling it again returns the same value, so a customer who lost the secret can re-fetch it without rotating the api_key.

> **Why not just use the API key as the HMAC secret?** Because then capturing one signed request (TLS-terminating proxy logs, mistakenly logged headers, etc.) gives an attacker the secret AND the ability to forge any future request — defeating the whole point of HMAC. With the values separated, capturing a signed request reveals only the api_key + ts + nonce + sig + body, none of which let the attacker recover the signing secret.

---

## What the SDK does for you

| Concern | What we do | Why |
|---|---|---|
| **Authentication** | Adds `X-AgentGuard-Key` header from your API key (bearer identifier) | BE looks up the agent row by api_key |
| **Request signing** | HMAC-SHA256 over `{ts, nonce, method, path, sha256(body)}` keyed by `signing_secret`; `X-AgentGuard-Signature: v1=<hex>` | Replay protection (BE rejects requests outside ±5 min) and tamper detection. `signing_secret` is held only by you, never transmitted |
| **Retry on transient failure** | 3 attempts, full-jitter exponential backoff (250 ms → 8 s cap) | Handles MrProbe deploys + DNS blips without manual retry code |
| **Outbound redaction** | Regex-strips JWTs, AWS keys, GCP private keys, OpenAI / Anthropic / Google / Stripe / GitHub / Slack tokens before send | Prevents your agent from accidentally exfiltrating secrets to MrProbe |
| **Eager validation** | Length + enum checks at the call site | 422-round-trip turns into a `ValueError` with a clear message |
| **Connection pooling** | One `httpx.Client` (or `AsyncClient`) per `Client` instance | Single keep-alive socket; sub-50ms calls |

**What the SDK does NOT do** (intentional v1 scope):

* No custom in-memory queue / batch / drain semantics — every `observe()`
  is a single immediate POST. If you need batching, wrap the SDK in your
  own queue.
* No automatic correlation between inbound + outbound. You pass
  `attack_id` explicitly so a future SDK refactor can't invent a
  hidden-state bug.
* No DLP — the redaction bank is intentionally small (well-known token
  shapes only). Layer your own DLP in front for full coverage.

---

## Testing your integration

After registering an Email Agent in MrProbe, run a **canary attack** against
your registered agent:

1. In the MrProbe UI, open the agent → **Connection check** → "Send canary".
2. MrProbe sends a benign test message to your mailbox.
3. Your agent processes it (no harm — the canary contains no instructions).
4. Your agent calls `client.observe(...)` with the canary's `attack_id`.
5. MrProbe shows a green tick on the **End-to-end ready** card.

Both halves of the loop must be green before you run a real security campaign.

---

## Connection check from `agentguard_observe`

```python
from agentguard_observe import Client

with Client(
    api_key=KEY,
    agent_id=AGENT_ID,
    signing_secret=SIGNING_SECRET,
) as client:
    # No-op observation against a sentinel attack id — verifies auth +
    # signing without polluting any real campaign.
    try:
        client.observe(attack_id="canary-sdk-selftest")
        print("✓ SDK can reach MrProbe and authenticate")
    except Exception as exc:
        print(f"✗ SDK self-test failed: {exc}")
```

---

## Configuration

| Argument | Default | Description |
|---|---|---|
| `api_key` | (required) | The webhook key surfaced at agent registration. Treat as a secret. |
| `agent_id` | (required) | The agent's UUID. |
| `base_url` | `https://api.mrprobe.dev` | Override for on-prem / EU-residency / staging. |
| `timeout_s` | `10.0` | Per-request timeout. |
| `retry` | `RetryPolicy(max_attempts=3, base_delay_s=0.25, max_delay_s=8.0)` | Tune via `from agentguard_observe.retry import RetryPolicy`. |
| `redact` | `True` | Set `False` to disable outbound secret redaction (NOT RECOMMENDED). |
| `sign` | `True` | Set `False` to send unsigned requests during the 30-day deprecation window. |
| `http_client` | `None` | Pass your own `httpx.Client` / `httpx.AsyncClient` to share its connection pool. |

---

## Sample apps

The `samples/` directory ships two complete reference integrations that
mirror what the v1 customer base actually deploys:

* **[`samples/fastapi_middleware/`](samples/fastapi_middleware/)** — a
  FastAPI app that wraps any async agent handler and calls
  `AsyncClient.observe(...)` automatically.
* **[`samples/vertex_cloud_function/`](samples/vertex_cloud_function/)** —
  a 1st-gen GCP Cloud Function that wraps a Vertex AI Agent.

More samples (Bedrock Lambda, Azure Function, Express middleware) ship in
v1.1 — file an issue or ping `support@sniffr.ai` if you need them sooner.

---

## Versioning + wire-format guarantee

The SDK follows semver. The wire format (request body, headers, signing
algorithm) is part of the SDK's **public API** — any incompatible change
on either the SDK or the BE bumps the major version of *both*, and the
SDK ships a `vN+1` signing-string variant alongside `vN` for at least
30 days of overlap.

---

## Licence

MIT — vendor freely.

---

## Support

* **Bugs / feature requests:** [GitHub Issues](https://github.com/QDEXConsulting/agent-guard/issues) (label `sdk:python`)
* **Email:** support@sniffr.ai
* **Security disclosures:** security@sniffr.ai (PGP-encrypted preferred)
