# `save_person`

Atomically creates or edits one person in your own private Rolodex when you request it. Saving starts no search, changes no notes, and contacts nobody.

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

## Inputs

| Input | Required | Description |
| --- | --- | --- |
| `personId` | yes | Null creates a person; otherwise use an exact owned-person UUID. |
| `expectedVersion` | no | Required for edits: current opaque version from `get_person`. Omit when creating. |
| `requestId` | yes | New UUID per action; keep every argument on an identical retry. |
| `name` | no | Required for creation; trimmed 1–120 characters. Sets your private name for this person. |
| `emails` | no | Up to 20 valid addresses, each at most 254 characters. Adds addresses without removing existing ones; normalized to lowercase and deduplicated. |
| `linkedIn` | no | Partial object with `url`, `company`, or `position`. Supplied null clears that field; omitted fields stay unchanged. |
| `sections` | no | Partial object containing one or more of the six profile sections below. |
| `basis` | no | Required with sections: trimmed 1–500 character supporting member statement or source, retained privately in the request record. |

Supply at least one requested change. Creation requires a name and a nonempty `sections.relationshipContext` explaining how you know this person, plus `basis`. Email is optional. Use `find_people` to check an existing identity before creating. A name does not prove identity; an existing email returns a rejection and the owned person's current state for review. Read that exact person and prepare an edit instead of merging automatically. Emails assigned to different people reject the whole save.

LinkedIn `url` must be an HTTPS LinkedIn profile URL, at most 2,048 characters. Company and position are 1–200 characters when non-null. Identity details remain private to your Rolodex. A supplied name changes your owner-authored label, preserves the contact-authored name, and takes precedence over later Gmail name updates.

## Profile sections

`summary` is one nonempty string of up to 4,000 characters, or null. The other fields—`general`, `areasOfDepth`, `currentWork`, `currentNeeds`, and `relationshipContext`—are arrays of up to 40 single-line claims, each 1–400 characters after trimming, or null.

Each supplied section replaces that entire section. Preserve unrelated claims verbatim when changing one claim. Omitted sections remain unchanged. An empty array clears a claim section. Null removes your correction and restores the derived section; it does not delete source material. Keep attribution, uncertainty, negation, and timing. Supporting basis belongs in `basis`, not profile prose. Notes use the separate `save_person_notes` lifecycle.

## Returns and retries

`{ status: "success", outcome, created, requestId, state, changedFields, message }`. `outcome` is `saved` or `no_change`; `state` is the current `get_person` state. `changedFields` names the changed fields, including dotted LinkedIn fields. A new person has `created: true` on its initial save; an identical retry reports current state with `created: false` and no repeated effects.

A stale edit version returns `version_conflict` before any changes. The member-scoped request ID binds the normalized arguments and original effects. Identical retries return `no_change` while those effects still hold; unrelated later edits are allowed. A later change to a requested field prevents replay rather than restoring the older value. Reusing the ID with different arguments returns `request_id_conflict`. After a conflict, review current details and prepare a new action with a new ID. Never overwrite newer details automatically.

## Example

```json save_person
{
  "personId": "00000000-0000-4000-a000-000000000005",
  "expectedVersion": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
  "requestId": "00000000-0000-4000-a000-000000000002",
  "sections": {
    "currentWork": [
      "Building a synthetic product"
    ]
  },
  "basis": "Member explicitly described this current work."
}
```

Result (synthetic data):

```json result:save_person
{
  "status": "success",
  "outcome": "saved",
  "created": false,
  "requestId": "00000000-0000-4000-a000-000000000002",
  "state": {
    "person": {
      "personId": "00000000-0000-4000-a000-000000000005",
      "contactAuthoredName": "Synthetic Pat",
      "ownerAuthoredName": null,
      "emails": [
        "pat@example.test"
      ],
      "profile": {
        "summaries": [
          "Synthetic private profile"
        ],
        "general": [],
        "areasOfDepth": [],
        "currentWork": [
          "Building a synthetic product"
        ],
        "currentNeeds": [],
        "relationshipContext": [
          "You worked together"
        ]
      },
      "linkedIn": {
        "url": null,
        "company": "Synthetic company",
        "position": "Designer"
      }
    },
    "version": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
  },
  "changedFields": [
    "currentWork"
  ],
  "message": "Your private person changes were saved."
}
```

## Related

See [person editing](/docs/mcp/agent-guide#private-people-and-profile-sections), [`get_person`](/docs/mcp/tools/get_person), and [errors and limits](/docs/mcp/errors-and-limits).
