Skip to main content
The TypeScript SDK (@rkat/sdk) is a thin wrapper over Meerkat’s settled runtime-backed contracts. It spawns a local rkat-rpc subprocess and exposes the same session lifecycle used by the CLI, REST, JSON-RPC, and MCP surfaces as TypeScript-native Session and DeferredSession handles.

Getting started

1

Install the SDK

npm install @rkat/sdk
2

Install the RPC binary

cargo build -p meerkat-rpc --release
# Ensure target/release/rkat-rpc is on your $PATH
You also need an API key for at least one LLM provider (e.g. ANTHROPIC_API_KEY).
3

Configure tsconfig

The SDK is ESM. Your tsconfig.json must use Node16 module resolution:
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true
  }
}
4

Connect and run

import { MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect();

const session = await client.createSession("What is the capital of Sweden?");
console.log(session.text);
console.log(session.id);

const result = await session.turn("And what is its population?");
console.log(result.text);

await session.archive();
await client.close();

Method overview

MeerkatClient methods

MethodDescription
connect(options?)Spawn rkat-rpc, handshake, fetch capabilities
close()Kill the subprocess
createSession(prompt, options?)Create a session, run the first turn, return a Session
createSessionStreaming(prompt, options?)Create a session and stream typed events from the first turn
listSessions()List active sessions
readSession(sessionId)Read raw session state
readSessionHistory(sessionId, options?)Read committed session transcript history
createMob(options?)Create a mob, return a Mob
createDeferredSession(prompt, options?)Create a session without running the first turn; returns a DeferredSession
listMobs()List mobs
getConfig()Read runtime configuration
setConfig(config)Replace runtime configuration
patchConfig(patch)Merge-patch runtime configuration

Capability methods

MethodDescription
client.capabilitiesRead-only array of all Capability objects
client.hasCapability(id)Returns true if capability is "Available"
client.requireCapability(id)Throws CapabilityUnavailableError if unavailable

Session methods

MethodDescription
session.turn(prompt, options?)Run another turn (non-streaming), returns RunResult
session.stream(prompt, options?)Run another turn with streaming, returns EventStream
session.history(options?)Read committed transcript history for this session
session.interrupt()Cancel the currently running turn
session.archive()Remove the session from the server
session.invokeSkill(skillRef, prompt)Invoke a skill in this session
session.send(command)Send a comms command scoped to this session
session.peers()List peers visible to this session’s comms runtime
session.subscribeEvents()Open a standalone event subscription; returns EventSubscription<AgentEventEnvelope>

Mob methods

MethodDescription
mob.mobIdThe mob’s identifier
mob.status()Get mob status
mob.lifecycle(action)Stop, resume, complete, destroy, or reset the mob
mob.spawn(spec)Spawn a member
mob.retire(meerkatId)Retire a member
mob.respawn(meerkatId)Respawn a member
mob.wire(a, b)Wire two members
mob.unwire(a, b)Remove wiring
mob.listMembers()List mob members
mob.member(meerkatId).send(content, handlingMode?)Send work to a member
mob.listFlows()List available flows
mob.runFlow(flowId, params)Start a flow run
mob.flowStatus(runId)Get flow run status
mob.cancelFlow(runId)Cancel a flow run
mob.subscribeMemberEvents(meerkatId)Subscribe to member events; returns EventSubscription<AgentEventEnvelope>
mob.subscribeEvents()Subscribe to mob-wide events; returns EventSubscription<AttributedMobEvent>

MeerkatClient

Constructor

new MeerkatClient(rkatPath?: string)
Defaults to searching $PATH for rkat-rpc. If not found, automatically downloads the correct binary for the current platform and caches it in ~/.cache/meerkat/bin/rkat-rpc. Pass an explicit path or set MEERKAT_BIN_PATH to override.

connect()

async connect(options?: ConnectOptions): Promise<this>
Spawns rkat-rpc, performs initialize handshake, checks contract version compatibility, and fetches capabilities. Returns this for chaining.
export interface ConnectOptions {
  realmId?: string;
  instanceId?: string;
  realmBackend?: "jsonl" | "redb";
  isolated?: boolean;
  stateRoot?: string;
  contextRoot?: string;
  userConfigRoot?: string;
}
realmId and isolated are mutually exclusive. If realmId is omitted and isolated is not set, sessions are stored in the default realm. Reuse realmId to share sessions and config across processes or surfaces.

createSession()

async createSession(
  prompt: string | ContentBlock[],
  options?: SessionOptions,
): Promise<Session>
Creates a new session, runs the first turn with prompt, and returns a Session object. The Session holds the last RunResult and exposes convenience accessors for the most recent text, usage, and tool call counts.
const session = await client.createSession("Summarise this project", {
  model: "claude-sonnet-4-5",
  systemPrompt: "You are a senior engineer.",
  maxTokens: 4096,
});

console.log(session.text);
console.log(session.usage.inputTokens);

createSessionStreaming()

createSessionStreaming(
  prompt: string | ContentBlock[],
  options?: SessionOptions,
): EventStream
Creates a new session and returns an EventStream for the first turn. Iterate the stream to receive typed events as they arrive. The final RunResult is available on stream.result after iteration completes.
const stream = client.createSessionStreaming("Explain async/await in TypeScript");

for await (const event of stream) {
  if (event.type === "text_delta") {
    process.stdout.write(event.delta);
  }
}

console.log("\nTokens used:", stream.result.usage.outputTokens);

SessionOptions

All fields are camelCase:
interface SessionOptions {
  model?: string;
  provider?: string;
  systemPrompt?: string;
  maxTokens?: number;
  outputSchema?: Record<string, unknown>;
  structuredOutputRetries?: number;
  hooksOverride?: Record<string, unknown>;
  enableBuiltins?: boolean;
  enableShell?: boolean;
  enableMemory?: boolean;
  enableMob?: boolean;
  keepAlive?: boolean;
  commsName?: string;
  peerMeta?: Record<string, unknown>;
  budgetLimits?: Record<string, unknown>;
  providerParams?: Record<string, unknown>;
  preloadSkills?: string[];
  skillRefs?: SkillRef[];
  skillReferences?: string[];
}

Session

createSession() and createSessionStreaming() both produce a Session object that acts as the handle for all subsequent turns on the same conversation. The SDK does not own a second execution path; it only wraps the canonical runtime session identity.

Identity

session.id    // stable UUID string
session.ref   // optional human-readable reference (string | undefined)

Last-result shortcuts

These accessors always reflect the most recent completed turn:
session.text            // assistant text from the last turn
session.usage           // Usage from the last turn
session.turns           // number of LLM turns in the last run
session.toolCalls       // number of tool calls in the last run
session.structuredOutput // structured output from the last run (unknown | undefined)
session.lastResult      // the full RunResult object

turn()

async turn(
  prompt: string | ContentBlock[],
  options?: { skillRefs?: SkillRef[]; skillReferences?: string[] },
): Promise<RunResult>
Sends another turn to the session and returns the RunResult. Also updates the session’s last-result shortcuts.
const result = await session.turn("Now explain the second point in more detail");
console.log(result.text);

stream()

stream(
  prompt: string | ContentBlock[],
  options?: { skillRefs?: SkillRef[]; skillReferences?: string[] },
): EventStream
Sends another turn and returns an EventStream. Iterating it yields typed AgentEvent objects. The session’s last-result shortcuts are updated when iteration completes.
for await (const event of session.stream("Elaborate on point three")) {
  if (event.type === "text_delta") {
    process.stdout.write(event.delta);
  }
}
// session.text is now updated

interrupt()

async interrupt(): Promise<void>
Sends turn/interrupt for this session. Has no effect if no turn is running.

archive()

async archive(): Promise<void>
Removes this session from the server. The Session object should not be used after calling archive().

invokeSkill()

async invokeSkill(skillRef: SkillRef, prompt: string): Promise<RunResult>
Calls requireCapability("skills"), then runs a turn with the provided skill reference injected. SkillRef is either a SkillKey object or a legacy string:
// Structured (preferred)
const result = await session.invokeSkill(
  { sourceUuid: "abc123", skillName: "code-review" },
  "Review this function for safety issues",
);

// Legacy string (deprecated)
const result = await session.invokeSkill("/abc123/code-review", "Review this function");

send() and peers()

async send(command: Record<string, unknown>): Promise<Record<string, unknown>>
async peers(): Promise<Array<Record<string, unknown>>>
Scope comms operations to this session. Requires the comms capability.
await session.send({ kind: "peer_message", to: "agent-b", body: "Hello!" });
const peers = await session.peers();

subscribeEvents()

async subscribeEvents(): Promise<EventSubscription<AgentEventEnvelope>>
Open a standalone event subscription for this session. Returns an async-iterable EventSubscription that yields typed AgentEventEnvelope objects.
const sub = await session.subscribeEvents();
for await (const envelope of sub) {
  console.log(envelope.payload.type, envelope.seq);
}
await sub.close();

Capabilities

Capabilities are fetched automatically during connect(). Use them to guard code paths that depend on optional features.
// Read all capabilities
console.log(client.capabilities);
// [{ id: "sessions", description: "...", status: "Available" }, ...]

// Check a single capability
if (client.hasCapability("comms")) {
  await session.send({ kind: "peer_message", to: "agent-b", body: "Hi" });
}

// Assert and throw if unavailable
client.requireCapability("skills");  // throws CapabilityUnavailableError if not available
IDDescription
sessionsSession lifecycle
streamingReal-time event streaming
structured_outputJSON schema structured output
hooksLifecycle hooks
builtinsBuilt-in tools
shellShell tool
commsInter-agent communication
memory_storeSemantic memory
session_storeSession persistence
session_compactionContext compaction
skillsSkill loading

Config management

const config = await client.getConfig();
await client.setConfig({ ...config, model: "claude-opus-4-6" });
const updated = await client.patchConfig({ maxTokens: 8192 });

Examples

import { MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect();

const session = await client.createSession("List three European capitals", {
  outputSchema: {
    type: "object",
    properties: {
      capitals: { type: "array", items: { type: "string" } },
    },
    required: ["capitals"],
  },
  structuredOutputRetries: 3,
});

console.log(session.structuredOutput);
// { capitals: ["Paris", "Berlin", "Madrid"] }

await client.close();
import { MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect();

const session = await client.createSession("My name is Alice.", {
  model: "claude-sonnet-4-5",
});

const result = await session.turn("What is my name?");
console.log(result.text);  // Mentions "Alice"

await session.archive();
await client.close();
import { MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect();

const session = await client.createSession("You are a helpful assistant.", {
  systemPrompt: "Be concise.",
});

for await (const event of session.stream("Explain Rust lifetimes in two sentences")) {
  switch (event.type) {
    case "text_delta":
      process.stdout.write(event.delta);
      break;
    case "turn_completed":
      console.log(`\nStop reason: ${event.stopReason}`);
      console.log(`Tokens: ${event.usage.inputTokens} in / ${event.usage.outputTokens} out`);
      break;
  }
}

await client.close();
import { MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect();

client.requireCapability("skills");

const session = await client.createSession("Let's review some code.");

const result = await session.invokeSkill(
  { sourceUuid: "abc123", skillName: "code-review" },
  "Review this function for correctness and safety",
);

console.log(result.text);

await client.close();
import { MeerkatClient, ContentBlock } from "@rkat/sdk";
import { readFileSync } from "fs";

const client = new MeerkatClient();
await client.connect();

const imageData = readFileSync("screenshot.png").toString("base64");

const session = await client.createSession([
  { type: "text", text: "What do you see in this image?" },
  { type: "image", mediaType: "image/png", data: imageData },
]);

console.log(session.text);
await client.close();
import { MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect();

const stream = client.createSessionStreaming("Write a haiku about Rust");
const [fullText, result] = await stream.collectText();

console.log(fullText);
console.log("Input tokens:", result.usage.inputTokens);

await client.close();

See also