# How ai-memory Manages Links and Cross-References: The Rust Wiki Engine Architecture

> Learn how ai-memory manages links and cross-references by converting Markdown into canonical LinkTarget structs, supporting workspace and project boundaries.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: architecture
- Published: 2026-08-26

---

**ai-memory converts all Markdown links and cross-references into canonical `LinkTarget` structs through a multi-stage pipeline that extracts, scopes, and normalizes wikilinks into typed objects supporting workspace and project boundaries.**

The akitaonrails/ai-memory repository implements a knowledge management system where Markdown files form a navigable wiki. Links and cross-references are managed not as raw strings but as structured data, enabling the engine to resolve paths across project boundaries and maintain consistent indices.

## The LinkTarget Data Structure

At the core of ai-memory's link management system is the **`LinkTarget`** struct defined in [`crates/ai-memory-core/src/page.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/page.rs). This struct transforms untrusted Markdown links into validated, canonical pointers with three explicit components:

- **workspace**: An optional identifier for cross-workspace references (`[[workspace/project:path]]`)
- **project**: An optional project name for inter-project links (`[[project:path]]`)
- **path**: The normalized filesystem path to the target page, always ending with `.md`

This typed representation eliminates ambiguity when resolving links that span multiple projects or workspaces, allowing the engine to locate the correct target file without relying on fragile string matching.

## The Link Extraction Pipeline

