> ## Documentation Index
> Fetch the complete documentation index at: https://chahine.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Agents

> Use Meridian CRDTs as shared state for AI agents — Anthropic, OpenAI, and Gemini.

Meridian ships built-in tool use helpers for AI agents. Stateless agents (serverless functions, Lambda, Cloudflare Workers) can read and write CRDTs via pure HTTP — no persistent WebSocket required.

`getMeridianTools()` returns Anthropic-compatible tool definitions by default. Use `toOpenAITools()` or `toGeminiTools()` to convert to the format expected by OpenAI or Gemini.

## Install

```bash theme={null}
# Anthropic
npm install meridian-sdk @anthropic-ai/sdk

# OpenAI
npm install meridian-sdk openai

# Gemini
npm install meridian-sdk @google/generative-ai
```

## Quick start

```ts theme={null}
import Anthropic from "@anthropic-ai/sdk";
import { getMeridianTools, executeMeridianTool } from "meridian-sdk";
import type { ToolUseBlock } from "meridian-sdk";

const config = {
  baseUrl: "https://your-meridian-server.com",
  token: process.env.MERIDIAN_TOKEN!,
  namespace: "my-app",
};

const anthropic = new Anthropic();

// 1. Generate Anthropic-compatible tool definitions from your CRDT IDs
const tools = getMeridianTools(config, ["views", "notes", "tags"]);

// 2. Run the agentic loop
const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Increment the views counter and update the notes." },
];

while (true) {
  const response = await anthropic.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    tools,
    messages,
  });

  const toolResults: Anthropic.ToolResultBlockParam[] = [];

  for (const block of response.content) {
    if (block.type === "tool_use") {
      // 3. Execute each tool call against Meridian via HTTP
      const result = await executeMeridianTool(config, block as ToolUseBlock);
      toolResults.push({ type: "tool_result", tool_use_id: block.id, content: result });
    }
  }

  messages.push({ role: "assistant", content: response.content });

  if (toolResults.length === 0) break; // Claude is done

  messages.push({ role: "user", content: toolResults });
}
```

## OpenAI

```ts theme={null}
import OpenAI from "openai";
import { getMeridianTools, toOpenAITools, executeOpenAITool } from "meridian-sdk";

const openai = new OpenAI();
const tools = toOpenAITools(getMeridianTools(config, ["views", "notes", "tags"]));

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  tools,
  messages: [{ role: "user", content: "Increment views and update notes." }],
});

const msg = response.choices[0].message;
const toolResults = [];

for (const call of msg.tool_calls ?? []) {
  // Pass call directly — no wrapping needed
  const result = await executeOpenAITool(config, call);
  toolResults.push({ role: "tool", tool_call_id: call.id, content: result });
}
```

## Gemini

```ts theme={null}
import { GoogleGenerativeAI } from "@google/generative-ai";
import { getMeridianTools, toGeminiTools, executeGeminiTool } from "meridian-sdk";

const genai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const model = genai.getGenerativeModel({
  model: "gemini-1.5-pro",
  tools: [toGeminiTools(getMeridianTools(config, ["views", "notes", "tags"]))],
});

const result = await model.generateContent("Increment views and update notes.");
const parts = result.response.candidates?.[0].content.parts ?? [];

for (const part of parts) {
  if (part.functionCall) {
    // Pass part.functionCall directly — no id hack, no wrapping
    const toolResult = await executeGeminiTool(config, part.functionCall);
    // pass toolResult back as functionResponse part
  }
}
```

## `getMeridianTools`

```ts theme={null}
function getMeridianTools(
  config: MeridianAgentConfig,
  crdtIds: string[],
): Tool[]
```

Returns an Anthropic `Tool[]` array. Each CRDT ID exposes four tools:

| Tool | CRDT type | Description |
| - | - | - |
| `meridian_read_{id}` | any | Read current JSON state |
| `meridian_increment_{id}` | GCounter / PNCounter | Increment by `amount` |
| `meridian_set_{id}` | LWW Register | Set value (string, number, object) |
| `meridian_add_{id}` | ORSet | Add an element |

Special characters in CRDT IDs (`:`, `/`, `-`) are replaced with `_` in tool names.

## `executeMeridianTool`

```ts theme={null}
function executeMeridianTool(
  config: MeridianAgentConfig,
  toolUse: ToolUseBlock,
): Promise<string>
```

Executes a tool call returned by Claude and returns a JSON string suitable for the `content` field of a `tool_result` block.

Dispatches to the correct HTTP endpoint based on the tool name:

* `read` → `GET /v1/namespaces/:ns/crdts/:id`
* `increment` → `POST /v1/namespaces/:ns/crdts/:id/ops` (GCounter or PNCounter op)
* `set` → `POST /v1/namespaces/:ns/crdts/:id/ops` (LwwRegister op)
* `add` → `POST /v1/namespaces/:ns/crdts/:id/ops` (ORSet Add op)

## `toOpenAITools`

```ts theme={null}
function toOpenAITools(tools: Tool[]): OpenAITool[]
```

