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.
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 to help with setup. Never commit credentials.
2. Inspect the policy and executor#
The complete policy file is one JSON object on one line. It is expanded here for readability:
{
"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.
Here is the entire runnable Python file:
"""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#
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 explains their codes. Commit emus/emus.jsonl and the generated emus/.emu.lock.jsonl together.
4. Check both branches#
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.
5. Promote and execute#
After verifying both branches, explicitly promote this read-only policy in the development project and reconcile the local snapshot:
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 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 for structural checks and production workflow for reviewed deployments.
For a TypeScript integration, continue with the TypeScript SDK. For an existing application, start with one controlled execution boundary.