<!-- Generated by crates/swarmops-cli/src/reference/mcp_page.rs from the code: do not edit. Change the code, then run SWARMOPS_REGEN_REFERENCE=1 with the regen_reference test. -->

# commit

Records a changeset from an explicit change list, on parent or else on main's head. With advance it also moves that ref to the changeset in the same transaction, only if the ref still names parent, which must then be the full 64-hex id you read; otherwise it records nothing (E\_CAS\_MISMATCH, details.committed false). Check result.advanced: a server that does not know advance moves no ref. An idempotency\_key, a uuid v7 new for each commit, makes a retry safe: the same call again replays it (result.replayed) instead of recording it twice. A {"file": path} content is read by this server: give an absolute path.

```yaml
title: "commit"
description: "Commit a changeset."
since: "0.1.0"
mutates: true
identity_required: true
cli_command: "swarmops commit"
envelope_op: "commit"
request_type: "CommitReq"
annotations: {"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false}
max_result_chars: null
```

## Input schema

`arguments` is a `CommitReq`: `swarmops schema CommitReq` prints the type. A field the type does not know is ignored.

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `advance` | string or null | no |  | Also move this ref (`main` or `release/<name>`) to the new changeset, in the same transaction as the changeset itself, so that both are recorded or neither. The move happens only if the ref still names the changeset's parent (for a root commit, only if the ref does not exist yet): it is always a one-step fast-forward. Otherwise nothing is recorded and the commit fails with `E_CAS_MISMATCH` (`details.committed: false`, `details.actual` what the ref names). Give as `parent` the full id the ref held when you read it: without `parent` the commit builds on the ref's head when it runs, so the move catches a race from there on, not a change made since you read the ref (M1 has no merge). `swarmops mcp` refuses `advance` without a full 64-hex `parent` (`E_INVALID_ARG`). A store that does not know this field ignores it and moves no ref: its response has no `advanced`. |
| `allow_empty` | boolean | no | `false` | Record the changeset even if it changes nothing. |
| `changes` | array of object | yes |  | What to change, relative to the parent. The contents hold at most 1 GiB together, by the sizes they declare (inline text's length, the bytes base64 decodes to, a client file's length; a `blob` named by id counts nothing): more is `E_TOO_LARGE` with `scope` `commit`, refused before any content is read. Store large contents with `blob_put` first, and name them by id. |
| `idempotency_key` | string or null | no |  | Makes a retry of this commit safe: a uuid v7 (anything else is `E_INVALID_ARG`). The commit takes the key's time, to the second, as its signature time and records the key as `commit_key` metadata, so the same request on the same parent makes the same changeset, which the store holds already: the response says `replayed`, and nothing is written. With `advance`, a ref whose head carries the key is a replay too. Give `parent` by its full id: without it, a retry after the head moved commits again, on the new head. |
| `message` | string | yes |  | The commit message. |
| `parent` | string or null | no |  | The changeset to commit on top of. An explicit `parent` always wins; when it is omitted, the current head of the ref `advance` names is used, else of `main`, if that ref exists, and otherwise the commit is a root commit. `null` is the same as omitting it. Once `main` exists, a root commit needs an `advance` naming a ref that does not exist yet (a known M1 limitation). |

### JSON Schema

