Metadata-Version: 2.5
Name: aegis-governance
Version: 1.4.2
Summary: AEGIS governance Python client, async client, and local MCP server. The hosted evaluation service is offline; see https://undercurrentholdings.com/status/
Project-URL: Homepage, https://aegis.undercurrentholdings.com
Project-URL: Documentation, https://aegis.undercurrentholdings.com/docs/getting-started/quickstart-sdk
Project-URL: Changelog, https://pypi.org/project/aegis-governance/#history
Project-URL: Status, https://undercurrentholdings.com/status/
Author-email: Undercurrent Holdings <engineering@undercurrentholdings.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai,compliance,engineering,governance,mcp,risk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx[http2]>=0.27
Provides-Extra: dev
Requires-Dist: mcp<2.0.0,>=1.2; (python_version >= '3.10') and extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp<2.0.0,>=1.2; (python_version >= '3.10') and extra == 'mcp'
Provides-Extra: verify
Requires-Dist: cryptography<51.0.0,>=50.0.0; extra == 'verify'
Requires-Dist: liboqs-python<1.0.0,>=0.14.1; extra == 'verify'
Requires-Dist: rfc8785>=0.1.4; extra == 'verify'
Description-Content-Type: text/markdown

# AEGIS Python SDK

<!-- mcp-name: com.undercurrentholdings/aegis -->

> **Status: the hosted AEGIS service was taken offline in September 2026.**
> Evaluation is always a call to that service, so `evaluate()` does not
> succeed: it raises an `aegis.AegisError` subclass (for HTTP 429, 5xx,
> connection failure, and timeout, the message ends with
> `See https://undercurrentholdings.com/status/`), and the MCP evaluation tool
> returns that error as JSON. Nothing in this package evaluates a proposal
> locally: sandbox mode is also a server call. Product status:
> <https://undercurrentholdings.com/status/>.

## What this package contains

- **`Aegis`** — a synchronous client for the AEGIS REST API (`evaluate`,
  `risk_check`, `health`, customer and decision endpoints, attestations).
- **`AsyncAegis`** — the same surface on `httpx.AsyncClient`.
- **`aegis-mcp-server`** — a local stdio MCP server (with the `[mcp]` extra)
  that wraps the client. `tools/list` returns six tools:
  `aegis_evaluate_proposal`, `aegis_quick_risk_check`, `aegis_health`,
  `aegis_list_decisions`, `aegis_get_decision`, `aegis_get_usage`.
  Without an API key, `aegis_evaluate_proposal` and `aegis_health` call the
  API and the other four return an authentication error without calling it.
- **`verify_attestation_locally`** — an offline verifier for AEGIS attestation
  envelopes (with the `[verify]` extra). It makes no network call.

## Install

```bash
pip install aegis-governance            # client + async client
pip install "aegis-governance[mcp]"     # adds the local MCP server
pip install "aegis-governance[verify]"  # adds the offline attestation verifier
```

## What a call does today

```python
import aegis

client = aegis.Aegis()  # no key: sandbox mode, which calls the hosted service

try:
    decision = client.evaluate(proposal_summary="Add Redis caching layer")
except aegis.ServiceUnavailableError as e:  # HTTP 503 or no connection
    print(e.message)  # "... See https://undercurrentholdings.com/status/"
except aegis.AegisError as e:               # 429, other 5xx, timeout, ...
    print(e.message)
```

Errors raised by `evaluate()` (keyed or sandbox) for HTTP 429, 5xx, connection
failure, and timeout end with the status URL. `ServiceUnavailableError`
(new in 1.4.2) is raised for 503 and for connection failure. Each instance is
also the class 1.4.1 raised for the same cause: a 503 is also a `ServerError`
(`status_code == 503`), and a connection failure is also a `ConnectionError`
(`status_code is None`), so `except ServerError` / `except ConnectionError`
handlers route as before. Two cases that 1.4.1 let escape as non-`AegisError`
exceptions now raise `AegisError` subclasses: a connection reset or a server
that closes without replying (was a raw `httpx` exception), and a redirect or
non-JSON reply (was a raw `JSONDecodeError`).

