Open-source TypeScript alpha · WebMCP today

Add control and recovery to actions—not model responses.

Define an application action once. Remy adds policy, approval, receipts, and recovery, then exposes it through WebMCP today and other adapters later.

01

Five-minute quickstart

Install the public alpha packages from npm. Remy accepts Standard Schema validators; this example uses Zod.

Terminal
npm install @remy-ai/core@alpha @remy-ai/webmcp@alpha zod

Create a client around the service your application already owns, wrap one function, register the action, then expose it through WebMCP. The complete version lives in the typechecked document example.

Five-minute shape
import { createRemy, succeed } from "@remy-ai/core"
import { registerWebMCP } from "@remy-ai/webmcp"
import { z } from "zod"

const remy = createRemy({ context: () => documentService })

const renameDocument = remy.defineAction({
  name: "rename_document",
  title: "Rename document",
  description: "Change the open document title.",
  kind: "write",
  input: z.strictObject({ title: z.string().min(1).max(120) }),
  risk: "low",
  preview: ({ input, context }) => ({
    summary: `Rename document to ${input.title}.`,
    changes: [{ label: "Title", before: context.title, after: input.title }],
    recovery: { title: context.title },
  }),
  execute: ({ input, context }) => {
    context.rename(input.title)
    return succeed({ title: input.title })
  },
  recovery: {
    kind: "exact",
    execute: ({ receipt, context }) => {
      context.rename(receipt.recovery.title)
      return succeed({ title: receipt.recovery.title })
    },
  },
})

remy.register(renameDocument)
await registerWebMCP(remy, { signal })
InstallInstall core and the adapters your application uses. React stays optional.
DefinePass one schema and wrap existing preview, execute, and recovery functions.
ExposeregisterWebMCP creates imperative tools and uses the same runtime validation.
ApproveCore records awaiting_approval; adapters can await the same action so one invocation resumes after the decision.
RecoverExact or compensating handlers receive typed recovery data and execution output.
EnforceKeep authentication, authorisation, and side effects authoritative in the host service.

Remy wraps host functions. Keep authentication, authorisation, state, and durable side effects authoritative in your application.

To run the Morrow demo or contribute to Remy, clone the repository and start the local Next.js application:

Local demo
git clone https://github.com/MustafaK99/Remy.git
cd Remy
npm ci
npm run dev
02

Core concepts

Remy wraps the functions and services an application already uses. Your application keeps its data, authentication, authorisation, editor history, side effects, and interface.

Application contextA function returning your existing services. Remy never owns application state.
ActionA typed semantic operation with input, preview, execution, risk, and truthful recovery.
PolicyA replaceable function that allows, stages, denies, or asks for approval.
ReceiptA bounded, human-readable record of the decision and state change.
JournalAppend-only receipt and event persistence, independent from application persistence.
AdapterA protocol translator. It exposes actions but does not contain business policy.
03

Define an action

Call remy.defineAction() so the context, schema input, execution output, and recovery material are inferred. Pass one Standard Schema V1 validator. Zod 4 provides runtime validation and WebMCP JSON Schema from that same object.

examples/minimal.ts (abridged)
import { z } from "zod"
import { createRemy, succeed } from "@remy-ai/core"

const remy = createRemy({
  context: () => documentService,
})

const renameDocument = remy.defineAction({
  name: "rename_document",
  title: "Rename document",
  description: "Change the open document title.",
  kind: "write",
  input: z.strictObject({
    title: z.string().trim().min(1).max(120),
  }),
  risk: "low",
  preview: ({ input, context }) => ({
    summary: `Rename the document to “${input.title}”.`,
    resources: ["document:title"],
    changes: [{
      label: "Document title",
      before: context.getTitle(),
      after: input.title,
    }],
    recovery: { previousTitle: context.getTitle() },
  }),
  execute: async ({ input, context }) => {
    await context.setTitle(input.title)
    return succeed({ title: context.getTitle() })
  },
  recovery: {
    kind: "exact",
    execute: async ({ receipt, context }) => {
      await context.setTitle(receipt.recovery.previousTitle)
      return succeed({ title: context.getTitle() })
    },
  },
})

remy.register(renameDocument)
Run with inferred input and output
const result = await remy.run(
  renameDocument,
  { title: "Launch notes" },
  {
    actor: "agent",
    transport: "webmcp",
    runId: "run-42",
    taskId: "edit-launch-doc",
  },
)

