> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pnbr.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Make two entities one

> Two entities that are one thing become one entity, with their relationships and citations kept and the extra entity deleted.

Guide · graph

> **Use when**
>
> Two entities in the Graph are the same thing, and you have decided which one to keep.

> **Looking for something else?**
>
> Deciding whether two entities are the same thing (problem [Two entities have the same name](https://docs.pnbr.io/troubleshooting/same-name-two-entities.md)). A repeat that is still only an item in a workspace (problem [An extraction found an entity the Graph already has](https://docs.pnbr.io/troubleshooting/extraction-duplicates-existing-entity.md)).

## Outcome

One entity, the kept one, holds every relationship and property that either entity held, with their citations. The other entity is deleted, and you can restore it.

## Before you start

1. Confirm that the two entities are one thing (problem [same-name-two-entities](https://docs.pnbr.io/troubleshooting/same-name-two-entities.md)). Do not go on if you are unsure.
2. Choose the entity to keep. Prefer the one with more relationships and citations, or the one that people already use. Write down both ids and both revisions.
3. Know your credential: `penumbra whoami`. A delete and a submit from an API key or a connected agent wait for approval (card rules [approval.2](https://docs.pnbr.io/concepts/approval.md#rule-approval-2) and [workspace.10](https://docs.pnbr.io/concepts/workspace.md#rule-workspace-10)). One workspace holds both, so one approval covers them.
4. Use one credential for the whole recipe (card rule [workspace.6](https://docs.pnbr.io/concepts/workspace.md#rule-workspace-6)).
5. Choose a request id for the submit, such as `--request-id merge-<extra-id>-1` (problem [reply-lost](https://docs.pnbr.io/troubleshooting/reply-lost.md)).

## Steps

1. Read both entities. Write down the properties, the cited Files and every relationship of the extra entity.
   * CLI: `penumbra context entity <kept-id> --json` and `penumbra context entity <extra-id> --json`. Use `--relationship-limit 100` and the cursor it prints for more.
   * MCP: `read` with the reference `entity:<id>` for each. It shows the properties, the first 50 connections and the citations.
   * Program: `penumbra.domain.types.<Type>.get(id)` for each, and `entity.related(relationship, neighborType, {direction, limit, cursor})` for each relationship name that `grammar` lists for the domain. Each result carries `edge.id`.
   * Expect: for each relationship, its type, its direction, the id of the other end and its properties.
2. List the relationships of the extra entity in both directions.
   * CLI: `penumbra graph edges --node <extra-id> --direction both --limit 200`.
   * MCP: `traverse_graph` with `from: ["<extra-id>"]` and `direction: "both"` (full catalog).
   * Program: `entity.related(...)` with `direction: "outgoing"` and again with `"incoming"`, as in step 1.
   * Expect: one line for each relationship. A relationship whose other end is the kept entity is not moved. It is deleted with the extra entity (card rule [relationship.6](https://docs.pnbr.io/concepts/relationship.md#rule-relationship-6)).
3. List the relationships of the kept entity the same way. Cross out from your list each relationship that the kept entity already has, with the same type, direction and other end.
4. Open a workspace with a new name (card rule [workspace.1](https://docs.pnbr.io/concepts/workspace.md#rule-workspace-1)).
   * CLI: `penumbra graph workspace open "merge <extra-id>"`.
   * MCP: `workspace_open` (full catalog).
   * Program: `penumbra.workspaces.open({name})`.
5. Stage each relationship that is left on your list, from or to the kept entity. Name the other end by its id (card rule [relationship.3](https://docs.pnbr.io/concepts/relationship.md#rule-relationship-3)). Copy the type, the direction and the properties. Add `evidence` with citations you can support (card rule [relationship.8](https://docs.pnbr.io/concepts/relationship.md#rule-relationship-8)).
   * CLI: put one line in a file for each relationship, such as `{"type":"WORKS_AT","from":"<kept-id>","to":"<other-id>","properties":{}}`. Run `penumbra graph workspace capture "<workspace>" <file>`.
   * MCP: `capture` with `relationships` and an `idempotency_key` (full catalog).
   * Program: `penumbra.domain.stage({workspace, relationships: [{type, from, to}]})`. It sets no properties and no citations. Use the CLI when the relationship has them.
   * Expect: `staged` and one line for each relationship, naming the ids.
6. If the extra entity holds properties or citations that the kept entity lacks, check out the kept entity and stage them (card rule [entity.2](https://docs.pnbr.io/concepts/entity.md#rule-entity-2)).
   * CLI: `penumbra graph workspace hydrate "<workspace>" --node <kept-id>`. Then write one line `{"handle":"<handle>","changes":{"patch":{"properties":{"<name>":"<value>"}},"evidence":[{"file_id":"<id>","version_id":"<id>","content_digest":"<digest>"}]}}` into a file. Take the three values from step 1. Run `penumbra graph workspace modify "<workspace>" <file>`.
   * MCP: `workspace_hydrate` and `workspace_modify` (full catalog). Use a citation token from `read` as `evidence: [{"citation": "<token>"}]`.
   * Program: `w.hydrate({nodes: ["<kept-id>"]})`, then `w.modify({edit: [{handle, changes: {patch: {properties}, evidence: [{citation}]}}]})`. The patch merges into the properties of a checked-out entity.
   * Expect: the patch names only the properties the kept entity lacks. Skip this step if there are none.
7. Check out the extra entity for deletion.
   * CLI: `penumbra graph workspace hydrate "<workspace>" --node <extra-id> --intent delete`.
   * MCP: `workspace_hydrate` with `nodes` and `intent: "delete"` (full catalog).
   * Program: `w.hydrate({nodes: ["<extra-id>"], intent: "delete"})`.
   * Expect: one item with `intent=delete`.
8. Validate the workspace (card rule [workspace.3](https://docs.pnbr.io/concepts/workspace.md#rule-workspace-3)).
   * CLI: `penumbra graph workspace validate "<workspace>"`.
   * MCP: `workspace_validate`.
   * Program: `w.validate()`.
   * Expect: `valid: yes` and one deletion. Fix each line that starts with `error`, then validate again.
9. Tell the person which entity this deletes and which relationships move. Then submit as recipe [review-and-submit](https://docs.pnbr.io/guides/review-and-submit.md) says, with the revision that validate printed.
   * CLI: `penumbra graph workspace submit "<workspace>" --revision <n> --request-id <key>`.
   * MCP: `workspace_submit` with `expected_revision` (full catalog).
   * Program: `w.submitReviewed({expectedRevision})`. If the result is `pending`, follow it with `penumbra.actionStatus(id)`.
   * Expect: `submission: applied`. With an API key or a connected agent, expect exit code 4 and `outcome: pending` (problem [change-waits-for-approval](https://docs.pnbr.io/troubleshooting/change-waits-for-approval.md)).

## Check

1. Run `penumbra graph edges --node <kept-id> --direction both`. Each relationship on your list from steps 2 and 3 must appear with the same type and other end.
2. Run `penumbra graph get <kept-id>`. Each property you carried over must show.
3. Run `penumbra context entity <kept-id> --json`. The Files you cited in step 6 must be listed. MCP: `read` with `entity:<kept-id>` shows the sources cited for its current revision.
4. Run `penumbra graph get <extra-id>`. The output must have a `deleted:` line. MCP: `read` with `entity:<extra-id>` shows it as deleted, with its last properties and citations.
5. Run `penumbra graph search "<name>" --type <Type>`. It must return the kept entity and not the extra one.
6. Program: `entity.related(...)` on the kept entity, for each relationship name, lists each moved relationship.
7. If a check fails, do not submit again. Use the table below.

## If it goes wrong

| What you see | Go to |
| - | - |
| Exit code 4, `outcome: pending` and an approval URL | problem [change-waits-for-approval](https://docs.pnbr.io/troubleshooting/change-waits-for-approval.md). Nothing has changed yet. |
| `witness_stale` or `workspace_review_conflict` | problem [workspace-changed-since-you-read](https://docs.pnbr.io/troubleshooting/workspace-changed-since-you-read.md). Someone changed an entity after you checked it out. |
| `submission: partial` or `submission: unknown` | problem [submit-partly-applied](https://docs.pnbr.io/troubleshooting/submit-partly-applied.md) |
| No reply, or `outcome: unknown` | problem [reply-lost](https://docs.pnbr.io/troubleshooting/reply-lost.md) |
| `node_ambiguous` while you stage | Use the id of the entity. Card rule [relationship.3](https://docs.pnbr.io/concepts/relationship.md#rule-relationship-3). |
| `endpoint_deleted` at validate | Card rule [relationship.7](https://docs.pnbr.io/concepts/relationship.md#rule-relationship-7). An end of a relationship is already deleted. |
| `relationship_declaration_missing` at validate | Card rule [relationship.1](https://docs.pnbr.io/concepts/relationship.md#rule-relationship-1). The Shape does not allow that type between those entities. |
| The wrong entity was deleted | Run `penumbra graph restore <id>`. It brings back the entity only. Stage its relationships again from your notes. |
| `too_many_relationships` from `penumbra graph delete` | This recipe deletes through a workspace, so use step 7. |

## Worth offering

* If the Shape has no property for other names, offer to add one, so the extra
  name is kept (recipe [change-a-shape](https://docs.pnbr.io/guides/change-a-shape.md)).
* Offer to look for other entities of the same type that may be one thing
  (problem [same-name-two-entities](https://docs.pnbr.io/troubleshooting/same-name-two-entities.md)).

## Report

Tell the person:

* Which entity you kept and which you deleted, with both ids.
* The relationships that moved, by type and other end, and any that you left out and why.
* The properties and citations you carried over.
* That the deleted entity can be restored by id, and what a restore does not bring back.
* Whether the submit is applied or waits for approval, with the approval URL.
* The request id of the submit.

## Keep going

[Entity](https://docs.pnbr.io/concepts/entity.md) · [Relationship](https://docs.pnbr.io/concepts/relationship.md) · [Workspace](https://docs.pnbr.io/concepts/workspace.md) · [Approval](https://docs.pnbr.io/concepts/approval.md) · [Passage and citation](https://docs.pnbr.io/concepts/passage.md) · [Review a workspace and submit it](https://docs.pnbr.io/guides/review-and-submit.md) · [Two entities in the Graph are one thing](https://docs.pnbr.io/troubleshooting/merge-duplicates.md) · [Two entities have the same name](https://docs.pnbr.io/troubleshooting/same-name-two-entities.md) · [A change returned pending](https://docs.pnbr.io/troubleshooting/change-waits-for-approval.md) · [The workspace changed since you read it](https://docs.pnbr.io/troubleshooting/workspace-changed-since-you-read.md) · [A submit is partly applied or unresolved](https://docs.pnbr.io/troubleshooting/submit-partly-applied.md) · [The reply to a change was lost](https://docs.pnbr.io/troubleshooting/reply-lost.md)

Penumbra Grammar 0.6.26


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.