Declarative agent control gives coding agents a bounded place to change behavior—and gives reviewers an explicit contract to check. The useful outcome is not fewer lines of code. It is being able to say what a change permits, what it preserves, and which tests demonstrate the difference.
Consider “raise the automatic refund limit from USD 50 to USD 75.” A reviewer should be able to see that the limit changed without also accepting weaker ownership checks, broader tool permissions, or unsafe retries. Memrail separates policy conditions from the application code that authorizes and executes a refund.
A refund policy you can inspect#
Suppose a business permits refunds up to USD 50 for eligible, settled USD payments, within their remaining refundable balance. This is an example business rule, not a Memrail default.
An EMU (Executable Memory Unit) is a versioned condition/action policy. Here is a complete policy represented as a Python dictionary. Its trigger uses Memrail's actual expression language; state.* refers to structured facts supplied by your application.
Shown expanded for readability; the production form is one JSON object on one line in emus/emus.jsonl, managed through pull, plan, and apply, not SDK writes. Serialize the dictionary to JSON, including Python True as JSON true.
refund_policy = {
"emu_key": "support.refund_small",
"decision_point": "refund.before_execute",
"state": "shadow",
"trigger": (
"state.refund.request_id EXISTS "
"AND state.customer.owns_payment == true "
"AND state.refund.eligible == true "
"AND state.payment.status == 'settled' "
"AND state.payment.currency == 'USD' "
"AND state.refund.amount_cents > 0 "
"AND state.refund.amount_cents <= 5000 "
"AND state.refund.amount_cents <= state.payment.refundable_cents"
),
"action": {
"type": "tool_call",
"intent": "REFUND_PAYMENT",
"tool": {
"tool_id": "refund_payment",
"version": "1.0.0",
"args": {"request_id": "{{refund.request_id}}"},
"retry": {"policy": "none", "max_retries": 0}
}
},
"policy": {
"mode": "auto",
"idempotency": {
"enabled": True,
"boundary": "workspace",
"roles": "auto",
"scope": ["refund.request_id"]
}
},
"expected_utility": 0.8,
"confidence": 0.9
}
The action describes a call to an application-defined refund_payment tool, version 1.0.0; Memrail does not provide that payment integration. The {{refund.request_id}} template carries the request identifier to its handler. The handler must resolve the same persisted request and payment used to assemble the facts, not accept a replacement amount from the model.
Validate integer-cent amounts and nonempty identifiers before supplying context. Load ownership, eligibility, payment status, currency, and refundable balance from authoritative services. A model may identify refund intent, but it does not establish these facts. Each fact is a state atom: a key/value input, such as state("payment.currency", "USD") in the Python SDK.
Invoke this policy with decision_point="refund.before_execute" in trusted application code immediately before refund evaluation. Keep that binding unchanged in the review contract below.
The literal refund.request_id scope enables action-level idempotency; its EXISTS guard is already in the trigger. This complements, rather than replaces, durable executor duplicate protection. The no-retry tool setting is documented under Tool retries.
Start in shadow under the binding and lifecycle contract. Utility and confidence are ranking inputs, not evidence of eligibility.
Turn the requested change into a review contract#
Now the coding task has a precise starting point: the 5000 comparison. The task is still larger than replacing a number. Any application-enforced limit must agree with the approved policy, and other candidate policies may change which action is selected.
Give the coding agent both an edit boundary and an evidence requirement:
Raise support.refund_small from USD 50 to USD 75.
Change the limit from 5000 to 7500 cents and update the matching
application execution contract. Preserve every other trigger condition,
the tool ID/version, request binding, and executor duplicate protection.
Do not change input schemas, policy ranking, retry settings, or lifecycle.
Do not enable dispatch or deploy.
Return the policy/executor diff and before/after test results, including
competing policies. Flag any additional required change before making it.
That last instruction matters. If a test fails because evidence is missing, the agent should repair the fixture or identify the missing input producer—not remove the condition until the test passes. If the executor still caps refunds at USD 50, it should update that reviewed contract, not bypass authorization.
Make the expected change visible in tests:
| Case | USD 50 policy | USD 75 policy |
|---|---|---|
| 5,000 cents, all other facts valid | Trigger matches | Trigger matches |
| 5,001, 7,499, or 7,500 cents, sufficient balance | Does not match | Matches |
| 7,501 cents | Does not match | Does not match |
| Zero/negative amount, wrong currency, unsettled payment, ownership or eligibility false, any required fact missing | Does not match | Does not match |
| Amount exceeds refundable balance | Does not match | Does not match |
| Repeated request or uncertain payment timeout | Executor prevents duplicates and reconciles the outcome | Same requirement |
What changes when the policy is declarative?#
Imperative code says which steps run; declarative code describes what should hold and lets a runtime interpret that description. In a refund handler, 5000 can be a branch condition interleaved with fetching payment records and dispatching a tool. In the EMU, it is an explicit eligibility condition: the amount must not exceed 5,000 cents. You can read, diff, and test that condition independently of the payment-service implementation.
The runtime evaluates the policy; the application still supplies trusted facts and controls execution. Changing the threshold does not require rewriting how payment records are fetched or how a refund is issued. The before/after table states which eligibility outcomes should change while the surrounding obligations remain stable. That is the declarative decision-making demonstrated by this example.
Why does this matter more with coding agents? Agents generate implementation steps cheaply; the scarce resource is a contract stable enough to review. Naming the inputs, conditions, and outcome gives an agent a bounded edit and gives the reviewer a way to detect changes outside that boundary. Effect makes a computation's success, error, and requirements explicit in its type; Memrail aims to make a decision's inputs, conditions, and prescribed outcome explicit in policy.
Declarative does not mean correct. A rule can omit a check or conflict with another, and a well-designed imperative function can expose a clear contract too. The gain is a named policy, a small intended diff, and explicit tests for what must not change.
Review the policy diff, the input producers, and the executor together: trigger tests do not establish trusted evidence or authorized execution. A matching condition can also lose selection; test competing policies using the arbitration guidance.
Keep authority at the execution boundary#
Before dispatch, the application must check the approved policy version, current eligibility and balance, actor permissions, allowed tool version, and arguments. Register the tool schema in the policy's project and connect its real handler. See Agent control patterns for dispatch examples.
Schema validation checks shape and types; it cannot prove ownership or freshness. Apply the shared execution contract at the payment boundary.
Missing evidence, no eligible selection, or a decision-service failure should create a review task instead of an automatic refund. Use durable request-level duplicate protection in the executor and payment service. The action requests no automatic retries; the executor must honor that setting, reconcile an uncertain payment outcome before retrying, and record the actual result. A policy trigger is not a transaction lock.
Start with one reviewable change#
Choose an action whose eligibility is hard to explain. Name its policy, document its input sources, and connect the proposed change to trigger, arbitration, and executor tests before enabling it. Use the decision topology audit to find that boundary in an existing application, then follow the production workflow for controlled rollout.
To discover the next candidate change from recorded behavior, use Hindsight's reviewed improvement loop.