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

# Multi-Instance Skill, Flow & App Sync

> Share a single Git repository as the source of truth for Skills, Flows, and Apps across multiple Genesis instances.

## Overview

Genesis Skills, Flows, and Apps can be managed as Git-backed artifacts using
the same managed-repo pattern as [Blueprints](/docs/Blueprint/Multi-Instance-Blueprint-Sync).
A team running multiple Genesis instances — for example, four dev, four model,
and four prod — can author an artifact once, push it through the repository's
normal pull-request review, and have it propagate to every other instance on
the next sync.

This page is the operator guide for that workflow. For the per-field reference
on the underlying Git connection model, see
[Blueprint Library and Missions](/docs/Blueprint/Blueprint-Library-And-Missions).

<Info>
  When to use this pattern: your organization runs more than one Genesis
  instance — by environment, by business unit, by tenant — and you want the
  same Skills, Flows, and Apps on all of them without copying YAML around
  manually.
</Info>

## What's supported

| Capability | Skills | Flows | Apps |
| - | - | - | - |
| Configure the same Git repository as a managed source on multiple instances | ✓ | ✓ | ✓ |
| Authoring inside Genesis (PR/MR opened from the dashboard or via Eve) | ✓ | ✓ | ✓ |
| Authoring outside Genesis (manual edits merged to `main`) | ✓ | ✓ | ✓ |
| Dashboard workspace — open the artifact in an isolated worktree, edit, push to GitLab | ✓ | ✓ | ✓ |
| Authentication | PAT or per-user OAuth via the existing managed-repo Git connection | Same | Same |
| Force-push safety | Working-copy feature branch only — `main` is never force-pushed | Same | Same |
| Per-instance physical configuration | Tool-group advisory deps from `required-tools` frontmatter | Logical refs (`@snowflake.warehouse`) resolved per-instance via `flow_bindings.yaml` | Connection bindings via `requires:` block |
| Auto-promote on sync | N/A | Default on; override via `auto_promote_on_sync: false` | N/A |
| Manual pin protection | N/A | Production pins set by a real user (not by `managed_sync:...`) are preserved across syncs | N/A |
| Degraded-but-importable on missing deps | ✓ | ✓ | ✓ |

<Note>
  GitLab is the most common shared-repo provider for this pattern. GitHub and
  Bitbucket follow the same model where supported by Genesis's managed-repo
  connector.
</Note>

<Note>
  **Single repo, multiple artifact types.** Use **Config → Managed Sources**
  (requires `manage_repos`) or `POST /api/managed-sources` to register one
  `repo_id` with multiple `scan_types_enabled` values (for example `skills`,
  `flows`, and `apps`) and the matching `*_root` path for each type. Per-library
  **Manage Sources** modals still create sources scoped to one type; legacy
  `POST` routes on `/api/skills/repositories`, `/api/flows/repositories`, and
  `/api/apps/repositories` return a `Deprecation: true` response header. You
  cannot remove a scan type with `PATCH`; delete the source and recreate with
  the desired type set. Deleting a multi-type source must use
  `DELETE /api/managed-sources/{source_id}` — per-type delete routes return
  `409` with `error_code` `SOURCE_HAS_MULTIPLE_TYPES`.

  When creating a source with `documents` in `scan_types_enabled`, provide a
  non-empty `remote_url`. Otherwise creation is rejected with
  `error_code` `DOCUMENTS_REMOTE_URL_REQUIRED`; omit `documents` if the source
  has no remote repository.
</Note>

## Quick start

### 1. Register the shared repository on each instance

**Via the dashboard (one type per source):** open the relevant page (Skills,
Flows, or Apps Library) and use **Manage Sources** in the page header:

1. Click **Add Repository**.
2. Enter a `repo_id` (a short identifier for the local checkout), the remote
   URL, and pick the Git connection.
3. Click **Save**. Genesis clones the repo, scans it for artifacts of that type,
   and shows discovered items with their status.

**Via the dashboard (multiple types, one repo):** open **Config → Managed
Sources**, add a source, enable the scan types you need, and set each
`*_root` path. Requires `manage_repos`.

**Via Eve:** ask in natural language ("register this skills repo from GitLab")
or invoke the agent tool directly. There is one tool per artifact type:

