---
title: "Documentation Agent Skills"
description: "Documentation Agent Skills provide specialized capabilities and tooling that empower coding assistants to retrieve, verify, and maintain technical documentation and contextual knowledge. By establi..."
last_updated: "2026-10-11T17:37:10.229915+00:00"
canonical_url: "https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/technical/ecosystem-integrations/documentation-agent-skills"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [skills/mem0/scripts/mem0_doc_search.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py)
- [integrations/opencode-plugin/opencode-mem0.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/opencode-plugin/opencode-mem0.ts)
- [integrations/openclaw/skill-loader.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts)
- [integrations/mem0-agent-plugin/core/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/mem0-agent-plugin/core/memory_core.py)
- [integrations/codex-plugin/core/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/codex-plugin/core/memory_core.py)
- [integrations/deepseek-plugin/src/index.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/deepseek-plugin/src/index.ts)
- [integrations/agent-plugin-core/python/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/agent-plugin-core/python/memory_core.py)
- [integrations/antigravity-plugin/core/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/antigravity-plugin/core/memory_core.py)
- [integrations/pi-agent-plugin/src/commands.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/pi-agent-plugin/src/commands.ts)
- [integrations/openclaw/index.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/index.ts)
- [integrations/cursor-plugin/core/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/cursor-plugin/core/memory_core.py)
- [integrations/kimi-plugin/core/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/kimi-plugin/core/memory_core.py)
- [integrations/claude-code-plugin/core/memory_core.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/memory_core.py)
- [integrations/hermes-plugin-mem0/__init__.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/hermes-plugin-mem0/__init__.py)
- [integrations/agent-plugin-core/python/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/agent-plugin-core/python/mcp_server.py)
- [cli/python/src/mem0_cli/commands/agent_rush_cmd.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/cli/python/src/mem0_cli/commands/agent_rush_cmd.py)
- [integrations/mem0-agent-plugin/core/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/mem0-agent-plugin/core/mcp_server.py)
- [integrations/codex-plugin/core/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/codex-plugin/core/mcp_server.py)
- [integrations/kimi-plugin/core/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/kimi-plugin/core/mcp_server.py)
- [integrations/claude-code-plugin/adapters/claude/transcript.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/adapters/claude/transcript.py)
- [integrations/antigravity-plugin/core/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/antigravity-plugin/core/mcp_server.py)
- [integrations/cursor-plugin/core/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/cursor-plugin/core/mcp_server.py)
- [integrations/pi-agent-plugin/src/memory/tools.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/pi-agent-plugin/src/memory/tools.ts)
- [cli/node/src/commands/agent-rush.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/cli/node/src/commands/agent-rush.ts)
- [integrations/openclaw/recall.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/recall.ts)
- [integrations/claude-code-plugin/core/mcp_server.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py)
- [integrations/agent-plugin-core/typescript/src/prompts.ts](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/agent-plugin-core/typescript/src/prompts.ts)
- [scripts/check-llms-txt-coverage.py](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py)
</details>

## Overview

Documentation Agent Skills provide specialized capabilities and tooling that empower coding assistants to retrieve, verify, and maintain technical documentation and contextual knowledge. By establishing lightweight, on-demand search engines and verification scripts, the architecture prevents context bloat in developer environments while keeping reference materials accessible. Sources: [skills/mem0/scripts/mem0_doc_search.py:3-21](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L3-L21), [scripts/check-llms-txt-coverage.py:2-22](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L2-L22)

