External Control Layer

Adding an external control layer to one workflow

Start with one workflow, using parts most organizations already have, and widen the coverage once the layer has proved reliable there.

Eight steps

  1. Pick one workflow

    Choose a task where AI would save substantial work and the actions are clear, such as refunds under a set amount, bill payments, or supply reorders.

  2. List what the agent can do

    List its tools, credentials, and network routes, and mark which actions have external effects. That list is your register (P9).

  3. Take the credentials away from the agent

    Put the credentials for each system the agent can change behind something only the layer controls, such as an API gateway, a database proxy, a gateway in front of the agent’s tools, or a command broker in front of a machine controller. Rotate the old credentials. From then on the agent can only propose (P1, P2).

  4. Write the first rules

    Permit the routine cases, hold the exceptions, and deny what should never happen. Keep the rules short enough for the business owner to read and sign off on (P4).

  5. Run in shadow mode

    Before enabling the new layer, compare its decisions against recorded or simulated requests while existing safeguards remain active. Check the results against the agreed policy. Shadow results do not establish enforcement or an ECL-1 conformance claim.

  6. Turn on enforcement

    Deny by default. Send holds to the approval tools people already use, such as a ticket queue, a chat approval, or a confirmation in the customer’s app (P3, P5).

  7. Test the layer

    Run a fault-injection test, a stop drill, and an independent check of the records (P3, P5, P7). Keep the results, since auditors and buyers will ask for them.

  8. Widen the coverage

    Add action classes and sequence rules (P8), and publish what the layer covers and what it does not.

What most organizations already have

Each of the six responsibilities can usually start from infrastructure already in place.

ResponsibilityParts most organizations already run
DecisionA policy engine such as Open Policy Agent or Cedar, or a rules service the business already maintains
AuthorityThe identity provider, role directory, and approval workflows people already use
EnforcementAPI gateways, database proxies, a secrets manager, and narrowly scoped service accounts
ProvenanceSigning keys held in a key management service or hardware security module, and write-once storage
VerificationA short offline script that checks signatures and the hash chain, run by someone other than the operator
FleetConfiguration management, feature flags, and credential revocation

Run one bounded workflow

The public teaching example admits or denies a simulated record change and exercises an approval hold, changed parameters, replay, expiry and lost or stale proof. It produces newly bound test-key evidence for the admitted and denied examples, which the Python, TypeScript and Go verifiers can check.

It runs in one process. These teaching results establish no deployment conformance; its README states the simulation’s limits and the steps toward a real implementation.

  • Run the example (coming soon)
  • Choose an implementation path (coming soon)
  • Check the difficult cases (coming soon)

An illustration of the decision flow

This Python sketch illustrates a refund decision. It is not an ECL-1 reference implementation or production code: it does not implement the protocol formats, complete release checks or verifiable evidence bundles. Use it to understand the flow, then follow the standard’s requirements for an implementation.

control_layer.py, an illustration
import hashlib, json, time

POLICY = "refunds-v1"   # rules are versioned, and every record names the version

def decide(p, orders):
    """Readable rules. Anything no rule permits is denied."""
    order = orders.get(p["order_id"])   # evidence from the system of record, not the AI
    if p["action"] == "refund" and order["status"] == "delivered":
        if 0 < p["amount"] <= min(100, order["total"]):
            return "permit", "within the automatic refund limit"
        if 0 < p["amount"] <= min(1000, order["total"]):
            return "hold", "above the automatic limit; a supervisor decides"
    return "deny", "no rule permits this action"

class ControlLayer:
    def __init__(self, orders, executor, signer, log, approvals):
        self.orders = orders        # read-only view of the order system
        self.executor = executor    # the only holder of the payment credential
        self.signer = signer        # a signing key the AI system cannot reach
        self.log = log              # write-once storage
        self.approvals = approvals  # the queue people already work from
        self.prev = log.last_hash() # resume the chain where it left off

    def submit(self, proposal):
        try:
            decision, reason = decide(proposal, self.orders)
        except Exception as err:    # fail closed: no decision means no action
            decision, reason = "deny", f"policy error: {err}"
        record = self._record(proposal, decision, reason)
        if decision == "permit":
            self.executor.run(proposal)
        elif decision == "hold":
            self.approvals.enqueue(record)
        return record

    def _record(self, proposal, decision, reason):
        body = {"time": time.time(), "proposal": proposal, "policy": POLICY,
                "decision": decision, "reason": reason, "prev": self.prev}
        digest = hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()
        body.update(hash=digest, signature=self.signer.sign(digest))
        self.log.append(body)       # written before anything executes
        self.prev = digest
        return body
  • The AI system calls submit() and never holds the payment credential.
  • The rules are ordinary code in version control, and each record names the version it used.
  • The facts a rule relies on, such as the order’s status and total, come from the system of record rather than from the AI’s proposal.
  • Any error in evaluating the rules, including a missing field or an unknown order, ends in denial.
  • Each record carries the hash of the one before it and is signed with a key the AI system cannot reach, so that edits and gaps within the chain are detectable. A production layer also anchors the latest hash somewhere the operator cannot change, which makes deletion of the newest records detectable too.

The sketch is not production code. A production layer adds caller authentication, a durable approval queue whose timeouts end in denial, key management, a record of each execution’s outcome, sequence rules, an independent verifier, and high availability. Most of the engineering effort lies in these additions.

Common difficulties

Agents with a shell or a browser
Because their actions are free-form, put the layer in front of the resources they reach, such as databases, cloud control planes, and payment APIs, and run the agent where it holds no credentials.
Credentials that already leaked
An agent that once held a credential may still have a copy. Rotate each credential you move behind the layer.
Systems without APIs
Put the layer at the integration point, such as the database connection, the automation account, or the message queue.
Timing
Measure decision latency and failure recovery against the workflow’s declared time budget. Place and design the control layer so it can enforce the required safe state within that budget, including when a dependency fails.
Too many holds
If people approve everything, holds become a rubber stamp. Start with narrow holds, track how often they are approved unchanged, and tighten the rules.
Keeping the register accurate
Tools and integrations change over time, so compare the register with the agent’s actual tools and credentials on a schedule.