As `tools/list` gives it: each `$ref` replaced by its definition, so that it stands alone.

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "`commit`: record a changeset from an explicit change list. It moves no ref, unless\n`advance` names one.",
  "properties": {
    "advance": {
      "description": "Also move this ref (`main` or `release/<name>`) to the new changeset, in the same\ntransaction as the changeset itself, so that both are recorded or neither. The move\nhappens only if the ref still names the changeset's parent (for a root commit, only if the\nref does not exist yet): it is always a one-step fast-forward. Otherwise nothing is\nrecorded and the commit fails with `E_CAS_MISMATCH` (`details.committed: false`,\n`details.actual` what the ref names). Give as `parent` the full id the ref held when you\nread it: without `parent` the commit builds on the ref's head when it runs, so the move\ncatches a race from there on, not a change made since you read the ref (M1 has no\nmerge). `swarmops mcp` refuses `advance` without a full 64-hex `parent` (`E_INVALID_ARG`).\nA store that does not know this field ignores it and moves no ref: its response has no\n`advanced`.",
      "type": [
        "string",
        "null"
      ]
    },
    "allow_empty": {
      "default": false,
      "description": "Record the changeset even if it changes nothing.",
      "type": "boolean"
    },
    "changes": {
      "description": "What to change, relative to the parent. The contents hold at most 1 GiB together, by the\nsizes they declare (inline text's length, the bytes base64 decodes to, a client file's\nlength; a `blob` named by id counts nothing): more is `E_TOO_LARGE` with `scope`\n`commit`, refused before any content is read. Store large contents with `blob_put` first,\nand name them by id.",
      "items": {
        "description": "One change of a commit.",
        "properties": {
          "content": {
            "anyOf": [
              {
                "description": "A `put`'s content, as an object with one key.",
                "oneOf": [
                  {
                    "additionalProperties": false,
                    "description": "UTF-8 text, verbatim.",
                    "properties": {
                      "inline": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "inline"
                    ],
                    "type": "object"
                  },
                  {
                    "additionalProperties": false,
                    "description": "Bytes in standard, padded base64.",
                    "properties": {
                      "base64": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "base64"
                    ],
                    "type": "object"
                  },
                  {
                    "additionalProperties": false,
                    "description": "A blob the store holds already, by its id (64 hex digits), as `blob_put` returns it: the\ncommit neither sends nor reads its bytes. An id the store holds no blob under (none, or\na tree's or a changeset's) is `E_INVALID_ARG`, and nothing is written. `blob_put` takes\nthe other forms only.",
                    "properties": {
                      "blob": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "blob"
                    ],
                    "type": "object"
                  },
                  {
                    "additionalProperties": false,
                    "description": "A file on the client's machine. The client reads it and sends its bytes, so the store is\nnever asked to read a path.",
                    "properties": {
                      "file": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "file"
                    ],
                    "type": "object"
                  }
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The new content, at most 512 MiB (more is `E_TOO_LARGE` with `scope` `put`); a `put` needs\nit, a `delete` takes none."
          },
          "mode": {
            "anyOf": [
              {
                "description": "The kind of entry a `put` writes.",
                "oneOf": [
                  {
                    "const": "file",
                    "description": "A regular file (git mode 100644).",
                    "type": "string"
                  },
                  {
                    "const": "exec",
                    "description": "An executable file (git mode 100755).",
                    "type": "string"
                  },
                  {
                    "const": "symlink",
                    "description": "A symlink; the content is the link's target.",
                    "type": "string"
                  }
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The kind of entry a `put` writes; `file` when omitted."
          },
          "op": {
            "description": "Whether to write the path or remove it. What a change does to its path, in a commit's change list and a changeset's changes alike.",
            "oneOf": [
              {
                "const": "put",
                "description": "Write the content at the path, creating or replacing the entry.",
                "type": "string"
              },
              {
                "const": "delete",
                "description": "Remove the path.",
                "type": "string"
              }
            ]
          },
          "path": {
            "description": "The repository path: `/`-separated, relative to the root.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "op"
        ],
        "type": "object"
      },
      "type": "array"
    },
    "idempotency_key": {
      "description": "Makes a retry of this commit safe: a uuid v7 (anything else is `E_INVALID_ARG`). The\ncommit takes the key's time, to the second, as its signature time and records the key as\n`commit_key` metadata, so the same request on the same parent makes the same changeset,\nwhich the store holds already: the response says `replayed`, and nothing is written.\nWith `advance`, a ref whose head carries the key is a replay too. Give `parent` by its full\nid: without it, a retry after the head moved commits again, on the new head.",
      "type": [
        "string",
        "null"
      ]
    },
    "message": {
      "description": "The commit message.",
      "type": "string"
    },
    "parent": {
      "anyOf": [
        {
          "description": "A revision as the caller wrote it, for the store to resolve: a ref name (`main`,\n`release/1.2`), a full 64-hex changeset id or a unique prefix of at least 12 hex digits,\noptionally followed by `~N` for the N-th first-parent ancestor.",
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "The changeset to commit on top of. An explicit `parent` always wins; when it is omitted,\nthe current head of the ref `advance` names is used, else of `main`, if that ref exists,\nand otherwise the commit is a root commit. `null` is the same as omitting it. Once `main`\nexists, a root commit needs an `advance` naming a ref that does not exist yet (a known M1\nlimitation)."
    }
  },
  "required": [
    "changes",
    "message"
  ],
  "title": "CommitReq",
  "type": "object"
}
```

## Annotations

It adds to the store and changes nothing there. Run again, a `commit` adds again, another changeset signed at its own time, unless the call carries an `idempotency_key`, which makes the same call again a replay; a `blob_put` of the same content finds it stored, and says so in its result.

| Annotation | Value | Meaning |
| --- | --- | --- |
| `destructiveHint` | `false` | True: a tool that changes the store may overwrite or delete what is there, not only add to it. |
| `idempotentHint` | `false` | True: calling it again with the same arguments changes nothing more. |
| `openWorldHint` | `false` | True: it reaches beyond a closed set of things. Every tool here stays inside the store. |
| `readOnlyHint` | `false` | True: the tool does not change its environment, the store. |

Its entry in `tools/list` carries no `_meta`.

## CLI command

[`swarmops commit`](../commands/commit.md) runs the same operation and prints the same envelope.

## Examples

### MCP

```json
{
  "id": 5,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "changes": [
        {
          "content": {
            "blob": "[blob]"
          },
          "op": "put",
          "path": "README.md"
        },
        {
          "content": {
            "inline": "pub fn f() {}\n"
          },
          "op": "put",
          "path": "src/lib.rs"
        }
      ],
      "message": "first"
    },
    "name": "commit"
  }
}
```

```json
{
  "id": 5,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "text": "{\"ok\":true,\"op\":\"commit\",\"api\":1,\"result\":{\"changeset\":\"[c1]\",\"root\":\"[id1]\",\"files_changed\":2,\"blobs_written\":1,\"replayed\":false},\"meta\":{\"repo\":\"[tmp]/store\",\"elapsed_ms\":\"[elapsed_ms]\",\"resolved\":{},\"auth\":\"trust\"}}",
        "type": "text"
      }
    ],
    "isError": false,
    "structuredContent": {
      "api": 1,
      "meta": {
        "auth": "trust",
        "elapsed_ms": "[elapsed_ms]",
        "repo": "[tmp]/store",
        "resolved": {}
      },
      "ok": true,
      "op": "commit",
      "result": {
        "blobs_written": 1,
        "changeset": "[c1]",
        "files_changed": 2,
        "replayed": false,
        "root": "[id1]"
      }
    }
  }
}
```

The server ran as `swarmops mcp --repo [tmp]/store --as agent/worker-17`, after `initialize`. Recorded by the MCP tests. A name in square brackets, such as `[tmp]` or `[c1]`, stands for what differs from run to run: the test's temporary directory, an id, a time, the version.

## Details

A commit records a changeset from an explicit change list, on `parent` or else on main's head.

- **Advance a ref.** With `advance`, the same transaction moves that ref to the changeset, only if
  the ref still names `parent`; the server then needs `parent` as the full 64-hex id you read
  (`E_INVALID_ARG` otherwise). If the ref moved, nothing is recorded: `E_CAS_MISMATCH` with
  `details.committed` false. Check `result.advanced`: a server that does not know `advance`
  moves no ref.
- **Retry safely.** An `idempotency_key`, a uuid v7 new for each commit, makes the same call
  again a replay (`result.replayed`) instead of a second changeset.
- **Content.** A change carries its bytes inline or as base64, names a stored blob as
  `{"blob": id}` (see [`blob_put`](../mcp-tools/blob-put.md)), or names a file as `{"file": path}`. The
  server reads that file itself, so the path must be absolute and name a regular file, not the
  server's own stdin or stdout; anything else is refused with `E_INVALID_ARG`.

To publish the changeset on `main`, [`land`](../mcp-tools/land.md) it.

## Return information

A tool result: the envelope, whose `op` is `commit`, as `structuredContent` and again as the text of its one `content` block. `isError` is true exactly when the envelope's `ok` is false. On success the envelope's `result` is what [`swarmops commit`](../commands/commit.md#return-information) returns.

## Errors

A failed call is a tool result with `isError: true`; the envelope's `error.code` is one of the [error codes](../errors.md).

- [`E_INVALID_ARG`](../errors.md#e_invalid_arg): arguments that do not fit `CommitReq`, a field named twice among them included, or that break one of the server's own rules.
- [`E_IDENTITY_REQUIRED`](../errors.md#e_identity_required): the server runs with no identity.
- [`E_NOT_A_REPO`](../errors.md#e_not_a_repo): the server's store is not there; the server serves on.
- [`E_INTERNAL`](../errors.md#e_internal): the operation panicked, a bug.

Then the errors of the operation, as [`swarmops commit`](../commands/commit.md#errors) lists them.

## See also

- [`swarmops commit`](../commands/commit.md)
- [MCP tools](README.md)
- [MCP integration](../../develop/mcp.md)
- [Error codes](../errors.md)
