---
title: Write and validate EMUs
description: Design production-ready Executable Memory Units with reachable triggers, connected actions, safe policy controls, JSONL source, and server validation.
eyebrow: POLICY AUTHORING
keywords: Memrail EMU, executable memory unit, agent policy as code
last_updated: 2026-09-10
---

# Write and validate EMUs

An Executable Memory Unit is a deterministic condition/action policy. A production-ready EMU is not merely valid JSON: its trigger can be reached, its action is connected, its project is coherent, and its rollout state matches its risk.

## EMU anatomy

```json
{
  "emu_key": "support.critical_enterprise",
  "decision_point": "support.triage",
  "intent": "Escalate unresolved critical enterprise cases",
  "trigger": "state.customer.tier == 'enterprise' AND state.ticket.priority == 'critical' AND state.ticket.id EXISTS",
  "action": {
    "type": "tool_call",
    "intent": "ESCALATE_TICKET",
    "tool": {
      "tool_id": "ticket_escalator",
      "version": "1.0.0",
      "args": {
        "ticket_id": "{{ticket.id}}",
        "queue": "enterprise-critical"
      }
    }
  },
  "policy": {
    "mode": "auto",
    "priority": 9,
    "cooldown": {
      "seconds": 3600,
      "gate": "ack"
    },
    "idempotency": {
      "enabled": true,
      "boundary": "workspace",
      "roles": "auto",
      "scope": [
        "ticket.id"
      ]
    },
    "exclusion_groups": [
      "ticket-routing"
    ]
  },
  "expected_utility": 0.95,
  "confidence": 0.9,
  "state": "shadow"
}
```

Every `tool_call` includes its own `intent` and `tool.version`; the EMU's top-level `intent` is optional. Interpolated values have matching `EXISTS` guards. Start new consequential policy in an isolated shadow evaluation environment.

Invoke this EMU at `decision_point="support.triage"`, using the name supplied in its definition.

