# Setup files

> Reference · https://spoletta.com/docs/setup-files

A setup is everything built in the app as one YAML file: MCP servers, structs, procedures and flows, with every board, process, object, link and schedule on them. Export it to read, review or keep under version control; edit it or write one; import it back.

## Exporting and importing

On the flow list, **Export** downloads `spoletta-setup.yaml`, with every flow, procedure, struct and MCP server. **Import** reads a `.yaml`, `.yml` or `.json` file back in and reports *Imported <file>: N created, M updated.* Both buttons are shown to admins (or to everyone when sign-in is off).

A setup leaves out what runs produce: runs, versions, undo history and the values stored objects kept. MCP sign-ins stay out too, so after importing a new server, each person connects to it again.

## The shape of the file

```yaml
spoletta: 1              # the format's version, always first
mcpServers: [...]        # name, url, description
structs: [...]           # objects defined once and used by flows
procedures: [...]        # flows other flows run through procedure processes
flows: [...]             # the jobs that run
```

Every field except names is optional. Export writes only what differs from the default.

## What importing does

-   A flow, procedure or struct the app doesn't have yet is **created**.
-   One it has, by name, is **made over** to match the file. Processes, objects, states and attributes that match by name keep their identity, so past runs and stored values still point to them. Anything the file doesn't have is removed from its boards.
-   Flows the file doesn't mention are left alone. Import never deletes a flow.
-   The file is imported **whole or not at all**. A problem anywhere changes nothing, and the reply says what was wrong and where. A flow that is running can't be updated.
-   A flow made over is saved as a version first, and the import is a step of its history: `Cmd` `Z` on the board undoes it. Undo restores the board only; repositories, schedules and MCP servers stay as imported.

## Conventions

| Rule | What it means |
| --- | --- |
| Names are references | Processes name objects in `takes` and `yields`, and processes in `next`, `onFailure` and `start.next`. Process and object names are unique across a whole flow, inner boards included; state names within their object. |
| Same names need keys | If two share a name, give each after the first a `key` and refer to it by that. Simplest: name everything apart. |
| Unknown fields are refused | A typo fails the import rather than being dropped silently. |
| Positions are optional | `x`, `y`, `width`, `route` and `at` only round-trip a drawn board. Left out, new things are laid out from the start; existing things keep their place. Press **Tidy up** afterwards. |
| Shorthands | A link with nothing but its object is just the name; a state without a key or rule is just the name; `kind` defaults to `agent`. |
| Copies | `copyOf: {flow: Ticket}` uses a struct, `copyOf: {flow: P, object: X}` one of a procedure's objects. A copy lists none of its source's fields. |

## Stored, shared and looked up

`persistent: true` on an object stores what runs generate of it, and `unique: true` on an attribute tells stored values apart. `shared: true` on a struct shares its records with every flow using it; it needs a unique attribute and can't go with `persistent`. An input link looks up stored records with a `query`:

```yaml
takes:
  - object: Review
    query:
      where: score >= 4     # a rule on the fields; all records when left out
      state: approved       # any state when left out
      limit: 5              # 20 when left out, 100 at most
      oldest: true          # oldest first; newest first when left out
```

See [Looking up stored records](https://spoletta.com/docs/procedural-links.md#looking-up-stored-records) and [Shared records](https://spoletta.com/docs/structs.md#shared-records).

## A complete example

Release notes, drafted by an agent from the merged pull requests, approved by a person, revised on request and posted to Slack. Every construct in it is explained in [People in the loop](https://spoletta.com/docs/people.md) and [Review loops](https://spoletta.com/docs/loops.md).

spoletta-setup.yaml:

```yaml
spoletta: 1
mcpServers:
  - name: Slack
    url: https://mcp.example.com/slack
flows:
  - name: Release notes
    repositories:
      - name: shop
        url: https://github.com/acme/shop.git
        base: main
    start:
      next: Pick release
    objects:
      - name: Release manager
        human: true
      - name: Release
        attributes:
          - name: version
            definition: The version being released, such as 4.12.0.
          - name: previous
            definition: The version released before it, to compare against.
      - name: Notes
        states: [approved, changes requested, rejected]
    processes:
      - name: Pick release
        kind: human
        takes: [Release manager]
        yields: [Release]
      - name: Draft
        prompt: |
          Write release notes for the Release, covering every pull request
          merged between the previous version's tag and the new one. Group
          them under Features, Fixes and Internal. One line each, in plain
          language a customer understands; link the pull request.
        takes: [Release]
        yields: [Notes]
      - name: Approve
        kind: human
        takes: [Release manager, Notes]
        yields:
          - object: Notes
            states: [approved, changes requested, rejected]
      - name: Revise
        prompt: Revise the release notes for the Release as the reviewer asks.
        takes:
          - Release
          - object: Notes
            state: changes requested
        yields: [Notes]
      - name: Post
        prompt: Post the Notes to the #releases channel, as they are.
        mcpServers: [Slack]
        repositories: []
        takes:
          - Release
          - object: Notes
            state: approved
```

## Changing a flow through its file

1.  **Export, edit, import**
    
    The flow is made over by name, keeping what matches, so its runs and stored values survive.
    
2.  **Rename in the app, not the file**
    
    Renaming a process, object, state or attribute in the file makes a new one and drops the old. A stored object's values outlive it, but the new one doesn't see them, so duplicate detection on a unique attribute starts over. When stored values matter, rename on the board.
    

> **The assistant writes setup files too:** The [flow assistant](https://spoletta.com/docs/assistant.md) replies with the flow as a setup file, which the app imports for you, saving a version first.
