Skip to content
Browse docssave_goal

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#

InputRequiredTypeDescription
goalIdyesstring or nullnull to create; otherwise the exact owned goal ID.
textnostringRequired 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.
selectedGroupPublicIdsnolist of stringsComplete replacement audience when supplied, up to 100. Omit on edits to preserve it; new goals default to private. [] makes it private.
hiddenGroupPublicIdsnolist of stringsComplete replacement list of selected groups to hide the goal from. Omit to preserve kept groups' settings; added groups start shown.
additionalContextnostringComplete replacement private details, up to 4,000 characters. Omit to preserve them; "" clears them.
expectedVersionnostringOpaque goal.version from get_goal. Constrains an edit to that complete state, including visibility.
expectedScopeVersionnointeger or nullOptional matching-input precondition from goal.scopeVersion; omit or use null for creation.
expectedContextRevisionIdnostringOptional context precondition from goal.contextRevisionId.
requestIdyesstringNew 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.

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 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.

OutcomeMeaning
savedThe requested changes were committed.
no_changeAll supplied changes already match current state; the goal was unchanged and no search started.
endedThe 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. 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
{
  "goalId": null,
  "text": "Find design partners",
  "additionalContext": "Synthetic private details",
  "requestId": "00000000-0000-4000-a000-000000000002"
}

Change only wording:

json
{
  "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
{
  "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"
      }
    }
  }
}