```python theme={null}
skill_management(
    action="REGISTER_MANAGED_SOURCE",
    params={
        "repo_id": "team-skills",
        "remote_url": "https://gitlab.example.com/data/team-skills.git",
        "connection": "gitlab.shared",
    },
)

flow_action(
    action="REGISTER_MANAGED_SOURCE",
    params={
        "repo_id": "team-flows",
        "remote_url": "https://gitlab.example.com/data/team-flows.git",
        "connection": "gitlab.shared",
        "auto_promote_on_sync": False,  # optional; default True
    },
)

app_management(
    action="REGISTER_MANAGED_SOURCE",
    params={
        "repo_id": "team-apps",
        "remote_url": "https://gitlab.example.com/data/team-apps.git",
        "connection": "gitlab.shared",
    },
)
```

All three tool families require the matching manage permission
(`MANAGE_SKILLS`, `MANAGE_FLOWS`, `MANAGE_APPS`) and refuse the call for users
without it.

### 2. Author on instance A, push through review

**Via the dashboard** (recommended for everyday edits): the artifact tile on
each library page exposes an **Edit** button. Clicking it opens an isolated
per-user worktree on a feature branch, lets you edit files inline, and
provides a **Push to GitLab** button that opens an MR/PR against `main`.

**Via Eve:**

```python theme={null}
skill_management(
    action="PUSH_TO_REPO",
    params={"skill_name": "deploy-snowflake-app", "title": "Update deployment skill"},
)

flow_action(
    action="PUSH_TO_REPO",
    params={"flow_name": "weekly-metrics-export", "title": "Update weekly metrics flow"},
)

app_management(
    action="PUSH_TO_REPO",
    params={"app_id": "ops-alerts-app", "title": "Update ops alerts app"},
)
```

All three open a PR/MR against the configured managed repository.

### 3. Sync on instance B after merge

```python theme={null}
skill_management(action="SYNC_FROM_REPO", params={"repo_id": "team-skills"})
flow_action(action="SYNC_FROM_REPO", params={"repo_id": "team-flows"})
app_management(action="SYNC_FROM_REPO", params={"repo_id": "team-apps"})
```

Or use the **Sync** action in the Remote Repositories panel of the dashboard.

Managed Flow sync is scoped to the bot that starts the sync. The repository is
shared, but Genesis hydrates and reconciles the imported Flow for that bot's
managed Flow records rather than using another bot's record as the target. If
you delete the managed source, Genesis retires the source-owned Flow records
for every bot that used that source.

## Changing a governed skill release

When you accept a governed skill release or policy change from **Config →
Managed Sources**, Genesis saves and audits the change first, then syncs it in
the background. A toast reports whether the sync made the release live. Skills
from that source stay blocked until the job finishes on the audited commit and
loads every skill; if it cannot, use **Sync** to retry. Wait for an active sync
to finish before accepting another release change for the same source. The API
request and job response are documented in the
[managed-sources API reference](/docs/Genesis-API/API-Reference).

## Repository file formats

### Skills — `skills/<skill-name>/SKILL.md`

A managed skill is an **AgentSkill package**: a directory containing a
`SKILL.md` file with YAML frontmatter plus any additional context files the
skill needs (templates, examples, helper docs).

```markdown theme={null}
---
name: deploy-snowflake-app
description: Deploys an app package into Snowflake end to end.
allowed-tools: Read, Write, Bash
metadata:
  owner: platform-team
  version: "1.2.0"
  category: deployment
---

# Deploy a Snowflake App

Use this skill when the operator asks to deploy or redeploy a Snowflake-backed
microapp. Walk through the deployment checklist...
```

The directory name **must match** the frontmatter `name` field exactly — sync
rejects mismatches defensively to prevent silent identity drift. Bundled files
inside the package directory round-trip through sync.

Optional frontmatter `required-tools` (or `required_tools`) declares **advisory**
`tool_group` dependency rows at sync time. Genesis does not invent connection or
flow dependencies from Skill packages — bind those on Flows and Apps instead.

<Note>
  Skills authored before CLI-463 used a single `skill.yaml` file. The new
  format is a package directory with `SKILL.md`. Existing in-product skills
  remain readable; only managed-source skills must use the package layout.
</Note>

### Flows — `flows/<flow-name>.yaml`

```yaml theme={null}
name: weekly-metrics-export
description: Export weekly metrics into Iceberg and post a summary
parameters:
  target_date:
    type: date
    required: true
steps:
  - name: query_metrics
    type: sql
    connection_id: "@snowflake.warehouse"      # logical ref — resolved per-instance
    query: SELECT * FROM analytics.metrics
    output:
      target: iceberg
      table: "@iceberg.metrics_target"          # logical ref
  - name: post_summary
    type: python
    code: |
      result = {"status": "ok"}
on_failure: log
schedule: "0 6 * * MON"
tags: [reporting, weekly]
```

