# `save_goal`

Creates, patches, or ends a goal you own. Saving starts no search.

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: true` · `openWorldHint: false`

## When to use it

Only when asked to create, edit, share, hide, or end a goal. Resolve its exact identity and explain the audience for sharing changes. Explain that ending archives its introduction chats.

An edit supplies only the fields the member wants changed. Omitted fields stay unchanged. New authorized edits can update current state without a mandatory prior read. Use an optional version precondition when the change should depend on a particular state.

## Inputs

| Input | Required | Type | Description |
| --- | --- | --- | --- |
| `goalId` | yes | string or null | `null` to create; otherwise the exact owned goal ID. |
| `text` | no | string | Required and nonblank for creation, up to 2,000 characters. Omit on edits to preserve wording. Only a literal empty string ends a goal; whitespace-only wording is rejected. |
| `selectedGroupPublicIds` | no | list of strings | Complete replacement audience when supplied, up to 100. Omit on edits to preserve it; new goals default to private. `[]` makes it private. |
| `hiddenGroupPublicIds` | no | list of strings | Complete replacement list of selected groups to hide the goal from. Omit to preserve kept groups' settings; added groups start shown. |
| `additionalContext` | no | string | Complete replacement private details, up to 4,000 characters. Omit to preserve them; `""` clears them. |
| `expectedVersion` | no | string | Opaque `goal.version` from `get_goal`. Constrains an edit to that complete state, including visibility. |
| `expectedScopeVersion` | no | integer or null | Optional matching-input precondition from `goal.scopeVersion`; omit or use `null` for creation. |
| `expectedContextRevisionId` | no | string | Optional context precondition from `goal.contextRevisionId`. |
| `requestId` | yes | string | New UUID per action. Keep the same ID and arguments for a retry. |

Keep confidential details in `additionalContext`, outside shared `text`. Goals for a fixed beneficiary remain private and their beneficiary cannot change. See [Goals and audiences](/docs/mcp/concepts/goals).

## What each change does

- Wording, private context, and audience changes save immediately and cancel this goal's obsolete search. Other goals' searches continue.
- Visibility-only changes save immediately without changing matching inputs or stopping a search.
- Saving starts no replacement search. Call [`start_goal_matching`](/docs/mcp/tools/start_goal_matching) separately for an authorized search.
- Ending archives the goal's introduction chats and cancels its search. It cannot be reopened. End with just `text: ""` rather than combining ending with other edits.

All supplied fields commit together. Validation, access, version, and request-ID failures leave them unchanged. Obsolete workers cannot publish further results as current after the edit commits. Existing conversations remain attached to the goal.

## Returns and retries

Success returns `status`, `outcome`, `message`, `goalId`, `scopeVersion`, `matchingRunId`, `cancelledMatchingRunIds`, and `currentState`. MCP saves always return `matchingRunId: null`; an existing search can still appear in `currentState.matching.run`.

| Outcome | Meaning |
| --- | --- |
| `saved` | The requested changes were committed. |
| `no_change` | All supplied changes already match current state; the goal was unchanged and no search started. |
| `ended` | The goal was ended and its chats archived. |

`currentState` contains the owned goal, selected groups, and current search state using the same safe projections as `get_goal`. It is an observed snapshot, not a promise that other actors cannot change the goal afterward.

Retries compare every supplied field. Identical current state returns `no_change`, even when a supplied version is old. If this request was already applied and the desired fields now differ, return `version_conflict` and current state without reapplying it. A supplied stale version also rejects a new edit that would change state. Different arguments under an already-used request ID return `request_id_conflict`. No historical outcome is replayed.

Errors use the [common contract](/docs/mcp/errors-and-limits). For a conflict, explain that the goal changed since the edit was prepared, inspect `currentState`, and prepare a new action only within the member's intent.

## Examples

Create a private goal:

```json save_goal
{
  "goalId": null,
  "text": "Find design partners",
  "additionalContext": "Synthetic private details",
  "requestId": "00000000-0000-4000-a000-000000000002"
}
```

Change only wording:

```json save_goal
{
  "goalId": "00000000-0000-4000-a000-000000000001",
  "text": "Find design partners",
  "requestId": "00000000-0000-4000-a000-000000000002"
}
```

Example response for an existing goal (synthetic data):

```json result:save_goal
{
  "status": "success",
  "outcome": "saved",
  "message": "Goal saved.",
  "goalId": "00000000-0000-4000-a000-000000000001",
  "scopeVersion": 2,
  "matchingRunId": null,
  "cancelledMatchingRunIds": [],
  "currentState": {
    "goal": {
      "id": "00000000-0000-4000-a000-000000000001",
      "memberId": "00000000-0000-4000-a000-000000000003",
      "beneficiary": null,
      "ownerName": "Synthetic member",
      "ownerImageUrl": null,
      "text": "Find design partners",
      "additionalContext": "Synthetic private details",
      "clarificationQuestion": null,
      "version": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "contextRevisionId": "00000000-0000-4000-a000-000000000002",
      "scopeVersion": 2,
      "suggestedByGravity": false,
      "retiredAt": null,
      "createdAt": "2026-10-09T00:00:00.000Z",
      "isOwner": true
    },
    "selectedGroups": [
      {
        "publicId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "name": "Synthetic founders",
        "hidden": true
      }
    ],
    "matching": {
      "run": {
        "runId": "00000000-0000-4000-a000-000000000002",
        "goalId": "00000000-0000-4000-a000-000000000001",
        "goalScopeVersion": 2,
        "goalContextRevisionId": "00000000-0000-4000-a000-000000000002",
        "status": "running",
        "stage": "searching",
        "activities": [
          "researching"
        ],
        "searchProgress": {
          "estimatedPercent": 50,
          "completedNetworks": 1,
          "totalNetworks": 2,
          "completedTurns": 3
        },
        "fitPeopleCount": 1,
        "hasReviewedMatches": false,
        "completedAt": null,
        "error": null,
        "eligibleMembers": [
          {
            "displayName": "Synthetic member",
            "isCurrentMember": true,
            "reason": "updated"
          }
        ],
        "failedMembers": [],
        "coverage": {
          "searchedMemberCount": 1,
          "groupMemberCount": 2
        },
        "startedAt": "2026-10-09T00:00:00.000Z",
        "updatedAt": "2026-10-09T00:01:00.000Z"
      }
    }
  }
}
```

## Related

- [Goal editing workflow](/docs/mcp/agent-guides/create-and-edit-goals)
- [Errors and limits](/docs/mcp/errors-and-limits)
