> For the complete documentation index, see [llms.txt](https://docs.labs.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.labs.ai/conversations-and-orchestration/agent-sync-guide.md).

# Agent Sync (Live & ZIP)

## Overview

Agent Sync lets you synchronize agent configurations between two running EDDI instances **without exporting/importing ZIP files**. It uses the same structural matching and content diffing pipeline as ZIP imports, but reads directly from a remote EDDI instance over HTTP.

### When to Use

| Scenario                                      | Use                                            |
| --------------------------------------------- | ---------------------------------------------- |
| One-off agent migration between environments  | ZIP Import/Export                              |
| Regular dev → staging → production promotions | **Agent Sync**                                 |
| Keeping multiple EDDI instances in sync       | **Agent Sync**                                 |
| Sharing agents with external teams            | ZIP Import/Export                              |
| CI/CD pipeline deployments                    | Either (Sync for live, ZIP for artifact-based) |

## Prerequisites

* Both EDDI instances must be reachable over HTTP/HTTPS
* The agent only needs to **exist** in the source instance's agent store — it does not have to be deployed
* If the source requires authentication, you'll need a valid Bearer token

## Workflow

### 1. List Remote Agents

First, discover which agents are available on the remote instance:

```bash
curl -X GET "http://localhost:7070/backup/import/sync/agents?sourceUrl=https://source-eddi.example.com" \
  -H "X-Source-Authorization: Bearer <token>"
```

**Response:** List of agent descriptors from the remote instance.

### 2. Preview Changes (Single Agent)

Before syncing, preview what would change:

```bash
curl -X POST "http://localhost:7070/backup/import/sync/preview?sourceUrl=https://source-eddi.example.com&sourceAgentId=remote-agent-id&sourceAgentVersion=1&targetAgentId=local-agent-id" \
  -H "X-Source-Authorization: Bearer <token>"
```

**Response:** An `ImportPreview` with resource diffs:

```json
{
  "resources": [
    {
      "resourceType": "agent",
      "action": "UPDATE",
      "sourceId": "remote-agent-id",
      "targetId": "local-agent-id",
      "targetVersion": 3,
      "matchStrategy": "targetAgent"
    },
    {
      "resourceType": "langchain",
      "action": "UPDATE",
      "sourceId": "remote-llm-id",
      "targetId": "local-llm-id",
      "targetVersion": 2,
      "matchStrategy": "type"
    },
    {
      "resourceType": "behavior",
      "action": "SKIP",
      "sourceId": "remote-behavior-id",
      "targetId": "local-behavior-id",
      "targetVersion": 1,
      "matchStrategy": "type"
    }
  ]
}
```

`targetId` and `targetVersion` are `null` for a `CREATE`, and `matchStrategy` records how the match was found (e.g. `targetAgent`, `position`, `type`, `name` — `null` for `CREATE`).

`resourceType` uses the config file extension labels, not the v6 URI names — the full set is `agent`, `workflow`, `langchain`, `httpcalls`, `behavior`, `regulardictionary`, `property`, `output`, `mcpcalls`, `rag`, `snippet`.

**Actions explained:**

| Action     | Meaning                                                    |
| ---------- | ---------------------------------------------------------- |
| `CREATE`   | Resource doesn't exist locally — will be created           |
| `UPDATE`   | Resource exists locally — content differs, will be updated |
| `SKIP`     | Resource is identical — no changes needed                  |
| `CONFLICT` | Structural mismatch — review needed                        |

### 3. Preview Batch (Multiple Agents)

Preview sync for multiple agents at once. The request body is a JSON array of `SyncMapping` objects:

```bash
curl -X POST "http://localhost:7070/backup/import/sync/preview/batch?sourceUrl=https://source-eddi.example.com" \
  -H "Content-Type: application/json" \
  -H "X-Source-Authorization: Bearer <token>" \
  -d '[
    { "sourceAgentId": "agent-1", "sourceAgentVersion": 1, "targetAgentId": "local-1" },
    { "sourceAgentId": "agent-2", "sourceAgentVersion": 2, "targetAgentId": "local-2" }
  ]'
```

**Response:** A JSON array of `ImportPreview` objects, one per mapping.

### 4. Execute Sync

Once you've reviewed the preview and are satisfied:

```bash
curl -X POST "http://localhost:7070/backup/import/sync?sourceUrl=https://source-eddi.example.com&sourceAgentId=remote-agent-id&sourceAgentVersion=1&targetAgentId=local-agent-id" \
  -H "X-Source-Authorization: Bearer <token>"
```

You can also pass `selectedResources` and `workflowOrder` as query parameters for fine-grained control:

```bash
curl -X POST "http://localhost:7070/backup/import/sync?sourceUrl=https://source-eddi.example.com&sourceAgentId=remote-agent-id&sourceAgentVersion=1&targetAgentId=local-agent-id&selectedResources=res-1,res-2" \
  -H "X-Source-Authorization: Bearer <token>"
```

### Response codes

Every execute endpoint answers with one of three **2xx** statuses. A client must branch on the status code — checking `response.ok` alone reports a half-applied sync as a success:

| Status             | Meaning                                                                                          |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `200 OK`           | Source and target already agree. Nothing was written, no version was burned.                     |
| `201 Created`      | Everything landed and something was written.                                                     |
| `207 Multi-Status` | **Partially applied** — `failures[]` in the body names every resource that could not be written. |

The body of a single sync is an `UpgradeResult`:

```json
{
  "agentUri": "eddi://ai.labs.agent/agentstore/agents/local-agent-id?version=8",
  "agentUpdated": true,
  "updated": 3,
  "created": 0,
  "skipped": 5,
  "failures": [
    { "sourceId": "…", "resourceType": "langchain", "name": "GPT Config", "reason": "…" }
  ]
}
```

`/backup/import/sync/batch` answers a JSON array of `BatchSyncResult` (`sourceAgentId`, `targetAgentId`, `result`, `error`) — one entry per request, in request order, whether it succeeded or not. It answers `500` only when *every* agent failed, and `207` when some did. A batch **preview** row that failed carries `sourceAgentName: null` and an `error` field rather than encoding the failure into the agent's name.

### 5. Execute Batch Sync

Sync multiple agents in one call. The request body is a JSON array of `SyncRequest` objects:

```bash
curl -X POST "http://localhost:7070/backup/import/sync/batch?sourceUrl=https://source-eddi.example.com" \
  -H "Content-Type: application/json" \
  -H "X-Source-Authorization: Bearer <token>" \
  -d '[
    {
      "sourceAgentId": "agent-1",
      "sourceAgentVersion": 1,
      "targetAgentId": "local-1",
      "selectedResources": null,
      "workflowOrder": null
    },
    {
      "sourceAgentId": "agent-2",
      "sourceAgentVersion": 2,
      "targetAgentId": "local-2",
      "selectedResources": ["res-a", "res-b"],
      "workflowOrder": null
    }
  ]'
```

> **Partial success:** If one agent fails during batch sync, the remaining agents still sync. The response indicates success/failure per agent.

## API Reference

| Method | Path                                | Purpose                   |
| ------ | ----------------------------------- | ------------------------- |
| `GET`  | `/backup/import/sync/agents`        | List remote agents        |
| `POST` | `/backup/import/sync/preview`       | Single-agent sync preview |
| `POST` | `/backup/import/sync/preview/batch` | Multi-agent sync preview  |
| `POST` | `/backup/import/sync`               | Execute single-agent sync |
| `POST` | `/backup/import/sync/batch`         | Execute multi-agent sync  |

### Parameters

**Query parameters (all endpoints):**

| Parameter            | Required          | Description                                |
| -------------------- | ----------------- | ------------------------------------------ |
| `sourceUrl`          | Yes               | Base URL of the source EDDI instance       |
| `sourceAgentId`      | Yes (single)      | Agent ID on the remote instance            |
| `sourceAgentVersion` | No                | Version to sync (null = latest)            |
| `targetAgentId`      | No                | Local agent to upgrade (null = create new) |
| `selectedResources`  | No (execute only) | Comma-separated resource IDs to sync       |
| `workflowOrder`      | No (execute only) | Desired workflow order after sync          |

> **Note:** `sourceUrl` and agent parameters are query parameters. Batch endpoints accept `SyncMapping[]` / `SyncRequest[]` as a JSON request body for the per-agent mappings.

**Request header:**

| Header                   | Required | Description                                     |
| ------------------------ | -------- | ----------------------------------------------- |
| `X-Source-Authorization` | No       | Bearer token for authenticated source instances |

## How Structural Matching Works

Agent Sync uses **structural matching** — not ID matching — to pair source and target resources. This means it works even when the source and target agents were created independently.

| Resource Type  | Matching Strategy                             | Rationale                                   |
| -------------- | --------------------------------------------- | ------------------------------------------- |
| **Agent**      | Direct (by `targetAgentId` parameter)         | User explicitly selects the target          |
| **Workflows**  | Position index in agent's workflow list       | Workflows have a defined order              |
| **Extensions** | `WorkflowStep.type` URI (e.g., `ai.labs.llm`) | Each type appears at most once per workflow |
| **Snippets**   | `PromptSnippet.name` (natural key)            | Names are unique by convention              |

### Key Design Decisions

* **In-place upgrade:** Target resource IDs are preserved. URI references, deployments, and triggers continue to work
* **Version increments:** Each updated resource gets a new version (history preserved)
* **Secret scrubbing:** API keys and vault references are **never** transferred. The target instance uses its own secrets — and a value the source scrubbed is put back from the target's own configuration before anything is compared or written, so a credential neither leaks nor gets overwritten with a placeholder, and a config that differs *only* by the placeholder still counts as unchanged
* **SSRF protection:** The remote URL is validated (HTTPS required in production, private IPs blocked), and redirects are never followed — a 3xx from the source surfaces as a failed read rather than re-sending the bearer token elsewhere
* **New extensions are refused, not orphaned:** an extension the target workflow has no step for cannot be referenced once written, so it is reported in `failures[]` instead of being created as an unreferenced resource. Add the step to the target workflow (or import the source workflow as a new one) and sync again

## Upgrade Strategy (ZIP Import)

The same structural matching is available for ZIP imports using `strategy=upgrade`:

```bash
# Preview what would change
curl -X POST -H "Content-Type: application/zip" \
  --data-binary @agent-export.zip \
  "http://localhost:7070/backup/import/preview?targetAgentId=local-agent-id"

# Execute upgrade (updates existing resources in-place)
curl -X POST -H "Content-Type: application/zip" \
  --data-binary @agent-export.zip \
  "http://localhost:7070/backup/import?strategy=upgrade&targetAgentId=local-agent-id"
```

This is the same pipeline as Live Sync — the only difference is the transport (ZIP file vs HTTP).

## Selective Export

Export only the resources you want:

```bash
# 1. Preview the export tree
curl -X POST "http://localhost:7070/backup/export/agent-id/preview?agentVersion=1"

# 2. Select specific resources and export
curl -X POST "http://localhost:7070/backup/export/agent-id?agentVersion=1&selectedResources=res1,res2,res3"
```

The preview returns a resource tree with selectability flags. Agent and workflow skeletons are always included — you can deselect individual extensions, behavior rules, prompt snippets or scheduled triggers.

Snippets and schedules have their own parameters (`selectedSnippets`, `selectedSchedules`). Deselecting an extension **keeps the workflow step that referenced it** — the archive states what the source deployment actually runs, and the importer decides what to do with a reference it cannot satisfy: `merge` answers it from the target's own copy, `create` drops the step and logs a warning. See [Import/Export an Agent → Selecting What to Export](/getting-started/import-export-an-agent.md#selecting-what-to-export) for the three-state semantics of each parameter and for the archive retention window.

## See Also

* [Import/Export an Agent](/getting-started/import-export-an-agent.md) — ZIP-based import/export (create and merge strategies)
* [Agent Sync Architecture](/reference/agent-sync-architecture.md) — Internal architecture and matching algorithm details
* [Deployment Management](/conversations-and-orchestration/deployment-management-of-agents.md) — Deploying agents after sync
