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

# Sync

## Overview

The `SyncClient` is the Room API for live shared documents (`MeshDocuments`). Use it when multiple humans or agents need to read and edit the same structured document in real time.

## CLI commands

Start with the CLI help, then use a few common commands:

```bash bash theme={null}
meshagent room sync --help
meshagent room sync create --room myroom docs/briefing --schema ./briefing.schema.json
meshagent room sync get --room myroom docs/briefing
meshagent room sync grep --room myroom docs/briefing "priority"
meshagent room sync update --room myroom docs/briefing --patch '[]'
meshagent room sync inspect --room myroom docs/briefing
meshagent room sync import --room myroom --file ./conversations.json
```

These commands use the actual CLI command shapes. `create` expects `./briefing.schema.json` to be a MeshSchema JSON file, `update --patch '[]'` is a no-op JSON Patch example, and `import` expects a local Claude GDPR `conversations.json` export plus the optional MeshAgent agent and Anthropic packages installed.

## Why use the Sync API?

* Keep shared state in a live document instead of relying only on chat history.
* Let multiple participants collaborate on the same structured content at once.
* Define document schemas that help users and agents write valid data.

## How it works

You create or open a document by path. Opening a document starts a live synchronization session so local changes propagate to other participants in the room. The Python SDK also exposes `describe` when you want the current JSON state without opening a sync session, and `sync` is available when you already have encoded changes to send.

For the Python, JavaScript, TypeScript, and .NET standalone room examples on this page, start the script through `meshagent room connect --room={ROOM_NAME} --identity={AGENT_NAME} -- <command>` so MeshAgent injects the room connection environment automatically. For example: `meshagent room connect --room=my-room --identity=participant-name -- python3 documents-writing.py`. The Dart sample still shows the explicit protocol factory flow.

## Permissions and grants

The Sync API is controlled by the `sync` grant on the participant token.

In practice, sync grants are path-based and can be marked read-only. That lets you give a participant access to specific document paths without opening the entire sync surface.

See [API Scopes](../rest_api/api_scopes) and [Service YAML](../services/deployment/deploy_services).

## Defining Your Document Structure

To define the structure of a MeshDocument, you start by creating a schema using the MeshSchema class. A schema:

* Documents the structure of your MeshDocument for anyone in your organization.
* Ensures agents and users don’t write invalid data to the document.
* Allows agents to automatically generate, manipulate, and validate structured documents.
* Automatically generates LLM-compatible schemas for structured outputs.
* Synchronizes documents across all platforms that MeshAgent supports.

Similar to how a web page is structured, a MeshSchema begins with a root element that includes an allowed tag name and optional attributes. Afterward, you can define additional tags, their attributes, and the types of child nodes they can contain.

### Example Schema

Suppose you have a basic web page structure and want to define a schema for it.