The extraction logic resides in [`crates/ai-memory-wiki/src/markdown.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/markdown.rs), where raw Markdown content passes through a three-stage pipeline to identify and canonicalize references.

### Extracting Wikilinks from Markdown

The **`extract_links`** function serves as the entry point (lines 18-34), iterating through each line of a page while skipping fenced code blocks. It delegates to specialized helpers:

- **`extract_wikilinks`**: Parses the `[[...]]` syntax, strips any `|label` suffixes, and handles the internal link format
- **`extract_markdown_links`**: Processes standard Markdown link syntax

For wikilinks, the parser handles labeled references like `[[target|Label Text]]` by splitting on the pipe character and preserving only the target portion for further processing (lines 83-99).

### Parsing Scope and Namespaces

Before normalization, links undergo scope analysis via **`split_scope`** (lines 46-78). This function examines the link target for namespace prefixes:

- Detects URL schemes (`https://`, `mailto:`, etc.) and returns them as external links with `(None, None, target)`
- Parses `workspace/project:path` syntax to extract optional workspace and project identifiers
- Returns a tuple of `(Option<String>, Option<String>, String)` representing workspace, project, and the remaining path

This scoping step enables the distinction between local references, cross-project links, and external URLs before path resolution occurs.

### Normalization and Canonicalization

The **`normalize_link_target`** function (lines 32-68) enforces safety and consistency across all internal links:

- Discards empty targets, fragment-only anchors, external URLs, and unsafe schemes
- Strips surrounding `<...>` characters and removes query strings and URL fragments
- Appends `.md` extensions to non-Markdown files to maintain wiki consistency
- Resolves relative paths using **`resolve_relative`** (lines 70-94), which safely handles `.` and `..` segments by building clean path component lists

The resulting normalized string is converted into a `LinkTarget` and collected into a `BTreeSet` to ensure stable, deduplicated ordering (lines 35-42).

## Cross-Project and Cross-Workspace Links

ai-memory supports three distinct linking patterns through its scope-parsing logic:

**Intra-project links** use simple wikilink syntax: `[[decisions/0001.md]]` or `[[other]]`. These resolve within the current project context with `workspace` and `project` fields set to `None`.

**Cross-project links** use the colon separator: `[[infra:runbooks/02.md]]`. Here, `split_scope` identifies `infra` as the project name, leaving `workspace` as `None`.

**Cross-workspace links** include both identifiers: `[[zommehq/zomme:decisions/adr-1.md|ADR 1]]`. The parser extracts `zommehq` as the workspace and `zomme` as the project, supporting labeled aliases for readability.

```rust
use ai_memory_core::{PagePath, LinkTarget};
use ai_memory_wiki::markdown::extract_links;

// 1️⃣ Simple intra‑project link
let page = PagePath::new("notes/overview.md").unwrap();
let links = extract_links("See [[decisions/0001.md]] and [[other]]", &page);
assert!(links.iter().all(|l| l.project.is_none()));
assert!(links.iter().any(|l| l.path.as_str() == "decisions/0001.md"));
assert!(links.iter().any(|l| l.path.as_str() == "other.md"));

// 2️⃣ Cross‑project link
let cp = extract_links("Depends on [[infra:runbooks/02.md]]", &page);
let cross = cp.iter().find(|l| l.is_cross_project()).unwrap();
assert_eq!(cross.workspace, None);
assert_eq!(cross.project.as_deref(), Some("infra"));
assert_eq!(cross.path.as_str(), "runbooks/02.md");

// 3️⃣ Cross‑workspace with label
let ws = extract_links("[[zommehq/zomme:decisions/adr-1.md|ADR 1]]", &page);
let target = ws.iter().find(|l| l.is_cross_project()).unwrap();
assert_eq!(target.workspace.as_deref(), Some("zommehq"));
assert_eq!(target.project.as_deref(), Some("zomme"));
assert_eq!(target.path.as_str(), "decisions/adr-1.md");

```

## Security and Index Integration

The link management system integrates with ai-memory's indexing pipeline to maintain referential integrity. When pages are written or modified via `Wiki::write_page` or `Wiki::apply_batch` in [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs) (lines 539-571), the system re-derives all links from the updated Markdown content using the same extraction pipeline.

Security considerations are enforced in [`crates/ai-memory-wiki/src/watcher.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/watcher.rs) (lines 403-409), where the file watcher explicitly refuses to index symlinked pages or scope manifests. This prevents directory traversal attacks and ensures that the `LinkTarget` paths always correspond to real, authorized files within the wiki boundaries.

## Summary

- **ai-memory** treats links and cross-references as typed `LinkTarget` objects with explicit workspace, project, and path fields
- The extraction pipeline in [`markdown.rs`](https://github.com/akitaonrails/ai-memory/blob/main/markdown.rs) processes wikilinks through `extract_links`, `split_scope`, and `normalize_link_target` to create canonical references
- Cross-project links use `[[project:path]]` syntax, while cross-workspace links use `[[workspace/project:path]]` format
- The system validates links during indexing, rejects symlinks for security, and maintains stable ordering via `BTreeSet` collections

## Frequently Asked Questions

### What is the LinkTarget struct in ai-memory?

The **LinkTarget** struct is a core data structure defined in [`crates/ai-memory-core/src/page.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/page.rs) that represents normalized link targets with three optional fields: `workspace`, `project`, and `path`. This struct transforms raw Markdown links into typed objects that explicitly track cross-project and cross-workspace boundaries, enabling unambiguous link resolution across the wiki.

### How does ai-memory parse wikilinks from Markdown?

According to the source code in [`crates/ai-memory-wiki/src/markdown.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/markdown.rs), the **`extract_links`** function scans each line of Markdown content (skipping code blocks) and delegates to **`extract_wikilinks`** to parse `[[...]]` syntax. The parser strips labels (text after `|`), splits scope prefixes using **`split_scope`**, normalizes the path via **`normalize_link_target`**, and returns a deduplicated `BTreeSet<LinkTarget>` containing all internal references.

### How are cross-project links formatted in ai-memory?

Cross-project links in ai-memory use the syntax `[[project:path]]`, where the project name appears before the colon separator. For example, `[[infra:runbooks/02.md]]` links to the [`runbooks/02.md`](https://github.com/akitaonrails/ai-memory/blob/main/runbooks/02.md) file in the `infra` project. The **`split_scope`** function in [`markdown.rs`](https://github.com/akitaonrails/ai-memory/blob/main/markdown.rs) parses this prefix and populates the `LinkTarget.project` field, while cross-workspace links extend this pattern to `[[workspace/project:path]]`.

### Does ai-memory handle external URLs in wikilinks?

Yes, the **`split_scope`** function explicitly checks for URL schemes like `https://` and `mailto:` during the parsing phase. When external URLs are detected, the function returns them with `None` values for workspace and project, causing **`normalize_link_target`** to discard them from the internal link index. This ensures that only internal wiki references are tracked as typed `LinkTarget` objects, while external links are filtered out during the normalization stage.