How ai-memory Manages Links and Cross-References: The Rust Wiki Engine Architecture
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. 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, 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|labelsuffixes, and handles the internal link formatextract_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:pathsyntax 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
.mdextensions 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.
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 (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 (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
LinkTargetobjects with explicit workspace, project, and path fields - The extraction pipeline in
markdown.rsprocesses wikilinks throughextract_links,split_scope, andnormalize_link_targetto 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
BTreeSetcollections
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 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, 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 file in the infra project. The split_scope function in 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →