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

# Prompt workflow

> Initialize the prompt manifest, write prompt files, sync versions, and promote release labels with the moda CLI.

This page covers the day-to-day prompt management loop: create the manifest, write prompt files, check local state, sync versions, and promote release labels. All commands come from the `moda` CLI.

## Prerequisites

* `@moda-ai/cli` installed: `npm install -g @moda-ai/cli` (Node.js >= 18).
* An API key for `sync` and `promote`: set `MODA_API_KEY`, or run `moda init` once to provision and store one. `init`, `status`, and `diff` work offline.

<Steps>
  <Step title="Create the manifest">
    ```bash theme={"dark"}
    moda prompts init
    ```

    This writes `.moda/prompts.yml` if it does not exist (it never overwrites an existing manifest):

    ```yaml .moda/prompts.yml theme={"dark"}
    version: 1
    prompt_paths:
      - "prompts/**/*.prompt.md"
      - "prompts/**/*.prompt.json"
      - "prompts/**/*.prompt.yaml"
      - "prompts/**/*.prompt.yml"
    ```

    `moda init` (the full onboarding wizard) also creates this manifest and runs a first sync.

    The CLI discovers files by walking the directory before the first glob character and matching the `.prompt.<ext>` suffix. `node_modules`, `.git`, `dist`, and `build` are skipped, and symlinks are never followed.

    <Note>
      Although the default manifest lists `.prompt.yaml` and `.prompt.yml` patterns, the CLI parses those files with the same frontmatter parser as `.prompt.md`. Write prompts as `.prompt.md` or `.prompt.json`.
    </Note>
  </Step>

  <Step title="Write prompt files">
    **Markdown format** — optional frontmatter, then the prompt content as the body:

    ```md prompts/support/triage.prompt.md theme={"dark"}
    ---
    key: support.triage
    name: Support triage
    description: Classify incoming support requests
    category: support
    system_prompt: Route urgent account issues before answering.
    model: gpt-4o
    ---

    Classify the support request by urgency, account area, and next required tool.
    Return concise JSON with urgency, area, and rationale.
    ```

    Recognized frontmatter keys:

    | Key                                 | Effect                                                  |
    | ----------------------------------- | ------------------------------------------------------- |
    | `key`                               | Registry key. Defaults to the path-derived key (below). |
    | `name`                              | Display name. Defaults to the key.                      |
    | `description`                       | Shown in the dashboard.                                 |
    | `category`                          | Grouping metadata.                                      |
    | `system_prompt` (or `systemPrompt`) | System prompt stored with the version.                  |
    | `model`                             | Stored as `modelConfig.model`.                          |

    Frontmatter is parsed line by line as flat `key: value` pairs — nested YAML structures and lists are not supported, and `.prompt.md` files cannot declare variables. Use `.prompt.json` for structured prompts.

    **JSON format** — supports the full schema, including messages, variables, and tool references:

    ```json prompts/support/answer.prompt.json theme={"dark"}
    {
      "key": "support.answer",
      "name": "Support answer",
      "description": "Draft grounded support replies after tool checks.",
      "content": "Write a concise answer that cites the tool result and names the next action.",
      "systemPrompt": "Prefer verified account state over assumptions.",
      "messages": [
        { "role": "system", "content": "Prefer verified account state over assumptions." },
        { "role": "user", "content": "{{customer_request}}" }
      ],
      "variables": [
        { "name": "customer_request", "description": "The user's support request." }
      ],
      "variablesSchema": {
        "type": "object",
        "properties": {
          "customer_request": { "type": "string" }
        },
        "required": ["customer_request"]
      },
      "modelConfig": {
        "model": "gpt-4o",
        "temperature": 0.2
      },
      "toolRefs": [
        { "name": "lookup_account_status" }
      ]
    }
    ```

    Supported JSON fields: `key`, `name`, `description`, `category`, `content` (defaults to the pretty-printed `messages` array when omitted), `messages`, `systemPrompt`, `variables`, `variablesSchema`, `modelConfig`, `toolRefs`, `responseSchema`. `variables` and `variablesSchema` are stored as declared metadata with each version; the CLI and registry do not render or substitute variables.

    **Key derivation.** When no explicit `key` is set, the key comes from the file path: strip a leading `prompts/`, strip the `.prompt.<ext>` extension, and replace slashes with dots. `prompts/support/triage.prompt.md` becomes `support.triage`.
  </Step>

  <Step title="Check local state">
    ```bash theme={"dark"}
    moda prompts status
    ```

    `status` is local-only — no network call. It compares discovered files against `.moda/prompts.lock.json`:

    ```json theme={"dark"}
    {
      "manifest": ".moda/prompts.yml",
      "lockfile": ".moda/prompts.lock.json",
      "total": 2,
      "changed": 1,
      "new": 1,
      "deleted": 0,
      "prompts": [
        {
          "key": "support.triage",
          "sourcePath": "prompts/support/triage.prompt.md",
          "contentHash": "8c1f27ab…",
          "lockedHash": "2f9e40cd…",
          "versionId": "pver_0f31c2ab9d4e8877a1b2c3d4e5f60718",
          "state": "changed"
        },
        {
          "key": "support.answer",
          "sourcePath": "prompts/support/answer.prompt.json",
          "contentHash": "b47d19ce…",
          "lockedHash": null,
          "versionId": null,
          "state": "new"
        }
      ]
    }
    ```

    Per-prompt `state` is one of `new` (not in the lockfile), `unchanged` (hash matches), `changed` (hash differs), or `deleted` (in the lockfile but no longer on disk).

    `moda prompts diff` runs the same comparison and prints the same output — it is an alias for `status`, not a textual diff of prompt content.
  </Step>

  <Step title="Sync versions">
    ```bash theme={"dark"}
    moda prompts sync
    ```

    Sync uploads **all** discovered prompts, not only changed ones. Because versions are content-addressed, unchanged prompts resolve to their existing version IDs and create nothing new. On success the CLI writes `.moda/prompts.lock.json`:

    ```json .moda/prompts.lock.json theme={"dark"}
    {
      "version": 1,
      "generatedAt": "2026-08-16T09:12:44.310Z",
      "prompts": {
        "support.triage": {
          "key": "support.triage",
          "sourcePath": "prompts/support/triage.prompt.md",
          "contentHash": "8c1f27ab…",
          "promptId": "prompt_7d1a9c0b3e5f24681357ace9",
          "versionId": "pver_0f31c2ab9d4e8877a1b2c3d4e5f60718"
        }
      }
    }
    ```

    The lockfile holds the IDs you need for [runtime attribution](/prompt-management/attribution) and for `promote`. It is rewritten wholesale on each sync; commit it alongside your prompt files.

    Every sync also moves the `dev` label to the just-synced version of each prompt.

    Options:

    * `--dry-run` — the server computes and returns the IDs without writing anything; the lockfile is not updated.
    * `--watch` — re-check local files on an interval and re-sync when hashes change. `--interval=<ms>` sets the poll interval (default 2500, minimum 1000).

    `moda sync prompts` is an alias for `moda prompts sync`.
  </Step>

  <Step title="Promote a release label">
    ```bash theme={"dark"}
    moda prompts promote support.triage --label=staging --version=pver_0f31c2ab9d4e8877a1b2c3d4e5f60718
    ```

    `--label` accepts `dev`, `staging`, or `prod`; `--version` (alias `--version-id`) takes the version ID from the lockfile or from `moda prompts status`. The label moves atomically from whichever version held it to the target version.

    Promote to `staging` before `prod` if you validate releases in a pre-production environment. You can also promote from the dashboard: each version on the prompt's Versions tab has staging and prod promote buttons.

    <Warning>
      A `--label=dev` promotion is overwritten by the next `moda prompts sync`, because sync always moves `dev` to the version it just uploaded.
    </Warning>
  </Step>