<CodeGroup>
  ```python Python theme={null}
  from meshagent.api.schema import MeshSchema, ElementType, ChildProperty, ValueProperty

  schema = MeshSchema(
      root_tag_name="html",
      elements=[
          ElementType(
              tag_name="html",
              properties=[
                  # a ChildProperty describes the type of children that
                  # an element allows. There can be at most one child
                  # property for each element type, but the child property
                  # can allow multiple types of child elements.
                  ChildProperty(name="children", child_tag_names=["body"])
              ],
          ),
          ElementType(
              tag_name="body",
              properties=[
                  # Our body can only contain paragraph elements
                  ChildProperty(name="children", child_tag_names=["p"])
              ],
          ),
          ElementType(
              tag_name="p",
              properties=[
                  # A ValueProperty property describes an attribute that
                  # contains a single value
                  ValueProperty(name="class", type="string"),
              ],
          ),
      ],
  )

  ```

  ```javascript NodeJs theme={null}
  import {
    MeshSchema,
    ElementType,
    ChildProperty,
    ValueProperty,
  } from '@meshagent/meshagent';

  // Create the schema
  const schema = new MeshSchema({
    rootTagName: 'html',
    elements: [
      new ElementType({
        tagName: 'html',
        properties: [
          // A ChildProperty describes the type of children that an element allows.
          // There can be at most one child property for each element type, but
          // the child property can allow multiple types of child elements.
          new ChildProperty({
            name: 'children',
            childTagNames: ['body'],
          }),
        ],
      }),
      new ElementType({
        tagName: 'body',
        properties: [
          // Our body can only contain paragraph elements.
          new ChildProperty({
            name: 'children',
            childTagNames: ['p'],
          }),
        ],
      }),
      new ElementType({
        tagName: 'p',
        properties: [
          // A ValueProperty describes an attribute that contains a single value.
          new ValueProperty({
            name: 'class',
            type: 'string',
          }),
        ],
      }),
    ],
  });


  ```

  ```typescript TypeScript theme={null}
  import {
    MeshSchema,
    ElementType,
    ChildProperty,
    ValueProperty,
    SimpleValue,
  } from '@meshagent/meshagent';

  // Create the schema
  const schema = new MeshSchema({
    rootTagName: 'html',
    elements: [
      new ElementType({
        tagName: 'html',
        properties: [
          // A ChildProperty describes the type of children that an element allows.
          // There can be at most one child property for each element type, but
          // the child property can allow multiple types of child elements.
          new ChildProperty({
            name: 'children',
            childTagNames: ['body'],
          }),
        ],
      }),
      new ElementType({
        tagName: 'body',
        properties: [
          // Our body can only contain paragraph elements.
          new ChildProperty({
            name: 'children',
            childTagNames: ['p'],
          }),
        ],
      }),
      new ElementType({
        tagName: 'p',
        properties: [
          // A ValueProperty describes an attribute that contains a single value.
          new ValueProperty({
            name: 'class',
            type: SimpleValue.string,
          }),
        ],
      }),
    ],
  });

  ```

  ```dart Dart theme={null}
  import 'package:meshagent/meshagent.dart';

  final schema = MeshSchema(
    rootTagName: 'html',
    elements: [
      ElementType(
        tagName: 'html',
        description: 'The root html element.',
        properties: [
          // A ChildProperty describes the type of children that
          // an element allows. There can be at most one child
          // property for each element type, but the child property
          // can allow multiple types of child elements.
          ChildProperty(name: 'children', childTagNames: ['body']),
        ],
      ),
      ElementType(
        tagName: 'body',
        description: 'The document body element.',
        properties: [
          // Our body can only contain paragraph elements.
          ChildProperty(name: 'children', childTagNames: ['p']),
        ],
      ),
      ElementType(
        tagName: 'p',
        description: 'A paragraph element.',
        properties: [
          // A ValueProperty describes an attribute that
          // contains a single value.
          ValueProperty(name: 'class', type: SimpleValue.string),
        ],
      ),
    ],
  );

  void main() {
    // Here you could do something with `schema`, like
    // passing it to a registry or printing it out.
    print(schema);
  }

  ```

  ```dotnet C# theme={null}
  using Meshagent.Api.Schema;
  using System.Collections.Generic;

  var schema = new MeshSchema(
      rootTagName: "html",
      elements: new List<ElementType>
      {
          new ElementType(
              tagName: "html",
              properties: new List<ElementProperty>
              {
                  // A ChildProperty describes the type of children an element allows
                  new ChildProperty(
                      name: "children",
                      childTagNames: new List<string> { "body" }
                  )
              }
          ),
          new ElementType(
              tagName: "body",
              properties: new List<ElementProperty>
              {
                  // Our body can only contain paragraph elements
                  new ChildProperty(
                      name: "children",
                      childTagNames: new List<string> { "p" }
                  )
              }
          ),
          new ElementType(
              tagName: "p",
              properties: new List<ElementProperty>
              {
                  // A ValueProperty describes an attribute that contains a single value
                  new ValueProperty(
                      name: "class",
                      type: SimpleValue.String
                  )
              }
          )
      }
  );
  ```
</CodeGroup>

Many LLMs (such as OpenAI) and agent frameworks (such as MeshAgent) support JSON Schemas for defining inputs and outputs. Generating an OpenAI-compatible JSON schema from your MeshSchema requires only one line of code:

<CodeGroup>
  ```python Python theme={null}
  json_schema = schema.to_json()

  ```

  ```javascript NodeJs theme={null}
  const jsonSchema = schema.toJson();

  ```

  ```typescript TypeScript theme={null}
  const jsonSchema: Record<string, any> = schema.toJson();

  ```

  ```dart Dart theme={null}
  final jsonSchema = schema.toJson();

  ```

  ```dotnet C# theme={null}
  var jsonSchema = schema.ToJson();

  ```
</CodeGroup>

## Creating a MeshDocument

