build(agent): new-agents-2#7e3bbc iteration
This commit is contained in:
parent
51368d145b
commit
38f2d3c3ac
|
|
@ -0,0 +1,21 @@
|
|||
node_modules/
|
||||
.npmrc
|
||||
.env
|
||||
.env.*
|
||||
__tests__/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
dist/
|
||||
build/
|
||||
.cache/
|
||||
*.log
|
||||
.DS_Store
|
||||
tmp/
|
||||
.tmp/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
READY_TO_PUBLISH
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# GuardRail.Space AGENTS.md
|
||||
|
||||
Architectural guidelines and contribution rules for GuardRail.Space MVP.
|
||||
|
||||
- Architecture overview
|
||||
- Tech stack and interfaces
|
||||
- Testing commands and CI hints
|
||||
- Contribution and code style rules
|
||||
- MVP scope for safety contracts, policy engine, guard module, and adapters
|
||||
|
||||
The goal is to provide a production-ready scaffold that can be progressively extended:
|
||||
- DSL for SafetyContracts (JSON-like for MVP)
|
||||
- Runtime policy engine to veto or adjust actions
|
||||
- Shadow planner for risk-proof alternatives
|
||||
- Offline-first logging and reconciliation
|
||||
- CatOpt bridge adapters for cross-domain interoperability
|
||||
- HIL-ready integration hooks (Gazebo/ROS)
|
||||
- Governance, audit trails, and privacy controls
|
||||
|
||||
If you add features, update this document to reflect schema changes and testing commands.
|
||||
11
README.md
11
README.md
|
|
@ -1,3 +1,10 @@
|
|||
# guardrail-space-verifiable-safety-contra
|
||||
# GuardRail.Space
|
||||
|
||||
A lightweight, open-source safety framework to govern onboard AGI planners for space robotics (rovers, habitats, small satellites). It introduces a contract-based safety layer with (1) Safety Contracts defining pre/post conditions, budgets, and colli
|
||||
A lightweight, open-source safety framework to govern onboard AGI planners for space robotics. MVP includes:
|
||||
- JSON-like SafetyContract DSL
|
||||
- Runtime policy engine to veto or adjust actions
|
||||
- Guard module with deterministic fallback and shadow-planner hook
|
||||
- CatOpt bridge adapters skeleton for cross-domain interoperability
|
||||
- Offline-first design concepts and governance groundwork
|
||||
|
||||
This repository provides a minimal, production-ready scaffold to bootstrap a full MVP. The goal is to enable cross-domain experimentation while maintaining safety and auditability.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=40.8.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "guardrail_space"
|
||||
version = "0.1.0"
|
||||
description = "Verifiable safety contracts and offline-first guard for onboard AGI space robotics"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="guardrail_space",
|
||||
version="0.1.0",
|
||||
packages=find_packages(where="src"),
|
||||
package_dir={"": "src"},
|
||||
description="Verifiable safety contracts and offline-first guard for onboard AGI space robotics",
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
from .contracts import SafetyContract, parse_safety_contract_from_json, contract_to_minimal_dict
|
||||
from .policy_engine import PolicyEngine
|
||||
from .guard_module import GuardModule
|
||||
|
||||
__all__ = [
|
||||
"SafetyContract",
|
||||
"parse_safety_contract_from_json",
|
||||
"contract_to_minimal_dict",
|
||||
"PolicyEngine",
|
||||
"GuardModule",
|
||||
]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
from typing import Dict, Any
|
||||
|
||||
def catopt_to_canonic(obj: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {
|
||||
"Object": obj.get("subject", {}),
|
||||
"Morphism": obj.get("signal", {}),
|
||||
"Functor": obj.get("adapter", {}),
|
||||
"Meta": obj.get("meta", {}),
|
||||
}
|
||||
|
||||
def canonic_to_catopt(obj: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {
|
||||
"subject": obj.get("Object", {}),
|
||||
"signal": obj.get("Morphism", {}),
|
||||
"adapter": obj.get("Functor", {}),
|
||||
"meta": obj.get("Meta", {}),
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class SafetyContract:
|
||||
name: str
|
||||
pre: Optional[str]
|
||||
post: Optional[str]
|
||||
budgets: Dict[str, float]
|
||||
collision_rules: List[Dict[str, Any]]
|
||||
trust_policy: Dict[str, Any]
|
||||
|
||||
|
||||
def parse_safety_contract_from_json(text: str) -> SafetyContract:
|
||||
data = json.loads(text)
|
||||
return SafetyContract(
|
||||
name=data.get("name", "contract"),
|
||||
pre=data.get("pre"),
|
||||
post=data.get("post"),
|
||||
budgets=data.get("budgets", {}),
|
||||
collision_rules=data.get("collision_rules", []),
|
||||
trust_policy=data.get("trust_policy", {}),
|
||||
)
|
||||
|
||||
|
||||
def contract_to_minimal_dict(contract: SafetyContract) -> Dict[str, Any]:
|
||||
return {
|
||||
"name": contract.name,
|
||||
"pre": contract.pre,
|
||||
"post": contract.post,
|
||||
"budgets": contract.budgets,
|
||||
"collision_rules": contract.collision_rules,
|
||||
"trust_policy": contract.trust_policy,
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import threading
|
||||
from typing import Dict, Any, Tuple
|
||||
from .contracts import SafetyContract
|
||||
from .policy_engine import PolicyEngine
|
||||
|
||||
|
||||
class GuardModule:
|
||||
def __init__(self, contract: SafetyContract):
|
||||
self.contract = contract
|
||||
self.engine = PolicyEngine(contract)
|
||||
self.lock = threading.Lock()
|
||||
self.shadow_delta: Dict[str, Any] = {}
|
||||
|
||||
def decide(self, action: Dict[str, Any], context: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
|
||||
with self.lock:
|
||||
pre_ok, _ = self.engine.evaluate_pre(action, context)
|
||||
if not pre_ok:
|
||||
return False, {"reason": "precondition-failed", "fallback": self._fallback(action, context)}
|
||||
remaining = self.engine.remaining_budgets(context)
|
||||
cost = action.get("cost", {})
|
||||
for k, v in cost.items():
|
||||
rem = remaining.get(k, 0)
|
||||
if v > rem:
|
||||
return False, {"reason": f"budget-{k}-exceeded", "fallback": self._fallback(action, context)}
|
||||
post_ok, _ = self.engine.evaluate_post(action, context)
|
||||
if not post_ok:
|
||||
return False, {"reason": "postcondition-failed", "fallback": self._fallback(action, context)}
|
||||
return True, action
|
||||
|
||||
def _fallback(self, action: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
safe_action = {"name": "halt", "reason": "fallback", "context": context}
|
||||
thread = threading.Thread(target=self._run_shadow_planner, args=(action, context))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
return safe_action
|
||||
|
||||
def _run_shadow_planner(self, action: Dict[str, Any], context: Dict[str, Any]):
|
||||
delta = {"adjustment": {"reduce_cost": True}, "base_action": action}
|
||||
with self.lock:
|
||||
self.shadow_delta = delta
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
from typing import Dict, Any, Tuple
|
||||
|
||||
def _safe_eval(expr: str, context: Dict[str, Any]) -> bool:
|
||||
if not expr:
|
||||
return True
|
||||
allowed_globals = {"__builtins__": {}}
|
||||
local = dict(context)
|
||||
try:
|
||||
return bool(eval(expr, allowed_globals, local))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class PolicyEngine:
|
||||
def __init__(self, contract=None):
|
||||
self.contract = contract
|
||||
|
||||
def set_contract(self, contract) -> None:
|
||||
self.contract = contract
|
||||
|
||||
def evaluate_pre(self, action: Dict[str, Any], context: Dict[str, Any]) -> Tuple[bool, str]:
|
||||
if not self.contract or not self.contract.pre:
|
||||
return True, "no-precondition"
|
||||
ok = _safe_eval(self.contract.pre, {**context, "action": action})
|
||||
return (bool(ok), "precondition" if ok else "precondition-failed")
|
||||
|
||||
def evaluate_post(self, action: Dict[str, Any], context: Dict[str, Any]) -> Tuple[bool, str]:
|
||||
if not self.contract or not self.contract.post:
|
||||
return True, "no-postcondition"
|
||||
ok = _safe_eval(self.contract.post, {**context, "action": action})
|
||||
return (bool(ok), "postcondition" if ok else "postcondition-failed")
|
||||
|
||||
def remaining_budgets(self, context: Dict[str, Any]) -> Dict[str, float]:
|
||||
budgets = dict(self.contract.budgets) if self.contract and self.contract.budgets else {}
|
||||
spent = context.get("spent", {"time": 0, "energy": 0, "compute": 0})
|
||||
remaining = {}
|
||||
for k, v in budgets.items():
|
||||
spent_k = spent.get(k, 0)
|
||||
remaining[k] = max(0.0, v - spent_k)
|
||||
return remaining
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
echo "Running tests..."
|
||||
pytest -q
|
||||
echo "Building package..."
|
||||
python -m build
|
||||
echo "All tests and build succeeded."
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import json
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
||||
from guardrail_space.contracts import SafetyContract, parse_safety_contract_from_json
|
||||
|
||||
|
||||
def test_parse_basic_contract():
|
||||
text = json.dumps({
|
||||
"name": "Demo",
|
||||
"pre": "battery > 20",
|
||||
"post": "after_action",
|
||||
"budgets": {"time": 10, "energy": 5},
|
||||
"collision_rules": [{"type": "avoid", "distance": 1.0}],
|
||||
"trust_policy": {"overrides": ["operator"]}
|
||||
})
|
||||
c = parse_safety_contract_from_json(text)
|
||||
assert isinstance(c, SafetyContract)
|
||||
assert c.name == "Demo"
|
||||
assert c.pre == "battery > 20"
|
||||
assert c.budgets["time"] == 10
|
||||
assert c.collision_rules[0]["type"] == "avoid"
|
||||
|
||||
|
||||
def test_contract_to_dict_roundtrip():
|
||||
c = SafetyContract(
|
||||
name="X",
|
||||
pre=None,
|
||||
post=None,
|
||||
budgets={"time": 1},
|
||||
collision_rules=[],
|
||||
trust_policy={},
|
||||
)
|
||||
d = {
|
||||
"name": c.name,
|
||||
"pre": c.pre,
|
||||
"post": c.post,
|
||||
"budgets": c.budgets,
|
||||
"collision_rules": c.collision_rules,
|
||||
"trust_policy": c.trust_policy,
|
||||
}
|
||||
assert d["name"] == "X"
|
||||
assert d["budgets"]["time"] == 1
|
||||
Loading…
Reference in New Issue