</Steps>

## Verify in the dashboard

After the first sync, open **Dashboard → Prompts**. You should see each discovered prompt with one version, the `dev` label on that version, and the source path and commit you synced from. After a promote, the label badge moves to the target version on the prompt's Versions tab.

## Optional: sync automatically from CI

```bash theme={"dark"}
moda init --autosync
```

This writes `.github/workflows/moda-autosync.yml`, a GitHub Actions workflow that runs `moda prompts sync` (and `moda skills sync`) on every push to `main` — and only `main`, so unmerged versions are never published. It needs two repository secrets: `MODA_API_KEY` and `MODA_TENANT_ID`.

```yaml .github/workflows/moda-autosync.yml theme={"dark"}
name: Moda Autosync

on:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: moda-autosync
  cancel-in-progress: true

jobs:
  moda-sync:
    runs-on: ubuntu-latest
    env:
      MODA_API_KEY: ${{ secrets.MODA_API_KEY }}
      MODA_TENANT_ID: ${{ secrets.MODA_TENANT_ID }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Sync prompts to Moda
        run: npx -y --package=@moda-ai/cli@<version> moda prompts sync
      - name: Sync skills to Moda
        run: npx -y --package=@moda-ai/cli@<version> moda skills sync
```

In the generated file, `<version>` is the exact version of the CLI that wrote it — the pin makes CLI upgrades explicit, reviewable diffs. Rerunning `moda init --autosync` after upgrading the CLI bumps the pin.

## Deleting a prompt

Delete the file from your repo. `moda prompts status` reports it as `deleted`, and the next sync drops it from the lockfile. The prompt and its versions remain in the registry — there is no delete or archive API — but they stop receiving new versions.

## Troubleshooting

| Symptom                                                       | Fix                                                                                                                                                                   |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sync` or `promote` fails asking for an API key (exit code 4) | Set `MODA_API_KEY` or run `moda init` to provision and store a key.                                                                                                   |
| `sync` fails with `prompts must contain at least one prompt`  | No files matched the manifest patterns. Check the directory and the `.prompt.md` / `.prompt.json` extensions against `.moda/prompts.yml`.                             |
| `promote` returns 404                                         | The key or version ID does not exist in the registry. Copy `versionId` from `.moda/prompts.lock.json` or `moda prompts status`, and sync first if the version is new. |
| A version promoted with `--label=dev` loses the label         | The next sync moved `dev` to the newly synced version. Use `staging` or `prod` for durable pins.                                                                      |
| A changed name or description does not create a new version   | Expected: `name`, `description`, and `category` are not part of the content hash. Only content changes version.                                                       |

## Next steps

* [Prompt attribution](/prompt-management/attribution) — stamp the lockfile IDs on runtime calls so usage shows up per version.
* [Prompt experiments](/prompt-management/experiments) — A/B a candidate against the current prompt before promoting.
* [Prompt management overview](/prompt-management/overview) — the versioning and label model in one page.
* [CLI reference](/cli/reference) — output modes, exit codes, and environment variables shared by all CLI commands.