To create a MeshDocument based on your schema and enable synchronization across different clients, use the MeshAgent runtime:

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  from meshagent.api import RoomClient


  async def main():
      # Run with:
      # meshagent room connect --room=my-room --identity=participant-name -- python3 documents-writing.py
      path = "hello-world.document"

      async with RoomClient() as room:
          print(f"Connected to room: {room.room_name}")
          document = await room.sync.open(path=path, create=True)
          try:
              await document.synchronized
              document.root.append_child(
                  tag_name="body", attributes={"text": "hello world!"}
              )
              await asyncio.sleep(1)
          finally:
              await room.sync.close(path=path)


  asyncio.run(main())

  ```

  ```javascript NodeJs theme={null}
  import { RoomClient } from "@meshagent/meshagent";

  // Run with:
  // meshagent room connect --room=my-room --identity=participant-name -- <your node command>

  async function main() {
      const path = "hello-world.document";
      const room = new RoomClient();

      try {
          await room.start();
          console.log(`Connected to room: ${room.roomName}`);

          const document = await room.sync.open(path, { create: true });
          try {
              await document.synchronized;
              document.root.createChildElement("body", {
                  text: "hello world!",
              });
              await new Promise((resolve) => setTimeout(resolve, 1000));
          } finally {
              await room.sync.close(path);
          }
      } catch (error) {
          console.error("Error:", error);
      } finally {
          room.dispose();
      }
  }

  void main();

  ```

  ```typescript TypeScript theme={null}
  import { RoomClient } from "@meshagent/meshagent";

  // Run with:
  // meshagent room connect --room=my-room --identity=participant-name -- <your node command>

  async function main() {
      const path = "hello-world.document";
      const room = new RoomClient();

      try {
          await room.start();
          console.log(`Connected to room: ${room.roomName}`);

          const document = await room.sync.open(path, { create: true });
          try {
              await document.synchronized;
              document.root.createChildElement("body", {
                  text: "hello world!",
              });
              await new Promise((resolve) => setTimeout(resolve, 1000));
          } finally {
              await room.sync.close(path);
          }
      } catch (error) {
          console.error("Error:", error);
      } finally {
          room.dispose();
      }
  }

  void main();

  ```

  ```dart Dart theme={null}
  import 'package:meshagent/meshagent.dart';
  import 'package:meshagent/helpers.dart' show websocketProtocol;

  void main() async {
    // Define a unique room name and chose your participant name
    const String roomName = 'examples';
    const String participantName = 'example-participant';

    // Document name (the extension identifies the schema)
    const String path = 'hello-world.document';

    // Establish communication channel using participant token
    final protocolFactory = websocketProtocol(
      roomName: roomName,
      participantName: participantName,
    );

    // Instantiate a new RoomClient for interacting with the room
    final room = RoomClient(protocolFactory: protocolFactory);

    // Connect to the room
    await room.start();

    // Open our document
    final meshDocument = await room.sync.open(path);

    // Wait for the document to sync from the server
    await meshDocument.synchronized;

    meshDocument.root.createChildElement("body", {"text": "hello world!"});
  }

  ```

  ```dotnet C# theme={null}
  using System;
  using System.Collections.Generic;
  using System.Threading.Tasks;
  using Meshagent.Api.Room;

  // Run with:
  // meshagent room connect --room=my-room --identity=participant-name -- <your dotnet command>

  var path = "hello-world.document";

  await using var room = new RoomClient();
  await room.ConnectAsync();
  Console.WriteLine($"Connected to room: {room.RoomName}");

  var doc = await room.Sync.Open(path, create: true);
  try
  {
      await doc.Synchronized;
      doc.Root.AppendChild("body", new Dictionary<string, object?>
      {
          ["text"] = "hello world!",
      });
      await Task.Delay(1000);
  }
  finally
  {
      await room.Sync.Close(path);
  }

  ```
</CodeGroup>

## API reference

Use the methods below to create documents, open live sync sessions, inspect the current document state, and send low-level sync operations when needed.

### `create(path, json=None)`

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room sync create \
    --room myroom \
    my/path \
    --schema ./schema.json \
    --json '{"key":"value"}'

  ```

  ```python Python theme={null}
  await room.sync.create(path="my/path", json={"key": "value"})

  ```

  ```javascript NodeJs theme={null}
  await room.sync.create("my/path", { key: "value" });

  ```

  ```typescript TypeScript theme={null}
  await room.sync.create("my/path", { key: "value" });

  ```

  ```dart Dart theme={null}
  await room.sync.create("my/path", {"key": "value"});

  ```

  ```dotnet C# theme={null}
  await room.Sync.Create("my/path", new Dictionary<string, object?> { ["title"] = "Hello World" });

  ```