<Info>
  The connection and output table references in the flow file are **logical
  names** prefixed with `@`. The actual physical connection and table for each
  instance live in that instance's bindings file (next section). The repo file
  stays portable across all instances.
</Info>

### Apps — `apps/<app-id>/`

A managed app is a directory containing the app package: `metadata.json` plus
the source code and assets the app needs. Connection / flow dependencies are
declared in `metadata.json` via the optional `requires:` block so per-instance
bindings can resolve them at sync time:

```json theme={null}
{
  "app_id": "ops-alerts-app",
  "name": "Ops Alerts",
  "description": "Routes operational alerts from Snowflake into Slack.",
  "requires": {
    "connections": ["@snowflake.ops"],
    "flows": ["@flows.alert-router"]
  },
  "requires_flows": ["alert-router"]
}
```

The directory name must match `app_id`. As with Skills, bundled assets
round-trip through sync.

## Per-instance bindings

Each Genesis instance resolves logical references to its own physical resources
through bindings. Flows use the per-instance file
`STORAGE_PATH/flows/flow_bindings.yaml`:

Each Genesis instance resolves logical references to its own physical resources
through a local file at `STORAGE_PATH/flows/flow_bindings.yaml`:

```yaml theme={null}
bindings:
  weekly-metrics-export:
    "@snowflake.warehouse": snowflake.prod_warehouse_east
    "@iceberg.metrics_target": iceberg.prod_iceberg
    schedule_override:                   # optional per-instance schedule
      type: cron
      cron: "0 7 * * MON"
      timezone: "America/New_York"
    instance_id: prod-east-1
    enabled: true
```

Manage flow bindings via the REST surface (both endpoints require `MANAGE_FLOWS`):

```http theme={null}
GET  /api/flows/{flow_name}/bindings
PUT  /api/flows/{flow_name}/bindings
```

Skill and App bindings live in the **dependency ledger** (one row per
artifact + dependency) and are managed via `dependency_action` from Eve or
through the **Bind** action in the dependency panel of each library page.

<Warning>
  Do not commit `flow_bindings.yaml` to the managed repository. It lives in
  `STORAGE_PATH` (outside any checkout) by default. The file contains physical
  connection names and per-instance configuration; the whole point is that it
  stays local. The same applies to the dependency ledger.
</Warning>

## Git-backed vs Genesis-versioned (version-mode XOR)

Each Flow and App is either **git-backed** (managed Git is the source of truth) or
**genesis-versioned** (Genesis saved versions and promote/revert in the dashboard).
The modes are mutually exclusive. Git-backed artifacts hide Genesis versioning
controls until you **fork to standalone** (`POST /api/flows/{flow_id}/fork` or
`POST /api/apps/{app_id}/fork` with `{"new_name":"..."}`; agent verbs
`flow_action` / `apps_action` `FORK_TO_STANDALONE`).

Creating a new flow or app whose name collides with a git-backed registry entry
returns `NAME_COLLIDES_WITH_GIT_BACKED`. Calling Genesis versioning APIs on a
git-backed artifact returns `ARTIFACT_IS_GIT_BACKED` with guidance to fork first.

## Dependency model and bindings

Every managed artifact emits **dependency records** at sync time. There are
three kinds:

| Kind | Examples | Status semantics |
| - | - | - |
| `connection` | `@snowflake.warehouse`, `@iceberg.metrics_target` | **Blocking** — missing means the artifact lands as `degraded` and is not enabled / not scheduled. Bind via `dependency_action(BIND_ARTIFACT_DEPENDENCY)` or the **Bind** button. |
| `flow` | `@flows.alert-router` | **Blocking** — same as above. Bind to a local flow by name. |
| `tool_group` | `@tool_groups.snowflake` | **Advisory** — rendered as a gray pill. Fix path is enabling the tool group on the bot, not binding a row. Skills surface **only** `tool_group` rows (from `required-tools` frontmatter), not connection or flow slots. |

ZIP **app import** on a single instance uses the same binding model: validate
returns `unresolved_dependencies` and `available_connections`; import accepts
`source_mapping` (slot → local connection) and `proceed_unresolved=true` to land
degraded with deferred binds on the Apps Library card.

