Edge Tags

Edge tags turn ordinary tags into navigable relationships. Usually if a note has tag speaker: Kate, this is just a label. But when speaker is an _edge tag_, this becomes a reference (a graph edge) between the note and another note "Kate". Moreover, that note "Kate" has edges pointing back to all the notes where it was tagged.

This behavior is defined by the _tagdoc_: the note that defines the tag, which has id like .tag/*. When a .tag/KEY document declares _inverse: VERB, any document tagged with KEY=target creates a link to the target — and the target gets an automatic inverse listing under the VERB key in the unified tags: block.

Edge definitions are user-editable on purpose. They let you decide which relationships matter enough to become first-class navigation for you and your agent.

How it works

The bundled .tag/speaker tagdoc has _inverse: said. This means:

# Tag a conversation part with a speaker
keep put "I think we should refactor the auth module" -t speaker=Deborah

# Deborah now has an inverse listing
keep get Deborah

Output:

id: Deborah
tags:
  said:
    - conv1@P{5} [2025-03-15] "I think we should refactor the auth module"
    - conv2@P{3} [2025-03-18] "The API needs rate limiting..."

The said entries under tags: are computed from the edges table — they're not stored as tags on Deborah. Each entry links back to the source document, rendered as id [date] "summary".

Auto-vivification

If the target doesn't exist, it's created as an empty document automatically. In the example above, speaker=Deborah creates a Deborah document if one doesn't exist yet. You can add content to it later:

keep put "Deborah is the tech lead on project X" --id Deborah

The inverse edges survive — the said entries under tags: still show everything Deborah said.

Creating edge tags

Any tag can become an edge tag by adding _inverse to its tagdoc. Edge tagdocs are system documents, so _inverse is set via the tagdoc's frontmatter (like _constrained), not through keep put -t.

To create a custom edge tag, write a tagdoc with _inverse in its tags:

keep put "$(cat <<'EOF'
---
tags:
  _inverse: contents
---
# Tag: contains

Items that contain other items. The inverse contents shows
what container an item belongs to.
EOF
)" --id .tag/contains

Now contains=item-B on document A creates an edge, and get item-B shows contents: A [date] "summary" in its tags: block.

Symmetric tagdocs

When .tag/contains declares _inverse: contents, keep automatically creates .tag/contents with _inverse: contains (if it doesn't already exist). This makes the relationship navigable in both directions — tagging with either key creates edges that the other key can resolve. If .tag/contents already exists with a different _inverse, that's a conflict error.

Backfill

When you add _inverse to an existing tagdoc, keep automatically backfills edges for all documents already tagged with that key. This runs in the background — edges may take a moment to appear.

Bundled edge tags

General

Tag_inverseExampleMeaning
speakersaidspeaker: Deborah on a turnget Deborahsaid: entries
informsinformed_byinforms: auth-decision on a URLget auth-decisioninformed_by: entries
referencesreferenced_byreferences: other-note via link extractionget other-notereferenced_by: entries
duplicatesduplicatesduplicates: notes-v1 on a duplicateSymmetric: both sides show duplicates:
authorauthoredauthor: alice@example.com on a git commitget alice@example.comauthored: entries

Email

Tag_inverseExampleMeaning
fromsender_offrom: alice@example.com on an emailget alice@example.comsender_of: entries
torecipient_ofto: bob@example.com on an emailget bob@example.comrecipient_of: entries
cccc_recipient_ofcc: carol@example.com on an emailget carol@example.comcc_recipient_of: entries
bccbcc_recipient_ofbcc: dave@example.com on an emailget dave@example.combcc_recipient_of: entries
in-reply-tohas_replyin-reply-to: <msg-id> on a replyget <parent>has_reply: entries
attachmenthas_attachmentattachment: email-id on an attachmentget email-idhas_attachment: entries

Git

Tag_inverseExampleMeaning
git_commitgit_filegit_commit: git://repo#abc on a fileget git://repo#abcgit_file: entries

Rules

Edge vs meta: choosing the right tool

Use edge tags for explicit graph relationships:

Use meta docs for contextual reflection policy:

Edge tags optimize navigability and relationship fidelity. Meta docs optimize relevance and situational awareness.

Finding edge sources

Outbound edges are normal tags, so keep find works:

keep find -t speaker=Deborah    # All docs where Deborah is the speaker

Inverse edges (the resolved said: entries in tags:) are only visible through keep get on the target.

See Also