</CodeGroup>

* **Parameters**:
  * `path`: Document path.
  * `json`: Optional initial JSON payload.
* **Description**: Create a new `MeshDocument` at the given path. The CLI create command requires `--schema`; SDK callers can pass a `MeshSchema` when creating a document.

### `describe(path)`

<CodeGroup>
  ```python Python theme={null}
  doc_json = await room.sync.describe(path="my/path")

  ```
</CodeGroup>

* **Parameters**:
  * `path`: Document path.
* **Returns**: The current document root as JSON.
* **Description**: Read the current state of a `MeshDocument` without opening a live sync session.
* **Availability**: Python exposes `room.sync.describe` today. Other SDKs can use `open()` or a lower-level request until they add a helper.

### `open(path, create=True)`

<CodeGroup>
  ```python Python theme={null}
  doc = await room.sync.open(path="my/path", create=True)

  ```

  ```javascript NodeJs theme={null}
  const doc = await room.sync.open("my/path", { create: true });

  ```

  ```typescript TypeScript theme={null}
  const doc = await room.sync.open("my/path", { create: true });

  ```

  ```dart Dart theme={null}
  final doc = await room.sync.open("my/path", create: true);

  ```

  ```dotnet C# theme={null}
  var doc = await room.Sync.Open("my/path", create: true);

  ```
</CodeGroup>

* **Parameters**:
  * `path`: Document path.
  * `create`: When `true`, create the document if it does not already exist.
* **Returns**: A `MeshDocument` tied to that path.
* **Description**: Open a live synchronization session for the document. Local changes are sent automatically while the document remains open.

### `close(path)`

<CodeGroup>
  ```python Python theme={null}
  await room.sync.close(path="my/path")

  ```

  ```javascript NodeJs theme={null}
  await room.sync.close("my/path");

  ```

  ```typescript TypeScript theme={null}
  await room.sync.close("my/path");

  ```

  ```dart Dart theme={null}
  await room.sync.close("my/path");

  ```

  ```dotnet C# theme={null}
  await room.Sync.Close("my/path");

  ```
</CodeGroup>

* **Parameters**:
  * `path`: Document path.
* **Description**: Close the local sync session for that document path. Once closed, local changes are no longer synchronized.

### `sync(path, data)`

<CodeGroup>
  ```python Python theme={null}
  await room.sync.sync(path="my/path", data=b"some bytes")

  ```

  ```javascript NodeJs theme={null}
  await room.sync.sync("my/path", new Uint8Array([/*...bytes...*/]));

  ```

  ```typescript TypeScript theme={null}
  await room.sync.sync("my/path", new Uint8Array([/*...bytes...*/]));

  ```

  ```dart Dart theme={null}
  await room.sync.sync("my/path", Uint8List.fromList([/*...bytes...*/]));

  ```

  ```dotnet C# theme={null}
  await room.Sync.Sync("my/path", Encoding.UTF8.GetBytes("some bytes"));

  ```
</CodeGroup>

* **Parameters**:
  * `path`: Document path.
  * `data`: Serialized sync bytes.
* **Description**: Send encoded sync data directly to the server. Use this when you already have the wire-format change payload.

## Notes

* **Synchronized Changes**\
  Once a document is opened, any local changes you make in the associated `MeshDocument` are automatically queued for sending, thanks to `sendChangesToBackend`. You rarely need to call `sync()` manually unless you want to send raw data yourself.

* **Reference Counting**\
  The `SyncClient` internally tracks how many times a document is opened via the same path. Calling `open` multiple times for the same path returns the same underlying document. Only when all references are closed (via `close()`) will the client actually disconnect.

* **Error Handling**\
  If a request fails, the `RoomClient` may throw an error (like `RoomServerException`). Wrap calls in `try/catch` (or use `.catch(...)`) to handle them gracefully.

* **Concurrency and Performance**
  * For high throughput, ensure that your `RoomClient` and server are configured for concurrent operations.
  * The `SyncClient` uses a `StreamController` internally to manage queued updates, sending them asynchronously to the server.

## Related guides

* [Room API Overview](./overview)
* [Storage API](./storage)
