OWS Executable Policy
OWS pipes a PolicyContextJSON to the executable's stdin. The policy file's config object is injected as policy_config at runtime — this is where Memrail credentials and context mappings live. The executable decodes the raw transaction, maps fields to context, calls Memrail decide, and writes a PolicyResult to stdout.
Policy Executable
Save as ~/.ows/policies/memrail_gate.py
#!/usr/bin/env python3
"""
OWS Policy Executable — Memrail Policy Studio Gateway
OWS pipes a PolicyContext JSON to stdin (with policy_config injected from
the policy file's "config" field). This executable forwards the request to
the Policy Studio backend, which handles:
- EVM transaction decoding (raw_hex → to, value, data)
- Agent lookup by wallet address → budget enrichment
- Wei → USD conversion for budget triggers
- Memrail AMI decide call
- Atomic budget decrement on ALLOW
The executable itself is intentionally thin — all dynamic logic lives
server-side so it can evolve without redeploying policy files.
"""
import json, sys, urllib.request
POLICY_ID = "memrail-policy-gate"
def emit(allow, reason=None):
r = {"allow": allow, "policy_id": POLICY_ID}
if reason:
r["reason"] = reason
json.dump(r, sys.stdout)
sys.exit(0)
ctx = json.load(sys.stdin)
config = ctx.get("policy_config", {})
STUDIO_URL = config.get("studio_url", "http://localhost:3000")
API_KEY = config.get("api_key")
if not API_KEY:
emit(False, "api_key not set in policy_config")
# ── Parse raw_hex for EVM chains (OWS doesn't parse to/value/data yet) ──
tx = ctx.get("transaction", {}) or {}
to_addr = tx.get("to")
value_wei = tx.get("value")
tx_data = tx.get("data")
if not to_addr and tx.get("raw_hex"):
try:
raw = bytes.fromhex(tx["raw_hex"].replace("0x", ""))
if raw[0] == 0x02:
raw = raw[1:] # strip EIP-1559 type byte
def rlp_list(b):
if b[0] <= 0xF7:
length, off = b[0] - 0xC0, 1
else:
ll = b[0] - 0xF7
length, off = int.from_bytes(b[1:1+ll], "big"), 1 + ll
items, pos = [], off
while pos < off + length:
if b[pos] <= 0x7F:
items.append(b[pos:pos+1]); pos += 1
elif b[pos] <= 0xB7:
l = b[pos] - 0x80
items.append(b[pos+1:pos+1+l]); pos += 1 + l
elif b[pos] <= 0xBF:
ll = b[pos] - 0xB7
l = int.from_bytes(b[pos+1:pos+1+ll], "big")
items.append(b[pos+1+ll:pos+1+ll+l]); pos += 1 + ll + l
else:
break
return items
f = rlp_list(raw)
# EIP-1559: [chainId, nonce, maxPri, maxFee, gas, to, value, data, ...]
# Legacy: [nonce, gasPrice, gas, to, value, data, ...]
if len(f) >= 8:
to_addr = ("0x" + f[5].hex()) if f[5] else None
value_wei = str(int.from_bytes(f[6], "big")) if f[6] else "0"
tx_data = ("0x" + f[7].hex()) if f[7] else "0x"
elif len(f) >= 6:
to_addr = ("0x" + f[3].hex()) if f[3] else None
value_wei = str(int.from_bytes(f[4], "big")) if f[4] else "0"
tx_data = ("0x" + f[5].hex()) if f[5] else "0x"
except Exception:
pass # Triggers requiring these fields simply won't match
# ── Build request for Policy Studio ──────────────────────────
spending = ctx.get("spending", {}) or {}
body = {
"chain_id": ctx.get("chain_id", ""),
"wallet_id": ctx.get("wallet_id", ""),
"api_key_id": ctx.get("api_key_id", ""),
"transaction": {
"to": to_addr,
"value": value_wei,
"raw_hex": tx.get("raw_hex", ""),
"data": tx_data,
},
"spending": {
"daily_total": spending.get("daily_total", "0"),
"date": spending.get("date", ""),
},
"usd_rate": config.get("usd_rate", 0),
"decimals": config.get("decimals", 18),
"decision_point": config.get("decision_point", "ows-gate"),
"wallet_address": config.get("wallet_address"),
"agent_id": config.get("agent_id"),
}
try:
req = urllib.request.Request(
f"{STUDIO_URL}/api/gate/ows-decide",
data=json.dumps(body).encode(),
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
)
resp = json.load(urllib.request.urlopen(req, timeout=4))
emit(resp.get("allow", False), resp.get("reason"))
except Exception as e:
emit(False, f"Studio error: {e}")
Policy Definition
Save as memrail-policy.json — loading key...
{
"id": "memrail-policy-gate",
"name": "Memrail Policy Engine",
"version": 1,
"created_at": "2026-01-01T00:00:00Z",
"rules": [
{
"type": "allowed_chains",
"chain_ids": [
"eip155:8453",
"eip155:84532"
]
}
],
"executable": "~/.ows/policies/memrail_gate.py",
"config": {
"studio_url": "http://localhost:3000",
"api_key": "YOUR_MEMRAIL_API_KEY",
"decision_point": "ows-gate",
"usd_rate": 2500,
"decimals": 18,
"wallet_address": null,
"agent_id": null
},
"action": "deny"
}Install & Attach
Register the policy and create an API key scoped to it
# Save the policy executable
cp memrail_gate.py ~/.ows/policies/
chmod +x ~/.ows/policies/memrail_gate.py
# Create the policy (stores to ~/.ows/policies/memrail-policy-gate.json)
ows policy create --file memrail-policy.json
# Create an API key with the policy attached
ows key create \
--name "my-agent" \
--wallet agent-treasury \
--policy memrail-policy-gateHow it works
OWS injects the policy file's config as policy_config into the JSON piped to stdin. The executable decodes transaction.raw_hex (RLP) to extract to, value, and data since OWS currently only populates the raw hex in the signing path.
api_keyYour workspace-scoped Memrail API keyworkspace / projectMemrail org scope for EMU evaluationusd_rateToken-to-USD conversion rate (e.g., 2500 for ETH)decimalsToken decimals for wei conversion (18 for ETH)decision_pointGate identifier matching your EMU triggersextra_contextStatic context injected on every requestContext Mapping
The executable maps OWS PolicyContext fields to Memrail context:
| OWS Field | Memrail Context |
|---|---|
| chain_id | state.chain_id |
| wallet_id | state.wallet_id |
| api_key_id | state.api_key_id |
| transaction.raw_hex → to | state.request.to |
| transaction.raw_hex → value | state.request.value |
| value (wei → USD) | state.request.amount |
| spending.daily_total | state.spending.daily_total |
| daily_total (wei → USD) | state.budget.daily_spent |