reliability-gate 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ """
2
+ ReliabilityGate
3
+ ===============
4
+ An anti-gameable permission-to-act layer for autonomous agents.
5
+
6
+ Usage:
7
+ from reliability_gate import ReliabilityGate, AbstentionRequired
8
+
9
+ gate = ReliabilityGate(api_key="my-project", agent_id="gpt-4o")
10
+ gate.observe(prediction=72.0, actual=68.5, domain="finance")
11
+ gate.should_act() # → True/False — governs the next decision
12
+
13
+ (Prototyped internally under the codename "Wayne Brain"; the public name is
14
+ ReliabilityGate. Legacy names remain only as deprecated aliases.)
15
+ """
16
+ from reliability_gate.client import ( # noqa: F401
17
+ ReliabilityGate,
18
+ ReliabilityGateClient,
19
+ CISResult,
20
+ ReliabilityGateError,
21
+ AbstentionRequired,
22
+ APIError,
23
+ ConnectionError,
24
+ # Aliases legacy dépréciés (codename interne) :
25
+ WayneBrain,
26
+ WayneBrainError,
27
+ CognitiveLayer,
28
+ )
29
+ from reliability_gate.decision import ( # noqa: F401
30
+ ReliabilityDecision,
31
+ decide,
32
+ OBSERVE,
33
+ ADVISORY,
34
+ HARD_GATE,
35
+ )
36
+
37
+ __version__ = "0.1.0"
38
+
39
+ __all__ = [
40
+ "ReliabilityGate",
41
+ "ReliabilityGateClient",
42
+ "ReliabilityDecision",
43
+ "decide",
44
+ "CISResult",
45
+ "ReliabilityGateError",
46
+ "AbstentionRequired",
47
+ "APIError",
48
+ "ConnectionError",
49
+ "OBSERVE",
50
+ "ADVISORY",
51
+ "HARD_GATE",
52
+ "__version__",
53
+ ]
@@ -0,0 +1,636 @@
1
+ """
2
+ ReliabilityGate — Python client
3
+ ================================
4
+ An anti-gameable permission-to-act layer for autonomous agents.
5
+ Plug into any LLM in 3 lines — measure real reliability, abstain when unreliable.
6
+
7
+ Quickstart:
8
+ from reliability_gate import ReliabilityGate, AbstentionRequired
9
+
10
+ gate = ReliabilityGate(api_key="my-project", agent_id="gpt-4o")
11
+
12
+ # After each LLM decision — submit the real outcome
13
+ gate.observe(prediction=72.0, actual=68.5, domain="finance")
14
+
15
+ # Before each decision — automatic gate
16
+ if not gate.should_act():
17
+ return route_to_human(task)
18
+
19
+ # Or use the decorator — raises AbstentionRequired if unreliable
20
+ @gate.guard()
21
+ def call_llm(prompt: str) -> str:
22
+ return llm.complete(prompt)
23
+
24
+ Advisory usage (no hard gating): call should_act()/cis() and log the verdict
25
+ without enforcing it, or use @gate.guard(on_abstain="log") which warns but lets
26
+ the call through. Switch to on_abstain="raise" once you trust the signal.
27
+
28
+ Requires: httpx (pip install httpx) or stdlib urllib as fallback.
29
+
30
+ Historical note: this package was prototyped internally under the codename
31
+ "Wayne Brain". The public name is ReliabilityGate; the legacy `sdk.wayne_cog`
32
+ import path and the `WayneBrain` class name remain as deprecated aliases only.
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import functools
37
+ import json
38
+ import time
39
+ from typing import Any, Callable
40
+ from urllib.parse import quote
41
+
42
+ from reliability_gate.decision import ReliabilityDecision, decide, ADVISORY
43
+
44
+ try:
45
+ import httpx
46
+ _HAS_HTTPX = True
47
+ except ImportError:
48
+ _HAS_HTTPX = False
49
+
50
+ # ── Exceptions ────────────────────────────────────────────────────────────────
51
+
52
+ class ReliabilityGateError(Exception):
53
+ """Base client error."""
54
+
55
+
56
+ class AbstentionRequired(ReliabilityGateError):
57
+ """Raised by @gate.guard() when the agent should not act.
58
+
59
+ Catch this to route the task to a human reviewer.
60
+
61
+ Example:
62
+ try:
63
+ result = call_llm(prompt)
64
+ except AbstentionRequired as e:
65
+ route_to_human(task, cis=e.cis, reason=e.verdict)
66
+ """
67
+ def __init__(self, agent_id: str, cis: float, verdict: str, advice: str = "") -> None:
68
+ self.agent_id = agent_id
69
+ self.cis = cis
70
+ self.verdict = verdict
71
+ self.advice = advice
72
+ super().__init__(
73
+ f"Agent '{agent_id}' must abstain — CIS={cis:.3f} ({verdict}). {advice}"
74
+ )
75
+
76
+
77
+ class APIError(ReliabilityGateError):
78
+ """ReliabilityGate API returned an error."""
79
+ def __init__(self, status_code: int, detail: str) -> None:
80
+ self.status_code = status_code
81
+ self.detail = detail
82
+ super().__init__(f"ReliabilityGate API {status_code}: {detail}")
83
+
84
+
85
+ class ConnectionError(ReliabilityGateError): # noqa: A001
86
+ """Cannot reach the ReliabilityGate API."""
87
+
88
+
89
+ # ── CIS Result dataclass (dict-compatible) ────────────────────────────────────
90
+
91
+ class CISResult(dict):
92
+ """CIS response with typed accessors.
93
+
94
+ Behaves like a regular dict (JSON-serializable) but also provides
95
+ attribute access for the most common fields.
96
+
97
+ Example:
98
+ result = gate.cis()
99
+ print(result.score) # 0.712
100
+ print(result.verdict) # "calibrated"
101
+ print(result.should_act) # True
102
+ """
103
+
104
+ @property
105
+ def score(self) -> float:
106
+ return float(self.get("cis", 0.0))
107
+
108
+ @property
109
+ def verdict(self) -> str:
110
+ return str(self.get("verdict", "no_data"))
111
+
112
+ @property
113
+ def should_act(self) -> bool:
114
+ return not self.get("should_abstain", True)
115
+
116
+ @property
117
+ def n_outcomes(self) -> int:
118
+ return int(self.get("n_outcomes", 0))
119
+
120
+ @property
121
+ def advice(self) -> str:
122
+ return str(self.get("advice", ""))
123
+
124
+ @property
125
+ def components(self) -> dict[str, float]:
126
+ return self.get("components", {})
127
+
128
+ def __repr__(self) -> str:
129
+ return (
130
+ f"CISResult(score={self.score:.3f}, verdict={self.verdict!r}, "
131
+ f"n_outcomes={self.n_outcomes}, should_act={self.should_act})"
132
+ )
133
+
134
+
135
+ # NB : `ReliabilityDecision` (verdict action-aware) est défini dans
136
+ # `reliability_gate.decision` et importé en tête de module. `CISResult` reste le
137
+ # type de réponse du score CIS — ce sont deux objets distincts.
138
+
139
+
140
+ # ── Main client ───────────────────────────────────────────────────────────────
141
+
142
+ class ReliabilityGate:
143
+ """ReliabilityGate client — permission-to-act layer for autonomous agents.
144
+
145
+ Args:
146
+ api_key: Your API key (= tenant ID in the MVP).
147
+ agent_id: Unique identifier for this agent.
148
+ base_url: ReliabilityGate API URL (default: http://localhost:8001).
149
+ timeout: HTTP timeout in seconds (default: 5s — keep low for gate calls).
150
+ retries: Number of retries on transient errors (default: 2).
151
+
152
+ enforcement_mode: Default enforcement for action gating
153
+ ("observe" | "advisory" | "hard_gate").
154
+ Default "advisory" — never blocks; surfaces a
155
+ recommendation (observe-first friendly).
156
+
157
+ Example:
158
+ gate = ReliabilityGate(api_key="my-company", agent_id="gpt-4o-finance")
159
+ """
160
+
161
+ def __init__(
162
+ self,
163
+ api_key: str,
164
+ agent_id: str,
165
+ base_url: str = "http://localhost:8001",
166
+ timeout: float = 5.0,
167
+ retries: int = 2,
168
+ enforcement_mode: str = ADVISORY,
169
+ ) -> None:
170
+ self.api_key = api_key
171
+ self.agent_id = agent_id
172
+ self.base_url = base_url.rstrip("/")
173
+ self.timeout = timeout
174
+ self.retries = retries
175
+ self.enforcement_mode = enforcement_mode
176
+ self._headers = {
177
+ "X-API-Key": api_key,
178
+ "Content-Type": "application/json",
179
+ }
180
+
181
+ # ── HTTP helpers ──────────────────────────────────────────────────────────
182
+
183
+ def _request(self, method: str, path: str, body: dict | None = None) -> dict:
184
+ url = f"{self.base_url}{path}"
185
+ last_exc: Exception | None = None
186
+
187
+ for attempt in range(self.retries + 1):
188
+ try:
189
+ return self._do_request(method, url, body)
190
+ except (APIError, AbstentionRequired):
191
+ raise # ne pas retenter les erreurs métier
192
+ except Exception as exc:
193
+ last_exc = exc
194
+ if attempt < self.retries:
195
+ time.sleep(0.3 * (2 ** attempt)) # backoff exponentiel
196
+
197
+ raise ConnectionError(f"Cannot reach ReliabilityGate API at {url}: {last_exc}") from last_exc
198
+
199
+ def _do_request(self, method: str, url: str, body: dict | None) -> dict:
200
+ data = json.dumps(body).encode() if body else None
201
+
202
+ if _HAS_HTTPX:
203
+ fn = httpx.post if method == "POST" else httpx.get
204
+ kwargs: dict[str, Any] = {"headers": self._headers, "timeout": self.timeout}
205
+ if method == "POST":
206
+ kwargs["content"] = data
207
+ resp = fn(url, **kwargs)
208
+ if resp.status_code >= 400:
209
+ raise APIError(resp.status_code, resp.text[:200])
210
+ return resp.json()
211
+
212
+ # Fallback stdlib (zero dependencies)
213
+ import urllib.request as _urllib
214
+ import urllib.error as _urlerr
215
+ req = _urllib.Request(url, data=data, headers=self._headers, method=method)
216
+ try:
217
+ with _urllib.urlopen(req, timeout=self.timeout) as r:
218
+ return json.loads(r.read())
219
+ except _urlerr.HTTPError as e:
220
+ raise APIError(e.code, e.read().decode()[:200]) from e
221
+ except OSError as e:
222
+ raise ConnectionError(str(e)) from e
223
+
224
+ # ── Public API ────────────────────────────────────────────────────────────
225
+
226
+ def observe(
227
+ self,
228
+ prediction: float | None = None,
229
+ actual: float | None = None,
230
+ domain: str = "general",
231
+ source: str = "",
232
+ abstained: bool = False,
233
+ action: str | None = None,
234
+ metadata: dict[str, Any] | None = None,
235
+ ) -> dict:
236
+ """Submit a real outcome after an agent interaction.
237
+
238
+ Call this every time your agent makes a prediction and you later
239
+ observe the ground truth. ReliabilityGate uses these outcomes to
240
+ continuously recalibrate the agent's CIS.
241
+
242
+ Args:
243
+ prediction: Value the agent predicted (0–100 scale).
244
+ actual: Real observed value (0–100 scale).
245
+ domain: Business domain (e.g. "finance", "legal", "support").
246
+ source: Source identifier (URL, document ID, etc.).
247
+ abstained: True if the agent chose not to predict.
248
+ action: Action type this outcome relates to (e.g. "send_email").
249
+ Enables action-aware gating via should_act(action=...).
250
+ metadata: Any additional key-value pairs to store.
251
+
252
+ Returns:
253
+ Dict with updated CIS: {"cis_updated": 0.72, "verdict": "calibrated", ...}
254
+
255
+ Example:
256
+ gate.observe(prediction=72.0, actual=68.5, domain="finance")
257
+ gate.observe(prediction=80.0, actual=78.0, action="send_email")
258
+ gate.observe(abstained=True, domain="legal")
259
+ """
260
+ return self._request("POST", "/observe", {
261
+ "agent_id": self.agent_id,
262
+ "prediction": prediction,
263
+ "actual": actual,
264
+ "domain": domain,
265
+ "source": source,
266
+ "abstained": abstained,
267
+ "action": action,
268
+ "metadata": metadata or {},
269
+ })
270
+
271
+ def cis(self) -> CISResult:
272
+ """Return the current Cognitive Integrity Score for this agent.
273
+
274
+ Returns a CISResult with typed accessors:
275
+ result.score → float ∈ [0, 1]
276
+ result.verdict → "trusted" | "calibrated" | "learning" | "unreliable"
277
+ result.should_act → bool (False = agent should abstain)
278
+ result.components → {"mae_score": 0.81, "skill_score": 0.67, ...}
279
+
280
+ Example:
281
+ result = gate.cis()
282
+ print(f"CIS: {result.score} ({result.verdict})")
283
+ if not result.should_act:
284
+ route_to_human(task)
285
+ """
286
+ raw = self._request("GET", f"/cis/{self.agent_id}")
287
+ return CISResult(raw)
288
+
289
+ def cis_for_action(self, action: str) -> CISResult:
290
+ """Return the CIS computed over outcomes filtered to a single action type.
291
+
292
+ Example:
293
+ gate.cis_for_action("send_email").score
294
+ """
295
+ raw = self._request("GET", f"/cis/{self.agent_id}?action={quote(action, safe='')}")
296
+ return CISResult(raw)
297
+
298
+ def should_act(
299
+ self,
300
+ action: str | None = None,
301
+ risk_level: str = "medium",
302
+ enforcement_mode: str | None = None,
303
+ min_cis: float | None = None,
304
+ ) -> "bool | ReliabilityDecision":
305
+ """Gate check — has the agent earned the right to act?
306
+
307
+ Two modes (backward compatible):
308
+
309
+ - **Agent-only (legacy)** — `should_act()` with no `action` returns a
310
+ plain ``bool``: True if the agent is globally reliable enough.
311
+ - **Action-aware** — `should_act(action="send_email", ...)` returns a
312
+ :class:`ReliabilityDecision` (truthy iff ``allow``) that says whether
313
+ the agent has earned the right to perform *that specific action*.
314
+
315
+ Args:
316
+ action: Action type to gate (e.g. "send_email"). None →
317
+ legacy agent-only bool.
318
+ risk_level: "low" | "medium" | "customer_visible" | "high" |
319
+ "irreversible" | "destructive" | "financial".
320
+ Unknown → treated as high (fail-closed).
321
+ enforcement_mode: "observe" | "advisory" | "hard_gate". Defaults to
322
+ the client's enforcement_mode ("advisory"). Only
323
+ "hard_gate" can return allow=False.
324
+ min_cis: (legacy path only) optional CIS threshold override.
325
+
326
+ Example (action-aware):
327
+ decision = gate.should_act(action="send_email",
328
+ risk_level="customer_visible",
329
+ enforcement_mode="hard_gate")
330
+ if decision.allow:
331
+ send_email()
332
+ else:
333
+ print(decision.reason)
334
+ """
335
+ # ── Legacy agent-only path → bool ──────────────────────────────────────
336
+ if action is None:
337
+ try:
338
+ result = self.cis()
339
+ if min_cis is not None:
340
+ return result.score >= min_cis
341
+ return result.should_act
342
+ except ConnectionError:
343
+ return False # fail-safe: API unreachable → do not act
344
+
345
+ # ── Action-aware path → ReliabilityDecision ────────────────────────────
346
+ mode = (enforcement_mode or self.enforcement_mode)
347
+ try:
348
+ global_cis = self.cis()
349
+ action_cis = self.cis_for_action(action)
350
+ cis_score = global_cis.score
351
+ action_score = action_cis.score if action_cis.n_outcomes > 0 else None
352
+ sample_size = action_cis.n_outcomes
353
+ except ConnectionError:
354
+ # Fail-safe : API injoignable → 0 preuve. En hard_gate cela bloque les
355
+ # actions risquées (CIS 0 < seuil) ; en observe/advisory, ne bloque pas.
356
+ cis_score, action_score, sample_size = 0.0, None, 0
357
+
358
+ return decide(
359
+ agent_id=self.agent_id,
360
+ action=action,
361
+ risk_level=risk_level,
362
+ cis_score=cis_score,
363
+ action_score=action_score,
364
+ sample_size=sample_size,
365
+ enforcement_mode=mode,
366
+ )
367
+
368
+ def guard(
369
+ self,
370
+ on_abstain: str = "raise",
371
+ min_cis: float | None = None,
372
+ ) -> Callable:
373
+ """Decorator — automatically gates the function on agent reliability.
374
+
375
+ Args:
376
+ on_abstain: What to do when agent should abstain:
377
+ "raise" → raises AbstentionRequired (default — hard gate)
378
+ "none" → returns None silently
379
+ "log" → logs a warning, lets the call through (advisory mode)
380
+ min_cis: Optional CIS threshold override.
381
+
382
+ Advisory usage: start with on_abstain="log" to observe the gate's
383
+ verdicts without enforcing them; switch to "raise" once trusted.
384
+
385
+ Example:
386
+ @gate.guard()
387
+ def call_llm(prompt: str) -> str:
388
+ return openai.complete(prompt)
389
+
390
+ # Advisory (logs but does not block):
391
+ @gate.guard(on_abstain="log", min_cis=0.65)
392
+ def risky_decision(data: dict) -> dict:
393
+ ...
394
+ """
395
+ def decorator(fn: Callable) -> Callable:
396
+ @functools.wraps(fn)
397
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
398
+ result = self.cis()
399
+ should = result.score >= min_cis if min_cis is not None else result.should_act
400
+
401
+ if not should:
402
+ if on_abstain == "raise":
403
+ raise AbstentionRequired(
404
+ agent_id=self.agent_id,
405
+ cis=result.score,
406
+ verdict=result.verdict,
407
+ advice=result.advice,
408
+ )
409
+ elif on_abstain == "none":
410
+ return None
411
+ elif on_abstain == "log":
412
+ import warnings
413
+ warnings.warn(
414
+ f"[ReliabilityGate] Agent '{self.agent_id}' is unreliable "
415
+ f"(CIS={result.score:.3f}, {result.verdict}) — proceeding anyway.",
416
+ stacklevel=2,
417
+ )
418
+
419
+ return fn(*args, **kwargs)
420
+ return wrapper
421
+ return decorator
422
+
423
+ def calibrate(self, url: str, domain: str = "web") -> dict:
424
+ """Run a full real calibration cycle on a public URL.
425
+
426
+ ReliabilityGate will:
427
+ 1. Predict extraction yield based on past history
428
+ 2. Fetch the URL (read-only, no login, no writes)
429
+ 3. Measure real extraction yield
430
+ 4. Persist outcome and return updated CIS
431
+
432
+ Example:
433
+ gate.calibrate("https://news.ycombinator.com", domain="tech")
434
+ """
435
+ return self._request("POST", "/calibrate", {
436
+ "agent_id": self.agent_id,
437
+ "url": url,
438
+ "domain": domain,
439
+ })
440
+
441
+ # ── Commit-Reveal (anti-triche) ──────────────────────────────────────────
442
+ #
443
+ # QUAND UTILISER ?
444
+ # Utilisez commit-reveal quand la vérifiabilité est critique :
445
+ # finance, compliance, audit. Pour du dev/test, POST /observe suffit.
446
+ #
447
+ # FLOW EN 3 ÉTAPES :
448
+ # 1. gate.commit(prediction=72.5) → verrouille la prédiction
449
+ # 2. ... observer le résultat réel ...
450
+ # 3. gate.reveal(actual=68.0) → ReliabilityGate vérifie et persiste
451
+ #
452
+ # RACCOURCI :
453
+ # gate.observe_verified(prediction=72.5, actual=68.0)
454
+ # → fait le commit + reveal en un seul appel (pour les cas simples)
455
+
456
+ def commit(self, prediction: float, domain: str = "general") -> dict:
457
+ """Lock a prediction BEFORE observing the real outcome (anti-cheat).
458
+
459
+ Computes SHA-256(prediction|nonce) and sends the hash to ReliabilityGate.
460
+ The server stores the hash; the prediction cannot be changed after this call.
461
+
462
+ Args:
463
+ prediction: The value your agent predicted (0-100 scale).
464
+ domain: Business domain (e.g. "finance", "legal").
465
+
466
+ Returns:
467
+ Dict with commit_id and nonce — you'll need both for reveal().
468
+
469
+ Example:
470
+ commit = gate.commit(prediction=72.5, domain="finance")
471
+ # ... wait for the real outcome ...
472
+ gate.reveal(commit_id=commit["commit_id"], nonce=commit["nonce"],
473
+ prediction=72.5, actual=68.0)
474
+ """
475
+ import hashlib
476
+ import secrets
477
+
478
+ # Génère un nonce aléatoire (32 hex chars = 128 bits d'entropie)
479
+ # Le nonce empêche quiconque (même le serveur) de deviner la prédiction
480
+ nonce = secrets.token_hex(16)
481
+
482
+ # Hash la prédiction avec le nonce — c'est ce hash qui est envoyé au serveur
483
+ # Format : sha256("72.5|a1b2c3d4e5f6...")
484
+ prediction_hash = hashlib.sha256(
485
+ f"{prediction}|{nonce}".encode("utf-8")
486
+ ).hexdigest()
487
+
488
+ result = self._request("POST", "/commit", {
489
+ "agent_id": self.agent_id,
490
+ "prediction_hash": prediction_hash,
491
+ "domain": domain,
492
+ })
493
+
494
+ # On retourne le nonce au client pour qu'il puisse faire le reveal
495
+ # IMPORTANT : le client DOIT conserver le nonce, le serveur ne le connaît pas
496
+ result["nonce"] = nonce
497
+ result["prediction"] = prediction
498
+ return result
499
+
500
+ def reveal(
501
+ self,
502
+ commit_id: str,
503
+ prediction: float,
504
+ nonce: str,
505
+ actual: float,
506
+ metadata: dict[str, Any] | None = None,
507
+ ) -> dict:
508
+ """Reveal a committed prediction and submit the real outcome.
509
+
510
+ The server verifies that SHA-256(prediction|nonce) matches the stored hash.
511
+ If it matches → verified outcome persisted. If not → rejected (cheat detected).
512
+
513
+ Args:
514
+ commit_id: The ID returned by commit().
515
+ prediction: The SAME prediction you committed (must match the hash).
516
+ nonce: The SAME nonce returned by commit().
517
+ actual: The real observed value (0-100 scale).
518
+ metadata: Optional additional key-value pairs.
519
+
520
+ Returns:
521
+ Dict with verified=True and updated CIS.
522
+
523
+ Raises:
524
+ APIError(400): If the hash doesn't match (cheat detected).
525
+ APIError(404): If the commit_id is expired or invalid.
526
+ """
527
+ return self._request("POST", "/reveal", {
528
+ "commit_id": commit_id,
529
+ "prediction": prediction,
530
+ "nonce": nonce,
531
+ "actual": actual,
532
+ "metadata": metadata or {},
533
+ })
534
+
535
+ def observe_verified(
536
+ self,
537
+ prediction: float,
538
+ actual: float,
539
+ domain: str = "general",
540
+ metadata: dict[str, Any] | None = None,
541
+ ) -> dict:
542
+ """Shortcut: commit + reveal in one call (verified outcome).
543
+
544
+ Combines commit() and reveal() for cases where the prediction and
545
+ actual values are both known at the same time (e.g. batch processing,
546
+ historical data ingestion with proof).
547
+
548
+ The outcome will be flagged as verified=True.
549
+
550
+ Args:
551
+ prediction: Value the agent predicted (0-100 scale).
552
+ actual: Real observed value (0-100 scale).
553
+ domain: Business domain.
554
+ metadata: Optional additional data.
555
+
556
+ Example:
557
+ # Simple — one line, cryptographically verified
558
+ gate.observe_verified(prediction=72.5, actual=68.0, domain="finance")
559
+ """
560
+ commit = self.commit(prediction=prediction, domain=domain)
561
+ return self.reveal(
562
+ commit_id=commit["commit_id"],
563
+ prediction=prediction,
564
+ nonce=commit["nonce"],
565
+ actual=actual,
566
+ metadata=metadata,
567
+ )
568
+
569
+ def agents(self) -> list[dict]:
570
+ """List all agents in your tenant with their current CIS.
571
+
572
+ Returns:
573
+ List of dicts: [{"agent_id": "...", "cis": 0.72, "verdict": "calibrated"}, ...]
574
+ """
575
+ raw = self._request("GET", "/agents")
576
+ return raw.get("agents", [])
577
+
578
+ def __repr__(self) -> str:
579
+ return f"ReliabilityGate(agent_id={self.agent_id!r}, base_url={self.base_url!r})"
580
+
581
+
582
+ # ── Aliases ───────────────────────────────────────────────────────────────────
583
+ # `ReliabilityGateClient` : alias explicite pour ceux qui préfèrent un nom suffixé.
584
+ ReliabilityGateClient = ReliabilityGate
585
+
586
+ # Aliases legacy (codename interne "Wayne Brain") — dépréciés, conservés pour
587
+ # compatibilité ; les docs publiques n'utilisent que ReliabilityGate.
588
+ WayneBrain = ReliabilityGate
589
+ CognitiveLayer = ReliabilityGate
590
+ WayneBrainError = ReliabilityGateError
591
+
592
+
593
+ # ── CLI demo ─────────────────────────────────────────────────────────────────
594
+
595
+ if __name__ == "__main__":
596
+ """Quick demo — run with: python -m reliability_gate.client"""
597
+ import sys
598
+
599
+ print("ReliabilityGate — SDK demo\n" + "=" * 40)
600
+
601
+ gate = ReliabilityGate(
602
+ api_key="sdk-demo",
603
+ agent_id="demo-agent",
604
+ base_url="http://localhost:8001",
605
+ )
606
+
607
+ print("\n1. Submitting 10 outcomes (agent learning)...")
608
+ scenarios = [
609
+ (50, 20), (50, 80), (45, 55),
610
+ (48, 52), (50, 53), (51, 52),
611
+ (52, 53), (52, 52), (53, 53),
612
+ (53, 54),
613
+ ]
614
+ for i, (pred, actual) in enumerate(scenarios, 1):
615
+ r = gate.observe(prediction=float(pred), actual=float(actual), domain="demo")
616
+ print(f" [{i:2d}] pred={pred} actual={actual} → CIS={r['cis_updated']:.3f} ({r['verdict']})")
617
+
618
+ print("\n2. Current CIS:")
619
+ result = gate.cis()
620
+ print(f" {result}")
621
+ print(f" Should act autonomously: {result.should_act}")
622
+
623
+ print("\n3. Testing @guard decorator...")
624
+
625
+ @gate.guard(on_abstain="none")
626
+ def risky_llm_call(prompt: str) -> str | None:
627
+ return f"Response to: {prompt}"
628
+
629
+ output = risky_llm_call("What is the market outlook?")
630
+ if output is None:
631
+ print(" → Agent abstained (CIS too low). Task routed to human.")
632
+ else:
633
+ print(f" → Agent acted: {output!r}")
634
+
635
+ print("\n✅ Demo complete.")
636
+ sys.exit(0)