When a sync finishes, Eve narrates a summary like
`"3 artifacts imported (1 ready, 2 degraded). To wire up the 2 degraded
artifacts, bind @snowflake.warehouse and @iceberg.metrics_target."` Use
`dependency_action` to walk the operator through binding the missing refs;
the artifact flips from `degraded` to `ready` once all blocking deps resolve.

```python theme={null}
# What's missing on this source?
dependency_action(action="CHECK_SOURCE_DEPENDENCIES",
                  params={"source_id": "team-flows"})

# What local connections could satisfy this dependency?
dependency_action(action="LIST_DEPENDENCY_TARGETS",
                  params={"kind": "connection"})

# Bind it
dependency_action(action="BIND_ARTIFACT_DEPENDENCY",
                  params={"scope": "artifact",
                          "source_id": "team-flows",
                          "artifact_type": "flow",
                          "artifact_id": "weekly-metrics-export",
                          "kind": "connection",
                          "slot_id_or_role": "@snowflake.warehouse",
                          "target": "snowflake.prod_warehouse_east"})
```

## Workspace edit & push flow

Each library page (Skills, Flows, Apps) shows managed artifacts with an
**Edit** button. Clicking it opens an **isolated per-user worktree** on a
feature branch off the managed source's release ref:

1. The worktree is private to that user × that artifact × that bot — two
   users editing the same skill never see each other's changes.
2. You can edit files inline in the dashboard or via the API.
3. **Push to GitLab** opens an MR/PR against `main`. Genesis enables **Push**
   when the worktree has uncommitted edits or committed work ahead of the linked
   PR head; the push flow commits dirty workspace files before opening the MR.
   Once the MR merges, syncing on any instance picks the change up.
4. **Discard** drops the worktree without pushing anything.

Behind the scenes this is the same `PUSH_TO_REPO` action Eve uses. `main`
is never force-pushed; you can only ever open a PR against it.

## Auto-PROMOTE semantics

When a Flow sync succeeds, Genesis writes a new Flow version into the
`flow_versions` table. By default, the synced version is auto-promoted to
production on that instance because the repository is treated as the source of
truth.

**Manual production pins are protected.** If an admin has manually pinned a
production version (for example, after rolling back a broken release), the
next sync will:

1. Still write the new version into `flow_versions` (history is retained).
2. Detect that the existing pin was set by a real user (its `pinned_by` does
   not start with `managed_sync:`).
3. Refuse to overwrite the pin.
4. Mark the per-flow sync result as `auto_promoted: false` with
   `promote_skipped_reason: "manual_pin_protected"`.

To re-arm auto-promote after a manual rollback, explicitly promote the desired
version again from Genesis. New syncs will then resume managing the pin.

If you want auto-promote off entirely for a source, register or update the
managed source with `auto_promote_on_sync: false`.

### Flows with auto-promote off

When a source has `auto_promote_on_sync: false`, a changed Flow definition is
saved as a pending change rather than as a runnable Flow version. The currently
live definition stays in effect; a new Flow does not become runnable. An admin
must review and promote the pending definition before it becomes live. A synced
removal also waits for promotion before Genesis retires the Flow. Pending
promotion is local to this Genesis instance; another instance syncing the same
source has its own pending changes and promotion decision. See [Flows](/docs/User-Guide/Flows)
for where to review and promote pending definitions.

## Blueprints with auto-promote off

When a managed source that scans blueprints has `auto_promote_on_sync: false`
(the **Auto-promote on sync** checkbox is cleared), a synced blueprint change
does not go live until an administrator promotes it:

1. Each sync that changes a blueprint saves it as a new version. The version
   note names the commit it was synced from.
2. New missions keep running the production version. Open the blueprint's
   **Versions** tab to see the production version, the newest synced version,
   and the version that was production before it.
3. Select **Promote Version N** to make a synced version live, or
   **Return to Version N** to go back to the version that was production
   before it. You do not need to change the sync settings.
4. Every promotion is recorded in the admin audit log as
   `blueprint.version_promoted`, with the user, the version promoted, and the
   version it replaced.

The first sync after you clear the checkbox saves the content that was live at
that moment as Version 1 and keeps it in production. A blueprint that first
appears in the repository, or that moves to a new path, is saved as Version 1
and waits for promotion like any other change. A blueprint with no version in
production does not run: sync the source, then promote a version on its
**Versions** tab. Promotion applies to this Genesis instance only: other
instances that sync the same repository keep their own production version.
Only users who can manage the blueprint can promote it.