if (!result.ok) {
  console.error(result.code, result.error)
}
inputOne strict schema. Use jsonSchema only when the validator cannot expose a representable schema.
previewDescribe bounded resources and human-readable changes without mutating state.
executeCall the existing application function and return succeed(value) or fail(code, message).
redactInputExplicitly allowlist compact receipt fields; raw input is private by default.
exposeOutputExplicitly select the JSON-safe output returned through protocol adapters.

Authentication, authorisation, prices, permissions, and durable idempotency must still be enforced by the authoritative server.

04

Policy and capabilities

Built-in preview, ask, reversible, and trusted modes are presets. Supply a custom policy when the application needs different rules. Capabilities are generic strings such asdocuments.publish orcommerce.purchase.

Custom policy
import type { Policy } from "@remy-ai/core"

export const documentPolicy: Policy = ({ action, controls }) => {
  if (controls.paused) {
    return { outcome: "deny", reason: "Agent changes are paused." }
  }
  const missingGrant = action.requiredGrants.find(
    (grant) => !controls.grants.includes(grant),
  )
  if (missingGrant) {
    return { outcome: "require_approval", reason: "A grant is missing." }
  }
  return { outcome: "allow", reason: "Application policy allows it." }
}

Agent principals include an assurance level. The WebMCP identity tool creates self-reported attribution and never grants authority. Authenticated or verified principals must come from a trusted host integration.

Compilable source: examples/policy.ts

05

Exact recovery

Exact recovery restores the previous state. The preview returns typed private recovery material; the recovery handler consumes it. Remy checks declared resource versions first and appends a linked receipt instead of deleting the original.

In an editor, a native history token can be the recovery material. The host editor still owns and validates that history.

06

Compensation

Some effects cannot be rewound. A compensating recovery performs a new corrective action, such as cancelling a booking.

Compensating recovery
recovery: {
  kind: "compensating",
  execute: async ({ output, context }) => {
    const cancellation = await context.cancelBooking(output.id)
    return cancellation.ok
      ? succeed({ bookingId: output.id, cancelled: true })
      : fail("CANCEL_FAILED", cancellation.message)
  },
}

Label this as cancellation or compensation in the interface—not “undo.” Both the original and corrective receipts remain visible.

Compilable source: examples/action-variants.ts

07

Irreversible actions

Irreversible definitions expose no recovery handler. TypeScript rejects a fake undo on this branch of the action union. Use an explicit approval for genuinely consequential work.

Irreversible action
const publishDocument = remy.defineAction({
  name: "publish_document",
  title: "Publish document",
  description: "Publish the current document publicly.",
  kind: "write",
  input: z.strictObject({}),
  risk: "high",
  approval: "always",
  preview: () => ({
    summary: "Publish this document publicly.",
    changes: [{ label: "Visibility", before: "Draft", after: "Public" }],
  }),
  execute: async ({ context }) => succeed(await context.publishDocument()),
  recovery: { kind: "irreversible" },
})

Compilable source: examples/action-variants.ts

08

WebMCP adapter

The headless adapter imports only public core contracts. It feature detects document.modelContext, converts the action schema, registers each tool once, validates input again at execution, and reports partial registration failures. Approval-gated tool promises remain pending by default, while the protocol-neutral receipt stays authoritative in core.

Headless registration
import { registerWebMCP } from "@remy-ai/webmcp"

const lifecycle = new AbortController()
const registration = await registerWebMCP(remy, {
  signal: lifecycle.signal,
  approvalTimeoutMs: 120_000,
})

if (registration.status === "partial") {
  console.error(registration.failures)
}

// Unregister every tool when this integration unmounts.
lifecycle.abort()
get_remy_statusRead controls and attribution without changing application state.
identify_assistantAttach self-reported identity for receipts; never authorisation.
request_remy_controlsRestrictions apply; requests for more authority wait for the person.
get_action_historyReturn compact agent-action receipts, not browsing or transcripts.
revert_actionRequest version-safe exact or compensating recovery.

Unsupported browsers return unsupported. The application and manual controls continue to work normally.

09

React integration

React is optional. useRemySnapshot(remy)uses the engine as a proper external store with stable subscriptions and referentially stable snapshots. Build any accessible approval surface your product needs.

Client component
"use client"

import type { RemyClient } from "@remy-ai/core"
import { useRemySnapshot } from "@remy-ai/react"

export function AgentActivity({
  remy,
}: {
  readonly remy: RemyClient<unknown>
}) {
  const snapshot = useRemySnapshot(remy)
  const agentReceipts = snapshot.receipts.filter(
    (receipt) => receipt.actor === "agent" || receipt.reversesReceiptId,
  )

  return (
    <ol aria-label="Agent activity">
      {agentReceipts.map((receipt) => (
        <li key={receipt.id}>
          {receipt.summary} — {receipt.status}
        </li>
      ))}
    </ol>
  )
}