These skills bridge documentation distribution formats—such as `llms.txt` indices and Markdown source trees—with interactive agent runtimes and local Model Context Protocol servers. Through dynamic skill resolution, automated coverage audits, and structured prompt rendering, agents can query specific reference pages, audit missing documentation links, and expose semantic memory search tools uniformly across various IDE plugins. Sources: [skills/mem0/scripts/mem0_doc_search.py:6-7](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L6-L7), [integrations/openclaw/skill-loader.ts:323-327](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L323-L327), [integrations/agent-plugin-core/python/mcp_server.py:2-3](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/agent-plugin-core/python/mcp_server.py#L2-L3), [scripts/check-llms-txt-coverage.py:2-7](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L2-L7)

## Documentation search tool architecture

### Engine configuration and security boundaries

The documentation search utility defined in `mem0_doc_search.py` operates as an on-demand retrieval engine that queries `https://docs.mem0.ai/llms.txt` and fetches remote Markdown documentation pages. To prevent unauthorized redirection or arbitrary payload ingestion, hardcoded security constants govern all outgoing HTTP connections. The host domain `docs.mem0.ai` is enforced through URL splitting checks, and custom HTTP redirect handlers intercept intermediate location headers to verify that subsequent requests remain bound to the trusted host.

```python
DOCS_BASE = "https://docs.mem0.ai"
DOCS_HOST = urllib.parse.urlsplit(DOCS_BASE).netloc
LLMS_INDEX = f"{DOCS_BASE}/llms.txt"
ENTRY_URL = re.compile(r"\((https?://[^)\s]+)\)")
MAX_RESULTS = 20
MAX_PAGE_CHARS = 10000
MAX_INDEX_BYTES = 2_000_000

SECTION_PREFIXES = {
    "platform": ("/platform/",),
    "api": ("/api-reference/",),
    "open-source": ("/open-source/", "/components/"),
    "integrations": ("/integrations",),
}


def require_docs_url(url: str) -> None:
    """Exit unless the URL is an https URL on the Mem0 docs host."""
    parts = urllib.parse.urlsplit(url)
    if parts.scheme != "https" or parts.netloc != DOCS_HOST:
        sys.exit(f"Refusing to fetch {url}: only {DOCS_BASE} is allowed")


class DocsRedirectHandler(urllib.request.HTTPRedirectHandler):
    """Follow redirects only when they stay on the Mem0 docs host."""

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        require_docs_url(newurl)
        return super().redirect_request(req, fp, code, msg, headers, newurl)

```
Sources: [skills/mem0/scripts/mem0_doc_search.py:33-62](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L33-L62)

The engine enforces strict resource boundaries during HTTP retrieval operations. Byte-size caps and truncation limits prevent denial-of-service conditions or excessive memory consumption when reading remote index files or documentation assets over the network.

| Constant Name | Value | Purpose |
| :--- | :--- | :--- |
| `DOCS_BASE` | `https://docs.mem0.ai` | Canonical documentation root URL |
| `MAX_RESULTS` | `20` | Maximum matching search URLs returned in query payloads |
| `MAX_PAGE_CHARS` | `10000` | Character truncation threshold for individual documentation pages |
| `MAX_INDEX_BYTES` | `2_000_000` | Maximum byte size allowed for the `llms.txt` index file |

```python
DOCS_BASE = "https://docs.mem0.ai"
DOCS_HOST = urllib.parse.urlsplit(DOCS_BASE).netloc
LLMS_INDEX = f"{DOCS_BASE}/llms.txt"
ENTRY_URL = re.compile(r"\((https?://[^)\s]+)\)")
MAX_RESULTS = 20
MAX_PAGE_CHARS = 10000
MAX_INDEX_BYTES = 2_000_000
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:33-39](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L33-L39)

> [!CAUTION]
> Bypassing `require_docs_url` or removing the `DocsRedirectHandler` check allows open redirect vectors to fetch arbitrary external content into the agent context stream. Every redirect is validated against `DOCS_HOST` before the request is dispatched.

```python
def require_docs_url(url: str) -> None:
    """Exit unless the URL is an https URL on the Mem0 docs host."""
    parts = urllib.parse.urlsplit(url)
    if parts.scheme != "https" or parts.netloc != DOCS_HOST:
        sys.exit(f"Refusing to fetch {url}: only {DOCS_BASE} is allowed")


class DocsRedirectHandler(urllib.request.HTTPRedirectHandler):
    """Follow redirects only when they stay on the Mem0 docs host."""

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        require_docs_url(newurl)
        return super().redirect_request(req, fp, code, msg, headers, newurl)
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:49-61](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L49-L61)

### Section filtering and token scoring mechanics

Documentation search queries are processed by tokenizing the input string using regular expression matching and scoring index entries based on frequency and title appearance. When a section filter is supplied, index entries are pre-filtered against predefined URL prefix mappings before score calculation occurs.

```python
SECTION_PREFIXES = {
    "platform": ("/platform/",),
    "api": ("/api-reference/",),
    "open-source": ("/open-source/", "/components/"),
    "integrations": ("/integrations",),
}
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:41-46](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L41-L46)

The engine maps user-facing section keys to their corresponding documentation route paths. The supported section mappings and URL prefixes are structured as follows:

| Section Key | URL Prefixes | Target Documentation Area |
| :--- | :--- | :--- |
| `platform` | `("/platform/",)` | Platform features and managed services |
| `api` | `("/api-reference/",)` | REST API specifications and endpoints |
| `open-source` | `("/open-source/", "/components/")` | Open-source core libraries and modular components |
| `integrations` | `("/integrations",)` | Third-party integrations and agent frameworks |

```python
SECTION_PREFIXES = {
    "platform": ("/platform/",),
    "api": ("/api-reference/",),
    "open-source": ("/open-source/", "/components/"),
    "integrations": ("/integrations",),
}
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:41-46](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L41-L46)

> [!NOTE]
> The search scoring algorithm evaluates both general occurrence in the entry string and specific appearance in the page title segment, doubling points for matches occurring in the title substring before sorting results in descending order.