A mission that is already running moves to a newly promoted version at its next
phase. Its required checks stay as they were when the mission started.

Managed role definitions can also be included in a managed source. See the
[managed-source API reference](/docs/Genesis-API/API-Reference#unified-managed-sources-apimanaged-sources)
for their separate preview and apply flow.

## Missing bindings

If a Flow is synced but its bindings file does not provide a value for one of
its logical references, sync:

1. Writes the new Flow version into `flow_versions`.
2. Imports the Flow as **disabled** (`is_enabled: false`).
3. Returns `sync_status: "degraded"` with `failed_step: "bindings"` and the
   list of missing logical refs.
4. **Does not** register the Flow with the scheduler.

Configure the missing bindings via the bindings REST endpoint and re-run sync
to enable the flow.

## What syncs vs what stays local

The Git repository carries:

* **Skill definitions** from `skills/<name>/SKILL.md`: frontmatter metadata,
  prompt body, and any bundled context files.
* **Flow definitions** from `flows/<name>.yaml`: steps, parameters, tags,
  schedule intent (cron), failure policy, and logical references.
* **App packages** from `apps/<app-id>/`: metadata + source code + any
  `requires:` declarations.

The following remain **instance-local**:

* **Skill assignments** to specific bots or agents.
* **Flow bindings** mapping logical refs to physical connections and tables.
* **App dependency bindings** in the local dependency ledger.
* **Connection credentials** (PATs, OAuth tokens, account passwords).
* **Flow run history**, per-run state, and scheduler runtime state.
* **Workspace sessions** and unmerged feature branches.
* **Production pins set manually** (see [Auto-PROMOTE semantics](#auto-promote-semantics)).

## Identity and conflict handling

A Skill, Flow, or App may be registered to **exactly one** managed source at a
time. Registering the same `skill_name`, flow `name`, or `app_id` under a
different source returns a 409 conflict.

If you need to move ownership of an artifact to a different source, unregister
the prior source first, then register the new one.

<Note>
  This rule applies to Skills, Flows, and Apps. Blueprints and Missions
  tolerate cross-source name collisions for backward compatibility with
  existing installations.
</Note>

## Troubleshooting

| Symptom | Likely cause |
| - | - |
| Registration returns 409 | Another managed source already owns this skill/flow/app name. Unregister it first. |
| Sync reports `sync_status: degraded` | Inspect `failed_step` in the result. Common values: `bindings` (missing logical refs), `parse_validate` (invalid YAML/JSON or missing required field), `promote` (couldn't pin production version), `schedule` (scheduler refused). |
| A synced Skill is not visible to active threads immediately | Skill sync marks the capability registry dirty; the background reconciler picks it up within seconds. Active threads are not notified synchronously. |
| Flow syncs but stays disabled | Connection / flow dependencies are unbound. Bind via `dependency_action(BIND_ARTIFACT_DEPENDENCY)` or the Bind action in the dashboard. |
| `SKILL.md` `name` differs from the directory name | Sync rejects the mismatch defensively. Rename the directory or the frontmatter `name` field so they match. |
| App imports as degraded with unmet `requires.flows` | Bind the logical flow ref to a local flow with `dependency_action`. Advisory `tool_group` rows are gray and not blocking. |
| Auto-promote did not happen for a synced Flow | Either `auto_promote_on_sync: false` on the source, or a manual production pin is in place — check `promote_skipped_reason` in the sync result. |
| The Blueprints panel shows a skill/flow/app source | Should be filtered out; if you still see this, your backend may be running pre-CLI-463 bytes. Restart the backend. |

## See also

* [Multi-Instance Blueprint Sync](/docs/Blueprint/Multi-Instance-Blueprint-Sync) — the original pattern this extends.
* [Blueprint Library and Missions](/docs/Blueprint/Blueprint-Library-And-Missions) — Remote Repositories panel reference, glob include/exclude, sync recovery toasts.
* [Flows](/docs/User-Guide/Flows) — Flow authoring and execution.
* [Agents Center and Skills](/docs/User-Guide/Agents-Center-And-Skills) — Skill authoring and assignment to bots.
* [Genesis Apps](/docs/genesis-apps) — App authoring and the App Library.
* [CLI-501](https://linear.app/genesiscomputing/issue/CLI-501) — open ticket for single-source-multi-type.
