SDK REFERENCE • AMI · DSL v2

TypeScript SDK

Configure AMIClient, build typed context, invoke named decision points, emit events, trace policy selection, and dispatch Memrail actions in TypeScript.

View raw Markdown

@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.

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; 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<string, any> | 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.

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.

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, 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 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 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. 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.