Skip to content

Quickstart

OpenMatter is a TypeScript SDK, not a hosted runtime. Start with the in-memory Store and mock adapters, then replace infrastructure at the application boundary.

  1. Install the foundation

    Terminal window
    pnpm add effect @openmatter/runtime @openmatter/store-memory \
    @openmatter/integration-mock @openmatter/agent-mock
  2. Create an application

    import { makeMockAgentDriver } from "@openmatter/agent-mock";
    import { makeMockIntegration } from "@openmatter/integration-mock";
    import { createOpenMatter } from "@openmatter/runtime";
    import { makeMemoryStore } from "@openmatter/store-memory";
    const chat = makeMockIntegration({ id: "chat" });
    const worker = makeMockAgentDriver({ id: "worker", output: "Hello" });
    const app = createOpenMatter({
    store: makeMemoryStore(),
    integrations: { chat: chat.integration },
    agents: { worker: worker.driver },
    });
  3. Accept one work event

    await app.acceptFrom("chat", nativeWebhookBody);

A Loop packages the long-lived event behavior. Its handler explicitly constructs context, chooses session continuity, grants operations, and returns a terminal Reaction.

import { defineLoop } from "@openmatter/runtime";
const chatLoop = defineLoop(
{
id: "chat-agent",
spec: {
sources: ["chat.message.received"],
session: "per-work-thread",
},
},
(loopApp) =>
loopApp.on("chat.message.received", (work) =>
Effect.gen(function* () {
const context = yield* work.context.project({
scopeId: "project:openmatter",
workThreadId: "discussion:runtime",
items: [work.context.event()],
grants: ["chat.message.reply"],
});
const result = yield* work
.agent("worker")
.session({
scopeId: context.scopeId,
workThreadId: context.workThreadId,
privacyPartition: "team",
})
.turn({ context, allow: context.grants });
const reply = yield* work.effect(context, {
integrationId: "chat",
operation: "message.reply",
input: { text: result.output ?? null },
});
return work.react.effects([reply]);
}),
),
);
app.loop(chatLoop);

Every domain-complete accepted WorkEvent reaches one terminal Reaction. A valid outcome with no external work uses react.none(); infrastructure failures remain recoverable.

The definition above shows every decision explicitly. For the common Slack pattern—mentions, direct messages, slash commands, deterministic Scope and WorkThread bindings, and minimal reply grants—install the reusable Claude Tag Loop.