See the [binding and lifecycle contract](/concepts/#bindings-and-lifecycle) for JSONL updates.

## Required design questions

Before writing JSON, answer:

1. What business outcome does this policy authorize or steer?
2. At which named decision point does it apply?
3. Which state, tag, and event dependencies does the trigger require?
4. Can every dependency be produced on every relevant path?
5. Which action type expresses the outcome?
6. Is that action connected in the same project?
7. What prevents duplicate, conflicting, or repeated side effects?
8. How will shadow traces prove the policy behaves as intended?

If those answers are unknown, an LLM can generate syntactically plausible JSON but not a production-ready EMU.

## Choose an action

### Context directive

```json
{
  "type": "context_directive",
  "directive": "Use the verified enterprise support protocol for this response."
}
```

Best first action for agent steering. The application must add the directive to a prompt or operator context.

### Decision prompt

```json
{
  "type": "decision_prompt",
  "message": "This refund requires review. Choose an outcome.",
  "options": [
    { "id": "approve", "label": "Approve" },
    { "id": "decline", "label": "Decline" }
  ]
}
```

Requires a real human workflow with expiry and safe timeout behavior.

### Route

```json
{
  "type": "route",
  "destination": "queue.enterprise_support",
  "metadata": { "reason": "critical_case" }
}
```

Requires a route registry and destination handler.

### Tool call

```json
{
  "type": "tool_call",
  "intent": "ESCALATE_TICKET",
  "tool": {
    "tool_id": "ticket_escalator",
    "version": "1.0.0",
    "args": { "ticket_id": "{{ticket.id}}" }
  }
}
```

Register a compatible tool schema in the EMU's project and connect a real runtime executor. This is an application integration contract: selection does not prove a tool exists, its version matches, or its handler can execute. Give side effects duplicate protection, failure handling, and outcome events.

### Tool retries

`action.tool.retry` is a supported AMI v2 field. For a payment action that must not retry automatically, use this tool specification inside `action.tool`:

```json
{
  "tool_id": "refund_payment",
  "version": "1.0.0",
  "args": { "request_id": "{{refund.request_id}}" },
  "retry": { "policy": "none", "max_retries": 0 }
}
```

`retry.policy` accepts `none`, `fixed`, or `exponential` (the API default). `retry.max_retries` is a non-negative integer, defaulting to `2`, describing retries after the initial attempt. Set both `none` and `0` to request no automatic retries.

These are execution instructions for the selected tool, not retries of the Memrail API request. Ensure your executor or adapter honors them; SDK transport retry settings are separate. A retry setting does not provide duplicate protection: use [policy idempotency](#idempotency), durable executor/payment-service protection, and reconciliation after uncertain outcomes. See the [refund walkthrough](/declarative-agent-control/) for the complete policy.

## Configure policy

### Mode

- `auto`: eligible actions can be dispatched by the SDK executor, subject to lifecycle and application authorization.
- `require_human`: selection and execution require application-verified `options.human_consent: true`. This applies to all action types. It does not create an approval queue.
- `advisory`: tool calls and routes are not dispatched by the SDK executor; advisory decision prompts and context directives may run.

Obtain authenticated approval before setting consent. Use an advisory `decision_prompt` to request review through your application. Combined SDK helpers propagate literal `True`/`true` consent to both selection and execution. Keep policy and lifecycle metadata intact in custom dispatchers; missing or unknown policy must fail closed.

### Priority

Use an integer from 0 through 9. The default ranking starts with `0.7 × expected_utility + 0.3 × confidence`, then breaks ties by priority, utility, confidence, and creation time. Operator configuration can change the weights and tie-break order.

A priority-9 rule can lose to a priority-1 rule with a higher score. Do not rely on priority alone to enforce a safety denial. Test conflicting rules and enforce non-negotiable constraints in the application.

### Cooldown

```json
{ "seconds": 86400, "gate": "ack" }
```

Cooldown duration is seconds. `ack` creates the cooldown after a successful `acknowledged` request; `failed` does not. `activation` creates it during normal selection unless the effective operator configuration defers cooldown creation. Dry-run does not create these cooldowns.

Cooldowns are scoped to the workspace, project, and EMU key—not to a customer or ticket. The one-hour example above can suppress this policy for other tickets too. Choose that broad throttle deliberately and use entity-level duplicate protection separately.

ACK uses an activation receipt and works independently of tracing. Reconcile delivery failures with bounded retries; do not rerun a completed external action. Canary controls begin after successful execution ACK, so an out-of-cohort selection does not consume the cooldown.

### Idempotency

```json
{
  "enabled": true,
  "boundary": "workspace",
  "roles": "auto",
  "scope": ["refund.request_id"]
}
```

`scope` contains literal state atom keys, not `{{...}}` templates. Supply `state("refund.request_id", request_id)` and guard it with `state.refund.request_id EXISTS`. Missing or null required scope, extra, or explicit role inputs suppress selection; zero and false remain valid values. Optional roles under `roles: "auto"` remain optional.

The key also includes the configured boundary, versioned EMU identity, bound roles, and optionally the client idempotency key. Action locks have a TTL (default 7,200 seconds); they are not permanent exactly-once guarantees. Keep idempotency in the executor and external system as well.

### Exclusion groups

Put mutually incompatible outcomes in one group:

```json
{ "exclusion_groups": ["support-routing"] }
```

If both “route to enterprise” and “route to fraud review” match, this group lets arbitration choose its highest-ranked member. Test the complete overlapping group set. Exclusion groups are not a distributed lock or a substitute for executor authorization.

## Lifecycle states

| State | Purpose |
|---|---|
| `draft` | stored for authoring; not part of normal evaluation |
| `shadow` | evaluated in a separate diagnostic lane; absent from `selected` |
| `canary` | eligible for selection; SDK execution requires affirmative cohort membership |
| `active` | production evaluation and action handling |
| `inactive` | disabled without archival |
| `archived` | retained as history but no longer evaluated |

Use lowercase lifecycle values. Shadow arbitration is separate from live exclusion groups and top-k, so shadow candidates cannot displace active policy. In a matching shadow trace, inspect `reasons`, `score`, and `suppressed_by`; `passed` is false because the candidate was suppressed.

Configure an application-owned canary cohort decision in the SDK executor; there is no policy percentage field. `--target-state` applies to newly created EMUs, and explicit JSONL state must agree with that target. For existing policies, conflicting state edits are rejected before writes and omission preserves lifecycle. Use explicit lifecycle commands for transitions, then reconcile your JSONL snapshot.

`dry_run` is different from shadow: it still returns eligible active/canary selections while skipping new cooldown and action-lock writes. Combined SDK helpers also skip handlers and ACK; custom dispatchers must honor the same evaluation-only boundary. Billing and observability work may still occur.

## Source format

Production EMUs live in `emus/emus.jsonl`, one complete object per line:

```jsonl
{"emu_key":"support.enterprise_context","decision_point":"support.response","trigger":"state.customer.tier == 'enterprise'","action":{"type":"context_directive","directive":"Use the enterprise support protocol."},"policy":{"mode":"auto","priority":7},"expected_utility":0.75,"confidence":0.95,"state":"shadow"}
```

JSONL makes each policy independently diffable. The CLI manages `.emu.lock.jsonl`; commit both files and inspect the deployed definition after applying changes. Invoke this policy at `decision_point="support.response"`.

## Pull, plan, apply

```bash
memrail emu-pull ./emus/ -w production -p support-agent

# Edit emus/emus.jsonl.

memrail emu-plan ./emus/ -w production -p support-agent --strict
memrail emu-apply ./emus/ -w production -p support-agent --yes --strict --target-state shadow
```

Removing a tracked line from the source represents archival on the next apply. Always inspect the plan for unexpected creates, changes, or archives before applying. Run new-policy evaluation in a separate scope first; `--target-state shadow` is not a production isolation mechanism and does not change the state of existing EMUs.

SDK registration methods are acceptable for isolated tests and prototypes. They are not the recommended production write path because they bypass the Git-reviewed JSONL and lock-file workflow.

## Validate reachability and connectivity

```bash
memrail emu-validate -w production -p support-agent
memrail action-connectivity -w production -p support-agent
```

Common validation warnings include:

| Warning | Meaning | Corrective action |
|---|---|---|
| `WARN-NEVER-SEEN` | dependency absent from observed context | add or repair the atom producer |
| `WARN-SCHEMA-MISMATCH` | value type and operator conflict | align atom type and comparison |
| `WARN-LOW-REACH` | atom has become stale | inspect the ingestion path |
| `WARN-SEMANTIC-EQUIVALENT` | a similar event name exists | use the canonical emitted name |
| `WARN-ACTION-TOOL-NOT-FOUND` | tool is absent from the discovered schema, or no schema is available | register and inspect the project schema and runtime handler |
| `WARN-ACTION-PLACEHOLDER-NEVER-SEEN` | action interpolates unavailable context | emit the atom and add `EXISTS` |
| `WARN-POLICY-GAP` | policy-control heuristic flagged a possible gap | inspect current structured cooldown/idempotency settings |

The atom schema registry (ASR) records observed context. That evidence may be collected across workspaces; it does not prove that each caller supplies an atom at this decision point. Tool-schema lookup can fall back from project to workspace to organization; it does not prove runtime connectivity or tool-version compatibility. Validate those contracts in application tests.

Use structured `policy.idempotency` for policy-gap remediation, not the legacy `idempotency_key_template` field. Review suggested fixes before applying them.

## Machine-readable contracts

Download the [EMU JSON Schema](/schemas/emu.schema.json) to validate each object in `emus/emus.jsonl` with a JSON Schema 2020-12 validator. It describes the registration structure, including actions, policy controls, lifecycle, and `decision_point`. A schema-valid object still needs trigger parsing, reachability checks, and tool-registry validation through `emu-plan --strict`.

Use the [diagnostic-code catalog](/reference/diagnostic-codes.json) to interpret those findings. For a complete policy and executor that work together, use the [runnable quickstart](/getting-started/).

## Production-ready checklist

- Trigger syntax uses modern dot notation, single-quoted literals, and uppercase operators.
- Every dependency has a producer and compatible type at the named decision point.
- Model-derived tags use a fixed taxonomy.
- Negative event clauses have a real event producer.
- Multi-entity events are scoped with a template-literal `WHERE` filter.
- Action interpolation values are guarded with `EXISTS`.
- `tool_call` includes a registered tool ID and required version in the same project.
- Side effects have deliberately scoped cooldowns, literal-key idempotency, and executor-level duplicate protection.
- Conflicting actions share an exclusion group.
- Evaluation-only responses cannot reach the side-effect dispatcher; advisory handling and approval are explicit.
- Shadow traces cover positive, negative, missing-data, and failure cases without displacing live policy.
- Any canary cohort gate is implemented and tested outside the lifecycle label.
- Remote lifecycle and decision-point binding are verified after CLI operations.
- The JSONL and lock file are reviewed and committed.
