# Memrail documentation > Complete public documentation for Memrail agent steering and control. Canonical index: https://docs.memrail.com/ AMI means Adaptive Memory Intelligence. These guides use the v2 trigger DSL; HTTP routes retain the /v1 prefix. Read the [integration invariants](https://docs.memrail.com/concepts/#integration-invariants) first. Use the section URLs below for smaller context windows. Machine-readable contracts: [EMU JSON Schema](https://docs.memrail.com/schemas/emu.schema.json), [diagnostic codes](https://docs.memrail.com/reference/diagnostic-codes.json). Runnable files: [Python quickstart](https://docs.memrail.com/examples/quickstart.py), [EMU JSONL](https://docs.memrail.com/examples/emus.jsonl). The complete example is included below. --- Source: https://docs.memrail.com/ # Deterministic steering and control for AI agents Memrail provides deterministic steering and control for AI agents and automated systems. It evaluates structured context against explicit policies and returns prescribed actions; your application authorizes and executes them. Models can interpret, classify, and propose without deciding what the application permits. > Model output is input to policy, not permission to act. ## Set up your coding agent Give Codex, Claude Code, or OpenCode this installation request: ```text Read https://docs.memrail.com/agents/install-skill.md and install the complete Memrail skill for the coding agent I am using. Install only the skill, preserve any existing copy, and verify that SKILL.md and its references are available. Do not install the SDK or change this project. ``` Prefer your terminal? Install the skill with one command: ```bash curl -fsSL https://docs.memrail.com/install.sh | sh ``` The installer selects a single detected agent, or stops and asks you to choose explicitly. It installs only the skill and preserves existing copies. See [Install the Memrail skill](/agents/) for agent-specific commands, review-before-run instructions, and the optional `--with-sdk` command to add the Python library inside your active project virtualenv. ## Choose your path ### Start a new application Define the decisions that matter, name the context available at each decision point, and begin with a state-only policy in shadow mode. Follow [Start from scratch](/getting-started/) for a minimal Python integration and its TypeScript equivalent. ### Add Memrail to an existing application Do not begin by rewriting conditionals. First map where authority already lives: prompts, controller branches, model thresholds, feature flags, queues, and human approvals. Follow [Add to an existing project](/existing-project/) and then run the [decision topology audit](/topology-audit/). ### Give the work to an agent Install the Memrail skill, then give the agent a bounded request such as: ```text Use the memrail skill. Audit this repository's decision topology without changing code. Identify consequential decision points, atom sources, event gaps, and action executors. Return a prioritized topology report and do not implement until I approve it. ``` See [Install the Memrail skill](/agents/) for agent-specific paths and a verification checklist. ## The control contract At runtime, an integration has five parts: 1. Your application reaches a named **decision point** before a consequential action. 2. It sends typed state and tag **ATOMs**; separately ingested events provide temporal history. 3. Memrail evaluates versioned **EMUs**—deterministic condition/action policies—against that context. 4. Your application enforces execution authorization and handles the selected action through a connected executor, route, prompt, or context directive. 5. It records the material outcome so future temporal policy can reason over what actually happened. ```text observation → typed ATOMs → named decision point → selected policy → action → event └──────── decision trace ─────────┘ ``` Evaluation is deterministic for the complete context, policy set, event history, evaluation time, cooldown/idempotency state, and options. Repeated requests can differ as those inputs change. Model output may enter as a runtime-validated tag such as `tag.intent == 'refund_request'`; the model does not choose the policy outcome. ## What to read next | Goal | Read | |---|---| | Understand why explicit contracts matter for AI | [Why declarative agent control matters](/declarative-agent-control/) | | Understand the model | [Core concepts](/concepts/) | | Control tool use in an agent loop | [Agent control patterns](/agent-control/) | | Integrate Python | [Python SDK](/python/) | | Integrate TypeScript | [TypeScript SDK](/typescript/) | | Write valid conditions | [Trigger DSL](/triggers/) | | Manage rules as code | [Write EMUs](/emus/) | | Find hidden decision authority | [Audit decision topology](/topology-audit/) | | Improve policy from observed behavior | [Recursive self-improvement with Hindsight](/hindsight/) | | Isolate evaluation and control rollout | [Production workflow](/production/) | ## Built for machine consumption Every page has a canonical Markdown representation next to its HTML page. An agent can request the clean documentation URL with `Accept: text/markdown`, fetch the explicit `index.md`, or use one of the corpus indexes: ```bash # This page as Markdown curl -H 'Accept: text/markdown' https://docs.memrail.com/ # Documentation map for an LLM curl https://docs.memrail.com/llms.txt # Complete documentation corpus curl https://docs.memrail.com/llms-full.txt ``` The Markdown pages contain full examples and context rather than summaries of the HTML. Use the page-level files for focused context windows and `llms-full.txt` for retrieval, indexing, or complete-site copy. ## Integration essentials Start with the [integration invariants](/concepts/#integration-invariants), then run the [complete quickstart](/getting-started/). Validate definitions with the [EMU JSON Schema](/schemas/emu.schema.json) and interpret findings with the [diagnostic-code catalog](/reference/diagnostic-codes.json). AMI means Adaptive Memory Intelligence, the decision engine behind Memrail. These guides use the v2 trigger DSL; HTTP routes retain the `/v1` prefix. The [core concepts](/concepts/) define the remaining vocabulary. --- Source: https://docs.memrail.com/agents/ # Install the Memrail skill Give your coding agent the knowledge to work with Memrail: policy design, SDK integration, decision topology, and production workflows. Choose the agent you already use. You do not need a Memrail account or API key to install the skill. ## Ask your agent to install it Copy this request and paste it into Codex, Claude Code, or OpenCode: ```text Read https://docs.memrail.com/agents/install-skill.md and install the complete Memrail skill for the coding agent I am using. Install only the skill, preserve any existing copy, and verify that SKILL.md and its references are available. Do not install the SDK or change this project. ``` The [agent installation link](/agents/install-skill.md) is plain Markdown with the steps and verification checks. Your agent needs permission to fetch the source and write its skill directory; approve those actions when prompted. If it cannot access the internet or install files, use the terminal method below. ## Install from your terminal On macOS, Linux, or inside WSL, run: ```bash curl -fsSL https://docs.memrail.com/install.sh | sh ``` This installs **only the skill**. It checks for the `codex`, `claude`, and `opencode` commands or their usual configuration directories. When exactly one is detected, it selects that agent. When none or several are detected, it stops without downloading the skill or changing files and shows how to choose. Detection is a convenience, not proof of which client you prefer. ### Choose your agent explicitly **Codex** ```bash curl -fsSL https://docs.memrail.com/install.sh | sh -s -- --agent codex ``` **Claude Code** ```bash curl -fsSL https://docs.memrail.com/install.sh | sh -s -- --agent claude ``` **OpenCode** ```bash curl -fsSL https://docs.memrail.com/install.sh | sh -s -- --agent opencode ``` The client does not have to be running. The script copies the complete portable skill from the [official Memrail repository](https://github.com/cadenzai/memrail-claude-plugin), including its references. It does not install the full Claude plugin, configure credentials, edit project files, or register policies. ### Review before running A shell installer executes code with your user permissions. To inspect it and preview the destination first, download it to a fresh temporary directory: ```bash memrail_setup="$(mktemp -d)" curl -fsSL https://docs.memrail.com/install.sh -o "$memrail_setup/install.sh" less "$memrail_setup/install.sh" sh "$memrail_setup/install.sh" --agent codex --dry-run sh "$memrail_setup/install.sh" --agent codex ``` Replace `codex` with your choice. `--dry-run` makes no writes or downloads. The installer pins the skill to a specific repository commit and verifies its archive's SHA-256 before extracting it. This protects against a mismatched download; review the installer itself before trusting it. ## Where does the skill go? Installation is user-level, available across projects: | Agent | Default skill directory | Client documentation | |---|---|---| | Codex | `~/.agents/skills/memrail/` | [Codex skills](https://learn.chatgpt.com/docs/build-skills) | | Claude Code | `~/.claude/skills/memrail/` | [Claude Code skills](https://code.claude.com/docs/en/skills) | | OpenCode | `~/.config/opencode/skills/memrail/` | [OpenCode skills](https://opencode.ai/docs/skills/) | These are native skill locations; no plugin conversion is needed. OpenCode also discovers shared `.agents/skills` and `.claude/skills` locations, so you may already have access after installing for another client. Check before making duplicate copies. [OpenCode discovery](https://opencode.ai/docs/skills/) For Claude Code, the installer honors [`CLAUDE_CONFIG_DIR`](https://code.claude.com/docs/en/claude-directory). For another configured location, a repository-scoped install, or an older client using a different path, pass an absolute **skills parent** directory: ```bash curl -fsSL https://docs.memrail.com/install.sh | sh -s -- --dir "$PWD/.agents/skills" ``` This writes `.agents/skills/memrail` in the current repository; use it only when you want project files added. For native Windows shells, use the agent-assisted path or manually copy the complete `skills/memrail` folder from the repository to your client's documented skill location. Advanced overrides: `MEMRAIL_CODEX_SKILLS_DIR`, `MEMRAIL_CLAUDE_SKILLS_DIR`, and `MEMRAIL_OPENCODE_SKILLS_DIR` set per-client parents; `AGENT_SKILLS_DIR` is a fallback for Codex. `--dir` takes precedence. `MEMRAIL_INSTALL_HOME` changes the installer's default user-directory root, useful for isolated testing; it does not reconfigure your agent. ## Install the Python SDK too The skill teaches your coding agent; the SDK is a dependency of your application. To install both in one command, activate your project's Python 3.9+ virtualenv first: ```bash curl -fsSL https://docs.memrail.com/install.sh | sh -s -- --agent codex --with-sdk ``` Substitute `claude` or `opencode`, or omit `--agent` for single-agent detection. The SDK option requires an active virtualenv and installs `memrail` with that environment's pip, or `uv pip` targeting the same Python. It does not create an environment, modify a lockfile, or use system Python. Add the dependency through your existing package manager when your project manages a manifest or lockfile. See [Start from scratch](/getting-started/) or [Python SDK](/python/). For TypeScript, install the skill alone and follow the [TypeScript SDK guide](/typescript/). ## Verify and start using it Ask your agent: ```text Use the memrail skill. Confirm its installed location and list the reference files you can read. Do not change anything else. ``` A complete installation has `SKILL.md` and `references/`; copying just `SKILL.md` is not sufficient. Reload or restart your agent if the new skill is not discovered. Check the client's skill permissions if access is denied. Then give it a bounded first task: ```text Use the memrail skill to audit this repository's decision topology. Map consequential decisions, hidden model authority, context sources, event gaps, and action executors. Return a prioritized report. Do not edit files, install dependencies, register EMUs, or change external state. ``` See [Audit decision topology](/topology-audit/) for the full workflow. ## Update, replace, or remove An existing `memrail` directory or symlink stops the installer before download. Review local modifications before repeating your command with `--replace`. The old copy is moved under `~/.local/state/memrail/skill-backups/`, outside the usual skill-discovery directories; the installer prints its exact path. A failed SDK installation can leave a successfully installed skill in place, and is reported separately. Older installations may have copies in more than one client directory. Check those separately: this installer does not delete or relocate other copies. To remove the skill, remove only the installed `memrail` directory after reviewing any local edits. If you opted into the SDK, uninstall it through the same project environment or dependency manager. Neither operation removes Memrail policy, events, credentials, or application integration. ## Need the full Claude plugin? The [Claude plugin repository](https://github.com/cadenzai/memrail-claude-plugin) also includes specialized topology, architecture, implementation, and validation agents. The portable skill is enough for Memrail guidance across clients; use the full plugin only when you want its Claude-specific orchestration: ```bash git clone https://github.com/cadenzai/memrail-claude-plugin.git claude --plugin-dir ./memrail-claude-plugin ``` ## Guidance for agent implementers When the skill is active, use the [integration invariants](/concepts/#integration-invariants) as the shared contract, with these implementation conventions: - detect Python or TypeScript SDK usage before proposing code; - use `decide(context=...)` or `decide({ context: [...] })`; - write modern dot-notation triggers with uppercase operators; - verify trigger reachability before designing an EMU; - enforce same-project tool availability in the application's deployment and execution contract; registration alone is not a runtime authorization guarantee; - use JSONL pull/plan/apply for production writes; - start consequential policies in shadow, with diagnostics separate from live arbitration; - validate model tags against fixed taxonomies at runtime and preserve their provenance; - never treat a model proposal as a safe fallback when control evaluation fails; - use named `decision_point` bindings and preserve the reviewed execution boundary. Follow the [production workflow](/production/) for validation, lifecycle changes, and application-controlled rollout. --- Source: https://docs.memrail.com/declarative-agent-control/ # Why declarative agent control matters: one reviewable refund change 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](/emus/#pull-plan-apply), not SDK writes. Serialize the dictionary to JSON, including Python `True` as JSON `true`. ```python 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](/emus/#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](/emus/#tool-retries). Start in `shadow` under the [binding and lifecycle contract](/concepts/#bindings-and-lifecycle). 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: ```text 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](https://effect.website/docs/v3/getting-started/the-effect-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](/emus/#priority). ## 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](/agent-control/) for dispatch examples. Schema validation checks shape and types; it cannot prove ownership or freshness. Apply the shared [execution contract](/concepts/#evaluation-and-execution) 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](/topology-audit/) to find that boundary in an existing application, then follow the [production workflow](/production/) for controlled rollout. To discover the next candidate change from recorded behavior, use [Hindsight's reviewed improvement loop](/hindsight/). --- Source: https://docs.memrail.com/getting-started/ # Start from scratch Run one decision from policy definition through execution. This example binds an EMU (executable memory unit) to `support.greeting`, matches an enterprise customer, and executes a local greeting formatter. It sends no email, moves no money, and needs no model or agent framework. ## 1. Install and configure You need Python 3.9 or newer, a Memrail API key, and an organization with an isolated `development` workspace and `docs-demo` project. Create those scopes in your Memrail console before continuing. API calls use your account's normal usage allowance. ```bash mkdir memrail-quickstart cd memrail-quickstart python3 -m venv .venv . .venv/bin/activate python -m pip install --upgrade memrail export AMI_API_KEY='your-api-key' export AMI_ORG='your-org' export AMI_WORKSPACE='development' export AMI_PROJECT='docs-demo' mkdir emus curl -fsSLo quickstart.py https://docs.memrail.com/examples/quickstart.py curl -fsSLo emus/emus.jsonl https://docs.memrail.com/examples/emus.jsonl ``` Inspect the downloaded files before running them. You can also [give your coding agent the Memrail skill](/agents/) to help with setup. Never commit credentials. ## 2. Inspect the policy and executor The [complete policy file](/examples/emus.jsonl) is one JSON object on one line. It is expanded here for readability: ```json { "emu_key": "support.enterprise_greeting", "decision_point": "support.greeting", "intent": "Greet enterprise customers with the reviewed local formatter", "trigger": "state.customer.name EXISTS AND state.customer.tier == 'enterprise'", "action": { "type": "tool_call", "intent": "Format a greeting locally", "tool": { "tool_id": "format_greeting", "version": "1.0.0", "side_effect": false, "args": {"name": "{{customer.name}}"} } }, "policy": {"mode": "auto", "priority": 7}, "expected_utility": 0.8, "confidence": 0.95, "state": "shadow" } ``` The caller uses the same `decision_point` and supplies both state atoms. `{{customer.name}}` resolves from that context, not from model-written code. The local formatter has no external side effects, so this example intentionally omits action idempotency and cooldown; a validator may report `WARN-POLICY-GAP`. Review that finding as intentional only for this read-only demonstration. For consequential tools, follow the [integration invariants](/concepts/#integration-invariants). Here is the entire [runnable Python file](/examples/quickstart.py): ```python """Run the Memrail quickstart in an isolated development project.""" import argparse import asyncio import json from memrail import AsyncAMIClient from memrail.atoms import state from memrail.models import InvokeOptions, TraceOptions from memrail.tools import ToolRegistry TOOL_SCHEMA = { "type": "object", "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 80}}, "required": ["name"], "additionalProperties": False, } registry = ToolRegistry() @registry.tool( name="format_greeting", description="Format a greeting locally; no external side effects", schema=TOOL_SCHEMA, projects=["docs-demo"], ) async def format_greeting(args: dict) -> dict: name = args.get("name") if set(args) != {"name"} or not isinstance(name, str) or not 1 <= len(name) <= 80: raise ValueError("Expected one name between 1 and 80 characters") return {"greeting": f"Hello, {name}!"} async def run(client, *, tier="enterprise", execute=False): client.register_tool( name="format_greeting", description="Format a greeting locally; no external side effects", schema=TOOL_SCHEMA, projects=["docs-demo"], handler=format_greeting, ) return await client.decide_and_execute( decision_point="support.greeting", context=[state("customer.name", "Ada"), state("customer.tier", tier)], options=InvokeOptions(dry_run=not execute), trace=TraceOptions(enable=True), auto_ack=True, ) async def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--tier", choices=["enterprise", "free"], default="enterprise") parser.add_argument("--execute", action="store_true", help="Execute the local greeting tool") args = parser.parse_args() async with AsyncAMIClient(workspace="development", project="docs-demo") as client: result = await run(client, tier=args.tier, execute=args.execute) print(json.dumps({ "selected": [item.emu_key for item in result.invoke_result.selected], "trace": result.invoke_result.trace, "executions": [ {"executed": item.was_executed, "success": item.success, "output": item.output, "error": item.error} for item in result.execution_results ], "acks": [ {"activation_id": item.activation_id, "success": item.success, "error": item.error} for item in result.ack_results ], }, indent=2, default=str)) if any(not item.success for item in [*result.execution_results, *result.ack_results]): raise SystemExit(1) if __name__ == "__main__": asyncio.run(main()) ``` Module-level registration makes the tool discoverable by the CLI. Runtime registration connects the same schema and handler to the client executor. Importing this module does not create a client or make API calls. ## 3. Register, validate, and apply in shadow ```bash memrail tool-register --file ./quickstart.py \ -w development --project docs-demo # Observe the example atoms before checking reachability. python quickstart.py memrail emu-plan ./emus/ -w development -p docs-demo --strict memrail emu-apply ./emus/ -w development -p docs-demo \ --yes --strict --target-state shadow memrail emu-validate -w development -p docs-demo ``` The first run has no policy to select yet. Strict preflight checks complete candidate definitions before writes. Inspect all findings; the [diagnostic catalog](/reference/diagnostic-codes.json) explains their codes. Commit `emus/emus.jsonl` and the generated `emus/.emu.lock.jsonl` together. ## 4. Check both branches ```bash python quickstart.py python quickstart.py --tier free ``` For enterprise context, the shadow candidate's trace contains `shadow_state` suppression; `selected` and `executions` stay empty. For free context, the trigger does not match. The script defaults to dry run, following the shared [evaluation contract](/concepts/#evaluation-and-execution). ## 5. Promote and execute After verifying both branches, explicitly promote this read-only policy in the development project and reconcile the local snapshot: ```bash memrail change-state support.enterprise_greeting active \ -w development -p docs-demo memrail emu-pull ./emus/ -w development -p docs-demo python quickstart.py python quickstart.py --execute python quickstart.py --tier free --execute ``` | Run after promotion | Selection | Execution | ACK | |---|---|---|---| | Default enterprise dry run | `support.enterprise_greeting` | none | none | | Enterprise with `--execute` | `support.enterprise_greeting` | `{"greeting": "Hello, Ada!"}` | one successful activation | | Free with `--execute` | none | none | none | The combined `decide_and_execute` helper runs the registered handler and, with `auto_ack=True`, acknowledges successful execution. Its returned `execution_results` are separate from `invoke_result.selected`. Inspect failures before retrying; use the [outcome contract](/concepts/#outcomes-and-acknowledgment) when adding real side effects. ## 6. Make the next change reviewable Change the eligibility condition, add a test that should now match, and retain a test that should not. Keep the named binding and handler contract stable. Use the [EMU JSON Schema](/schemas/emu.schema.json) for structural checks and [production workflow](/production/) for reviewed deployments. For a TypeScript integration, continue with the [TypeScript SDK](/typescript/). For an existing application, start with [one controlled execution boundary](/existing-project/). --- Source: https://docs.memrail.com/existing-project/ # Add Memrail to an existing project Add Memrail to an existing application one consequential decision at a time. Begin with a read-only topology audit, compare policies in an isolated evaluation project, and enable the new execution path only after its authorization and fallback behavior are tested. ## Start with discovery, not extraction Run a read-only [decision topology audit](/topology-audit/) before changing runtime behavior. The report should identify: - every consequential point where context changes what the system does; - every model output that is treated as permission rather than evidence; - state, tags, and events available at each point; - current tool executors, routes, human approvals, and failure paths; - project boundaries and cross-project dependencies; - branches where no explicit default or denial behavior exists. This separates **where a decision happens** from **how it is currently expressed**. Several scattered `if` statements may belong to one named decision point; one agent loop may need several control points because it proposes tools, data access, and user-facing responses at different stages. ## 1. Detect the integration surface Check the project language and whether Memrail is already present: ```bash rg -n 'memrail|@memrail/sdk|AsyncAMIClient|AMIClient|\.decide\(' \ package.json pyproject.toml requirements*.txt src app lib 2>/dev/null ``` Use the [Python SDK](/python/) if the Python package is present and the [TypeScript SDK](/typescript/) for `@memrail/sdk`. A mixed repository can use both, but project ownership and event naming must remain coherent. ## 2. Inventory hidden authority Search patterns are clues, not proof. Review each result in context: ```bash rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'if .*tier|if .*priority|score.*[><=]|threshold|feature.?flag|variant|ab.?test' rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'tool_choice|function_call|execute_tool|send_email|refund|approve|deny|escalat|route' rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'system_prompt|systemPrompt|instructions|guardrail|moderation|classifier|predict\(' ``` Classify findings into three categories: | Category | Example | Migration treatment | |---|---|---| | Evidence | an LLM classifies intent | expose as a constrained `tag` | | Authority | a score above 0.8 issues a refund | move the threshold and action selection into an EMU | | Execution | a refund client calls the payment API | keep as a registered, versioned tool executor | The goal is not to remove inference. It is to prevent inference from silently acquiring authority over state changes. ## 3. Select one control seam Choose a decision that is consequential enough to matter and bounded enough to test. Good first seams include: - whether an agent may call a side-effecting tool; - whether a case is routed to a human; - which response protocol applies to a regulated or high-value user; - whether a workflow may advance to its next irreversible state. Avoid beginning with a global middleware gate unless all downstream paths share one atom contract and one failure policy. A named hook close to the action is usually easier to reason about. ```yaml decision_point: billing.refund current_authority: file: src/agents/billing.py behavior: model confidence above 0.82 calls refund tool proposed_boundary: model_role: classify request and extract bounded facts memrail_role: choose allow, require_human, or deny route executor_role: perform refund only for an authorized tool_call ``` ## 4. Compare in an isolated evaluation project Add an evaluation-only call on your `AsyncAMIClient` without changing the existing branch. Define a shadow EMU that expresses the current rule and compare its trace with actual application behavior. Use a separate project to keep migration fixtures and observations distinct. ```python decision = await client.decide( decision_point="billing.refund", context=[ state("customer.id", customer.id), state("refund.amount", amount), state("refund.currency", currency), tag("refund_reason", classified_reason, source="ml"), ], project="billing-evaluation", options=InvokeOptions(dry_run=True), trace=TraceOptions(enable=True), ) # Existing behavior continues during the comparison period. result = await legacy_refund_branch(...) ``` Bind candidate policies to `billing.refund`. Validate classifier enums at runtime, preserve source labels, and keep authoritative facts application-owned. Keep the existing branch as the sole executor during comparison. Inspect trace `reasons`, `score`, and `suppressed_by` under the [evaluation contract](/concepts/#evaluation-and-execution). ## 5. Prove trigger reachability For every EMU proposed at the seam, create an atom contract table: | Trigger dependency | Source | Always present? | Type/domain | Failure behavior | |---|---|---:|---|---| | `state.refund.amount` | request validator | yes | number ≥ 0 | reject malformed request | | `state.customer.id` | authenticated session | yes | string | deny if absent | | `tag.refund_reason` | bounded classifier | no | fixed enum | require human if absent | | `event.customer.received.refund` | payment success event | after rollout | configured retention (default 90 days) | duplicate guard unavailable before complete instrumentation | Positive predicates on absent state/tag atoms evaluate false; outer `NOT` can invert this, and `OR` can match another branch. Guard required IDs with `EXISTS`, including IDs interpolated into event filters. A negated event that is never emitted becomes true. Audit both positive and negative dependencies, retention, and producer coverage. Observed schema validation alone does not prove every caller supplies the required atoms. ## 6. Connect actions explicitly Build an action connectivity matrix before activation: ```yaml action_connectivity: context_directive: status: connected handler: build_billing_system_prompt decision_prompt: status: connected handler: refund_review_queue tool_call.refund_payment: status: connected executor: payments.refund version: 1.0.0 idempotency: refund_request.id route.billing_manual_review: status: missing ``` An EMU that references a missing executor is not complete. Verify the project, tool version, schema, and real runtime handler. The SDK executor enforces policy/lifecycle checks; custom dispatchers must preserve that metadata and apply equivalent checks. Neither dry-run nor advisory tool selections may become live execution merely because they were returned. Handler permissions and business duplicate protection remain application responsibilities. ## 7. Cut over one branch at a time Promote only after comparison data shows equivalence or an intentional difference. A safe sequence is: 1. instrument context and events; 2. evaluate the equivalent policy in an isolated shadow project; 3. make the selected action observable but non-executing; 4. route an explicit application-owned cohort to a reversible or human-reviewed branch (`canary` state alone does not sample traffic); 5. route all traffic through Memrail at that decision point; 6. remove the legacy authority after the rollback window; 7. keep the executor and domain validation in application code. The new boundary should fail closed for dangerous actions and preserve a documented default for ordinary flow. Network failure must not silently hand authority back to the model. ## Definition of done - Every consequential path reaches the named decision point or is explicitly out of scope. - The atom contract covers every trigger dependency and interpolation value. - All model-derived tags use fixed taxonomies with explicit unknown/failure handling. - Evaluation-only mode and application authorization are enforced before every dispatch, independently of SDK selection. - Every selected action type has an application handler. - Every tool is registered, versioned, and in the same project as its EMUs. - Material outcomes emit events with entity identifiers needed by `WHERE` clauses. - Shadow traces have been compared against real behavior. - Rollback restores a safe known behavior, not implicit model authority. - Production EMUs and `.emu.lock.jsonl` are committed and reviewed. --- Source: https://docs.memrail.com/concepts/ # Core concepts Memrail's AMI (Adaptive Memory Intelligence) engine evaluates typed context (ATOMs) against versioned condition/action policies (EMUs). A decision point is where your application requests that evaluation; the application then authorizes and handles the selected action. These guides use the v2 trigger DSL; HTTP routes retain the `/v1` prefix. Neither identifies an SDK package version. For the architectural motivation, read [Why declarative agent control matters](/declarative-agent-control/). ## From context to connected action Your application supplies structured context—state scalars, bounded tags, and timestamped events—at a named decision point. Memrail evaluates that context against explicit EMU policy and returns a prescribed action for the application to handle. Tool calls, routes, and agent context map directly to Memrail action types. Skills are connected by your application through a reviewed tool, route, or context directive; `skill` is not a separate AMI action type. ## Steering, control, and authority **Steering** shapes what an agent considers or proposes. A `context_directive` can add policy-relevant guidance before the next model call. **Control** constrains what the surrounding system permits. A selected route, approval prompt, or tool call is handled outside the model by application code. **Authority** is the right to cause a consequential state change. A well-enforced Memrail integration gives explicit policy and application authorization checks that authority. Models can provide bounded evidence, but they must not get an unobserved fallback path to execute. The engine returns prescriptions; it does not enforce your application's tool dispatcher for you. This framing is practical: you can use Memrail only for prompt steering, only as a pre-tool control gate, or at several decision points across a workflow. ## Decision points A decision point is a named invocation site where behavior may change based on context. Examples: - `agent.before_tool_call` - `support.response_protocol` - `billing.refund` - `onboarding.next_step` - `security.account_access` Pass the name with `decision_point` in Python or `decisionPoint` in TypeScript. Stable names make traces, policy ownership, and topology audits legible even when code moves. Set `"decision_point": "support.response"` on the EMU and pass that same name to the SDK. Named invocations select only EMUs bound to that exact point. Choose the name in application code, never from model output or an unchecked request field. One decision point should have a documented atom contract and a bounded family of actions. Avoid a universal `agent.decide_everything` hook whose context and semantics change on every call. ## Integration invariants This is the shared execution and deployment contract for every guide and example. ### Bindings and lifecycle Keep the EMU's `decision_point` and the caller's name identical. In JSONL updates, omission preserves an existing binding; explicit `null` clears it. Review binding changes as routing changes. `--target-state` sets the initial state of new EMUs. Transition existing EMUs with `change-state`, then reconcile the JSONL and lock snapshot with `emu-pull`. Conflicting lifecycle edits in JSONL are rejected. Shadow candidates have separate arbitration, appear in traces, and never enter `selected`. Canary is a lifecycle state, not a traffic percentage: the application must supply affirmative cohort membership before SDK execution. ### Evaluation and execution Dry run can return action payloads, but they must not reach handlers or live prompts. The combined SDK execution helpers skip handlers and ACK automatically; custom dispatchers need the same guard. Dry run skips new cooldown and action-idempotency locks, not existing suppressions, schema observation, traces, analytics, or billing. Selection alone does not authorize a side effect. Preserve the full selection's policy and lifecycle metadata through dispatch. The SDK executor enforces supported modes and lifecycle states; your handlers still enforce business permissions, tool arguments, and durable duplicate protection. `require_human` needs authenticated consent for the specific operation, not a model assertion. [Policy modes](/emus/#mode) specifies selection and execution behavior by action type. ### Outcomes and acknowledgment Emit success events after material outcomes, not after selection; use distinct failure topics and include entity identifiers for later `WHERE` filters. Event ingestion is not automatically idempotent: reconcile ambiguous delivery before retrying. Events and activation ACK are separate records. ACK only after the intended outcome. Live activation receipts are independent of tracing and expire after seven days; dry runs create none. Retry ACK delivery without repeating a completed action, and retain a durable application execution record. ### Two layers of idempotency A request idempotency key identifies one immutable evaluation within the full tenant/project scope. During its 120-second cache lifetime, identical retries replay the selection with a new invocation ID; changed context, point, options, or project returns `409 IDEMPOTENCY_KEY_REUSED`. Keys must fit within 256 UTF-8 bytes. Action idempotency is a separate policy control. For side-effecting tools, use literal atom keys in `policy.idempotency.scope` and supply them on every call. Neither layer guarantees exactly-once business execution; retain executor/provider duplicate protection, including across policy version changes and rollbacks. ## ATOMs: typed decision context ATOMs are the facts evaluated by policy. | Type | Meaning | Example | Source | |---|---|---|---| | State | a current structured fact | `state.customer.tier` | database, request, service | | Tag | a bounded classification | `tag.intent` | classifier, metadata, deterministic mapping | | Event | a timestamped occurrence | `event.agent.sent.email` | material outcome or observation | ```python from memrail.atoms import state, tag context = [ state("customer.id", "cust_123"), state("customer.tier", "enterprise"), state("ticket.priority", "critical"), tag("intent", "escalation"), ] ``` State values are scalars. Flatten nested objects with `atoms_from_dict(data, prefix="customer")` or `atomsFromDict(data, "customer")`. Use an LLM to produce tags only when its output is constrained to a fixed taxonomy. Include an explicit `unknown` value when classification can fail. Never insert free-form model prose into a trigger. ## EMUs: executable memory units An EMU is a versioned conditional action. Its trigger evaluates deterministically; its action describes what the application should do; its policy controls arbitration and execution behavior. ```json { "emu_key": "support.critical_enterprise", "decision_point": "support.response_protocol", "trigger": "state.customer.tier == 'enterprise' AND state.ticket.priority == 'critical'", "action": { "type": "context_directive", "directive": "Follow the critical enterprise escalation protocol." }, "policy": { "mode": "auto", "priority": 9 }, "expected_utility": 0.9, "confidence": 0.95, "state": "shadow" } ``` Invoke this policy at `decision_point="support.response_protocol"`. Determinism applies to the complete evaluation state: context, policies, event history, evaluation time, cooldowns, locks, and options. Repeated requests can differ when those inputs change. EMUs are not selected by an LLM. ## Action types | Action | What it asks the application to do | Required connection | |---|---|---| | `context_directive` | add guidance to a prompt or UI | prompt/context builder | | `decision_prompt` | ask a human to choose from options | review UI, queue, or chat handler | | `route` | send work to a destination | route registry and destination handler | | `tool_call` | execute a versioned tool with arguments | registered tool schema and executor | Connect actions under the [integration invariants](#integration-invariants). Every `tool_call` tool object also requires a version such as `"version": "1.0.0"`. ## Policy and arbitration Policy metadata controls what happens when EMUs match: - `mode="require_human"` suppresses all action types without API `options.human_consent`; it does not automatically create a review task. Advisory actions can still be selected; - the default score is `0.7 × expected_utility + 0.3 × confidence`; `priority` (0–9) breaks score ties, so a priority-9 denial is not guaranteed to win; - `cooldown` suppresses the EMU key across its workspace/project, not separately for each customer; - action `idempotency` reduces duplicate activations; executor-level duplicate protection remains necessary; - `exclusion_groups` prevent conflicting policies from winning together; - `expected_utility` and `confidence` provide explicit arbitration signals. Treat policies with side effects more strictly than context directives. Give tool calls cooldown, idempotency, timeouts, error handling, and a material outcome event. ## Trigger reachability A trigger can match only when its dependencies are available. ```dsl state.customer.tier == 'enterprise' AND tag.intent == 'refund' ``` This positive conjunction requires both `state("customer.tier", ...)` and `tag("intent", ...)` at the decision point. A positive predicate on an absent atom evaluates false, including `!=`; outer `NOT` can make it true, and `OR` can match another branch. Guard required inputs with `EXISTS` before negation. Reachability includes values, not just keys. A builder that omits `days_since_last_login` when the user has never logged in makes `state.user.days_since_last_login > 30` false for the most inactive users. Represent that business state explicitly with a separate boolean or a documented sentinel. Negative event clauses require special attention. If `event.agent.sent.email` is never emitted, `NOT event.agent.sent.email IN 'P7D'` is always true. The absence of instrumentation looks like permission. ## Action connectivity Trigger reachability asks, “Can this policy match?” Action connectivity asks, “Can the selected outcome actually happen?” A complete integration has both. For each EMU, verify: 1. at least one decision point supplies its atom dependencies; 2. the action type has a handler; 3. a `tool_call` has a matching registered tool and runtime executor; 4. route and prompt destinations exist; 5. success and failure are acknowledged or recorded as designed. ## Project coherence Use the project as an explicit integration boundary for EMUs, decision points, tools, and events. Verify every tool schema, version, and executor in that boundary. This is an application contract, not a registry security guarantee: the validator can fall back to workspace/organization schemas, and runtime selection does not check that an executor exists. Cross-project event queries are possible within a workspace, but they create a declared dependency and should be documented in the topology. Do not use them to bypass unclear ownership. ## Events and traces Events are ingested separately from `decide` calls and retained for 90 days by default; check the organization's configured retention. Name them as `subject.verb.object`, include timestamps, and attach the entity attributes required for scoped `WHERE` clauses. A decision trace records context, matches, suppressions, and selected results. Use it alongside the [outcome and acknowledgment contract](#outcomes-and-acknowledgment), then follow the [production rollout](/production/). ## SOMA and AMI terminology SOMA AMI is the implementation name used in the API and SDK. AMI means Adaptive Memory Intelligence. The atom schema registry (ASR) records observed atom keys, types, and values for validation and reachability analysis; observation is not a guarantee that every invocation supplies an input. For product conversations, “deterministic agent steering and control” describes the outcome more directly. --- Source: https://docs.memrail.com/agent-control/ # AI agent steering and control patterns Memrail works beside an agent framework. The agent still observes, reasons, and proposes; Memrail gives the surrounding application a deterministic way to steer context and control execution at named seams. ## The authority boundary The strongest integration separates three responsibilities: ```text MODEL MEMRAIL APPLICATION interpret input evaluate explicit policy validate arguments classify into fixed tags → select policy outcome → authorize and execute tool propose an action trace the decision emit result event ``` Model inference is useful evidence. It becomes unsafe authority when an unbounded output, hidden confidence threshold, or prompt instruction is allowed to mutate external state directly. Use fixed taxonomies for model-derived tags: ```python from memrail.atoms import tag REFUND_INTENTS = {"duplicate_charge", "service_failure", "fraud", "other", "unknown"} # Validate actual runtime output; a type annotation alone does not do this. if classified_intent not in REFUND_INTENTS: raise ValueError("Invalid refund intent") context.append(tag("refund_intent", classified_intent, source="ml")) ``` Combine classifications with trusted state and event history, and give `unknown` an explicit safe outcome. The SDK preserves source metadata. Label model-derived values accurately and prevent model output from impersonating trusted application facts. Each example invokes a named `decision_point`. Bind its policies to that exact name and follow the [integration invariants](/concepts/#integration-invariants). `your_app.execution_contract`, `your_app.review_queue`, `model`, and executor helpers below are application-owned integration points, not SDK functions. The contract receives the full selection, including policy and lifecycle metadata, and must deny unauthorized modes/states, verify the reviewed action, and enforce caller permissions. An EMU-key allowlist alone is insufficient for consequential policies. The [Python](/python/#handle-selected-actions) and [TypeScript](/typescript/#dispatch-selected-actions) references also describe SDK-managed dispatch. ## Pattern 1: steer before reasoning Invoke Memrail before the model generates its next response. Use `context_directive` actions to add applicable protocol, safety, or customer context. ```python from memrail.models import InvokeOptions evaluation_only = True # Change only through the application's rollout control. guidance = await client.decide( decision_point="agent.before_response", context=[ state("customer.tier", customer.tier), state("conversation.turn_count", turn_count), tag("intent", intent, source="ml"), ], options=InvokeOptions(dry_run=evaluation_only), ) directives = [ item.action["directive"] for item in guidance.selected if not evaluation_only and your_app.execution_contract.allows(item, principal=current_user) and item.action and item.action.get("type") == "context_directive" ] response = await model.generate( system="\n".join([base_instructions, *directives]), messages=messages, ) ``` Use this for response protocols and context shaping. Do not treat a directive as a hard execution barrier; the model can still produce unexpected text. Put consequential actions behind a separate control point. ## Pattern 2: control before a tool call When the model proposes a tool, validate its proposed arguments and translate them into typed state and tags, then ask Memrail for a policy outcome. Verify monetary amounts and entitlement against trusted domain records, not merely the model's extraction. ```python proposal = await model.propose_tool(messages) amount = validate_refund_amount(proposal.arguments["amount"], customer) decision = await client.decide( decision_point="agent.before_tool_call", context=[ state("agent.tool_name", proposal.name), state("customer.id", customer.id), state("customer.tier", customer.tier), state("refund.amount", amount), state("refund.request_id", request_id), tag("refund_intent", classified_intent, source="ml"), ], options=InvokeOptions(dry_run=evaluation_only), ) for item in decision.selected: if evaluation_only: continue # Dry run may contain ordinary actionable selections. if not your_app.execution_contract.allows(item, principal=current_user): continue if item.action and item.action.get("type") == "decision_prompt": await your_app.review_queue.create(item.action) elif item.action and item.action.get("type") == "tool_call": await your_app.execute_authorized_tool(item.action["tool"]) ``` The executor uses the Memrail-selected tool specification, not the original free-form model proposal. It still performs ordinary schema validation and authorization close to the underlying service. Design the default explicitly. For a side-effecting proposal, an empty selection, timeout, malformed context, or unavailable control service should normally stop execution or route to a human. It should not fall through to “let the model decide.” ## Pattern 3: deterministic tool selection When policy can choose the tool directly, bypass model tool selection for the final step: ```json { "emu_key": "billing.large_refund_review", "decision_point": "agent.before_tool_call", "trigger": "state.refund.request_id EXISTS AND (state.refund.amount > 500 OR tag.refund_intent == 'fraud')", "action": { "type": "tool_call", "intent": "CREATE_REFUND_REVIEW", "tool": { "tool_id": "refund_review_queue", "version": "1.0.0", "args": { "request_id": "{{refund.request_id}}" } } }, "policy": { "mode": "auto", "priority": 9, "cooldown": { "seconds": 3600, "gate": "ack" }, "idempotency": { "enabled": true, "boundary": "workspace", "roles": "auto", "scope": [ "refund.request_id" ] } }, "expected_utility": 0.95, "confidence": 0.95, "state": "shadow" } ``` The trigger already guards the interpolated request ID with `EXISTS`. Idempotency `scope` contains literal state keys, not `{{...}}` templates. This one-hour cooldown is shared by the EMU across its workspace/project, not per refund request; omit or shorten it if independent requests must create review tasks immediately, and enforce request-level duplication in the queue. Priority 9 only breaks score ties; it does not guarantee this rule wins. ## Pattern 4: require a human Use an advisory `decision_prompt` to request review while keeping the consequential operation blocked. After authenticating approval for that operation, pass `InvokeOptions(human_consent=True)` in Python or `options: { human_consent: true }` in TypeScript. Combined SDK helpers propagate consent to selection and execution. `require_human` applies to every action type and does not create an approval queue automatically. Never infer consent from model output. Good human handoffs contain: - the decision and bounded options; - the verified facts that caused the escalation; - the relevant trace or invocation identifier; - an expiry and safe timeout outcome; - an idempotency key so retries do not create duplicate work. Human review is an execution state, not a log message. Do not continue the consequential branch while approval is pending. ## Pattern 5: record the outcome The selection trace says what policy prescribed. Emit a material event after the executor reports what happened: ```python from datetime import datetime, timezone from memrail.atoms import event if evaluation_only or not your_app.execution_contract.allows(item, principal=current_user): raise PermissionError("Execution is not authorized") result = await your_app.execute_authorized_tool(item.action["tool"]) if result.success: await client.emit_event( event( "agent.completed.refund_review", ts=datetime.now(timezone.utc), attributes={ "request_id": request_id, "activation_id": item.activation_id, }, ) ) await client.ack(item.activation_id, "acknowledged", code="SUCCESS") ``` Event emission is not an activation ACK. An ACK-gated cooldown starts only after a successful `acknowledged` status. ACK uses a receipt saved before the decision response, independently of tracing. Reconcile delivery failures without rerunning the side effect. Persist outcome/event/ACK work durably (for example, in an outbox) so a failure in one does not lose the others. Scoped event attributes allow future policy to ask about the current entity: ```dsl state.refund.request_id EXISTS AND NOT event.agent.completed.refund_review WHERE request_id == '{{refund.request_id}}' IN 'P7D' ``` Without `request_id` in the event attributes, that guard cannot distinguish one refund request from another. Without the state ID, an unresolved placeholder may match no events and make the negation true; keep the presence guard. Missing producers and expired event history also invalidate an absence-based duplicate check. ## Place control points by consequence | Seam | Typical action | Default on failure | Useful context | |---|---|---|---| | before retrieval | choose allowed corpus or route | return bounded public context | identity, purpose, data class | | before response | add protocol directives | use base instructions | user tier, locale, intent | | before tool call | allow, reroute, or require review | deny side effect | tool, amount, entity, risk tag | | between workflow steps | authorize transition | hold current state | current state, prerequisites | | after execution | acknowledge and emit outcome | retry or reconcile | activation, result, entity IDs | Use multiple narrow points when consequences differ. A policy that controls read-only search should not share an ambiguous contract with one that sends money or deletes data. ## Control failures to test - **Missing atom:** a positive predicate evaluates false, but `NOT` can invert it and `OR` can match another branch. Test explicit presence guards. - **Ghost negative event:** a `NOT event...` guard is always true because no producer emits that event. - **Unscoped event:** an event from another user satisfies the current user’s policy. - **Unknown classifier output:** free-form or new labels bypass expected branches. - **Disconnected action:** an EMU selects a tool or route the application cannot handle. - **Project mismatch:** a schema fallback hides that the expected project-scoped executor is absent; verify actual connectivity, not only validation output. - **Unsafe fallback:** Memrail is unavailable and execution falls back to model authority. - **Evaluation leak:** dry-run/advisory selections reach a live dispatcher, or comparison code and legacy logic both execute a side effect. - **Metadata loss:** a custom adapter drops policy or lifecycle information before authorization. - **No outcome event:** selection is recorded, but completion and failure cannot be distinguished. Build tests around these failure classes, not only the happy-path policy match. ## Framework integration rule Keep the Memrail call in an adapter around the framework's proposal/execution seam. Do not bury it inside the model prompt or a generic callback where the application cannot enforce the result. The adapter builds typed context, invokes the control point, and dispatches only supported, explicitly authorized live actions. --- Source: https://docs.memrail.com/python/ # Python SDK The `memrail` package provides the asynchronous client, typed ATOM builders, event ingestion, tracing, and the CLI used to manage EMUs and tools. ## Install Requires Python 3.9 or newer. ```bash python3 -m venv .venv . .venv/bin/activate python -m pip install --upgrade memrail ``` Verify the import without printing credentials: ```bash python -c "import memrail; from importlib.metadata import version; print(version('memrail'))" memrail --help ``` ## Configure Environment configuration keeps scope consistent across calls: ```bash export AMI_API_KEY='your-api-key' export AMI_ORG='your-org-slug' export AMI_TEAM='default' # optional export AMI_WORKSPACE='development' export AMI_PROJECT='support-agent' ``` The client reads `AMI_BASE_URL` automatically. An explicit `base_url` overrides it; otherwise the default is `https://api.memrail.com`. Set `use_env=False` to disable environment fallbacks. ```python from memrail import AsyncAMIClient from memrail.atoms import state async with AsyncAMIClient() as client: response = await client.decide(decision_point="support.before_response", context=[state("customer.tier", "enterprise")]) ``` For applications that choose scope at runtime, configure explicitly: ```python import os from memrail import AsyncAMIClient async with AsyncAMIClient( api_key=os.environ["AMI_API_KEY"], org="acme", team="platform", workspace="production", project="support-agent", ) as client: ... ``` Use the async context manager so underlying resources are closed cleanly. ## Build state and tags ```python from memrail.atoms import state, tag, atoms_from_dict context = [ state("customer.id", "cust_123"), state("customer.tier", "enterprise"), state("ticket.priority", "critical"), tag("intent", "escalation"), ] customer_context = atoms_from_dict( { "id": "cust_123", "tier": "enterprise", "account": {"region": "us-east", "active": True}, }, prefix="customer", ) ``` State values should be strings, numbers, or booleans. `atoms_from_dict` flattens nested dictionaries into dot notation. Keep keys lowercase and give them at least two segments after the `state` namespace. Validate model-derived tags at runtime, not only with type annotations: ```python from typing import Literal from pydantic import TypeAdapter from memrail.atoms import tag Intent = Literal["billing", "cancellation", "technical", "other", "unknown"] intent = TypeAdapter(Intent).validate_python(model_classification) intent_atom = tag("intent", intent, source="ml") ``` The SDK preserves state/tag `source` in decision requests. Label model-derived values with `source="ml"`, and ensure they cannot impersonate trusted application facts. Provenance labels complement application authorization; they do not replace it. ## Decide ```python from memrail import AsyncAMIClient from memrail.atoms import state, tag from memrail.models import InvokeOptions async def steer_response(customer, ticket, validated_intent, *, evaluation_only=True): async with AsyncAMIClient() as client: response = await client.decide( decision_point="support.before_response", context=[ state("customer.id", customer.id), state("customer.tier", customer.tier), state("ticket.priority", ticket.priority), tag("intent", validated_intent, source="ml"), ], options=InvokeOptions(dry_run=evaluation_only), ) for item in response.selected: await handle_selected_action( item, control_point="support.before_response", evaluation_only=evaluation_only, ) return response ``` Use `context`; `atoms` is also a supported alias in this version. Provide exactly one. `context_atoms` is a wire-body field, not a `decide()` keyword. Bind the policy to `support.before_response` and pass that exact name on every call. See the [integration invariants](/concepts/#integration-invariants). When an application serves multiple scopes, a call may override the client defaults with `workspace="staging"` and `project="support-agent"`. ## Dry run and trace ```python from memrail.models import InvokeOptions, TraceOptions response = await client.decide( context=context, decision_point="support.before_response", options=InvokeOptions( dry_run=True, top_k=5, ), trace=TraceOptions(enable=True), ) ``` Use the [evaluation and execution contract](/concepts/#evaluation-and-execution); the combined SDK helper keeps dry-run results out of handlers and ACK. Tracing is enabled by default. Use `TraceOptions(enable=True)` and inspect `response.trace["candidates"]` for eligibility and suppression details. Shadow EMUs appear in traces but are excluded from `selected`. ## Handle selected actions Keep dispatch explicit and fail closed. `your_app.execution_contract`, `your_app.prompt_builder`, `your_app.review_queue`, `your_app.route_registry`, and `your_app.execute_authorized_tool` below are application-owned code, not SDK methods: ```python async def handle_selected_action(item, *, control_point, evaluation_only=True): if evaluation_only: return None # Never dispatch evaluation results. action = item.action if not action: return None if not your_app.execution_contract.permits(item, control_point): raise PermissionError("Selected action is not authorized for execution") if action["type"] == "context_directive": return your_app.prompt_builder.add_directive(action["directive"]) if action["type"] == "decision_prompt": return your_app.review_queue.create(action["message"], action["options"]) if action["type"] == "route": return your_app.route_registry.dispatch(action["destination"], action.get("metadata", {})) if action["type"] == "tool_call": return await your_app.execute_authorized_tool(action["tool"]) raise ValueError(f"Unsupported Memrail action type: {action['type']}") ``` Selections expose `policy`, `lifecycle_state`, `emu_version`, and `action`. The custom contract above receives the full selection and must check those fields, the allowed control point, tool version, arguments, caller permissions, and duplicate protection. Deny missing/unknown policy and unauthorized lifecycle states; do not reduce a selection to its action payload before authorization. Supply verified approval through `InvokeOptions(human_consent=True)`, following the [execution contract](/concepts/#evaluation-and-execution). For SDK-managed dispatch, register reviewed handlers and use `await client.decide_and_execute(context=context, decision_point="support.before_response", options=InvokeOptions(dry_run=True), auto_ack=True)`. The synchronous equivalent is `client.decide_and_execute_sync(...)`. The [runnable quickstart](/getting-started/) includes registration, execution, and ACK. For a dangerous decision point, an empty selection or client error should resolve to a safe documented outcome. Catching every exception and continuing with the model proposal defeats the control boundary. ## Ingest material events Events are written separately and queried by temporal triggers: ```python from datetime import datetime, timezone from memrail.atoms import event await client.emit_event( event( "agent.sent.email", ts=datetime.now(timezone.utc), attributes={ "customer_id": customer.id, "message_id": message.id, "activation_id": item.activation_id, }, ), workspace="production", project="support-agent", ) ``` Follow the [outcome contract](/concepts/#outcomes-and-acknowledgment). Event retention defaults to 90 days and is configurable by organization; check it before choosing lookback windows. For an ACK-gated cooldown, emitting an event is not enough. Acknowledge the successful activation separately: ```python await client.ack( activation_id=item.activation_id, status="acknowledged", code="SUCCESS", ) ``` Follow the [outcome and acknowledgment contract](/concepts/#outcomes-and-acknowledgment) when reconciling delivery failures. ## Idempotent calls Use a stable key when repeated requests represent the same logical decision: ```python response = await client.decide( decision_point="billing.capture_payment", project="billing", context=[ state("order.id", order.id), ], idempotency_key=f"billing:billing.capture_payment:live:{operation_id}", ) ``` `operation_id` identifies one immutable request. See [two layers of idempotency](/concepts/#two-layers-of-idempotency) for cache lifetime, conflicts, and executor protection. ## Validate and inspect from the CLI ```bash memrail list-emus -w production -p support-agent memrail get-emu support.critical_enterprise -w production -p support-agent memrail emu-validate -w production -p support-agent memrail action-connectivity -w production -p support-agent memrail tool-get-schema -w production -p support-agent ``` These are the Python-installed CLI commands. For production changes, use the [strict JSONL workflow](/production/) rather than SDK write methods. For programmatic read-only preflight, use `await client.validate_emu_candidates(candidates, workspace=..., project=...)` with complete EMU definitions. ## Error handling Catch specific errors when recovery differs. Error classes below are from the SDK; `your_app.ConfigurationError`, `your_app.ControlEvaluationError`, and `your_app.safe_control_fallback` belong to the application: ```python from memrail.errors import AMIUnauthorized, AMINotFound, AMIServerError, AMIError try: response = await client.decide(context=context, decision_point="support.before_response") except AMIUnauthorized: raise your_app.ConfigurationError("Memrail API key was rejected") except AMINotFound: raise your_app.ConfigurationError("Memrail workspace or project does not exist") except AMIServerError: return your_app.safe_control_fallback() except AMIError as exc: raise your_app.ControlEvaluationError(str(exc)) from exc ``` Retry transient service errors with bounded exponential backoff only when the surrounding operation is still safe to retry. Never log the API key or full sensitive context. ## Workspace reset `purge-workspace` deletes workspace data and requires an organization-level key. Use it only for development or explicit test resets: ```bash memrail purge-workspace development --targets emus,traces,events --yes ``` This is destructive and is not part of ordinary deployment. --- Source: https://docs.memrail.com/typescript/ # TypeScript SDK `@memrail/sdk` provides the Memrail client and ATOM builders without requiring an agent framework. Use a supported Node.js LTS release; the CLI requires Node.js 20 or newer. ## Install ```bash npm install @memrail/sdk # or: pnpm add @memrail/sdk # or: yarn add @memrail/sdk ``` The package also exposes a local `memrail` CLI for EMU and tool management. Run it through your package manager, for example `npx --no-install memrail --help`; a local install does not place it on the shell's global PATH. ## Configure ```bash export AMI_API_KEY='your-api-key' export AMI_ORG='your-org-slug' export AMI_TEAM='default' # optional export AMI_WORKSPACE='development' export AMI_PROJECT='support-agent' ``` ```typescript import { AMIClient } from '@memrail/sdk'; const client = new AMIClient(); ``` Or pass the scope explicitly: ```typescript const client = new AMIClient({ apiKey: process.env.AMI_API_KEY!, org: 'acme', team: 'platform', workspace: 'production', project: 'support-agent', }); ``` ## Build state and tags ```typescript import { state, tag, atomsFromDict } from '@memrail/sdk'; const context = [ state('customer.id', 'cust_123'), state('customer.tier', 'enterprise'), state('ticket.priority', 'critical'), tag('intent', 'escalation'), ]; const customerContext = atomsFromDict({ id: 'cust_123', tier: 'enterprise', account: { region: 'us-east', active: true }, }, 'customer'); ``` Use scalar state values. `atomsFromDict` flattens nested objects into namespaced keys. Constrain model-derived categories before creating tags. A Zod enum validates at runtime (install `zod` separately if the project does not already use it): ```typescript import { z } from 'zod'; const RefundIntent = z.enum([ 'duplicate_charge', 'service_failure', 'fraud', 'other', 'unknown', ]); const intent = RefundIntent.parse(modelClassification); const intentAtom = tag('refund_intent', intent, 'ml'); ``` The SDK preserves state/tag `source` in decision requests. Label model-derived values with `'ml'`, and ensure they cannot impersonate trusted application facts. Provenance labels complement application authorization; they do not replace it. ## Decide ```typescript import { AMIClient, state, tag } from '@memrail/sdk'; async function steerResponse( customer: Customer, ticket: Ticket, validatedIntent: string, evaluationOnly = true, ) { const response = await client.decide({ decisionPoint: 'support.before_response', context: [ state('customer.id', customer.id), state('customer.tier', customer.tier), state('ticket.priority', ticket.priority), tag('intent', validatedIntent, 'ml'), ], options: { dry_run: evaluationOnly }, }); for (const item of response.selected) { await handleSelectedAction(item, 'support.before_response', evaluationOnly); } return response; } ``` Use `decide({ context: [...] })`. `atoms` is also supported as an alias; pass one or the other, not both. Bind the policy to `support.before_response` and pass that exact name on every call. See the [integration invariants](/concepts/#integration-invariants). ## Dry run and trace ```typescript const response = await client.decide({ context, decisionPoint: 'support.before_response', options: { dry_run: true, top_k: 5, }, trace: { enable: true }, }); ``` Use the [evaluation and execution contract](/concepts/#evaluation-and-execution); inspect `response.trace?.candidates` for suppression details. Use snake_case `dry_run` and `top_k` inside `options`, even though the outer argument is `decisionPoint`. Use `trace: { enable: true }` for tracing, which is enabled by default. ## Dispatch selected actions ```typescript import type { SelectedItem } from '@memrail/sdk'; async function handleSelectedAction( item: SelectedItem, controlPoint: string, evaluationOnly = true, ) { if (evaluationOnly) return; // Never dispatch evaluation results. const action = item.action as Record | undefined; if (!action) return; if (!yourApp.executionContract.permits(item, controlPoint)) { throw new Error('Selected action is not authorized for execution'); } switch (action.type) { case 'context_directive': return yourApp.promptBuilder.addDirective(action.directive); case 'decision_prompt': return yourApp.reviewQueue.create(action.message, action.options); case 'route': return yourApp.routeRegistry.dispatch(action.destination, action.metadata); case 'tool_call': return yourApp.executeAuthorizedTool(action.tool); default: throw new Error(`Unsupported Memrail action type`); } } ``` All handlers and `yourApp.executionContract` in this example are application-owned code, not SDK APIs. The contract receives the full selection, including `policy`, `lifecycle_state`, `emu_version`, and `action`. It must check these fields, the control point, tool/version, arguments, permissions, and duplicate protection. Deny missing/unknown policy and unauthorized lifecycle states. A selected tool or route with no handler is an integration failure. Supply verified approval through `options: { human_consent: true }`, following the [execution contract](/concepts/#evaluation-and-execution). For SDK-managed dispatch, register reviewed handlers and use `await client.decideAndExecute({ context, decisionPoint: 'support.before_response', options: { dry_run: true }, autoAck: true })`. Business permissions and duplicate protection remain in your handlers; see the [integration invariants](/concepts/#integration-invariants). ## Emit events ```typescript import { event } from '@memrail/sdk'; await client.emitEvent({ event: event('agent.sent.email', { ts: new Date(), attributes: { customer_id: customer.id, message_id: message.id, activation_id: item.activation_id, }, }), workspace: 'production', project: 'support-agent', }); ``` Use the [outcome and acknowledgment contract](/concepts/#outcomes-and-acknowledgment), including entity identifiers required by later event filters. An event is not an activation acknowledgment. After a successful execution with an ACK-gated cooldown, acknowledge separately: ```typescript if (!item.activation_id) throw new Error('Missing activation ID'); await client.ack({ activationId: item.activation_id, status: 'acknowledged', code: 'SUCCESS', }); ``` Follow the [outcome and acknowledgment contract](/concepts/#outcomes-and-acknowledgment) when reconciling delivery failures. ## Idempotent decisions ```typescript const response = await client.decide({ decisionPoint: 'billing.capture_payment', project: 'billing', context: [ state('order.id', order.id), ], idempotencyKey: `billing:billing.capture_payment:live:${operationId}`, }); ``` `operationId` identifies one immutable request. See [two layers of idempotency](/concepts/#two-layers-of-idempotency) for cache lifetime, conflicts, and executor protection. ## CLI workflow The TypeScript and Python CLIs are separate implementations with overlapping commands, not identical flags. These commands use the locally installed TypeScript CLI; inspect `--help` for your installed version: ```bash npx --no-install memrail emu-pull ./emus/ --workspace staging --project support-agent npx --no-install memrail emu-plan ./emus/ --workspace staging --project support-agent --strict npx --no-install memrail emu-apply ./emus/ --workspace staging --project support-agent --yes --strict --target-state shadow npx --no-install memrail emu-validate --workspace staging --project support-agent npx --no-install memrail action-connectivity --workspace staging --project support-agent ``` These commands consistently target staging. Use a separate directory and lock file for each scope and follow the [binding and lifecycle contract](/concepts/#bindings-and-lifecycle). Install the Python CLI separately if following its commands elsewhere on this site. For programmatic read-only preflight, use `client.validateEMUCandidates({ candidates, workspace, project })` with complete EMU definitions. Production policy writes belong in reviewed JSONL, not ad hoc SDK registration calls. ## Error strategy Handle authentication and missing scope as configuration failures. Treat transient service errors according to the risk of the decision point. A pre-tool control point should usually deny or require review when evaluation is unavailable; a context-only guidance point may continue with a documented base prompt. ```typescript try { return await client.decide({ context, decisionPoint: 'support.before_response' }); } catch (error) { yourApp.logger.error({ category: 'control_evaluation_failed' }, 'Memrail unavailable'); return yourApp.requireHumanReview(); } ``` Here `context` supplies the reviewed policy's atom dependencies; `yourApp.logger` and `yourApp.requireHumanReview` are application code. Do not include secrets or raw sensitive context in logs. Bound retries and ensure the original agent proposal cannot execute while recovery is in progress. --- Source: https://docs.memrail.com/triggers/ # Trigger DSL An EMU trigger is a boolean expression over typed context. The language is deliberately small so a policy is inspectable, reproducible, and statically analyzable. ## Quick reference ```dsl state.user.tier == 'premium' state.user.tier IN ['gold', 'platinum'] state.account.verified == true AND state.account.suspended == false tag.intent == 'refund_request' event.agent.sent.email IN 'PT24H' COUNT event.user.failed.login IN 'PT10M' >= 5 event.order.shipped.package WHERE order_id == 'ord-456' IN 'P7D' state.user.email ENDS_WITH '@company.com' state.document.code MATCHES '^[A-Z]{3}-\d{4}$' ``` Logical and special operators are uppercase. String literals and durations use single quotes. State keys are lowercase and namespaced, such as `state.user.tier`; a single key such as `state.tier` is invalid. ## State conditions ```dsl state.user.tier == 'premium' state.ticket.priority != 'low' state.order.total >= 1000 state.account.balance < 0 state.account.verified NOT state.user.banned ``` Send numeric state for numeric comparisons. The evaluator also coerces numeric-looking strings, so validate input types before calling it rather than relying on coercion. Ordinary state string equality is case-sensitive. Boolean shorthand checks truthiness, not a strict boolean contract. Nonzero numbers, including negative numbers, are true. Strings other than `''`, `'false'`, `'no'`, and `'0'` are true, ignoring case for those four exclusions. Thus `'unknown'` is true. For permission-bearing fields, emit actual booleans and use explicit `== true` or `== false`. ### Compare facts and do arithmetic ```dsl state.refund.amount > state.customer.refund_limit state.request.cost + state.budget.spent <= state.budget.limit ``` State comparisons can read another state or tag accessor. Numeric arithmetic supports `+`, `-`, `*`, and `/`; multiplication and division bind more tightly than addition and subtraction. Missing operands and division by zero make the arithmetic comparison false. Invalid nonnumeric arithmetic is an evaluation error, not a successful comparison. ### Presence ```dsl state.user.email EXISTS state.refund.request_id EXISTS AND state.refund.amount > 0 ``` `EXISTS` tests that a value is present and non-null. A positive comparison, including `!=`, returns false when either referenced value is missing. But the entire trigger does **not** necessarily become false: `NOT` flips that result, and an `OR` branch can independently match. ```dsl // With user.banned missing, the first expression is true; the second is false. NOT state.user.banned state.user.banned EXISTS AND state.user.banned == false ``` Guard required context before negation and before interpolating an action or event filter. Do not treat absence as an authorization decision. ### Membership ```dsl state.user.role IN ['admin', 'owner'] state.ticket.priority IN ['high', 'critical'] tag.intent IN ['refund_request', 'charge_dispute'] ``` String membership uses Unicode NFKC normalization, case folding, and trimming. For example, a state value of `'ADMIN'` matches `IN ['admin']` but does not match `== 'admin'`. Numeric list membership does not use the numeric-string coercion of comparison operators. ### String operations | Operator | Example | |---|---| | `STARTS_WITH` | `state.case.reference STARTS_WITH 'EU-'` | | `ENDS_WITH` | `state.user.email ENDS_WITH '@company.com'` | | `CONTAINS` | `state.message.subject CONTAINS 'URGENT'` | | `MATCHES` | `state.document.code MATCHES '^[A-Z]{3}-\d{4}$'` | String operations are case-sensitive. `MATCHES` uses Python regular-expression search semantics; anchor with `^` and `$` when the whole value must match. Keep expressions bounded and test representative inputs. The literal `\d` above is valid in the DSL: the lexer preserves that escape. When putting the trigger inside JSON, escape the backslash as `\\d`; host-language string literals may require another escaping layer. ## Tags Tags are classifications or metadata: ```dsl tag.intent == 'upgrade_request' tag.sentiment == 'negative' tag.channel == 'email' ``` Tag equality normalizes the kind and string value with Unicode NFKC, case folding, and trimming. Keep producer keys consistently lowercase even though normalized equality accepts other casing. If a model produces a tag, constrain it to a fixed enum before building the ATOM. Define an explicit branch for `unknown` and label its model provenance; the SDK preserves source metadata on requests. See [agent control patterns](/agent-control/#the-authority-boundary). ## Events Modern event predicates require a `subject.verb.object` name followed by `IN` and a single-quoted ISO 8601 duration. A bare event name is not a complete predicate: ```dsl event.user.failed.login IN 'PT15M' event.customer.completed.purchase IN 'P7D' NOT event.agent.responded.ticket IN 'PT2H' ``` Event retention defaults to 90 days and is configurable at the organization level, inherited by workspaces. Events receive a per-document expiry based on their event timestamp and that effective policy. A longer query window can still match recent retained events, but cannot recover expired history; the validator can report `WARN-WINDOW-OUT-OF-RETENTION`. Confirm the effective retention before relying on a negative history check. An existence query can name another project in the same workspace with `event.project_slug.subject.verb.object IN 'PT1H'`. Document this cross-project dependency in your topology. `COUNT` accepts only the three-token `subject.verb.object` form and does not support that project prefix. ### Count events ```dsl COUNT event.user.failed.login IN 'PT15M' >= 5 COUNT event.customer.placed.order IN 'P30D' >= 3 ``` Both `COUNT event.user.failed.login IN 'PT15M' >= 5` and `COUNT(event.user.failed.login IN 'PT15M') >= 5` are supported. Use one house style consistently. A count requires a comparison against a number; with no matching events, the count is zero, so `== 0` is true. ### Scope events to the current entity Without a `WHERE` clause, an event query may match any event of that name in the project context. Scope multi-entity policy with event attributes: ```dsl event.customer.received.refund WHERE customer_id == '{{customer.id}}' IN 'P30D' ``` The template literal reads a current state value and converts it to a string. Emit entity identifiers as string attributes so the types match. An unresolved placeholder stays literal and normally matches nothing; under `NOT`, that produces true. Add an explicit `state.customer.id EXISTS` guard when the identifier is required. Do not write a state accessor directly as a `WHERE` value: ```dsl // Invalid event.customer.received.refund WHERE customer_id == state.customer.id IN 'P30D' // Valid event.customer.received.refund WHERE customer_id == '{{customer.id}}' IN 'P30D' ``` The event producer must include `customer_id` in its attributes. Attribute filters support equality conditions joined by `AND`; values are single-quoted strings, numeric literals, or quoted templates. Numeric range operators and state accessors are not supported as `WHERE` values: ```dsl COUNT event.agent.called.tool WHERE customer_id == '{{customer.id}}' AND tool_name == 'refund' IN 'PT1H' >= 3 ``` ## Durations | Literal | Window | |---|---| | `'PT5M'` | five minutes | | `'PT1H'` | one hour | | `'PT24H'` | twenty-four hours | | `'P1D'` | one day | | `'P7D'` | seven days | | `'P30D'` | thirty days | | `'P90D'` | ninety days | Use durations only for event history. EMU cooldowns use an object with integer seconds, not an ISO string. Fractional seconds are preserved: `'PT1.5S'` means a 1.5-second window, including an event exactly on its boundary. Fractional hours such as `'PT1.5H'` are invalid, so write `'PT1H30M'`. Months mean 30 days and years mean 365 days, not calendar-aware intervals. ```json { "cooldown": { "seconds": 3600, "gate": "ack" } } ``` ## Operator precedence From highest to lowest: `NOT`, `AND`, then `OR`. Use parentheses whenever mixed operators would force a reviewer to remember precedence. ```dsl (state.user.tier == 'premium' OR state.user.tier == 'enterprise') AND state.account.verified ``` ## Reachability review For every trigger, list the required inputs: ```dsl state.customer.tier == 'enterprise' AND state.customer.id EXISTS AND tag.intent == 'refund_request' AND NOT event.customer.received.refund WHERE customer_id == '{{customer.id}}' IN 'P30D' ``` | Dependency | Requirement | |---|---| | `state.customer.tier` | supplied at this decision point | | `tag.intent` | classifier always returns a declared enum | | `state.customer.id` | supplied because the event filter interpolates it | | `event.customer.received.refund` | emitted after successful refund with `customer_id` attribute | The negative event predicate evaluates true when there is no matching event, including when a producer is absent, a filter identifier is missing, or relevant history has expired. Reliable interpretation requires complete producer coverage, required context, and an appropriate retention window. ## Common invalid expressions | Invalid | Correct | Reason | |---|---|---| | `state.tier == 'premium'` | `state.user.tier == 'premium'` | state key is not namespaced | | `state.User.Tier == 'premium'` | `state.user.tier == 'premium'` | keys must be lowercase | | `... and ...` | `... AND ...` | logical operators are uppercase | | `state.user.tier == "premium"` | `state.user.tier == 'premium'` | strings use single quotes | | `event.user.login IN PT1H` | `event.user.failed.login IN 'PT1H'` | S.V.O name and quoted duration required | | `WHERE user_id == state.user.id` | `WHERE user_id == '{{user.id}}'` | state accessors are invalid as filter values | | `event.user.created.account` | `event.user.created.account IN 'P7D'` | modern event predicates require a quoted time window | ## Complete control example ```dsl state.refund.request_id EXISTS AND state.customer.id EXISTS AND state.refund.amount > 500 AND tag.refund_intent IN ['service_failure', 'fraud'] AND NOT event.agent.created.refund_review WHERE request_id == '{{refund.request_id}}' IN 'P7D' ``` This expression is syntactically valid regardless of which inputs arrive. For its positive conditions to match, the call must supply both IDs, a numeric amount above 500, and a matching tag. For the negative event check to be meaningful, the application must emit `agent.created.refund_review` with a matching string `request_id` after the action succeeds. --- Source: https://docs.memrail.com/emus/ # 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. --- Source: https://docs.memrail.com/topology-audit/ # Audit an application's decision topology A decision topology audit maps where actions are proposed, authorized, and executed, and which inputs influence them. Use it to find ungoverned paths and prioritize where to add agent control before changing runtime behavior. Run discovery read-only. Do not move logic, register EMUs, or add runtime calls until the report is reviewable and its scope is approved. ## What the audit must answer For each consequential outcome: 1. Where can it be proposed? 2. Where is it authorized today? 3. Which state, model outputs, and historical events influence it? 4. Which code executes it? 5. Is there a human review path? 6. What happens when context is missing or a dependency fails? 7. Can one named Memrail decision point cover every path to that outcome? 8. Would every candidate EMU be reachable and every action connected? The result is not a count of `if` statements. It is a graph of authority and execution. ## Audit scope Include: - agent loops, planners, routers, and tool dispatchers; - API handlers, middleware, webhooks, queues, and scheduled jobs; - prompts and system instructions that change behavior; - classifier thresholds and model confidence checks; - feature flags, experiments, configuration policy, and database-driven rules; - human approval screens and escalation queues; - action executors and external side effects; - existing Memrail calls, ATOM builders, EMU JSONL, tools, and events. Exclude generated code, vendored dependencies, fixtures, and tests during initial discovery, then use tests to confirm intended behavior after the topology is mapped. ## 1. Establish project boundaries Identify services, deployable units, and existing Memrail projects. Use project coherence as an application contract: verify each EMU's tool schema, version, and actual executor together. A validator schema fallback is not proof of project-scoped runtime connectivity. ```bash rg --files -g 'package.json' -g 'pyproject.toml' -g 'requirements*.txt' \ -g 'Dockerfile*' -g 'docker-compose*.yml' -g 'Chart.yaml' -g 'kustomization.yaml' rg -n --glob '*.{py,ts,tsx,js,jsx,json,jsonl,yaml,yml}' \ 'AMI_PROJECT|project=|project:|projects=|emus\.jsonl|ToolRegistry' ``` Record ownership and deployment boundaries even when Memrail is not yet installed. ## 2. Find existing control integration ```bash rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'AsyncAMIClient|AMIClient|@memrail/sdk|from memrail|\.decide\(|decision_point|decisionPoint' rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'state\(|tag\(|atoms_from_dict|atomsFromDict|ingest_event|emit_event|emit_events|emitEvent|emitEvents' ``` For each `decide` call, capture its stable name, file and symbol, target project, atoms supplied, callers, action handlers, and failure behavior. Compare each call with its stored EMU bindings: `decision_point` must match exactly. Verify source, policy, and lifecycle metadata survive application adapters. ## 3. Discover consequential decisions Search broad patterns, then inspect each result in context: ```bash rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'if .*\.(tier|priority|status|role)|switch .*status|match .*status' rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'threshold|confidence|score.*[><=]|feature.?flag|experiment|variant|ab.?test' rg -n --glob '*.{py,ts,tsx,js,jsx}' \ 'execute_tool|tool_call|function_call|send|create|update|delete|refund|approve|deny|escalat|route' rg -n --glob '*.{py,ts,tsx,js,jsx,md,yaml,yml}' \ 'system.?prompt|instructions|must not|never |always |guardrail|moderation' ``` Mark a finding consequential when it can change money, access, state, user-visible communication, routing, legal/compliance posture, or an irreversible workflow transition. Do not automatically migrate every conditional. Input validation, local invariants, low-level error checks, and executor safety checks usually remain in code. ## 4. Trace authority from proposal to effect For each material action, work backward from the side effect: ```text payment_client.refund() ← refund tool executor ← agent tool dispatcher ← model tool proposal ← billing agent prompt + refund classifier ← API request + customer record ``` Label each edge: - **observation**: provides facts; - **inference**: produces a prediction or bounded tag; - **authority**: chooses whether or what should happen; - **execution**: performs the state change; - **recording**: captures the result. An inference-to-execution edge with no explicit authority check is the highest-value candidate for agent control. ## 5. Map data flows into ATOM candidates For every proposed decision point, inventory the context that is available before execution: ```yaml decision_point: billing.refund location: src/agents/billing.py:RefundAgent.execute callers: - POST /v1/refunds - retry_refund_job state_candidates: customer.id: source: authenticated request type: string presence: always refund.amount: source: validated request type: number presence: always tag_candidates: refund_intent: source: bounded classifier values: [duplicate_charge, service_failure, fraud, other, unknown] presence: always event_candidates: customer.received.refund: producer: payment success callback attributes: [customer_id, refund_id, request_id] ``` Capture conditional omissions and sentinel behavior. “Builder exists” is insufficient if it is never called on the path where the EMU must match. ## 6. Inventory action connectivity ```yaml actions: tool_call.refund_payment: schema_registered: true executor: src/tools/payments.py:refund_payment version: 1.0.0 projects: [billing] retry_policy: bounded idempotency: refund_request_id outcome_event: payment.completed.refund decision_prompt.refund_review: handler: operations refund queue timeout: 24h timeout_outcome: decline route.fraud_review: handler: missing ``` Inspect real handlers, not only registrations. A stub, unconfigured credential, nonexistent destination, or swallowed error is a partial connection. Trace authorization separately from selection. Test every dispatcher and adapter against the [integration invariants](/concepts/#integration-invariants), including consent, evaluation, lifecycle, and metadata preservation. ## 7. Build the reachability matrix For every current or proposed EMU, cross-reference dependencies and actions: | EMU | Point | Required context | Trigger reachable | Action connected | Project coherent | |---|---|---|---:|---:|---:| | `billing.small_refund` | `billing.refund` | amount, request ID, reason tag | yes | yes | yes | | `billing.repeat_refund` | `billing.refund` | customer ID, refund event | partial | yes | yes | | `billing.fraud_route` | `billing.refund` | fraud tag | yes | no route handler | yes | Flag four distinct failures: - **invoke gap:** a consequential path never reaches the decision point; - **atom gap:** required context is absent, stale, conditionally omitted, or wrongly typed; - **action gap:** the selected action has no complete handler; - **coherence gap:** the EMU and tool belong to different projects. Test required-atom presence on every caller. Positive predicates on absent atoms return false, but negation can make them true. The atom schema registry (ASR)'s observed schemas may span team workspaces; a clean validation report is evidence, not proof of per-point coverage. ## 8. Inspect event correctness Events deserve a separate pass because absence can invert policy behavior. For every `event.*` clause: - confirm a producer runs after the material outcome; - confirm the emitted name exactly follows the trigger’s `subject.verb.object` order; - confirm timestamps are timezone-aware; - confirm attributes include entity IDs referenced by `WHERE`; - confirm query windows fit the organization's configured retention (90-day default), not an assumed universal cap; - guard required IDs with `EXISTS` before negated event filters; unresolved placeholders can leave a negative predicate true; - confirm a negative event clause is not a ghost guard that is always true. ## 9. Rank findings by authority and consequence Use a simple qualitative priority: | Priority | Condition | Example | |---|---|---| | Critical | model or heuristic can directly cause irreversible/high-stakes effect | payment, deletion, account access | | High | control can be bypassed or required event/action connection is missing | alternate tool dispatcher path | | Medium | policy is reachable but difficult to audit or safely evolve | thresholds duplicated in services | | Low | naming, documentation, or non-consequential topology debt | unnamed read-only routing hint | Do not collapse the audit into one percentage. Report counts by gap type and preserve the evidence path for each finding. ## Deliverable template Create `TOPOLOGY_STATE.yaml` or an equivalent review artifact: ```yaml metadata: generated_at: 2026-09-06 scope: repository root mode: read-only summary: projects: 2 decision_points: 6 consequential_paths: 11 invoke_gaps: 2 atom_gaps: 4 action_gaps: 1 coherence_gaps: 0 hidden_authority_sites: 3 decision_points: [] hidden_authority: [] atom_contracts: [] event_producers: [] action_connectivity: [] reachability_matrix: [] prioritized_findings: - id: AUTH-001 severity: critical evidence: file: src/agents/billing.py symbol: RefundAgent.execute behavior: model-selected refund tool executes directly recommendation: insert billing.refund control point before dispatcher migration_phase: 1 unknowns: [] out_of_scope: [] ``` Use exact file and symbol evidence. Line numbers are helpful during one revision but should not be the only locator because they drift. ### Visualize the topology for inspection After creating or updating `TOPOLOGY_STATE.yaml`, try to generate `TOPOLOGY_STATE.html` beside it and open the page for visual inspection. The YAML remains the canonical review artifact; the HTML is a derived view and must not add facts, relationships, or conclusions that are absent from the YAML. Make the visualization useful for inspecting the system, not just reading serialized YAML. Show project and deployment boundaries, proposal → authority → execution → outcome paths, decision points, atom/event/action connections, hidden-authority sites, bypasses, gaps, prioritized findings, evidence locations, and unknowns when those sections exist. Label inferred relationships and render missing information as unknown rather than inventing edges. Prefer one self-contained HTML file with embedded CSS and, only when useful, embedded JavaScript. It should work locally without a framework, CDN, network request, or newly installed dependency. Escape repository-derived text before inserting it into HTML. If a browser or HTML preview is available, inspect the result for clipping, unreadable labels, disconnected edges, and misleading grouping. If the environment cannot render HTML, still produce the best-effort file when possible and report that it was not visually verified. Do not widen a read-only audit merely to create the derived view. ## Agent-ready audit prompt ```text Use the memrail skill to perform a read-only decision topology audit of this repository. Do not edit code, register EMUs, change external state, or expose secrets. Map: 1. project and deployment boundaries; 2. existing Memrail SDK usage, decision points, atoms, EMUs, tools, and events; 3. consequential decisions in code, prompts, flags, model thresholds, and human workflows; 4. every proposal → authority → execution → outcome path; 5. candidate atom contracts and event producers; 6. trigger reachability, action connectivity, and project coherence gaps; 7. unsafe fallbacks and paths that bypass explicit control. Produce TOPOLOGY_STATE.yaml using exact file and symbol evidence. Rank findings by consequence and authority. Separate verified facts from inferences and unknowns. Recommend phased seams for migration, but do not implement them. After the YAML is complete, try to generate a self-contained TOPOLOGY_STATE.html visualization beside it and open it for visual inspection. Treat the YAML as canonical. Show boundaries, decision paths, connections, gaps, findings, evidence, and unknowns without inventing missing relationships. If HTML rendering is unavailable, report that the derived file was not visually verified. ``` Install the skill first from the [agent installation page](/agents/) if it is unavailable. ## Audit completion criteria - Every in-scope material executor has a traced proposal and authority path. - Every consequential path is mapped to a current or candidate decision point. - Every atom candidate has a source, type, presence rule, and value domain. - Every event clause has an exact producer and required attributes. - Every action has a real handler and project assignment. - Bypass paths, unsafe fallbacks, and unknowns are explicit. - Findings cite repository evidence and distinguish fact from inference. - `TOPOLOGY_STATE.html` was generated from the canonical YAML when possible, opened for visual inspection when the environment supports it, and its verification status was reported. - No runtime, policy, credential, or external-system changes occurred during discovery. --- Source: https://docs.memrail.com/hindsight/ # Recursive self-improvement with Hindsight Memrail Hindsight enables recursive agent improvement by turning recorded behavior into proposals for better policy. Analyze what happened, review a proposed change, deploy a tested policy version, and observe its effects. Those new observations become evidence for the next cycle. The thing that improves is the agent's explicit steering and control policy—not its model weights. Hindsight supplies retrospective analysis and proposals; your application and release process supply outcome measurement, authorization, and controlled deployment. Improvement is a hypothesis to test, not a guarantee of running the loop. ## What makes the loop recursive? An **EMU (Executable Memory Unit)** is a versioned condition/action policy. A **decision trace** records an evaluation; an **event** records an occurrence supplied by your application. Hindsight examines these artifacts and proposes changes to the policy that will shape subsequent behavior. ```text Policy version → decisions → authorized actions → recorded outcomes ↑ ↓ Reviewed release ← tests and review ← proposal ← Hindsight analysis ``` This is more than saving a reflection in a prompt. A useful lesson becomes an inspectable rule with named inputs, a condition, and a prescribed action. The next cycle examines behavior under that changed rule. Runtime trigger evaluation remains deterministic for its complete inputs; Hindsight's LLM-assisted interpretation is a separate, probabilistic step. Consider a support agent that repeatedly sends billing cases to a general queue. This illustrative sequence shows how the loop can improve routing without expanding the agent's permissions: | Cycle | Evidence to examine | Bounded change to test | |---|---|---| | Establish a baseline | Routing traces plus application events show billing cases being transferred twice | Propose a billing-route EMU using a validated intent tag and trusted queue-availability facts | | Evaluate the new policy | New outcomes show fewer transfers, but some cases reach a queue that cannot serve their language | Refine the routing condition to require a supported language; retain the general-queue fallback | | Check the refinement | Compare later cases with the baseline and held-out cases | Keep, revise, or revert the change based on correct routing and resolution—not merely how often the new rule matches | These are example hypotheses, not promised Hindsight findings. A reviewer must distinguish a missing policy from a bad classifier, missing input, or disconnected executor. Sometimes the right improvement is repairing an input producer, not adding another rule. ## What does Hindsight analyze? Analysis runs asynchronously at **workspace scope**, with three selectable pipelines: | Pipeline | Evidence and questions | |---|---| | `event_analysis` | Event co-occurrence and coverage gaps: which patterns recur, and which lack corresponding policy? Deterministic statistics help ground the LLM's interpretation. | | `emu_analysis` | Existing policies: where are rules redundant, stale, or potentially conflicting? | | `trace_analysis` | Decision traces and available policy context: which conditions fail, which rules are never selected, and where might behavior need a new or revised policy? | An insight includes a pattern description, confidence scores, and available evidence references. Trace insights can include sample trace IDs; other insights can reference events and EMUs. Proposals link back to insights and can propose creating, merging, or archiving EMUs. Inspect `proposed_emu` for create/merge candidates and the target references for archive candidates. These are not all equally strong forms of evidence. Co-occurrence is not causation; a rarely selected rule may be an important safeguard. Confidence and expected utility are estimates, not proof that a change improves business outcomes. ## Record evidence the next cycle can use Enable decision tracing and emit events after material actions. Capture the actual result: executed, failed, declined, or still uncertain. A selected action in a decision trace does not establish that the action ran or helped the user. See [Python tracing and events](/python/) or [TypeScript tracing and events](/typescript/). Keep request/correlation identifiers and policy versions in your application records so reviewers can connect decisions to later outcomes. For routing, measure transfers and resolution; for refunds, measure duplicate prevention and reconciliation as well as successful payments. These outcome definitions and joins belong to your application; Hindsight does not infer a trustworthy reward function for you. Send only data you are authorized to use for LLM-assisted analysis. Redact unnecessary personal data and secrets before ingestion, and treat user-authored event text as evidence, not instructions to the reviewing agent. Analysis cannot recover expired history: raw decision traces have a default seven-day retention; event retention is organization-configurable. See [events and traces](/concepts/#events-and-traces). ## Start an analysis through the API Use a workspace with relevant recorded activity and a deployment with Hindsight processing enabled. Set `AMI_API_KEY`, `AMI_ORG`, `AMI_TEAM`, and `AMI_WORKSPACE` for that workspace; set `AMI_BASE_URL` to your API origin, such as `https://api.memrail.com` (without `/v1`). Keep the key in your environment, not in a prompt or repository. This request queues analysis and can consume LLM tokens. It creates insights/proposals; it does not activate policies: ```bash curl --fail-with-body --silent --show-error \ --request POST \ "${AMI_BASE_URL}/v1/workspaces/${AMI_WORKSPACE}/hindsight/analyze" \ --header "Authorization: AMI-Key ${AMI_API_KEY}" \ --header "X-AMI-Org: ${AMI_ORG}" \ --header "X-AMI-Team: ${AMI_TEAM}" \ --header 'Content-Type: application/json' \ --data '{"pipelines":["event_analysis","emu_analysis","trace_analysis"],"analysis_window":"P7D","force":false}' ``` The API returns HTTP `202` with `job_id`, initial `status: "pending"`, and a `status_url`. Poll that URL on the same API origin using the same authentication and organization/team headers. Jobs move through `pending` and `processing` to `completed`, `failed`, or `cancelled`. An already-active workspace job normally returns `409`; wait for it instead of forcing overlapping work. All paths below are relative to the API origin and require the same headers. Replace `{workspace}`, `{job_id}`, and `{proposal_id}` with the relevant values. | Method and path | Purpose | |---|---| | `GET /v1/workspaces/{workspace}/hindsight/jobs/{job_id}` | Poll progress and retrieve summary counts or an error | | `GET /v1/workspaces/{workspace}/hindsight/jobs` | Inspect full job results, including skipped pipelines and budget-limited completion | | `GET /v1/workspaces/{workspace}/hindsight/insights` | Read detected patterns and supporting evidence | | `GET /v1/workspaces/{workspace}/hindsight/proposals?state=PROPOSAL&source=hindsight` | Read candidates awaiting review | | `GET /v1/workspaces/{workspace}/hindsight/proposals/{proposal_id}/validation` | Read a stored validation report, if present; `404` can mean no report exists yet | | `POST /v1/workspaces/{workspace}/hindsight/proposals/{proposal_id}/validate` | Refresh the proposal's validation report without deploying or promoting it | List responses are paginated with `limit`, `offset`, and `total`. A completed job is not a coverage certificate: pipelines may be skipped or processing may stop at a budget limit. Inspect the full job's `result.skipped_pipelines` and `result.budget_exceeded`, not only its status or proposal count. ## Turn a proposal into a reviewed policy change Generated candidates start in proposal state `PROPOSAL`. They are not active EMUs. Refresh and inspect the proposal's validation report, then validate the exact JSONL candidate through `emu-plan --strict` before release. The **atom schema registry** records observed input schemas; checks against it do not establish business correctness, authorization, or that every caller supplies those inputs. Revalidation updates feedback, not deployment approval. For production, use Hindsight as a source of candidate changes for the [JSONL workflow](/production/): 1. Inspect the insight's evidence, the proposed rule, and the exact workspace/project and affected EMUs. Keep the insight/proposal IDs with the review. 2. Translate the intended change into `emus/emus.jsonl`; do not copy the entire proposal envelope as a deployable EMU. Verify tool IDs/versions, trusted atom sources, policy fields, and literal-key idempotency for side effects. 3. Run positive, negative, missing-input, competing-policy, and executor tests. For a merge or archive, demonstrate which existing behavior remains covered. 4. Review `emu-plan --strict`, which validates the complete candidate before writes, and evaluate in staging with dispatch disabled. Release only with explicit approval, application-controlled rollout, outcome checks, and a rollback plan. The proposal lifecycle API can also register EMUs and change their lifecycle. Treat those operations as policy writes, not harmless review labels; do not assume it enforces your staged approval process. Avoid mixing direct promotion with a Git-managed production policy. See [lifecycle behavior](/emus/#lifecycle-states) for the limits of shadow and canary states. Give a coding agent a bounded assignment: ```text Review this Hindsight insight and proposal against the current application. Verify the evidence and input producers. State the expected outcome change. Prepare the smallest policy-as-code diff and regression tests, including competing policies and executor authorization. Preserve permissions, approval requirements, duplicate protection, and fallback behavior. Do not apply, promote, archive, enable dispatch, or deploy. Flag any additional required change before making it. ``` The [reviewable refund change](/declarative-agent-control/) shows what this contract looks like for a concrete policy edit. ## How do later cycles avoid repeating the same work? Run subsequent analysis from your own workflow after enough new evidence arrives. Hindsight uses input watermarks and processed-record tracking to reduce repeated analysis, plus canonical pattern/proposal deduplication. Proposal generation skips patterns already proposed or rejected; optional semantic deduplication can reduce similar suggestions. Repeated runs are incremental, not guaranteed full replays of the requested window. That makes iteration more manageable, but not exhaustive. Check coverage and retain a separate evaluation dataset. Rejection history helps avoid repeating a proposal; it is not model retraining or proof that the model learned a general constraint. Define how your team will reconsider a rejected hypothesis when materially new evidence arrives. ## Can the improvement process itself improve? Hindsight uses versioned analysis prompts and records prompt/model provenance on insights and proposals. That gives you a second reviewable surface: compare analysis versions against a fixed dataset and assess evidence quality, duplicate suggestions, missed cases, and review cost. Changing those prompts is a separate controlled change. Hindsight does not automatically rewrite its own prompts, objectives, permissions, or model weights. Keep approval authority and evaluation criteria outside the proposing agent's control. Start with one decision point, one outcome measure, and one reviewed candidate. After release, record what actually changed and feed that evidence into the next analysis. The loop is recursive because each tested policy changes the behavior the next cycle studies—not because the system is allowed to approve its own conclusions. --- Source: https://docs.memrail.com/production/ # Production workflow Manage EMUs as reviewed policy-as-code: pull, edit, plan, apply, and commit the updated lock file. Test in staging, control lifecycle transitions explicitly, and verify deployed policy before enabling execution. The examples below use a separate `staging` workspace. Evaluate there before repeating a reviewed workflow against production with its own directory and lock file. Do not change a directory's workspace/project target casually: the lock file belongs to that deployed scope. ## Repository layout ```text your-project/ ├── emus/ │ ├── emus.jsonl │ └── .emu.lock.jsonl ├── src/ │ └── control/ │ ├── atoms.py │ ├── decision_points.py │ └── tools.py └── .github/ └── workflows/ └── memrail.yml ``` Commit both JSONL files. `emus.jsonl` is the desired policy; `.emu.lock.jsonl` tracks the deployed state and content hashes used for reliable plans. ## Pull remote state ```bash memrail emu-pull ./emus/ -w staging -p support-agent ``` Pull before editing when teammates or automation may have changed remote state. Do not hand-edit `.emu.lock.jsonl`. ## Edit one EMU 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"} ``` Formatting each policy on one physical line makes additions, changes, and removals unambiguous in code review. Use an editor or formatter that validates each line independently. Invoke the JSONL policy at `decision_point="support.response"`; keep routing names in trusted application code. Follow the shared [binding and lifecycle contract](/concepts/#bindings-and-lifecycle). ## Plan with validation ```bash memrail emu-plan ./emus/ -w staging -p support-agent --strict ``` Review: - policies to create, update, or archive; - trigger syntax and schema errors; - intended decision-point bindings and any explicit lifecycle transitions; - changes to action tools, versions, or interpolated arguments; - policy controls such as priority, cooldown, idempotency, and exclusions. Removing a tracked policy line represents archival on apply. Treat an unexpected archive as a blocking plan error. `--strict` validates the complete candidate set before writes and stops for errors, unavailable validation, or incomplete reports. `--validate` requests the same diagnostics without making them a blocking gate; `--validate-only` performs local JSONL/DSL checks and cannot be combined with `--strict`. ## Apply new policies in shadow ```bash memrail emu-apply ./emus/ \ -w staging \ -p support-agent \ --yes \ --strict \ --target-state shadow ``` Use shadow traces to compare proposed behavior under the [binding and lifecycle contract](/concepts/#bindings-and-lifecycle). Strict preflight is not a multi-record transaction. Serialize deployments, inspect apply results, and reconcile partial failures. The lock file records sync state; it is not a distributed lock. ## Validate the whole project ```bash memrail emu-validate -w staging -p support-agent memrail action-connectivity -w staging -p support-agent memrail tool-get-schema -w staging -p support-agent ``` Validation is broader than syntax. Investigate never-seen atoms, stale producers, mismatched types, placeholder gaps, and missing tools. The atom schema registry (ASR) observations may span team workspaces; they do not prove that an atom is present on every caller's path. Tool-schema checks do not prove a real handler, compatible version, or external credentials exist. Resolve `WARN-POLICY-GAP` findings with structured cooldown and `policy.idempotency` settings. Use literal state keys such as `"scope": ["refund.request_id"]`, not templates or the legacy `idempotency_key_template` field. ## Register tools coherently A Python tool registry used by CLI discovery belongs at module scope and should import without the rest of the application: ```python from memrail.tools import ToolRegistry registry = ToolRegistry() @registry.tool( name="ticket_escalator", description="Move a ticket to a named support queue", schema={ "type": "object", "properties": { "ticket_id": {"type": "string"}, "queue": {"type": "string"}, }, "required": ["ticket_id", "queue"], }, projects=["support-agent"], ) async def ticket_escalator(ticket_id: str, queue: str) -> dict: ... ``` ```bash memrail tool-list --path ./src/control --project support-agent memrail tool-register --file ./src/control/tools.py \ -w staging --project support-agent memrail action-connectivity -w staging -p support-agent ``` Replace the function body with your executor and keep its definition module importable by the CLI. Register a compatible schema in each project, then test the tool ID, version, arguments, permissions, and handler together. The server validator can fall back from project-level to workspace- or organization-level schemas. Runtime selection does not enforce tool availability or execute the tool. Maintain the project-level connection as an application contract; do not mistake a clean schema report for execution authorization. ## Observe before promotion During isolated shadow evaluation, compare candidate trace reasons, scores, and suppressions with actual application behavior. A matching shadow candidate has a `shadow_state` suppression and `passed: false`; it does not appear in `selected`. Sample: - expected positive matches; - expected negative cases; - missing optional and required context; - each model tag value, including `unknown`; - cooldown, exclusion, and idempotency suppression; - alternate callers of the same executor; - Memrail timeout and service failure; - executor success, retry, and terminal failure. Record mismatches as atom-contract, trigger, topology, or action-connectivity defects. Do not tune a trigger around missing instrumentation without correcting the producer. If you use `dry_run` against active/canary policies, the response can contain selections. Combined SDK helpers skip execution and ACK; custom dispatchers must also honor evaluation-only mode. Diagnostics and accounting may still occur. Reusing an invocation idempotency key with changed context, scope, or options returns a conflict; use a new key for a live request following a dry run. The SDK executor enforces returned policy and lifecycle metadata. Advisory tool calls/routes are not dispatched; advisory prompts/directives may run. `require_human` needs application-verified consent for every action type. Your handlers still enforce business permissions and duplicate protection. ## Promote through canary Configure and test an explicit application cohort decision before changing a policy to `canary`. The SDK executor skips canaries outside that cohort; the API does not assign a traffic percentage. Use stable entity-based assignment when exposure must be sticky. A skipped canary does not consume its action lock or cooldown. Successful execution ACK establishes those controls, so business-level duplicate protection must be in place before dispatch. After those controls and your review criteria are in place, use explicit lifecycle commands, then verify the remote result: ```bash memrail change-state support.enterprise_context canary \ -w staging -p support-agent memrail change-state support.enterprise_context active \ -w staging -p support-agent ``` These commands target staging. Production promotion is a separate reviewed operation in the production scope. Criteria should include match accuracy, action success, latency, review volume, and safe-fallback use—not only error rate. Do not rely on a JSONL state edit to perform the lifecycle change. ## Acknowledge and emit outcomes Apply the [outcome and acknowledgment contract](/concepts/#outcomes-and-acknowledgment). For an ACK-gated cooldown, `status="acknowledged"` starts the cooldown; `status="failed"` does not. This closes the loop: ```text selected → executed → acknowledged → event emitted → later policy can query outcome ``` ## CI planning example This example prepares a strict plan; it does not deploy. Set `MEMRAIL_REQUIREMENT` to your project's tested dependency requirement. A production apply job repeats strict validation and requires business tests, reviewed scope/lifecycle operations, and deployment approval. ```yaml name: Memrail policy plan on: pull_request: paths: ['emus/**'] workflow_dispatch: jobs: policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - run: python -m pip install "${MEMRAIL_REQUIREMENT:?Set a tested Memrail build requirement}" env: MEMRAIL_REQUIREMENT: ${{ vars.MEMRAIL_REQUIREMENT }} - name: Plan and validate run: memrail emu-plan ./emus/ --strict env: AMI_API_KEY: ${{ secrets.AMI_API_KEY }} AMI_ORG: acme AMI_WORKSPACE: staging AMI_PROJECT: support-agent ``` Use protected environments or equivalent approval for consequential changes. Provide credentials only to trusted workflows; forked pull requests should not receive them. Do not print plans containing secrets or sensitive interpolated data. ## Rollback Rollback combines reviewed policy content with explicit scope and lifecycle verification: 1. revert the reviewed `emus.jsonl` commit; 2. run `emu-plan` and inspect the resulting content changes; 3. apply, inspect validation feedback, and verify remote definitions; 4. restore lifecycle through an explicit transition and reconcile the JSONL snapshot; 5. confirm the safe application fallback remains in effect; 6. investigate traces and executor outcomes before retrying promotion. Reverting source does not rewind remote history or undo external side effects. Review routing changes and duplicate protection under the [integration invariants](/concepts/#integration-invariants) before rollback. For immediate containment, change the affected EMU to `inactive` if your incident procedure authorizes it. Preserve traces and outcome events needed for analysis. ## Release gate - Plan shows only intended create, update, and archive operations. - JSONL and lock file are committed at the deployed revision. - Remote lifecycle and decision-point binding match the reviewed execution contract. - Strict candidate validation succeeds before writes; business and execution tests pass. - All trigger dependencies are present and type-compatible. - All actions are connected in the same project. - Side effects have deliberately scoped cooldown, literal-key idempotency, and bounded retry. - Shadow evidence covers intended and unintended matches without interfering with live arbitration. - Advisory/approval handling and evaluation-only dispatch suppression are explicit and tested. - A real canary cohort gate, metrics, and fallback use meet explicit thresholds. - ACK delivery failures have a reconciliation path independent of trace persistence. - Rollback has been exercised or is mechanically verifiable. --- Source: https://docs.memrail.com/troubleshooting/ # Troubleshooting To diagnose a missing Memrail action, separate trigger matching, policy selection, and application execution. Inspect decision traces for matches and suppressions, and application logs for what actually ran. Keep application dispatch disabled during diagnostic evaluations. ## An EMU does not fire Enable dry run and tracing at the call site, with application dispatch disabled. Pass the policy's named decision point: ```python from memrail.atoms import state from memrail.models import InvokeOptions, TraceOptions response = await client.decide( decision_point="support.triage", context=context, options=InvokeOptions(dry_run=True), trace=TraceOptions(enable=True), ) ``` Then check: 1. Is the EMU in a state that is evaluated? 2. Does the EMU's `decision_point` exactly match the named call? 3. Is the call scoped to the expected organization, workspace, and project? 4. Is every state and tag dependency present? 5. Are the values and types capable of satisfying the operators? 6. Do event names, attributes, and timestamps match? 7. Was the match suppressed by cooldown, idempotency, or exclusion? Run project validation: ```bash memrail emu-validate -w production -p support-agent ``` ### Missing atom ```dsl // Trigger requires two inputs. state.customer.tier == 'enterprise' AND tag.intent == 'refund' ``` If the decision call sends only the tier, this positive conjunction evaluates false. That does not mean every trigger with a missing input is false: `NOT` reverses a false predicate, and another `OR` branch may match. In particular, `NOT state.user.banned` is true when the key is missing. Use explicit presence and boolean contracts for authorization: ```dsl state.user.banned EXISTS AND state.user.banned == false ``` ### Conditional omission A builder may omit the exact value needed for the exceptional case: ```python # Broken for users who never logged in. if user.last_login_at is not None: context.append(state("user.days_since_last_login", days)) ``` Represent “never” explicitly with a separate boolean or documented sentinel. Do not assume missing is equivalent to zero, false, or infinity. ### Wrong atom kind `state("ticket.priority", "high")` does not satisfy `tag.priority == 'high'`. Match the trigger namespace and source semantics; state keys also need at least two namespaced segments. ## A negative event guard always passes ```dsl NOT event.agent.sent.email IN 'P7D' ``` If no producer emits `agent.sent.email`, this condition is always true. Search emitters and compare exact name order: ```bash rg -n 'emit_event|ingest_event|emitEvent|agent.*sent.*email|email.*sent.*agent' src ``` Emit the canonical event after the email service confirms success. Do not “fix” the trigger by removing the duplicate guard. Also check event retention, ingestion lag, scope, and missing filter identifiers. Ninety days is the default organization event-retention setting, not a fixed maximum. A negative query over expired or never-collected history cannot establish that the action never happened. ## An event matches the wrong entity Unscoped event queries may see another entity’s event. Add an attribute at emission and filter it with a template literal: ```dsl state.customer.id EXISTS AND event.agent.sent.email WHERE customer_id == '{{customer.id}}' IN 'P7D' ``` `WHERE customer_id == state.customer.id` is invalid. The event attributes must actually contain `customer_id`, with the same string value as the resolved template. Missing placeholders stay literal and normally match nothing; a surrounding `NOT` would then evaluate true. The explicit `EXISTS` guard prevents missing context from becoming permission. ## An action is selected but nothing happens First distinguish “selected” from “executed.” A decision trace proves policy selection, not external side-effect completion. ```bash memrail action-connectivity -w production -p support-agent memrail tool-get-schema -w production -p support-agent ``` For a tool call, verify: - `tool_id` exactly matches a registered tool; - the EMU includes required `tool.version`; - the intended tool registration and runtime executor are connected to this project; do not infer that from a validator result that may use broader registry fallbacks; - the runtime dispatcher recognizes the action type and version; - credentials and network access exist in the executor environment; - errors are surfaced rather than swallowed; - the activation is acknowledged only after the intended outcome; - success emits a material event. For routes and decision prompts, inspect the destination registry and timeout behavior. For context directives, confirm they are actually added to the next prompt rather than merely logged. ## Registration returns 422 Check the enforced schema rules: - every `tool_call.tool` includes `"version": "1.0.0"` or the actual registered version; - lifecycle `state` is lowercase; - cooldown is `{ "seconds": N, "gate": "ack" }` or uses a supported gate, not an ISO string; - idempotency is a structured object; - state keys are lowercase and namespaced; - the authorization scheme for direct HTTP is `AMI-Key`, not `Bearer`. Run `memrail emu-plan ./emus/ --strict` before applying. Strict mode validates candidate definitions against the server and rejects errors, unavailable validation, or incomplete reports before writes. See [Production workflow](/production/) for release checks. ## Trigger syntax is rejected Common causes: ```dsl // Invalid: lowercase logical operator and double quotes state.user.tier == "premium" and tag.intent == "upgrade" // Valid state.user.tier == 'premium' AND tag.intent == 'upgrade' ``` Also check the mandatory `IN 'duration'` clause, three-token `subject.verb.object` event names, numeric comparisons against numeric state, and template literals in event `WHERE` filters. A duration beyond configured retention is syntactically valid but may be flagged as a temporal-feasibility warning. See the [Trigger DSL reference](/triggers/). ## A model tag creates inconsistent behavior Inspect the boundary before Memrail: - Is the classifier output constrained to a fixed enum? - Is casing normalized before building the tag? - Is `unknown` a supported value? - Is the tag always produced or conditionally omitted? - Did a model or prompt version change the distribution? Do not solve classifier drift by adding free-form variants to deterministic policy. Restore the contract at the inference boundary. ## Wrong project or workspace Print non-secret scope values at startup and compare them with CLI arguments. Default team behavior can hide an unexpected scope if one service sets `AMI_TEAM` and another does not. ```bash env | rg '^AMI_(ORG|TEAM|WORKSPACE|PROJECT|BASE_URL)=' memrail list-emus -w production -p support-agent ``` Never print `AMI_API_KEY`. Keep the EMU, intended tool registration, and executor coherently scoped to the same project as an integration contract; current registry fallback behavior is not a hard guarantee of that coherence. Cross-project event queries are separate and should use an intentional documented dependency. ## Diagnostic codes Download the [error, warning, and suppression catalog](/reference/diagnostic-codes.json). It separates EMU validation findings, decision trace suppressions, and HTTP error codes, so agents can branch on identifiers instead of parsing English messages. Not every HTTP error has a `code`; always retain its status and response details. For invalid EMU structure, validate against the [EMU JSON Schema](/schemas/emu.schema.json), then use strict preflight for DSL and registry checks. The catalog includes suggested responses; a warning is evidence to investigate, not automatic permission to change policy. ## Evaluation changes live behavior Check custom dispatch against the [evaluation and execution contract](/concepts/#evaluation-and-execution), and cohort configuration against the [lifecycle contract](/concepts/#bindings-and-lifecycle). Use [production rollout checks](/production/#observe-before-promotion) to test those boundaries. ## Acknowledgment returns 404 Check the activation ID, organization/workspace/project scope, and receipt eligibility under the [outcome and acknowledgment contract](/concepts/#outcomes-and-acknowledgment). Retry transient delivery failures with bounded backoff. ## Lock file is out of sync Pull the current remote state, inspect the change, then resolve source differences: ```bash memrail emu-pull ./emus/ -w production -p support-agent memrail emu-plan ./emus/ -w production -p support-agent --strict ``` Do not manually forge lock hashes. If remote policy changed outside the reviewed workflow, preserve that evidence and reconcile it explicitly. ## Unsafe fallback detected An agent control call that fails and then executes the original model proposal is a bypass. Define failure by decision-point consequence: | Decision point | Reasonable fallback | |---|---| | prompt steering | base prompt without optional directive | | read-only retrieval | bounded public corpus or no result | | external tool with side effects | deny or require human review | | irreversible workflow transition | hold current state | | notification | queue for retry with idempotency | Test timeout, authentication failure, malformed response, and empty selection. Fallback is part of the control policy even when implemented in application code. ## Reset a development workspace Workspace purge is destructive and requires an organization-level key: ```bash memrail purge-workspace development --targets emus,traces,events --yes ``` Confirm the exact workspace and target list before running it. Do not use a production example as a copy-paste default. ## Minimal diagnostic report When escalating an issue, include: - SDK and CLI versions; - non-secret org/workspace/project and decision-point names; - EMU key and lifecycle state; - trigger and declared action type; - redacted context keys with value types; - validation warning codes; - invocation or trace ID; - whether selection, dispatch, acknowledgement, and outcome event occurred; - expected and actual behavior. Do not include API keys, personal data, or raw sensitive prompts. --- Source: https://docs.memrail.com/agents/install-skill.md # Install the Memrail skill These instructions are for a coding agent whose user has explicitly requested installation. Reading this page alone does not authorize writes, dependency installation, an audit, or implementation. ## Scope Install **only the complete Memrail skill** for the client the user is using. Preserve existing skills. Do not install the Python or TypeScript SDK, edit a repository, configure credentials, or register Memrail policy unless the user separately requests it. The official source is https://github.com/cadenzai/memrail-claude-plugin. Its `skills/memrail/` folder is already a portable Agent Skills package: `SKILL.md` plus all of `references/`. The rest of the Claude plugin is not required for a standalone skill install. ## Identify the client and destination Use your actual client identity and configuration, not whichever unrelated CLI is first on PATH. If uncertain, ask which client the user wants. | Client | Installer flag | Default destination | |---|---|---| | Codex | `--agent codex` | `~/.agents/skills/memrail/` | | Claude Code | `--agent claude` | `~/.claude/skills/memrail/` | | OpenCode | `--agent opencode` | `~/.config/opencode/skills/memrail/` | Confirm custom configuration and installed-client support before writing. Claude uses `CLAUDE_CONFIG_DIR` when set. OpenCode also discovers shared `.agents/skills` and `.claude/skills`; check for an existing complete skill there before adding a duplicate. For project scope, custom paths, or another supported client, use `--dir` with an absolute skills parent; do not add repository files without authorization. Client references: [Codex](https://learn.chatgpt.com/docs/build-skills), [Claude Code](https://code.claude.com/docs/en/skills), [OpenCode](https://opencode.ai/docs/skills/). ## Review and install Explain the destination and skill-only scope. If authorized, download the installer into a fresh temporary directory, inspect its contents, and run it for the chosen client. This example is for Codex; substitute the correct flag: ```bash memrail_setup="$(mktemp -d)" curl -fsSL https://docs.memrail.com/install.sh -o "$memrail_setup/install.sh" sh "$memrail_setup/install.sh" --agent codex --dry-run sh "$memrail_setup/install.sh" --agent codex ``` Read the downloaded script before executing it. It pins the official skill archive to a commit and verifies its SHA-256. It installs the complete folder at the selected destination; no plugin conversion is needed. Default auto-detection is only a convenience for humans with one installed client. Agents should pass an explicit choice. If a skill already exists, report its location and inspect it rather than automatically replacing it. Use `--replace` only when the user authorizes replacing the existing skill. That option preserves the old copy under `~/.local/state/memrail/skill-backups/`, outside normal discovery paths, and prints the backup location. Do not remove other clients' copies. If you cannot fetch or execute the installer, give the user the [human installation guide](https://docs.memrail.com/agents/). A manual alternative is to inspect the official repository, select a trusted revision, and copy its entire `skills/memrail` directory into the chosen client's documented location, refusing to overwrite an existing copy. Do not claim success without filesystem evidence. ## Verify 1. Report the exact installed directory and confirm `SKILL.md` exists there. 2. Confirm its `references/` directory and all files linked from `SKILL.md` are present. Do not substitute verification of another client's installation. 3. Read `SKILL.md` completely before Memrail work. Read each task-relevant reference it selects completely too. 4. If the client does not discover the new skill, reload or restart it, then check discovery and skill permissions. Distinguish files installed from skill actually loaded. 5. Stop after installation and verification unless the user requested a subsequent task. Never expose credentials in the report. ## Optional SDK installation Only when requested, install the application dependency using its existing package manager. Python projects use `memrail`; TypeScript projects use `@memrail/sdk`. Do not infer a Python dependency from a skill-install request. For a Python 3.9+ project with an active virtualenv, the installer supports `--with-sdk` alongside the selected `--agent`. It uses that virtualenv's pip or explicitly targeted `uv pip`; it does not create an environment or update dependency manifests. Verify the import and package version inside the same project environment. If the SDK step fails, the skill may already be installed; report partial completion and do not blindly replace it. Before implementing an integration, read the [integration invariants](https://docs.memrail.com/concepts/#integration-invariants) and the reference for the project's SDK. ## First safe task If the user asked for an audit but did not authorize implementation, perform a read-only topology audit. Do not edit code, register EMUs, change external services, or expose credentials. Map: - consequential decision points and hidden model authority; - state, tag, and event sources; - trigger reachability and value reachability; - action executors, routes, prompts, and connectivity gaps; - project coherence and bypass paths; - safe seams for incremental migration. Return evidence with file and symbol locations, distinguish facts from inferences, and wait for approval before implementing. ## Integration requirements - Use `decide(context=..., decision_point="your.point")` in Python and `decide({ context, decisionPoint: 'your.point' })` in TypeScript. Bind each EMU to that same name. - Use modern lowercase dot-notation keys and uppercase DSL operators. - Every production `tool_call` includes `tool.version`. - Manage production EMUs with `emu-pull`, `emu-plan`, and `emu-apply` JSONL workflow. - Check trigger reachability and action connectivity before deployment. - Enforce same-project tool availability in the application's deployment and execution contract; a registered tool is not a runtime authorization guarantee. - Use an isolated project for integration-test fixtures. - Validate model-derived categories at runtime, not only with Python `Literal` or TypeScript annotations. - Apply the linked integration contract to evaluation, execution, and outcome recording. ## Integration contract Use named `decision_point` bindings and follow the [integration invariants](https://docs.memrail.com/concepts/#integration-invariants). Validate policy structure with the [EMU schema](https://docs.memrail.com/schemas/emu.schema.json), interpret the [diagnostic catalog](https://docs.memrail.com/reference/diagnostic-codes.json), and start from the [complete runnable example](https://docs.memrail.com/getting-started/). Canonical human installation guide: https://docs.memrail.com/agents/