---
title: TypeScript SDK
description: Configure AMIClient, build typed context, invoke named decision points, emit events, trace policy selection, and dispatch Memrail actions in TypeScript.
eyebrow: SDK REFERENCE
keywords: Memrail TypeScript SDK, AMIClient, TypeScript AI agent control
last_updated: 2026-09-10
---

# 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<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](/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.