The Morrow Action Center is demo UI, not a required Remy design. Agent activity excludes ordinary user clicks; developer run summaries belong in developer tooling, not the end-user action panel.

Compilable source: examples/react-action-center.tsx

10

Persistence and privacy

The application and Remy have separate persistence boundaries. Remy stores schema-versioned semantic receipts and journal events; the Morrow demo stores its fictional shop state separately.

StoredAction/version, IDs, bounded actor, decision, summary, allowlisted changes, resource versions, status, time, duration, error code, and recovery links.
Private in memoryValidated input, execution output, pending execution data, and recovery material.
Never by defaultApplication state, raw input/output, prompts, transcripts, DOM recordings, secrets, payment details, or binary payloads.

Use createMemoryJournalStore() or the safe, namespaced createBrowserJournalStore(). Browser restore validates version 1 data and fails closed on corrupt or incompatible payloads. Durable recovery needs an explicit, redacted application serializer in a future store integration.

11

Testing

Run the same checks used in CI:

Terminal
npm run lint
npm run typecheck
npm run test:run
npm run build
npm run verify:packages
TypesCheck inferred input/output and invalid recovery combinations with @ts-expect-error assertions.
PolicyTest allow, stage, deny, approvals, capabilities, and custom policy injection.
State safetyTest stale approvals, version conflicts, idempotency, linked recovery, and safe restoration.
AdaptersRegister a non-commerce action, execute it, and abort every registration during cleanup.
12

Production checklist

  • Authenticate and authorise each state-changing operation at the server or owning service.
  • Recompute totals, eligibility, permissions, and versions from authoritative state.
  • Use strict schemas and allowlist only receipt fields safe for operators and users.
  • Keep credentials, payment details, prompts, transcripts, and application snapshots out of the journal.
  • Apply CSRF protection to cookie-authenticated writes and rate-limit abuse-prone operations.
  • Enforce idempotency at the real side-effect boundary, not only inside a browser tab.
  • Fail closed when approval context, recovery material, or resource versions are stale.
  • Set retention, export, and deletion rules before connecting durable storage.
  • Treat self-reported WebMCP identity as attribution only.
13

API reference

These alpha exports are curated from @remy-ai/core, @remy-ai/webmcp, and @remy-ai/react.

createRemy(options)Create a generic client around a host-owned context, policy, resources, and journal.
remy.defineAction(definition)Infer context, input, output, and recovery types and validate configuration early.
remy.register(action)Register one definition. Duplicate action names throw an actionable error.
remy.run(action, input, meta?)Typed application execution. Input and success output follow the action.
remy.runByName(name, unknown, meta?)Runtime-validated string dispatch for protocol adapters.
remy.waitForAction(id, options?)Wait for any adapter's action record to settle; timeout or cancellation preserves the receipt.
remy.approve(id) / reject(id)Resolve staged or waiting work after validating its current state.
remy.revert(id, meta?)Run exact or compensating recovery and append a linked receipt.
registerWebMCP(remy, options?)Register generic tools and return ready, partial, unsupported, or error status.
useRemySnapshot(remy)Subscribe to cached external-store snapshots from a client component.
summarizeActionRun(receipts)Derive action-only developer metrics; it is not required end-user UI.
14

Troubleshooting

WebMCP unsupportedThe browser does not expose document.modelContext. Manual application use should still work.
Schema registration failedUse a Standard Schema implementation with JSON Schema support or pass a restrictive jsonSchema override.
ACTION_NOT_REGISTEREDCall remy.register(action), then pass that same action object to remy.run().
STALE_APPROVALA referenced resource changed after preview. Prepare the action again.
VERSION_CONFLICTLater state makes exact recovery unsafe. Use an application-specific corrective path.
WAIT_TIMEOUT / WAIT_ABORTEDOnly the caller's bounded wait ended. The pending action receipt remains available.
RECOVERY_DATA_UNAVAILABLEPrivate recovery material is not persisted by default. Prepare a corrective action after reload.
npm cannot resolve an alphaInstall with the explicit @alpha tag and confirm the registry is https://registry.npmjs.org. The packages require Node 18.18 or later.
15

Roadmap and versioning

Receipts carry schemaVersion: 1; action definitions have their own version. The source API is alpha and may still change between alpha releases. The boundary is intentionally stable: host services → semantic actions → Remy core → adapters and optional UI.

Ship agents users aren't afraid to trust.

Try policy, approval, receipts, and recovery in the Morrow demo.

Open the live demo