```python
def search_docs(query: str, section: str | None = None) -> dict:
    """Search the llms.txt index for entries matching the query terms, best matches first."""
    terms = re.findall(r"[a-z0-9_]+", query.lower())
    entries = index_entries()

    if section in SECTION_PREFIXES:
        entries = [e for e in entries if entry_path(e).startswith(SECTION_PREFIXES[section])]

    scored = []
    for entry in entries:
        haystack = entry.lower()
        title = haystack.split("]")[0]
        score = sum((term in haystack) + (term in title) for term in terms)
        if score:
            scored.append((score, entry))
    scored.sort(key=lambda item: -item[0])

    return {
        "source": "llms_txt_index",
        "query": query,
        "matching_urls": [entry for _, entry in scored[:MAX_RESULTS]],
        "suggestion": "Fetch specific pages with --page <path> for detailed content",
    }
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:93-115](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L93-L115)

## Documentation index parsing and retrieval

### Index extraction and entry parsing

The retrieval engine processes the remote `llms.txt` document index by fetching the raw bytes and parsing line-item entries that conform to Markdown link structures. The `index_entries` function verifies that the fetched payload does not exceed `MAX_INDEX_BYTES` before splitting lines and extracting targets beginning with `- [`.

```python
def index_entries() -> list:
    """Return the page entries listed in the llms.txt index."""
    content, truncated = fetch_url(LLMS_INDEX, MAX_INDEX_BYTES)
    if truncated:
        sys.exit(f"Error: {LLMS_INDEX} exceeds {MAX_INDEX_BYTES} bytes")
    return [line.strip()[2:] for line in content.splitlines() if line.strip().startswith("- [")]

```
Sources: [skills/mem0/scripts/mem0_doc_search.py:79-85](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L79-L85)

Individual index lines are evaluated using a compiled regular expression pattern `ENTRY_URL` that isolates target URLs enclosed in parentheses. The helper function `entry_path` parses the matched URI to extract its relative URL path for downstream prefix filtering.

```source
skills/mem0/scripts/mem0_doc_search.py:36| ENTRY_URL = re.compile(r"\((https?://[^)\s]+)\)")
skills/mem0/scripts/mem0_doc_search.py:87-91
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:36](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L36), [skills/mem0/scripts/mem0_doc_search.py:87-91](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L87-L91)

> [!WARNING]
> If the `llms.txt` payload exceeds 2 megabytes, `index_entries` immediately terminates execution via `sys.exit` to prevent excessive heap allocation during remote agent execution.

```python
content, truncated = fetch_url(LLMS_INDEX, MAX_INDEX_BYTES)
if truncated:
    sys.exit(f"Error: {LLMS_INDEX} exceeds {MAX_INDEX_BYTES} bytes")
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:81-83](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L81-L83)

### Page fetching and markdown resolution

When an agent requests a specific documentation page via path parameter, `fetch_page` constructs the target URL by joining `DOCS_BASE` with the requested path, ensuring that missing `.md` file extensions are automatically appended.

```python
def fetch_page(page_path: str) -> dict:
    """Fetch a specific documentation page as markdown."""
    parts = urllib.parse.urlsplit(urllib.parse.urljoin(DOCS_BASE, page_path))
    path = parts.path.rstrip("/")
    if not path.endswith(".md"):
        path = f"{path}.md"
    url = urllib.parse.urlunsplit((parts.scheme, parts.netloc, path, "", ""))
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:118-124](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L118-L124)

Fetched content is bounded by `MAX_PAGE_CHARS` multiplied by four during retrieval to detect downstream truncation, returning a dictionary containing the resolved URL, truncated content string, and a boolean flag indicating whether size limits were exceeded.

```source
skills/mem0/scripts/mem0_doc_search.py:38| MAX_PAGE_CHARS = 10000
skills/mem0/scripts/mem0_doc_search.py:125-126
```
Sources: [skills/mem0/scripts/mem0_doc_search.py:38](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L38), [skills/mem0/scripts/mem0_doc_search.py:125-126](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L125-L126)

| Function Name | Return Type | Purpose |
| :--- | :--- | :--- |
| `get_index()` | `dict` | Returns total page count, full URL list, and available section keys |
| `list_section(section)` | `dict` | Returns matching index entries for a validated section or error payload |
| `fetch_page(page_path)` | `dict` | Resolves `.md` routes and retrieves markdown content with truncation status |

