How Macro's Bidirectional @linking System Connects Docs, Messages, Tasks, and Emails

Macro's bidirectional @linking system uses XML-style <m-document-mention> tags embedded with JSON payloads to create first-class references between any entities, storing symmetric link records in an entity_mentions table that enables fast reverse lookups.

Every item in Macro—whether a Markdown document, channel message, task, or email thread—can reference any other item through a unified linking architecture. The system, implemented across the macro-inc/macro Rust codebase, treats connections as data rather than simple hypertext, enabling the backend to answer "what mentions this?" as efficiently as "what does this mention?"

How the @linking System Works

At its core, the system relies on XML-style tags that wrap a minimal JSON payload. When users type @ in the Macro interface, the frontend inserts a <m-document-mention> tag containing the target entity's UUID, type, and optional parameters. The backend's mention_utils::parse module then extracts these tags from raw text and persists bidirectional relationships to the database.

This design decouples the rendering layer from the storage layer. Because the tags travel with the content as plain text, any service—document storage, message queues, or email ingestion—can parse links without proprietary APIs.

The <m-document-mention> tag is the universal linking primitive. It appears in Markdown bodies, message text, and email content with a structure like this:

// Example payload structure from crates/prompt/src/mentions.rs
<m-document-mention>{
  "documentId": "a2f3c4d5-...",
  "blockName": "task",
  "blockParams": {}
}</m-document-mention>

The blockName field determines the entity type ("md" for documents, "channel" or "chat" for messages, "task" for tasks). The documentId holds the target UUID. This generic schema allows the parser in crates/mention_utils/src/parse.rs to handle every entity type without type-specific logic:

// crates/mention_utils/src/parse.rs
const TAG_NAME: &str = "m-document-mention";

fn parse_mentions(input: &str) -> Vec<Mention> {
    // 1️⃣ Find every `<m-document-mention>` … `</m-document-mention>` block
    // 2️⃣ Pull the inner JSON payload
    // 3️⃣ Deserialize into MentionPayload { documentId, blockName, blockParams }
    // 4️⃣ Return Mention structs for persistence
}

Storing Bidirectional Connections

Once parsed, links are materialized as symmetric database records in the entity_mentions table. Instead of a single directed edge, the system writes two rows: one from source to target, and one from target to source. This pattern, visible in crates/properties/src/domain/service_impl/task_properties.rs, ensures that reverse lookups require only a simple indexed query rather than full-table scans.

The table schema stores:

  • source_entity_type and source_entity_id: The entity containing the tag
  • target_entity_type and target_entity_id: The referenced entity
  • created_at: For chronological ordering

When establishing a link, services call helpers that insert both directions atomically:

// crates/properties/src/domain/service_impl/task_properties.rs
pub async fn link_parent_task(child_id: Uuid, parent_id: Uuid) -> Result<()> {
    // Forward link (child → parent)
    db::entity_mentions::create(&Mention {
        source_entity_type: "task",
        source_entity_id: child_id,
        target_entity_type: "task",
        target_entity_id: parent_id,
    }).await?;
    // Reverse link (parent → child)
    db::entity_mentions::create(&Mention {
        source_entity_type: "task",
        source_entity_id: parent_id,
        target_entity_type: "task",
        target_entity_id: child_id,
    }).await?;
    Ok(())
}

Linking Specific Entity Types

Documents

Documents use <m-document-mention> tags with blockName: "md" to reference other documents, tasks, or emails. The parser scans the raw Markdown content and creates entity_mentions rows linking the document ID to each target entity mentioned within it. The logic for document mention guidance resides in crates/prompt/src/mentions.rs and the serialization utilities in crates/mention_utils/src/serialize.rs.

Channel and Chat Messages

Messages embed the same <m-document-mention> tag but specify blockName: "channel" or blockName: "chat" in the payload, along with a channel_message_id in blockParams. When the message service processes incoming text, it extracts these tags and writes message-mention records to the entity_mentions table, allowing any mentioned document to surface the referencing message in its backlinks.

Tasks

Task relationships—including parent-child hierarchies and cross-references—are managed through the task_properties service. The entity_mentions table stores these relationships so that a parent task knows all its subtasks and vice versa. Database helpers in crates/properties/src/outbound/task_property_queries.rs handle the insertion and retrieval of these bidirectional task links.

Emails

Email threads connect to documents via the document_email table, defined in crates/macro_db_client/src/document/document_email.rs. When an email is saved with an attachment, the create_document_email function writes a row tying the email_thread_id to the document_id. This relationship is also represented in the generic entity_mentions view, enabling the email service to fetch all attached documents using the same reverse-lookup queries as other entity types.

Querying Relationships in Reverse

Because every link exists in both directions, any entity can query its backlinks—the set of other entities that reference it. Services use a standardized query pattern against the entity_mentions table:

// crates/message_service/src/db.rs (excerpt)
pub async fn get_mentions_of_message(msg_id: Uuid) -> Result<Vec<Mention>> {
    sqlx::query_as!(
        Mention,
        r#"SELECT * FROM entity_mentions WHERE target_entity_id = $1"#,
        msg_id
    )
    .fetch_all(pool)
    .await
}

This query returns all documents, tasks, or other messages that mention the given message. The same pattern applies to documents querying their inbound references from tasks, or emails finding all documents that linked to them.

Summary

  • Universal Tags: The <m-document-mention> XML tag with JSON payload provides a generic linking primitive used across documents, messages, tasks, and emails.
  • Bidirectional Storage: Every link is stored twice in entity_mentions (source→target and target→source), enabling efficient reverse lookups without scanning all content.
  • Centralized Parsing: The mention_utils::parse module in crates/mention_utils/src/parse.rs handles extraction for all entity types, making the system extensible to new entities by adding new blockName values.
  • Consistent Query Pattern: Services retrieve backlinks with simple WHERE target_entity_id = ? queries against the entity_mentions table, regardless of whether the target is a document, message, task, or email.

Frequently Asked Questions

The system passes raw text through mention_utils::parse::parse_mentions, which scans for <m-document-mention> tags, extracts the inner JSON, and deserializes it into a MentionPayload struct containing the target documentId, blockName (entity type), and blockParams. This parser is invoked by domain services handling document creation, message posting, task updates, and email ingestion.

Can tasks reference documents and vice versa in Macro?

Yes. The entity_mentions table uses generic source_entity_type and target_entity_type columns that accept values like "document", "task", "message", or "email". This allows a task to mention a document, a document to mention a task, or any other combination. The task_properties service in crates/properties/src/domain/service_impl/task_properties.rs manages these cross-type links using the same bidirectional insertion pattern as intra-task relationships.

Storing two directed edges (source→target and target→source) in the entity_mentions table transforms "who mentions me?" queries from expensive graph traversals into simple, indexed SQL lookups. This design prioritizes read performance for backlink panels and notification systems, accepting the minimal write-time cost of inserting two rows instead of one. The atomic insertion ensures consistency between both directions.

Where is the email-to-document linking implemented?

Email thread attachments are linked to documents in crates/macro_db_client/src/document/document_email.rs. The create_document_email function writes rows to the document_email table, which is also represented in the entity_mentions view. This dual representation allows email services to use the same reverse-lookup queries as the rest of the platform to find all documents attached to a specific email thread.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →