safety-protocol 0.3.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,201 @@
1
+ """
2
+ Safety Protocol Framework for LLM Agents
3
+
4
+ A framework for building safe, accountable AI agents that operate
5
+ through enforced safety protocols rather than relying on the model's
6
+ good behavior.
7
+
8
+ The core principle: the user is accountable for their agents. Every
9
+ agent action passes through a safety protocol that enforces binding,
10
+ scope, budget, approval gates, monitoring, audit trail, and kill switch.
11
+
12
+ The agent operates freely WITHIN these bounds. The bounds are enforced
13
+ by infrastructure, not by hoping the agent behaves.
14
+
15
+ When the agent messes up, the audit trail tells you what happened, the
16
+ binding tells you who's accountable, and the next iteration has tighter
17
+ scope.
18
+
19
+ This framework implements a reference architecture for safe agents:
20
+
21
+ Layer 1: Safety Protocol (enforcement) — scope, budget, approval, monitoring,
22
+ audit trail (off-chain complete record), kill switch.
23
+
24
+ Layer 2: On-chain anchors — SBT-style non-transferable binding, on-chain audit
25
+ for key events (high-value actions, approvals, kill switch, scope violations).
26
+ Verifiable by anyone. Tamper-resistant.
27
+
28
+ Layer 3: Insurance interface — claims-ready evidence, underwriter reports,
29
+ control-adjusted exposure estimates. The protocol makes the agent insurable
30
+ by providing verifiable evidence of what happened and controls that operated.
31
+
32
+ The framework is framework-agnostic: works with any LLM, any runtime, any chain.
33
+ In production, swap the simulated on-chain layer for real ERC-5192/ERC-8004 + a
34
+ real chain, and the insurance interface for a real insurer's claims process.
35
+
36
+ Installation:
37
+ pip install safety-protocol
38
+
39
+ Quick start:
40
+
41
+ from safety_protocol import (
42
+ SafetyProtocol, BoundAgent, ScopeRule, AuditTrail, Monitor
43
+ )
44
+
45
+ audit = AuditTrail()
46
+ protocol = SafetyProtocol(
47
+ agent_id="agent-001",
48
+ user_id="alice",
49
+ scope_rules=[
50
+ ScopeRule(
51
+ action_type="api_call",
52
+ allowed_targets=["https://api.example.com/v1/*"],
53
+ forbidden_targets=["admin", "billing"],
54
+ max_cost=5.0,
55
+ ),
56
+ ],
57
+ budget_limit=50.0,
58
+ approval_threshold_cost=10.0,
59
+ audit=audit,
60
+ )
61
+
62
+ agent = BoundAgent(
63
+ agent_id="agent-001",
64
+ user_id="alice",
65
+ safety_protocol=protocol,
66
+ )
67
+
68
+ result = agent.propose_action(
69
+ action_type="api_call",
70
+ target="https://api.example.com/v1/search",
71
+ params={"query": "test"},
72
+ estimated_cost=2.50,
73
+ )
74
+
75
+ print(result.outcome) # ActionOutcome.ALLOWED
76
+
77
+ For the full reference architecture with on-chain binding, on-chain audit,
78
+ and insurance interface:
79
+
80
+ from safety_protocol.deployment import ReferenceDeployment
81
+
82
+ deployment = ReferenceDeployment(
83
+ agent_id="agent-001",
84
+ user_id="alice",
85
+ agent_name="ResearchAgent",
86
+ agent_role="AI research assistant",
87
+ )
88
+
89
+ # Agent proposes action — full stack enforces
90
+ result = deployment.agent_propose_action(
91
+ action_type="api_call",
92
+ target="https://api.example.com/v1/search",
93
+ params={"query": "test"},
94
+ estimated_cost=2.50,
95
+ )
96
+
97
+ # Get binding proof (verifiable by anyone)
98
+ proof = deployment.get_binding_proof()
99
+
100
+ # Get claims evidence (for insurance)
101
+ evidence = deployment.get_claim_evidence(
102
+ claim_description="Agent made incorrect API call",
103
+ )
104
+
105
+ # Get underwriter package (for binding coverage)
106
+ underwriter = deployment.get_underwriter_package(
107
+ agent_description="Research agent that calls APIs",
108
+ task_profile="API calls, message sending",
109
+ max_potential_loss=5000.0,
110
+ )
111
+
112
+ The problem this solves:
113
+ -----------------------
114
+ AI agents can take actions with real consequences: spending money, calling
115
+ APIs, modifying systems, sending messages, spawning subagents. The model's
116
+ good behavior is not reliable enough to depend on. The safety protocol makes
117
+ the agent *safe to operate* by enforcing constraints in infrastructure, not
118
+ in prompts.
119
+
120
+ When the agent messes up, you need to know: what happened, who's accountable,
121
+ what controls operated, what can be reconstructed. The binding + audit trail
122
+ + on-chain record answer these questions. The insurance interface makes the
123
+ agent insurable by providing claims-ready evidence.
124
+
125
+ The enforcement library (Layer 1) is production-usable. The on-chain layer is
126
+ simulated (same interface as a real chain). In production, use ERC-5192 SBT or
127
+ ERC-8004 on a real chain, and connect the insurance interface to a real
128
+ insurer's claims process.
129
+
130
+ The architecture is composable: use the protocol alone, add on-chain binding,
131
+ add on-chain audit, add insurance interface — each layer is independent and
132
+ adds value on its own.
133
+
134
+ Documentation:
135
+ - Core protocol: src/safety_protocol/protocol.py
136
+ - On-chain binding: src/safety_protocol/onchain.py
137
+ - On-chain audit: src/safety_protocol/onchain_audit.py
138
+ - Insurance interface: src/safety_protocol/insurance.py
139
+ - Reference deployment: src/safety_protocol/deployment.py
140
+ - Example (LLM agent): examples/llm_agent.py
141
+ - Example (reference deployment): examples/reference_deployment.py
142
+ """
143
+ from .core import (
144
+ ActionOutcome,
145
+ ActionRequest,
146
+ ActionResult,
147
+ ApprovalRecord,
148
+ AuditTrail,
149
+ Monitor,
150
+ ProtocolState,
151
+ ScopeRule,
152
+ )
153
+ from .deployment import ReferenceDeployment
154
+ from .guard_service import GuardService, build_protocol_from_config
155
+ from .insurance import InsuranceInterface
156
+ from .onchain import OnChainBindingRegistry, OnChainBoundProtocol
157
+ from .onchain_audit import DualAudit, OnChainAudit
158
+ from .onchain_payment_verifier import OnChainPaymentVerifier
159
+ from .payments import PaymentEnvelope, SafeSpendAgent, SimWallet
160
+ from .protocol import BoundAgent, SafetyProtocol
161
+ from .real_wallet import HAS_REAL_CRYPTO, RealWallet
162
+ from .scope_linter import Finding, Severity, lint_report, lint_rules
163
+
164
+ try:
165
+ from importlib.metadata import PackageNotFoundError
166
+ from importlib.metadata import version as _version
167
+
168
+ __version__ = _version("safety-protocol")
169
+ except PackageNotFoundError: # running from a source checkout
170
+ __version__ = "0.3.0"
171
+ __all__ = [
172
+ "ActionOutcome",
173
+ "ProtocolState",
174
+ "ActionRequest",
175
+ "ActionResult",
176
+ "ApprovalRecord",
177
+ "ScopeRule",
178
+ "AuditTrail",
179
+ "Monitor",
180
+ "SafetyProtocol",
181
+ "BoundAgent",
182
+ "OnChainBindingRegistry",
183
+ "OnChainBoundProtocol",
184
+ "OnChainAudit",
185
+ "DualAudit",
186
+ "InsuranceInterface",
187
+ "ReferenceDeployment",
188
+ "SafeSpendAgent",
189
+ "SimWallet",
190
+ "PaymentEnvelope",
191
+ "RealWallet",
192
+ "HAS_REAL_CRYPTO",
193
+ "OnChainPaymentVerifier",
194
+ "lint_rules",
195
+ "lint_report",
196
+ "Finding",
197
+ "Severity",
198
+ "GuardService",
199
+ "build_protocol_from_config",
200
+ "__version__",
201
+ ]
safety_protocol/cli.py ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ``safety-guard`` — the CLI developers use to run and lint the guard.
4
+
5
+ Subcommands (all read the USER-CONTROLLED config; none let the agent widen
6
+ scope):
7
+
8
+ init write a default, least-privilege config to disk
9
+ serve start the HTTP guard (fails closed if rules fail lint)
10
+ check ask the guard whether an intent is allowed (--action/--target/...)
11
+ pay request a gated payment (--recipient/--usd)
12
+ lint lint the config ruleset and exit non-zero if too broad
13
+
14
+ Run:
15
+ safety-guard init --out guard_config.json
16
+ safety-guard serve --config guard_config.json
17
+ safety-guard check --config guard_config.json \\
18
+ --action api_call --target https://api.research.example/v1/search \\
19
+ --method POST --params '{"query":"hi"}'
20
+ safety-guard pay --config guard_config.json \\
21
+ --recipient 0xMerchant1111111111111111111111111111111 --usd 0.10
22
+ safety-guard lint --config guard_config.json
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import argparse
27
+ import json
28
+ import sys
29
+ from importlib.resources import files
30
+
31
+ from safety_protocol.guard_service import GuardService
32
+ from safety_protocol.guard_service import serve as serve_http
33
+ from safety_protocol.scope_linter import Severity
34
+
35
+
36
+ def load_cfg(path):
37
+ with open(path) as f:
38
+ return json.load(f)
39
+
40
+
41
+ def cmd_init(args):
42
+ """Write the packaged default (least-privilege) config to disk."""
43
+ import os
44
+
45
+ if os.path.exists(args.out) and not args.force:
46
+ print(f"refusing to overwrite existing file: {args.out} (use --force)")
47
+ sys.exit(1)
48
+ src = files("safety_protocol").joinpath("data/guard_config.json")
49
+ with open(args.out, "w", encoding="utf-8") as f:
50
+ f.write(src.read_text(encoding="utf-8"))
51
+ print(f"wrote default guard config to {args.out}")
52
+
53
+
54
+ def cmd_serve(args):
55
+ # Delegate to the HTTP server entry (fails closed on lint via GuardService).
56
+ serve_http(args.config, args.host, args.port)
57
+
58
+
59
+ def cmd_check(args):
60
+ svc = GuardService(load_cfg(args.config))
61
+ params = json.loads(args.params) if args.params else {}
62
+ r = svc.guard(args.action, args.target, method=args.method,
63
+ params=params, cost=args.cost)
64
+ print(json.dumps(r, indent=2))
65
+ sys.exit(0 if r["allowed"] else 1)
66
+
67
+
68
+ def cmd_pay(args):
69
+ svc = GuardService(load_cfg(args.config))
70
+ r = svc.pay(args.recipient, args.usd, resource=args.resource)
71
+ print(json.dumps(r, indent=2))
72
+ sys.exit(0 if r["allowed"] else 1)
73
+
74
+
75
+ def cmd_lint(args):
76
+ cfg = load_cfg(args.config)
77
+ # Rebuild via the same path the guard uses, so lint + fail-closed match.
78
+ from safety_protocol.guard_service import build_protocol_from_config
79
+ from safety_protocol.scope_linter import lint_report
80
+ try:
81
+ _, findings = build_protocol_from_config(cfg)
82
+ except SystemExit as e:
83
+ # fail-closed: broad rules -> non-zero exit
84
+ print(str(e))
85
+ sys.exit(1)
86
+ print(lint_report(findings))
87
+ blocking = [f for f in findings
88
+ if f.severity in (Severity.ERROR, Severity.WARN)]
89
+ sys.exit(1 if blocking else 0)
90
+
91
+
92
+ def main(argv=None):
93
+ ap = argparse.ArgumentParser(prog="safety-guard", description="Safety guard CLI")
94
+ sub = ap.add_subparsers(dest="cmd", required=True)
95
+
96
+ i = sub.add_parser("init", help="write a default guard config")
97
+ i.add_argument("--out", default="guard_config.json")
98
+ i.add_argument("--force", action="store_true", help="overwrite if it exists")
99
+ i.set_defaults(func=cmd_init)
100
+
101
+ s = sub.add_parser("serve", help="start the HTTP guard")
102
+ s.add_argument("--config", required=True)
103
+ s.add_argument("--host", default="127.0.0.1")
104
+ s.add_argument("--port", type=int, default=8080)
105
+ s.set_defaults(func=cmd_serve)
106
+
107
+ c = sub.add_parser("check", help="check if an intent is allowed")
108
+ c.add_argument("--config", required=True)
109
+ c.add_argument("--action", required=True)
110
+ c.add_argument("--target", required=True)
111
+ c.add_argument("--method")
112
+ c.add_argument("--params", default="")
113
+ c.add_argument("--cost", type=float, default=0.0)
114
+ c.set_defaults(func=cmd_check)
115
+
116
+ p = sub.add_parser("pay", help="request a gated payment")
117
+ p.add_argument("--config", required=True)
118
+ p.add_argument("--recipient", required=True)
119
+ p.add_argument("--usd", type=float, required=True)
120
+ p.add_argument("--resource", default="weather")
121
+ p.set_defaults(func=cmd_pay)
122
+
123
+ lint_p = sub.add_parser("lint", help="lint the config ruleset")
124
+ lint_p.add_argument("--config", required=True)
125
+ lint_p.set_defaults(func=cmd_lint)
126
+
127
+ args = ap.parse_args(argv)
128
+ args.func(args)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()