Converts Meridian tools to OpenAI Chat Completions format (`{ type: "function", function: { name, description, parameters } }`).

## `toGeminiTools`

```ts theme={null}
function toGeminiTools(tools: Tool[]): GeminiTool
```

Converts Meridian tools to Google Gemini format (`{ functionDeclarations: [...] }`). Returns a single `GeminiTool` object to pass directly in the `tools` array of `model.generateContent()`.

## `executeOpenAITool`

```ts theme={null}
function executeOpenAITool(
  config: MeridianAgentConfig,
  toolCall: OpenAIToolCall,
): Promise<string>
```

Executes a tool call from OpenAI. `OpenAIToolCall` structurally matches `ChatCompletionMessageToolCall` from the `openai` package — pass `tool_calls[n]` directly. Parses `function.arguments` (JSON string) internally.

## `executeGeminiTool`

```ts theme={null}
function executeGeminiTool(
  config: MeridianAgentConfig,
  functionCall: GeminiFunctionCall,
): Promise<string>
```

Executes a tool call from Gemini. `GeminiFunctionCall` structurally matches `FunctionCall` from `@google/generative-ai` — pass `part.functionCall` directly.

## `MeridianAgentConfig`

```ts theme={null}
interface MeridianAgentConfig {
  /** HTTP base URL of the Meridian server */
  baseUrl: string;
  /** Bearer token with read+write permissions for the namespace */
  token: string;
  /** Namespace scoping all CRDT operations */
  namespace: string;
}
```

## Persistent agent memory

Because Meridian CRDTs are durable, an agent can use them as long-term memory across runs — reading its previous state at startup and writing an updated summary before exiting.

```ts theme={null}
// On startup — read previous memory
const lastRun = await fetch(`${baseUrl}/v1/namespaces/${ns}/crdts/memory-last-run`, {
  headers: { Authorization: `Bearer ${token}` },
}).then(r => r.ok ? r.json() : null);

// Pass previous memory as context to Claude
const prompt = lastRun
  ? `Your last run summary: ${JSON.stringify(lastRun.value)}\n\nContinue from where you left off.`
  : `This is your first run.`;

// ... agentic loop ...

// On exit — write updated memory
await executeMeridianTool(config, {
  type: "tool_use", id: "1", name: "meridian_set_memory_last_run",
  input: { value: JSON.stringify({ timestamp: new Date(), summary: "..." }), client_id: 1 },
});
```

A full working example is in [`examples/ai-agents/agents/memory.ts`](https://github.com/Chahine-tech/meridian/tree/main/examples/ai-agents/agents/memory.ts). It uses three CRDTs (namespace: `ai-agents`):

| CRDT | Type | Purpose |
| - | - | - |
| `memory-run-count` | GCounter | Total runs ever |
| `memory-last-run` | LWW Register | Summary of the last run |
| `memory-insights` | ORSet | Observations accumulated across all runs |

> [!NOTE]
>
> CRDT IDs must use hyphens only — colons are not supported. `getMeridianTools` converts special characters to `_` in tool names and `executeMeridianTool` converts them back to `-` when calling the API.

## Multi-agent debate

Two agents can use CRDTs as a shared communication channel — each writing its argument to an LWW-Register and reading the opponent's latest argument before composing a rebuttal.

A full working example is in [`examples/ai-agents/agents/debate.ts`](https://github.com/Chahine-tech/meridian/tree/main/examples/ai-agents/agents/debate.ts). It runs two Claude agents (FOR / AGAINST) in interleaved rounds, with each agent's argument history accumulated in an ORSet. A React UI displays both sides live.

## Live UI

The [AI agents example](https://github.com/Chahine-tech/meridian/tree/main/examples/ai-agents) ships a React dashboard with four tabs — **Agents** (multi-worker coordinator), **Tool Use** (single Claude agent), **Memory** (persistent memory across runs), and **Debate** (two-agent debate) — all subscribing to the same CRDTs via WebSocket and displaying live state as agents write.

## SSE streaming

For agents that need to react to changes without polling, use the SSE endpoint:

```
GET /v1/namespaces/:ns/crdts/:id/events
Authorization: Bearer <token>
Accept: text/event-stream
```

Each event is a base64-encoded msgpack delta. The SDK `HttpClient.syncCrdt()` handles decoding automatically. Keep-alive pings are sent every 15 seconds to prevent proxy timeouts.

```ts theme={null}
const es = new EventSource(
  `${baseUrl}/v1/namespaces/${ns}/crdts/${id}/events`,
  { headers: { Authorization: `Bearer ${token}` } }
);

es.onmessage = (e) => {
  const delta = decode(base64ToBytes(e.data)); // msgpack delta
  // apply delta to local CRDT state
};
```

## Token requirements

The token must have `read` and `write` permissions for all CRDT IDs the agent will access. Issue one via the admin API:

```bash theme={null}
curl -X POST https://your-server.com/v1/tokens \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "my-app",
    "client_id": 42,
    "permissions": { "read": ["*"], "write": ["*"], "admin": false }
  }'
```

See [Tokens](https://chahine.mintlify.site/self-hosting/tokens.md) for details.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.