## Error classes

All are exported from `aegis` and subclass `aegis.AegisError`.

| Class | Raised for |
|-------|------------|
| `AuthenticationError` | HTTP 401/403 |
| `ValidationError` | HTTP 400, non-idempotency 422, and client-side checks |
| `ConflictError` | HTTP 409 (e.g. same `Idempotency-Key` still in flight) |
| `IdempotencyBodyMismatchError` | HTTP 422: `Idempotency-Key` reused with a different body |
| `RateLimitError` | HTTP 429 (`retry_after` from `Retry-After`) |
| `SandboxLimitError` | HTTP 429 from the sandbox endpoint |
| `ServerError` | HTTP 5xx |
| `ServiceUnavailableError` | HTTP 503 or connection failure on `evaluate()` (new in 1.4.2) |
| `ConnectionError` | Network connection failed |
| `TimeoutError` | Request timed out |
| `HaltError` | `@aegis_gate(fail_on_halt=True)` got a HALT decision |
| `AttestationCollisionError` | `attest`: HTTP 409, decision id owned by another customer |
| `AttestationNotFoundError` | `get`: HTTP 404 (absent or another customer's) |
| `AttestationProviderUnavailableError` | attestation HTTP 503 (server signing unavailable) |
| `AttestationSchemaDriftError` | attestation HTTP 410 (stored predicate fails validation) |

Pass `idempotency_key="..."` to `evaluate()` (keyed mode) for cross-process
deduplication; sandbox mode does not send one.

## Local MCP server

The server runs on your machine over stdio. It is a thin client of the hosted
service, so its evaluation tools return the same status error.

**Claude Code:**

```bash
claude mcp add aegis -- aegis-mcp-server
```

**Cursor / Windsurf** (`.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "aegis": { "command": "aegis-mcp-server" }
  }
}
```

## Offline attestation verification

`verify_attestation_locally` checks an in-toto Statement v1 / DSSE v1 envelope
signed with hybrid Ed25519 + ML-DSA-65 (design record ADR-011) against
public keys you supply. It needs no server.

```python
from aegis import AttestationVerifyKey, verify_attestation_locally

keys = AttestationVerifyKey(
    ed25519_public=b"...32 bytes raw...",
    mldsa65_public=b"...1952 bytes raw...",
)
valid, error_class = verify_attestation_locally(
    envelope=envelope,
    expected_digest="<sha256 lowercase hex 64>",
    expected_environment="production",  # "production" | "staging" | "preview"
    keys=keys,
)
```

It returns the same `error_class` strings as the server's
`POST /attestations/verify` (for example `AttestationDigestMismatch`,
`AttestationExpired`, `AttestationMLDSAVerifyFailed`). Issuing a new attestation
needs the hosted service; verifying one does not.

## Reading the source

The source ships with the package. The sdist on PyPI
(`aegis_governance-<version>.tar.gz`, "Download files" on the project page)
contains `src/aegis/` and `tests/`; an installed copy is under
`site-packages/aegis/`.

- `src/aegis/_client.py`, `_async_client.py` — the clients. `evaluate()` sends
  the proposal to `/evaluate` (keyed) or `/sandbox/evaluate` (no key).
- `src/aegis/_http.py` — transport: TLS, retries on 429 with `Retry-After` and
  on 500/502/503/504, idempotency keys, error mapping.
- `src/aegis/_errors.py` — the error classes and the status-URL note.
- `src/aegis/_mcp.py` — the local MCP server and its six tools.
- `src/aegis/_verify_local.py` — the offline verifier.
- `tests/` — the test suite (in the unpacked sdist:
  `pip install -e ".[dev,verify]" && pytest`).

The changelog is `CHANGELOG.md`.

## License

Apache 2.0 - see [LICENSE](LICENSE) for details.
