Integrate

Connect OWS to your policy engine

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.

Loading integration key...
1

Policy Executable

Save as ~/.ows/policies/memrail_gate.py

JSON
#!/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}")
2

Policy Definition

Save as memrail-policy.json — loading key...

JSON
{
  "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"
}
3

Install & Attach

Register the policy and create an API key scoped to it

JSON
# 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-gate

How 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 key
workspace / projectMemrail org scope for EMU evaluation
usd_rateToken-to-USD conversion rate (e.g., 2500 for ETH)
decimalsToken decimals for wei conversion (18 for ETH)
decision_pointGate identifier matching your EMU triggers
extra_contextStatic context injected on every request

Context Mapping

The executable maps OWS PolicyContext fields to Memrail context:

OWS FieldMemrail Context
chain_idstate.chain_id
wallet_idstate.wallet_id
api_key_idstate.api_key_id
transaction.raw_hex → tostate.request.to
transaction.raw_hex → valuestate.request.value
value (wei → USD)state.request.amount
spending.daily_totalstate.spending.daily_total
daily_total (wei → USD)state.budget.daily_spent