Skip to main content
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). A repeat that is still only an item in a workspace (problem An extraction found an entity the Graph already has).

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). 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 and workspace.10). One workspace holds both, so one approval covers them.
  4. Use one credential for the whole recipe (card rule workspace.6).
  5. Choose a request id for the submit, such as --request-id merge-<extra-id>-1 (problem reply-lost).

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).
  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).
    • 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). Copy the type, the direction and the properties. Add evidence with citations you can support (card 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).
    • 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).
    • 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 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).

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

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).
  • Offer to look for other entities of the same type that may be one thing (problem same-name-two-entities).

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 · Relationship · Workspace · Approval · Passage and citation · Review a workspace and submit it · Two entities in the Graph are one thing · Two entities have the same name · A change returned pending · The workspace changed since you read it · A submit is partly applied or unresolved · The reply to a change was lost