```python
def get_index() -> dict:
    """Fetch the full documentation index from llms.txt."""
    urls = index_entries()
    return {"total_pages": len(urls), "urls": urls, "sections": list(SECTION_PREFIXES)}


def list_section(section: str) -> dict:
    """List the llms.txt index entries in a documentation section."""
    if section not in SECTION_PREFIXES:
        return {"error": f"Unknown section: {section}", "available": list(SECTION_PREFIXES)}
    pages = [e for e in index_entries() if entry_path(e).startswith(SECTION_PREFIXES[section])]
    return {"section": section, "pages": pages}

```
Sources: [skills/mem0/scripts/mem0_doc_search.py:129-141](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/skills/mem0/scripts/mem0_doc_search.py#L129-L141)

## Agent skill definition and loading

### Skill resolution and triage prompt loading

The OpenClaw skill loading mechanism dynamically resolves skill files such as `memory-triage`, merges domain-specific overlays, and handles user configuration knobs to construct injectable system prompts. The function `loadSkill` reads the skill file, parses its frontmatter and body, and incorporates configuration parameters like categories, importance thresholds, credential scanning patterns, and custom inclusion or exclusion rules.

```typescript
export function loadSkill(
  skillName: string,
  config: SkillsConfig = {},
): LoadedSkill | null {
  const raw = readSkillFile(skillName);
  if (!raw) return null;

  const parsed = parseSkillFile(raw);
  const parts: string[] = [parsed.body];

  // Domain overlays only apply to the skill they target (checked via applies_to)
  if (config.domain) {
    const overlay = readDomainOverlay(config.domain, skillName);
    if (overlay) {
      parts.push("\n" + overlay);
    }
  }

  // Inject user-configured categories into triage skill prompt
  if (skillName === "memory-triage" && config.categories) {
    const mergedCats = resolveCategories(config);
    parts.push(renderCategoriesBlock(mergedCats));
  }

  // Inject triage knobs (importanceThreshold, credentialPatterns)
  if (skillName === "memory-triage") {
    const knobs = renderTriageKnobs(config);
    if (knobs) parts.push(knobs);
  }

  // Append user custom rules to the triage prompt.
  if (skillName === "memory-triage" && config.customRules) {
    const rulesBlock: string[] = ["\n## User Custom Rules\n"];
    if (config.customRules.include?.length) {
      rulesBlock.push("Additionally extract:");
      for (const rule of config.customRules.include) {
        rulesBlock.push(`- ${rule}`);
      }
    }
    if (config.customRules.exclude?.length) {
      rulesBlock.push("\nAdditionally skip:");
      for (const rule of config.customRules.exclude) {
        rulesBlock.push(`- ${rule}`);
      }
    }
    parts.push(rulesBlock.join("\n"));
  }

  return {
    name: skillName,
    prompt: parts.join("\n"),
    frontmatter: parsed.frontmatter,
  };
}
```
Sources: [integrations/openclaw/skill-loader.ts:324-377](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L324-L377)

> [!NOTE]
> Domain overlays apply exclusively to the specific skill they target by evaluating the `applies_to` attribute during file parsing, ensuring that domain-specific guidelines do not bleed into unrelated agent skills.

```typescript
if (config.domain) {
  const overlay = readDomainOverlay(config.domain, skillName);
  if (overlay) {
    parts.push("\n" + overlay);
  }
}
```
Sources: [integrations/openclaw/skill-loader.ts:335-340](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L335-L340)

### Config injection and rule rendering

User configurations such as active categories and custom extraction rules are formatted into markdown blocks and appended directly to the `memory-triage` skill prompt. When custom rules exceed the character budget defined by `COMPACT_CUSTOM_RULE_CHAR_BUDGET`, the loader condenses the rule text into a compact summary view containing rule counts and truncated previews.

```typescript
function renderCompactCustomRules(config: SkillsConfig): string[] {
  const includeRules = config.customRules?.include ?? [];
  const excludeRules = config.customRules?.exclude ?? [];
  if (includeRules.length === 0 && excludeRules.length === 0) {
    return [];
  }

  const formatRuleList = (label: string, rules: string[]) =>
    `${label}: ${rules.map((rule) => `"${rule}"`).join("; ")}`;

  const lines: string[] = [];
  if (includeRules.length > 0) {
    lines.push(formatRuleList("Include", includeRules));
  }
  if (excludeRules.length > 0) {
    lines.push(formatRuleList("Exclude", excludeRules));
  }

  const combined = lines.join(" ");
  if (combined.length <= COMPACT_CUSTOM_RULE_CHAR_BUDGET) {
    return ["## Active Custom Rules", "", ...lines.map((line) => `- ${line}`)];
  }

  const preview = [
    ...includeRules.slice(0, COMPACT_CUSTOM_RULE_PREVIEW_LIMIT).map((rule) => `include "${rule}"`),
    ...excludeRules.slice(0, COMPACT_CUSTOM_RULE_PREVIEW_LIMIT).map((rule) => `exclude "${rule}"`),
  ];

  return [
    "## Active Custom Rules",
    "",
    `- ${includeRules.length} include rule(s) and ${excludeRules.length} exclude rule(s) are configured.`,
    `- Prompt kept compact, full rule text omitted because it exceeded ${COMPACT_CUSTOM_RULE_CHAR_BUDGET} characters.`,
    ...(preview.length > 0
      ? [`- Preview: ${preview.join("; ")}`]
      : []),
  ];
}
```
Sources: [integrations/openclaw/skill-loader.ts:256-293](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L256-L293)

> [!WARNING]
> If custom rule descriptions exceed 280 characters, full rule text is omitted from the prompt injection, replaced by a count summary and a preview limited to two items per rule category to prevent prompt bloat.

```typescript
const COMPACT_CUSTOM_RULE_CHAR_BUDGET = 280;
const COMPACT_CUSTOM_RULE_PREVIEW_LIMIT = 2;
…
if (combined.length <= COMPACT_CUSTOM_RULE_CHAR_BUDGET) {
  return ["## Active Custom Rules", "", ...lines.map((line) => `- ${line}`)];
}

const preview = [
  ...includeRules.slice(0, COMPACT_CUSTOM_RULE_PREVIEW_LIMIT).map((rule) => `include "${rule}"`),
  ...excludeRules.slice(0, COMPACT_CUSTOM_RULE_PREVIEW_LIMIT).map((rule) => `exclude "${rule}"`),
];

return [
  "## Active Custom Rules",
  "",
  `- ${includeRules.length} include rule(s) and ${excludeRules.length} exclude rule(s) are configured.`,
  `- Prompt kept compact, full rule text omitted because it exceeded ${COMPACT_CUSTOM_RULE_CHAR_BUDGET} characters.`,
  ...(preview.length > 0
    ? [`- Preview: ${preview.join("; ")}`]
    : []),
];
```
Sources: [integrations/openclaw/skill-loader.ts:229-230](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L229-L230), [integrations/openclaw/skill-loader.ts:275-292](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L275-L292)

### Lifecycle integration and recall strategy

During the `before_prompt_build` lifecycle hook in OpenClaw, the plugin evaluates session triggers and injects the loaded triage protocol into `prependSystemContext` for provider-level caching. Depending on the configured recall strategy, the agent executes semantic searches against long-term or session-scoped memory stores and injects retrieved context into `prependContext` on every turn.

```typescript
export function loadTriagePrompt(config: SkillsConfig = {}): string {
  // Try to load the full skill with all config-driven overlays
…
let systemContext = loadCompactTriagePrompt(cfg.skills ?? {});
if (isSubagent) {
  systemContext =
    "You are a subagent — use these memories for context but do not assume you are this user. Do NOT store new memories.\n\n" +
    systemContext;
}

// Dynamic recall goes in prependContext (changes every turn).
// Strategy controls how much the plugin searches automatically:
//   "always" — long-term + session search every turn (2 searches)
//   "smart"  — long-term search only, no session search (1 search) [default]
//   "manual" — no auto-recall; agent controls all search via memory_search (0 searches)
let recallContext = "";
const recallEnabled = cfg.skills?.recall?.enabled !== false;
const recallStrategy = cfg.skills?.recall?.strategy ?? "smart";

if (recallEnabled && recallStrategy !== "manual") {
  const recallStart = Date.now();
  try {
    const query = lifecycle.prepareUserText(sanitizeQuery(event.prompt));

    // Smart mode: skip session search (saves 1 API call per turn)
    const sessionIdForRecall =
      recallStrategy === "always"
        ? isSubagent
          ? undefined
          : sessionId
        : undefined; // smart: long-term only

    const recallResult = await skillRecall(
      provider,
      query,
      userId,
      cfg.skills ?? {},
      sessionIdForRecall,
    );

    api.logger.info(
      `openclaw-mem0: skills-mode recall (strategy=${recallStrategy}) injecting ${recallResult.memories.length} memories (~${recallResult.tokenEstimate} tokens)`,
    );

    _captureEvent("openclaw.hook.recall", {
      strategy: recallStrategy,
      memory_count: recallResult.memories.length,
      latency_ms: Date.now() - recallStart,
    });

    recallContext = recallResult.context;
  } catch (err) {
    api.logger.warn(
      `openclaw-mem0: skills-mode recall failed: ${String(err)}`,
    );
  }
} else if (recallEnabled && recallStrategy === "manual") {
  api.logger.info(
    "openclaw-mem0: skills-mode recall strategy=manual, agent controls search",
  );
}

return {
  prependSystemContext: systemContext, // cached by provider
  prependContext: recallContext, // per-turn dynamic
};
```
Sources: [integrations/openclaw/skill-loader.ts:390-391](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/skill-loader.ts#L390-L391), [integrations/openclaw/index.ts:467-529](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/index.ts#L467-L529)

| Recall Strategy | Session Search Behavior | Target Context Property | Search Cost per Turn |
| :--- | :--- | :--- | :--- |
| `"always"` | Searches both long-term and session stores | `prependContext` | 2 search calls |
| `"smart"` | Searches long-term memory store only | `prependContext` | 1 search call |
| `"manual"` | Disables automatic recall completely | None | 0 search calls |

```typescript
//   "always" — long-term + session search every turn (2 searches)
//   "smart"  — long-term search only, no session search (1 search) [default]
//   "manual" — no auto-recall; agent controls all search via memory_search (0 searches)
let recallContext = "";
…
const sessionIdForRecall =
  recallStrategy === "always"
    ? isSubagent
      ? undefined
      : sessionId
    : undefined; // smart: long-term only
```
Sources: [integrations/openclaw/index.ts:476-479](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/index.ts#L476-L479), [integrations/openclaw/index.ts:489-494](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/openclaw/index.ts#L489-L494)

## Documentation coverage verification workflows

### Two-way diff audit mechanisms

The coverage verification script `scripts/check-llms-txt-coverage.py` executes a two-way diff between documentation source files on disk and indexed URLs in `docs/llms.txt`. It scans `docs/**/*.mdx` to construct a canonical set of repository pages, filters out paths matching ignore prefixes loaded from `scripts/llms-txt-ignore.txt`, and parses indexed URLs matching `https://docs.mem0.ai/` to identify missing entries and stale links.

```python
def load_ignore_prefixes() -> list[str]:
    if not IGNORE_FILE.exists():
        return []
    prefixes: list[str] = []
    for raw in IGNORE_FILE.read_text().splitlines():
        line = raw.strip()
        if line and not line.startswith("#"):
            prefixes.append(line)
    return prefixes


def canonical_repo_pages(ignore_prefixes: list[str]) -> tuple[set[str], set[str]]:
    all_pages: set[str] = set()
    included_pages: set[str] = set()
    for path in DOCS_DIR.rglob("*.mdx"):
        rel = path.relative_to(DOCS_DIR).with_suffix("").as_posix()
        all_pages.add(rel)
        if not any(rel.startswith(p) for p in ignore_prefixes):
            included_pages.add(rel)
    return all_pages, included_pages


def indexed_urls(text: str) -> set[str]:
    urls: set[str] = set()
    for match in URL_RE.finditer(text):
        path = match.group(1).rstrip("/")
        if path:
            urls.add(path)
    return urls
```
Sources: [scripts/check-llms-txt-coverage.py:41-69](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L41-L69)

| Drift Type | Detection Logic | Default Exit Code | `--write` Mode Behavior |
| :--- | :--- | :--- | :--- |
| `missing` | `included_pages - linked` | `1` | Appends placeholders under `## Unclassified - needs triage` |
| `stale` | `linked - all_pages` | `1` | Reported in output, but never auto-removed |

```python
default   read-only; exits 1 if any drift is found.
--write   appends placeholder entries for missing pages under a
          "## Unclassified - needs triage" H2 at the end of
          docs/llms.txt. Stale URLs are reported but never removed
          automatically (human decides whether a page was renamed
          or genuinely deleted). Exits 1 if anything was written,
          0 if the file was already in sync.
…
missing = sorted(included_pages - linked)
stale = sorted(linked - all_pages)
```
Sources: [scripts/check-llms-txt-coverage.py:9-15](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L9-L15), [scripts/check-llms-txt-coverage.py:111-112](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L111-L112)

> [!CAUTION]
> Stale URLs in `docs/llms.txt` are never automatically removed when running with `--write` because human intervention is required to determine whether a page was renamed or genuinely deleted.

```python
docs/llms.txt. Stale URLs are reported but never removed
automatically (human decides whether a page was renamed
or genuinely deleted). Exits 1 if anything was written,
…
print(
    "\n(Stale URLs are never auto-removed: check whether the page was "
    "renamed, and update the link by hand.)"
)
```
Sources: [scripts/check-llms-txt-coverage.py:12-14](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L12-L14), [scripts/check-llms-txt-coverage.py:122-125](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L122-L125)

### Scaffolding and triage block generation

When invoked with `--write`, the script formats placeholder entries for every missing documentation page and appends them to `docs/llms.txt`. If the triage header `## Unclassified - needs triage` is already present, new entries are appended directly beneath it; otherwise, a complete preamble instructing maintainers to classify scopes and rewrite descriptions is inserted before the placeholder list.

```python
def format_placeholder(page: str) -> str:
    title = page.rsplit("/", 1)[-1].replace("-", " ").replace("_", " ").title()
    return (
        f"- [{title}]({BASE_URL}{page}) [TODO: Platform|OSS|Both]: "
        "TODO - rewrite as 'Use when ...' and move into the correct section."
    )


def append_triage_block(text: str, missing_pages: list[str]) -> str:
    entries = "\n".join(format_placeholder(p) for p in missing_pages)
    if TRIAGE_HEADER in text:
        return text.rstrip() + "\n" + entries + "\n"
    preamble = (
        "> New docs pages were detected by CI. For each entry below: replace "
        "the scope tag with `[Platform]`, `[OSS]`, or `[Both]`; rewrite the "
        "description as `Use when ...`; move it into the correct section; "
        "then delete this H2 once empty.\n"
    )
    return text.rstrip() + "\n\n" + TRIAGE_HEADER + "\n\n" + preamble + "\n" + entries + "\n"

…
if args.write:
    if missing:
        new_text = append_triage_block(text, missing)
        LLMS_TXT.write_text(new_text)
        print(
            f"\nAppended {len(missing)} placeholder entries under "
            f"'{TRIAGE_HEADER}' in docs/llms.txt."
        )
```
Sources: [scripts/check-llms-txt-coverage.py:72-91](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L72-L91), [scripts/check-llms-txt-coverage.py:131-138](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L131-L138)

```python
def format_placeholder(page: str) -> str:
    title = page.rsplit("/", 1)[-1].replace("-", " ").replace("_", " ").title()
    return (
        f"- [{title}]({BASE_URL}{page}) [TODO: Platform|OSS|Both]: "
        "TODO - rewrite as 'Use when ...' and move into the correct section."
    )
```
Sources: [scripts/check-llms-txt-coverage.py:72-77](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/scripts/check-llms-txt-coverage.py#L72-L77)

## Coding assistant MCP server interfaces

### JSON-RPC protocol handling and tool registration

Plugin harnesses such as [Claude Code Plugin](https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/technical/agent-plugins/claude-code-plugin) implement a local Model Context Protocol (MCP) server over standard input and output streams using Python core scripts like `integrations/claude-code-plugin/core/mcp_server.py`. The server runs on protocol version `2024-11-05`, declaring the `search_memories` tool with read-only, idempotent, and open-world hints. Incoming JSON-RPC requests are parsed line by line from `sys.stdin`, routing methods such as `initialize`, `ping`, `tools/list`, and `tools/call` to appropriate handlers while catching deserialization and internal errors.

```python
PROTOCOL_VERSION = "2024-11-05"
TOOL_NAME = "search_memories"
TOOL_DESCRIPTION = (
    "Search memories from earlier work in this repository. Use it before "
    "repeating investigation or when earlier decisions, fixes, commands, or "
    "results may help."
)
…
def handle_request(message: Any) -> dict[str, Any] | None:
    if not isinstance(message, dict):
        return None
    request_id = message.get("id")
    method = message.get("method")

    if method == "notifications/initialized":
        return None
    if method == "initialize":
        requested = (message.get("params") or {}).get("protocolVersion")
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "result": {
                "protocolVersion": requested or PROTOCOL_VERSION,
                "capabilities": {"tools": {"listChanged": False}},
                "serverInfo": {"name": "mem0", "version": PLUGIN_VERSION},
            },
        }
    if method == "ping":
        return {"jsonrpc": "2.0", "id": request_id, "result": {}}
```
Sources: [integrations/claude-code-plugin/core/mcp_server.py:21-27](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L21-L27), [integrations/claude-code-plugin/core/mcp_server.py:152-172](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L152-L172)

| JSON-RPC Method | Request Parameters | Response Structure / Action |
| :--- | :--- | :--- |
| `initialize` | `protocolVersion`, `clientInfo` | Returns protocol version, tool capabilities, and server info (`name: "mem0"`) |
| `ping` | None | Returns empty result object `{}` |
| `tools/list` | None | Returns `search_memories` schema, description, and annotations |
| `tools/call` | `name`, `arguments`, `_meta` | Executes memory search or returns `ToolInputError` / execution failure response |

```python
if method == "initialize":
    requested = (message.get("params") or {}).get("protocolVersion")
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "protocolVersion": requested or PROTOCOL_VERSION,
            "capabilities": {"tools": {"listChanged": False}},
            "serverInfo": {"name": "mem0", "version": PLUGIN_VERSION},
        },
    }
if method == "ping":
    return {"jsonrpc": "2.0", "id": request_id, "result": {}}
if method == "tools/list":
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "tools": [
                {
                    "name": TOOL_NAME,
                    "description": TOOL_DESCRIPTION,
                    "inputSchema": TOOL_SCHEMA,
                    "annotations": {
                        "readOnlyHint": True,
                        "idempotentHint": True,
                        "openWorldHint": True,
                    },
                }
            ]
        },
    }
…
if method == "tools/call":
    params = message.get("params") or {}
    if params.get("name") != TOOL_NAME:
        result = _tool_response("Unknown Mem0 tool.", is_error=True)
    else:
        try:
            result = _tool_response(
                call_search_memories(params.get("arguments"), _workspace_cwd(params))
            )
        except ToolInputError as exc:
            result = _tool_response(str(exc), is_error=True)
        except Exception:
            result = _tool_response("Memory search failed.", is_error=True)
    return {"jsonrpc": "2.0", "id": request_id, "result": result}
```
Sources: [integrations/claude-code-plugin/core/mcp_server.py:160-191](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L160-L191), [integrations/claude-code-plugin/core/mcp_server.py:192-205](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L192-L205)

> [!WARNING]
> When `tools/call` receives an unknown tool name or encounters an unexpected exception during execution, it returns an explicit tool response dictionary with `isError: True` rather than raising a protocol-level transport error.

```python
params = message.get("params") or {}
if params.get("name") != TOOL_NAME:
    result = _tool_response("Unknown Mem0 tool.", is_error=True)
else:
    try:
        result = _tool_response(
            call_search_memories(params.get("arguments"), _workspace_cwd(params))
        )
    except ToolInputError as exc:
        result = _tool_response(str(exc), is_error=True)
    except Exception:
        result = _tool_response("Memory search failed.", is_error=True)
return {"jsonrpc": "2.0", "id": request_id, "result": result}
```
Sources: [integrations/claude-code-plugin/core/mcp_server.py:193-205](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L193-L205)

### Tool schema validation and workspace resolution

The tool input schema mandates a `query` string property up to 2,000 characters and accepts optional parameters including `top_k`, `category`, `scope`, and `run_id`. The server validates argument types via `_validate_arguments`, raising `ToolInputError` if unknown keys are passed or if boundary checks fail. Workspace current working directories are extracted dynamically from turn metadata or environment variables such as `CLAUDE_PROJECT_DIR` before invoking `search_memories`.

```python
TOOL_SCHEMA = {
    "type": "object",
    "properties": {
        "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "A direct question about earlier work in this repository.",
        },
        "top_k": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "Maximum memories to return. Uses Mem0's configured default when omitted.",
        },
        "category": {
            "type": "string",
            "enum": list(CODING_MEMORY_CATEGORY_NAMES),
            "description": "Optional memory category. Omit to search every category.",
        },
        "scope": {
            "type": "string",
            "enum": list(SEARCH_SCOPES),
            "description": (
                "Which memories to search. 'repo' (default) is the whole repository's "
                "shared memory plus your own preferences, 'dir' narrows the shared "
                "part to the current directory, 'mine' is your preferences alone."
            ),
        },
        "run_id": {
            "type": "string",
            "minLength": 1,
            "description": (
                "Optional coding-agent session ID. With any scope, restricts results to memories "
                "saved in that session. Omit to recall memories across sessions."
            ),
        },
    },
    "required": ["query"],
    "additionalProperties": False,
}
…
def _validate_arguments(
    arguments: Any,
) -> tuple[str, int | None, str | None, str | None, str | None]:
    if not isinstance(arguments, dict):
        raise ToolInputError("Search arguments must be an object.")

    unknown = set(arguments) - {"query", "top_k", "category", "scope", "run_id"}
    if unknown:
        raise ToolInputError(f"Unknown search argument: {sorted(unknown)[0]}")

    query = arguments.get("query")
    if not isinstance(query, str) or not query.strip():
        raise ToolInputError("query must be a non-empty string.")
    query = query.strip()
    if len(query) > 2000:
        raise ToolInputError("query must be at most 2,000 characters.")

    top_k = arguments.get("top_k")
    if top_k is not None and (
        isinstance(top_k, bool) or not isinstance(top_k, int) or not 1 <= top_k <= 20
    ):
        raise ToolInputError("top_k must be an integer from 1 to 20.")

    category = arguments.get("category")
    if category is not None and category not in CODING_MEMORY_CATEGORY_NAMES:
        raise ToolInputError("category must be one of Mem0's supported categories.")

    scope = arguments.get("scope")
    if scope is not None and scope not in SEARCH_SCOPES:
        raise ToolInputError(f"scope must be one of {list(SEARCH_SCOPES)}.")

    run_id = arguments.get("run_id")
    if run_id is not None:
        if not isinstance(run_id, str) or not run_id.strip():
            raise ToolInputError("run_id must be a non-empty string.")
        run_id = run_id.strip()

    return query, top_k, category, scope, run_id
```
Sources: [integrations/claude-code-plugin/core/mcp_server.py:28-68](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L28-L68), [integrations/claude-code-plugin/core/mcp_server.py:75-112](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L75-L112)

> [!NOTE]
> The search scope parameter validates against allowed values defined by `SEARCH_SCOPES` (`"repo"`, `"dir"`, `"mine"`), while categories must match valid coding memory category names.

```python
"scope": {
    "type": "string",
    "enum": list(SEARCH_SCOPES),
    "description": (
        "Which memories to search. 'repo' (default) is the whole repository's "
        "shared memory plus your own preferences, 'dir' narrows the shared "
        "part to the current directory, 'mine' is your preferences alone."
    ),
},
…
category = arguments.get("category")
if category is not None and category not in CODING_MEMORY_CATEGORY_NAMES:
    raise ToolInputError("category must be one of Mem0's supported categories.")

scope = arguments.get("scope")
if scope is not None and scope not in SEARCH_SCOPES:
    raise ToolInputError(f"scope must be one of {list(SEARCH_SCOPES)}.")
```
Sources: [integrations/claude-code-plugin/core/mcp_server.py:48-56](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L48-L56), [integrations/claude-code-plugin/core/mcp_server.py:98-104](https://github.com/blade47/mem0/blob/b7ad69afda6b6ed030347c66d48a13e4de9dec08/integrations/claude-code-plugin/core/mcp_server.py#L98-L104)

## Related

- [Plugin Synchronization](https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/technical/cli-tools/plugin-synchronization)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/faa36707-7c28-4f69-a18f-700ff61c704e/llms.txt) for all pages in this wiki.
