Metadata-Version: 2.4
Name: agaas-sdk
Version: 0.1.3
Summary: Official Python SDK for AGaaS — the Autonomous Governance Gateway. Ask before you act.
Project-URL: Homepage, https://agaas.phy357ai.com
Author: SCITECH
License: MIT License
        
        Copyright (c) 2026 SCITECH
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: agaas,agents,ai-safety,calibration,governance
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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 :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: async
Requires-Dist: httpx>=0.24; extra == 'async'
Provides-Extra: dev
Requires-Dist: httpx>=0.24; extra == 'dev'
Requires-Dist: mypy>=1.5; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff<0.10,>=0.4; extra == 'dev'
Provides-Extra: httpx
Requires-Dist: httpx>=0.24; extra == 'httpx'
Description-Content-Type: text/markdown

# agaas-sdk

Official Python SDK for **AGaaS** — the Autonomous Governance Gateway.

AGaaS answers one question about an autonomous action: *should it proceed?*
The engines behind that answer are allowed to **refuse**, and this SDK is built
so a refusal is impossible to mistake for approval.

```bash
pip install agaas-sdk          # no dependencies
```

```python
from agaas_sdk import AgaasClient

with AgaasClient(api_key="agaas_live_…") as agaas:
    resp = agaas.caf_gate("acme", "checkout-agent", state, action)
    if resp:
        do_it()                       # allow
    else:
        hold(resp.verdict.reasons)    # abstain — every cause, not just the first
```

**One call; your manifest decides.** Which judges run is declared in your
manifest — not in this code and not on your API key — so the key needs only
`caf:gate`. Every decision lands in a tamper-evident chain (`resp.audit_seq`
locates it), and an abstain is *returned*, not raised: it is a verdict you must
act on, and the reasons say why.

The ten **measurement** engines are also callable directly, for calibration and
analysis — see [Direct engine calls](#direct-engine-calls). The four **judges**
are not: they are reachable only through the gate.

---

## Getting started from zero

The snippet above assumes you already hold a key. If you don't:

**1. Create an account** at <https://agaas.phy357ai.com> and add credits. Calls
are metered — including calls that get refused, which is the main reason to care
about the pre-flight section below.

**2. Turn on the engines you intend to use**, in the dashboard under
**Settings → Engines**. This is per ACCOUNT, not per key. An engine that is off
is refused with `403 engine_not_enabled` wherever it could be reached.

**3. Create the key** under **API Keys**. It asks for a name and nothing else —
the key inherits the operations enabled in **Settings → Scopes**, and follows
them if you change your mind later. The secret is shown once.

**4. Call something.**

```bash
export AGAAS_API_KEY="agaas_live_…"
```

```python
from agaas_sdk import AgaasClient

with AgaasClient() as agaas:
    print(agaas.limits())            # what this deployment will accept
    v = agaas.measure_transport(reference=yesterday, candidate=today)
    print(v.wasserstein)
```

### Which door each engine is behind

Two kinds of engine, reached two different ways. This is the thing to get right
before writing any code:

| | engines | how you call it | governed by |
|---|---|---|---|
| **Judges** | CPE, Concord, Arrow, Covenant | **`caf_gate()` only** | your manifest **and** Settings → Engines |
| **Measures** | the other ten | directly | Settings → Engines |

A judge produces a *verdict*, so the path it is reached by is the product: your
manifest names which judges run, and the answer is hash-chained and replayable.
Calling one directly would return the same number with no manifest and no chain,
so it is refused:

```python
agaas.govern_risk(...)      # 403 direct_engine_call — submit it to caf_gate()
```

A measure returns a number with no verdict and no abstain, and a manifest has no
syntax to declare one — so the account switch is its whole security axis.

| method | kind |
|---|---|
| `govern_risk` · `govern_coherence` · `govern_driven` · `measure_guarantee` | judge — via `caf_gate()` |
| `govern_coherence_relative` · `certify_reach` · `measure_signature` · `measure_spectrum` · `measure_coordination` · `measure_transport` · `measure_topology` · `measure_topology_drift` · `measure_entropy_production` · `measure_consensus` | measure — direct |

Note `measure_guarantee` (Covenant) is a **judge** despite the `measure:`
prefix — it is judged, not measured. Grouping by prefix is the one mistake this
table exists to prevent.

### Which engine answers which question

No type can encode this, so it is the part worth reading rather than
autocompleting:

| you want to know | call |
|---|---|
| is this action's near-term risk acceptable? | `govern_risk` |
| is my agent swarm still aligned? | `govern_coherence` |
| …and is *this* unusual for us specifically? | `govern_coherence_relative` |
| is something really driving this, or is it noise? | `govern_driven` |
| can I *prove* the system never reaches an unsafe state? | `certify_reach` |
| are these actors coordinating, above chance? | `measure_coordination` |
| how far has this distribution moved? | `measure_transport` |
| do my prediction intervals still hold? | `measure_guarantee` |

---

## Direct engine calls

The ten measurement engines can be called on their own — the right tool for
calibration, analysis, or when you deliberately want one number rather than a
governed verdict. They need no scope on the key; what they need is to be enabled
for the account in **Settings → Engines**.

```python
v = agaas.measure_transport(reference=last_week, candidate=today)
```

The four judges are **not** available this way. `govern_risk`,
`govern_coherence`, `govern_driven` and `measure_guarantee` answer
`403 direct_engine_call` — submit the decision to `caf_gate()` and let your
manifest declare which of them run.

## What makes this different from a generated client

An AGaaS engine can decline to answer. Most SDKs have nowhere to put that, so
it arrives as an error to swallow or a zero to misread. Three rules follow.

**A refusal never looks like a number.** `sigma_above_null` and `divergence`
are unions on the wire — a float, or `{"abstain": "reason"}`. A binding typed
as `float` works in testing and breaks in production the first time an engine
declines. Here they are `MetricValue`, which is falsy when withheld and raises
on arithmetic rather than reading as `0.0`.

```python
if result.divergence.available:
    use(float(result.divergence))
else:
    log(result.divergence.reason)
```

**An abstain is never retried.** A 422 is a verdict on your *input*, so the
same payload abstains forever. Retrying bills you again to be refused again.
The SDK retries 429, `engine_at_capacity`, infrastructure 503s and pre-response
transport failures — nothing else.

**Your key never touches a plaintext connection.** A non-loopback `http://`
base URL raises at construction, before any I/O. `http://127.0.0.1` stays
allowed, because that traffic never reaches a wire.

---

## The 422 trap

AGaaS returns two error formats that **share status codes**:

| | body | means |
|---|---|---|
| `422` + `{"error": …}` | JSON envelope | the **engine abstained** — valid input, refused answer |
| `422` + plain text | bare string | **your payload is malformed** — a field has the wrong type |

The second comes from the web framework before AGaaS code runs, so no envelope
exists. Discriminating on status alone reports a client bug as a server verdict.
This SDK discriminates on body shape (not `Content-Type`, which a proxy may
rewrite) and raises `AbstainedError` vs `BadRequestError` accordingly.

---

## Don't wait to be refused

Over-cap requests abstain. That costs no credits — an abstain returns before the
audit commit, and the usage event rides that commit — but it still costs a round
trip, a rate-limit token, and the time of whatever was waiting on the verdict.
Pre-flight validation is on by default and catches them locally, using the
server's own vocabulary:

```python
agaas.govern_coherence({"solo": [1.0, 2.0]})
# LimitViolation: coherence: too_few_agents — got 1, graph connectivity needs >= 3
```

Nothing was sent. Offline, and free:

```bash
agaas-calibrate check coherence payload.json
agaas-calibrate limits --offline
```

### Is my setup right? — `doctor`

The first-run failures look alike from the outside and have completely different
fixes: a dead key (401), an engine switched off (403), a judge you tried to call
directly (403, different reason), and an empty balance (402). Finding them one at
a time through failed calls is a poor introduction to a governance API.

```bash
agaas-calibrate doctor
```

```
server   https://agaas.phy357ai.com
reach    ok
key      valid
caps     12 engines, live from server

engines
  [ok]        measure:transport
  [gate]      govern:risk          judge — call it through caf_gate()
  [off]       measure:spectrum     off for this account (AGaaS Settings → Engines)

summary  1 callable, 4 via the gate, 9 off
```

**It costs no credits, and that is checkable rather than a promise.** The guard
decides before the rate limit and long before the handler, so a refused probe
consumes nothing; a permitted one passes the guard and the deliberately
unparseable probe body is rejected by the JSON extractor — no handler runs, no
audit is committed, and the usage event rides that same commit.

None of `[gate]`, `[off]` or `[absent]` is an error: a judge being gate-only is
the design, and an engine switched off is a deliberate choice. For CI, name what
you actually need:

```bash
agaas-calibrate doctor --require measure_transport,measure_spectrum
```

That fails the build before your workload meets its first 403 in production.

> **Server compatibility.** Older deployments refuse `GET /v1/limits`. When a
> server does, `limits()` falls back to an embedded snapshot and sets
> `.stale = True` — an SDK cannot assume the deployment it is pointed at has
> been upgraded. Check that flag before treating a cap as authoritative.

---

## Calibration

Several engines are *relative*: they answer "is this unusual **for you**",
which is unanswerable until they know what usual looks like. The full guide —
which engine needs what, and the mistakes that silently disable a gate — is in
[CALIBRATION.md](CALIBRATION.md). In short, skipping calibration gives you one
of two quiet failures:

- a gate that **abstains forever** (never enough history), or
- a gate that **never fires** — a baseline so wide nothing is anomalous.

The second is the dangerous one. A governance gate that approves everything is
indistinguishable from a working one until the day it was meant to stop
something.

```bash
agaas-calibrate run history.json -o baselines.json   # offline, free
agaas-calibrate verify baselines.json                # one real call per engine
```

```python
from agaas_sdk import calibrate_coherence

cal = calibrate_coherence(past_coherence_values)
if not cal.ready:
    print(cal.missing)   # "22 more observations (have 8, want >= 30)"
```

**Everything is computed from data you supply.** There is no synthetic default
and no "typical" baseline, because one copied from someone else's traffic
produces confident verdicts about a distribution that was never observed. Where
history is insufficient, calibration **refuses and says what is missing** — the
same stance the engines take. `run` exits non-zero if anything is blocked, so
CI can gate on it.

Two failures it catches that are otherwise silent:

- **Constant history.** Zero variance means the z-score divides by zero and
  nothing can ever be anomalous.
- **Arrow's null band on the wrong scale.** `govern_driven` compares the
  engine's entropy-production rate of your live series against a null band, so
  the band must be entropy-production rates too — build it with
  `measure_null_scores`, which measures each equilibrium window through the
  engine. A locally-invented statistic miscalibrates the gate. (Arrow is also
  the one calibrator that sends data to the service — see
  [CALIBRATION.md](CALIBRATION.md).)

---

## Design

Hexagonal, with three ports (`Transport`, `Clock`, `CredentialStore`) as
structural `Protocol`s. Supply your own by shape — no subclassing.

```python
client = AgaasClient(key, transport=MyRecordingTransport(), clock=FakeClock())
```

The whole test suite runs with no network and no wall-clock time.

**Zero required dependencies.** The default transport is `urllib`. This package
carries a credential, and every transitive dependency is code that could read
it. `pip install agaas-sdk[httpx]` adds pooling and HTTP/2;
`agaas-sdk[async]` adds `AsyncAgaasClient`.

Request construction lives in one module shared by both clients, and the
calibration CLI ships inside the SDK rather than beside it. Splitting them puts
the same credential handling in two codebases, where a fix applied to one can
silently miss the other.

---

## Coverage

All 14 engines: `govern_risk` · `govern_coherence` ·
`govern_coherence_relative` · `govern_driven` · `certify_reach` ·
`measure_signature` · `measure_spectrum` · `measure_coordination` ·
`measure_transport` · `measure_topology` · `measure_topology_drift` ·
`measure_entropy_production` · `measure_guarantee` · `measure_consensus`

### Errors

| exception | status | retried |
|---|---|---|
| `AbstainedError` | 422 + envelope | never — same input abstains forever |
| `BadRequestError` | 400 / 415 / 422 plain | never — the bug is local |
| `AuthenticationError` | 401 | no |
| `PermissionError_` | 403 | no |
| `PaymentRequiredError` | 402 | no — carries `topup_url` |
| `PayloadTooLargeError` | 413 | no |
| `RateLimitError` | 429 | yes, honouring `Retry-After` |
| `CapacityError` | 503 `engine_at_capacity` | yes — clears in ~1 s |
| `ServiceUnavailableError` | 503 infra | yes, backing off hard |
| `ServerError` | 500 | no — deterministic engines fail identically |
| `TransportError` | no response | yes |

Backoff jitter is derived by hashing `(key, attempt)`: reproducible for one
client, decorrelated across clients, so a fleet does not retry in lockstep
after a shared outage.

---

## Support and security

Questions, bugs: **scitechfamily357@gmail.com** (`agaas-sdk` in the subject).

Security issues: report them **privately** to the same address — see
[SECURITY.md](SECURITY.md). This package handles API credentials, so a public
report exposes every installed copy before a fixed version can ship, and a
released version on an index can never be overwritten, only superseded.

Never include a real API key in a report. If one leaked, rotate it in the
dashboard first — that is faster and more certain than any fix.

## License

MIT
