diff --git a/.agents/skills/debug-session/SKILL.md b/.agents/skills/debug-session/SKILL.md deleted file mode 100644 index ced9bb545b15..000000000000 --- a/.agents/skills/debug-session/SKILL.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -name: debug-session -description: Start a debugging session with worklog file -user-invocable: true -disable-model-invocation: true ---- - -# Start Debug Session - - - -Create a structured debugging session for an issue in the Dynamo ecosystem. - -## Step 1: Get the Bug Report - -Ask the user how they want to provide the bug: - -**Option A: Linear ticket** -- User provides ticket ID (e.g., "DYN-123") -- Fetch via Linear MCP tools -- Extract: title, description, reproduction steps - -**Option B: GitHub issue** -- User provides issue URL -- Fetch via `gh issue view ` -- Extract: title, description, reproduction steps - -**Option C: Paste** -- Ask user to paste the bug report directly -- Parse out the key details - -## Step 2: Discover Environment - -Gather environment information: - -!`nvidia-smi --query-gpu=name,count --format=csv,noheader 2>/dev/null || echo "No GPU detected"` - -!`uname -a` - -!`which python && python --version` - -This tells you: -- GPU type and count (L40s, H100s, etc.) -- OS/platform -- Python environment - -**Note**: The user's `~/.claude/CLAUDE.md` may have more details about their dev environment (paths, aliases, preferences). Check there for additional context. - -## Step 3: Create Worklog - -Create a worklog file to track the investigation: - -- Filename: `.md` in current directory -- Template: - -```markdown -# Debug: [Issue Title] - -**Date**: [today's date] -**Source**: [Linear ticket / GitHub issue / user report] -**Status**: investigating -**Environment**: [GPU type/count from nvidia-smi] - -## Problem -[Description of the issue] - -## Reproduction Steps -1. [Step to reproduce] -2. ... - -## Expected vs Actual -- **Expected**: -- **Actual**: - -## Investigation Log - -### [timestamp] -[Notes on what you tried/found] - -## Root Cause -[Fill in when found] - -## Fix -[Fill in when implemented] -``` - -## Step 4: Set Up Testing - -### Build Commands - -Rebuild Dynamo after making changes: -```bash -cd lib/bindings/python && maturin develop --uv && cd ../../.. && uv pip install -e . -``` - -If a framework change is required (sglang, vllm, trtllm), check the user's `~/.claude/CLAUDE.md` for rebuild instructions specific to that framework. - -### Running Examples - -Examples are located at: `/home/ubuntu/dynamo/examples/backends/` - -Available backends: -- `sglang/launch/` - SGLang backend examples -- `vllm/launch/` - vLLM backend examples -- `trtllm/launch/` - TensorRT-LLM backend examples - -Based on the bug report, determine which backend is relevant: -- If unclear, **ask the user** which backend/example to run -- Run the example in the background -- Wait for model to be ready - -### Verifying the Model is Up - -```bash -curl localhost:8000/v1/models -``` - -### Testing with a Request - -```bash -curl http://localhost:8000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "", - "messages": [{"role": "user", "content": "Hello"}], - "max_tokens": 50 - }' -``` - -## Step 5: Begin Investigation - -### Dynamo Infrastructure Debugging - -**KV cache and routing issues:** -- Check KV event logs in `lib/llm/src/block_manager/kv_consolidator/tracker.rs` -- Look at block manager state and consolidation behavior -- Inspect routing decisions in the KV-aware router - -**ZMQ / networking issues:** -- Check ZMQ socket configuration and endpoint bindings -- Look for connection timeouts or message drops -- Verify nats/etcd connectivity for service discovery - -**Multi-node / disaggregated issues:** -- Check prefill/decode worker assignment -- Verify DGD (disaggregated) status reporting -- Inspect inter-node communication via `nvidia-smi` on each node -- Check NCCL and GPU direct RDMA status - -**Process inspection:** -- `ps aux | grep dynamo` - check running processes -- `nvidia-smi` - GPU utilization and memory -- `ss -tlnp | grep 8000` - check port bindings -- `journalctl -u dynamo` - systemd logs if applicable - -### General Debugging Workflow - -1. **Reproduce first** - verify you can trigger the bug before attempting fixes -2. **Document as you go** - update the worklog with findings -3. **Minimal changes** - fix the bug, do not refactor surrounding code -4. **Verify the fix** - confirm the reproduction case now passes - -Performance-critical code - avoid unnecessary abstractions or comments. diff --git a/.agents/skills/dep-create/SKILL.md b/.agents/skills/dep-create/SKILL.md deleted file mode 100644 index 8488b2938e44..000000000000 --- a/.agents/skills/dep-create/SKILL.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -name: dep-create -description: Create or update Dynamo Enhancement Proposals as GitHub issues, including lightweight DEPs, implementation plans, and retroactive DEPs for ai-dynamo/dynamo. ---- - -# Skill: Create a DEP as a GitHub Issue - - - -## Purpose - -Create a new Dynamo Enhancement Proposal (DEP) as a GitHub Issue on -`ai-dynamo/dynamo`. The issue number becomes the DEP number. Also -handles adding implementation plans and retroactive DEPs for existing -work. - -## When to Use - -When the user wants to propose a new feature, architecture change, or -process improvement via the issue-based DEP workflow. Also when adding -an implementation plan to an existing DEP, or filing a retroactive DEP -for work already merged. - -## Workflow - -### Create a New DEP - -1. **Ask for source material**: Prompt the user for a Google Doc, - Confluence page, or other NVIDIA-internal document that contains - the background, customer context, or detailed requirements. Read - it using the appropriate tool (gdocs, Confluence MCP, WebFetch). - Include the link in the issue's References section — the document - is only accessible to NVIDIA employees and serves as the record - for customer-specific context that cannot appear in the public - issue prose. - -2. **Gather required fields** from the user and source doc (prompt - if missing): - - **Summary**: One-paragraph description of the proposal - - **Motivation**: Why this change is needed - - **Proposal**: Detailed description of the proposed change - -3. **Determine the area label** based on proposal content. Area labels - are bare names (e.g., `frontend`, `router`, `backend-vllm`) that - correspond to CODEOWNERS teams. - -4. **Decide template**: full or lightweight. - Use lightweight if only Summary, Motivation, and Proposal are needed. - -5. **Create the issue** (full DEP): - -```bash -gh issue create \ - --repo ai-dynamo/dynamo \ - --title "DEP: " \ - --label "dep:draft" \ - --label "" \ - --body "$(cat <<'EOF' -## Summary - - -## Motivation - - -## Proposal - - -## Alternate Solutions - - -## Requirements - - -## References - -EOF -)" -``` - - **For lightweight DEP**, use: - -```bash -gh issue create \ - --repo ai-dynamo/dynamo \ - --title "DEP (light): " \ - --label "dep:draft" \ - --label "dep:lightweight" \ - --label "" \ - --body "$(cat <<'EOF' -## Summary - - -## Motivation - - -## Proposal - -EOF -)" -``` - -6. **Report** the created issue number and URL to the user. - -### Add an Implementation Plan - -1. **Read the DEP issue** and its discussion: - -```bash -gh issue view --repo ai-dynamo/dynamo -gh issue view --repo ai-dynamo/dynamo --comments -``` - -2. **Draft the plan** with phases, tasks, effort estimates, - dependencies, risks, and testing strategy. - -3. **Post as a comment**: - -```bash -gh issue comment --repo ai-dynamo/dynamo --body-file /tmp/plan.md -``` - -### Retroactive DEP - -For work already merged without a DEP, file with `dep:implementing` -or `dep:done` and reference the existing PRs. - -## Notes - -- The issue body IS the spec — treat it as a living document. -- `dep:draft` is applied automatically. PIC changes to - `dep:under-review` when ready. -- For lightweight DEPs, use `dep:lightweight` label and omit optional - sections. -- For plan revisions, post a new comment with a changelog at the top. - Do not edit the original — preserve the timeline. -- **Customer name stripping**: Before creating or updating a DEP, - scan the summary, motivation, proposal, and all other fields for - specific customer names, company names, or partner names. Replace - them with generic references (e.g., "a customer", "a cloud - partner", "an enterprise user"). DEPs are public — no customer - names should appear in issue bodies, comments, or plans. diff --git a/.agents/skills/dep-status/SKILL.md b/.agents/skills/dep-status/SKILL.md deleted file mode 100644 index b58ac70f8223..000000000000 --- a/.agents/skills/dep-status/SKILL.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -name: dep-status -description: Check Dynamo Enhancement Proposal issue status, list DEPs by lifecycle state or area, and find related DEP issues in ai-dynamo/dynamo. ---- - -# Skill: Check DEP Status - - - -## Purpose - -List DEP issues with their current status, area, PIC, and approval -state. Find related DEPs for a given topic or component. - -## When to Use - -When the user wants to see the status of one or more DEPs, check what's -pending review, find DEPs related to a component, or get a triage -summary. - -## Workflow - -1. **List open DEP issues**: - -```bash -gh issue list --repo ai-dynamo/dynamo \ - --search 'label:"dep:draft","dep:under-review","dep:approved","dep:implementing"' \ - --json number,title,labels,assignees,createdAt,updatedAt -``` - -2. **Filter by area** (if requested): - -```bash -gh issue list --repo ai-dynamo/dynamo \ - --label "" \ - --json number,title,labels,assignees -``` - -3. **Filter by status** (if requested): - -```bash -gh issue list --repo ai-dynamo/dynamo \ - --label "dep:" \ - --json number,title,labels,assignees -``` - -4. **Format as a summary table**: - -```text -| # | Title | Status | Area | PIC | Updated | -|---|-------|--------|------|-----|---------| -| 42 | DEP: KV router scheduling | dep:under-review | router | @pic | 2026-03-28 | -``` - -5. **Find related DEPs** by searching issue titles and bodies: - -```bash -gh issue list --repo ai-dynamo/dynamo \ - --search 'DEP label:"dep:draft","dep:under-review","dep:approved","dep:implementing","dep:done"' \ - --json number,title,labels,state -``` - -6. **Include closed DEPs** if requested: - -```bash -gh issue list --repo ai-dynamo/dynamo \ - --state closed \ - --search 'label:"dep:done","dep:deferred","dep:rejected","dep:replaced"' \ - --json number,title,labels,assignees,closedAt -``` - -## Notes - -- For a full triage view, include both open and recently closed DEPs. -- Cross-reference with `dep:lightweight` label to distinguish full vs. - lightweight DEPs. -- Area labels are bare names (e.g., `frontend`, `router`) — no prefix. diff --git a/.agents/skills/dep-update/SKILL.md b/.agents/skills/dep-update/SKILL.md deleted file mode 100644 index 72efae221bb8..000000000000 --- a/.agents/skills/dep-update/SKILL.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -name: dep-update -description: Update Dynamo Enhancement Proposal lifecycle state in GitHub, including triage, PIC assignment, review, approval, and status label changes. ---- - -# Skill: Update DEP Lifecycle - - - -## Purpose - -Update DEP status through its lifecycle — triage, review, approve, -defer, or close. Covers the PIC workflow from initial assignment -through final approval. - -## When to Use - -When triaging DEP issues, reviewing a DEP as PIC or reviewer, -approving a DEP that is under review, or updating DEP status. - -## Workflow - -### Triage (assign PIC) - -1. **List unassigned DEPs**: - -```bash -gh issue list --repo ai-dynamo/dynamo \ - --label "dep:draft" \ - --json number,title,labels,assignees \ - --jq '.[] | select(.assignees | length == 0)' -``` - -2. **Assign PIC** based on the area label: - -```bash -gh issue edit --repo ai-dynamo/dynamo \ - --add-assignee "" -``` - -3. **Move to review** when the spec is ready: - -```bash -gh issue edit --repo ai-dynamo/dynamo \ - --remove-label "dep:draft" \ - --add-label "dep:under-review" -``` - -### Review - -1. **Read the DEP issue and discussion**: - -```bash -gh issue view --repo ai-dynamo/dynamo -gh issue view --repo ai-dynamo/dynamo --comments -``` - -2. **Post review feedback** as comments on the issue. - -3. **Request changes** or clarifications from the author. - -### Approve - -1. **Verify the issue is under review**: - -```bash -gh issue view --repo ai-dynamo/dynamo --json labels -``` - -2. **Post the approval comment**: - -```bash -gh issue comment --repo ai-dynamo/dynamo --body "/approve" -``` - -3. **If this is the PIC approving** (or all required reviewers have - approved), update the label: - -```bash -gh issue edit --repo ai-dynamo/dynamo \ - --remove-label "dep:under-review" \ - --add-label "dep:approved" -``` - -## Notes - -- For straightforward DEPs, the PIC's `/approve` is sufficient. -- For multi-reviewer DEPs, the PIC maintains a pinned approval - checklist and updates the label only when all required approvals are - collected. -- `/approve` comments are searchable for audit: - `gh search issues --repo ai-dynamo/dynamo "/approve" in:comments` -- Area labels are bare names (e.g., `frontend`, `router`) — no prefix. diff --git a/.agents/skills/dynamo-clone-hotpath-audit/SKILL.md b/.agents/skills/dynamo-clone-hotpath-audit/SKILL.md deleted file mode 100644 index 3a117fada55b..000000000000 --- a/.agents/skills/dynamo-clone-hotpath-audit/SKILL.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -name: dynamo-clone-hotpath-audit -description: Audit Dynamo Rust hot-path `.clone()` calls, explain which clones are removable and why, and only apply clone-removal patches when explicitly requested. -license: Apache-2.0 -metadata: - author: NVIDIA - tags: - - dynamo - - rust - - performance - - code-review - - allocation - permissions: - - file_read - - file_write ---- - -# Dynamo Clone Hotpath Audit - - - -## Purpose - -Find `.clone()` calls in Dynamo Rust request, scheduling, KV, block-manager, and -runtime hot paths that can be removed without changing ownership semantics. This -is an audit-first workflow, not a blanket clone-removal tool. - -Default behavior is read-only audit. Do not edit files, commit, or open a PR -unless the user explicitly asks to fix, patch, apply, implement, or create an -MR/PR. A skill invocation such as `$dynamo-clone-hotpath-audit`, "audit", -"check", or "scan" is not patch permission. - -## Prerequisites - -- Rust source checkout of `ai-dynamo/dynamo`. -- Python 3.10+ for the inventory script. -- Ability to run targeted Rust validation commands for touched crates. -- Subagent tooling if available. If subagents are unavailable, run the same - roles as separate, explicit review passes and say they were not independent. - -## Instructions - -### 1. Build The Inventory - -Run the read-only scanner from the repository root: - -```bash -python3 .agents/skills/dynamo-clone-hotpath-audit/scripts/clone_inventory.py \ - --only-actionable \ - --limit 80 -``` - -Use narrower paths when the user names a subsystem: - -```bash -python3 .agents/skills/dynamo-clone-hotpath-audit/scripts/clone_inventory.py \ - --paths lib/kv-router/src/scheduling lib/llm/src/backend \ - --only-actionable -``` - -Treat the scanner as a triage aid. It ranks likely expensive clones but does not -prove removability. - -### 2. Split The Audit By Hot Path - -Prioritize in this order: - -1. per-request LLM paths: `lib/llm/src/backend.rs`, preprocessor, HTTP/gRPC - services, protocol conversion, migration -2. KV routing and scheduling: `lib/kv-router/src/scheduling`, - `lib/kv-router/src/sequences`, `lib/kv-router/src/indexer` -3. KV/block manager event paths: `lib/llm/src/block_manager`, - `lib/bindings/kvbm/src/block_manager`, `lib/kvbm-engine/src/offload` -4. runtime request/transport paths: `lib/runtime/src/component`, - `lib/runtime/src/pipeline`, `lib/runtime/src/transports` -5. tests, examples, debug paths, and benchmark setup only after hot paths - -### 3. Use Independent Review Roles - -For non-trivial audits, use subagents. Give each subagent only the inventory -slice and relevant source files, not your intended answer. - -Required roles: - -- candidate finder: identify high-value clones and classify cheap/required ones -- ownership refactorer: propose concrete borrow, move, `Arc`, or `mem::take` - changes -- correctness adversary: reject changes that alter sharing, extend lock - lifetimes, borrow across `.await`, break spawned task ownership, or make APIs - less clear -- validation planner: choose the smallest tests, clippy commands, or benchmarks - that prove the touched behavior - -If two roles disagree, keep the clone unless you can write down a precise -ownership proof and validation plan. - -### 4. Classify Every Candidate - -Use these buckets: - -- cheap required: `Arc`, sender, cancellation token, runtime handle, watch - receiver, metrics handle, or tracing span clone needed to share ownership -- semantic required: the original value must stay available for a later use, - retry, fan-out, or async task -- cold/test-only: outside production hot paths -- removable move: value is cloned immediately before its final ownership use -- removable borrow: callee does not need ownership and can accept `&T`, `&str`, - slice, or iterator -- structural refactor: requires changing data layout, e.g. `Vec` to - `Arc<[T]>`, or changing an API family - -Do not patch candidates in the first three buckets. - -### 5. Patch In Small Batches - -Only run this section when the user explicitly asks for fixes or a patch. If the -user only asked for an audit, stop after the report and list recommended patch -batches as follow-up work. - -Keep each patch batch narrow: one subsystem or one repeated pattern. Avoid a -single repository-wide clone cleanup PR unless the user explicitly asks for it. - -Preferred fixes: - -- move a value when the clone is immediately consumed and not used afterward -- pass `&T`, `&str`, `&[T]`, or an iterator when the callee only reads -- consume batches with `into_iter()` instead of indexing and cloning -- extract small `Copy` fields before moving a large event -- use `std::mem::take` only when leaving the source value empty is part of the - intended semantics -- use `Arc` only when shared ownership is semantically right, not just to avoid - borrow-checker work - -Do not: - -- remove clones that cross `tokio::spawn`, channel send, callback storage, or - task lifetime boundaries without proving ownership -- extend mutex/RwLock guard lifetimes to avoid a clone -- borrow across `.await` unless the borrow is local and compiler-verified -- trade a clear cheap clone for a confusing lifetime-heavy API -- make public APIs borrow data whose ownership was intentionally independent - -### 6. Required Finding Format - -For every proposed change, report: - -- `file:line` -- hot-path rationale -- cloned value and likely clone cost -- bucket and recommended change -- removal rationale: why this clone is unnecessary, not only why it is expensive -- required-clone check: why it is not cheap required or semantic required -- correctness proof: why the old and new ownership semantics match -- validation command - -Example: - -```text -lib/llm/src/preprocessor.rs:123 -Hot path: every embeddings request. -Cost: clones Vec before moving into spawn_blocking. -Bucket: removable move. -Change: move input_strs into the closure. -Removal rationale: the clone feeds the only owned consumer and the original is -not read after closure construction. -Required-clone check: no fan-out, retry, async task sharing, or later logging -uses the original value. -Proof: the closure receives the same owned Vec; all later behavior reads -from that moved value. -Validation: cargo test -p dynamo-llm preprocessor -``` - -### 7. Validate Before Finishing - -If patches were made, always run formatting and the narrowest relevant Rust -tests. For broad API changes, also run clippy or crate-level tests for each -touched crate. If no patches were made, do not run tests just to make the audit -look validated; report the inventory command and any read-only review checks. - -Return: - -- clone candidates reviewed -- candidates intentionally left alone and why -- patches made, or `none: audit-only` -- tests run and results, or `not run: no files changed` -- residual high-value candidates that should be separate PRs - -## Available Scripts - -| Script | Purpose | Arguments | -|---|---|---| -| `scripts/clone_inventory.py` | Rank Rust `.clone()` call sites by hot-path likelihood and removal potential | `--paths`, `--limit`, `--format`, `--only-actionable`, `--include-tests` | - -Invoke via the agentskills.io `run_script()` protocol: - -```python -run_script("scripts/clone_inventory.py", args=["--only-actionable", "--limit", "80"]) -``` - -## Output Contract - -Return a concise audit report or PR summary with: - -- scope and inventory command -- top findings with buckets -- per finding: removal rationale, required-clone check, correctness proof, and - validation command -- exact refactors made, or `none: audit-only` -- independent review role outcomes -- validation commands and status -- follow-up candidates excluded from the current audit or patch - -## Limitations - -- The scanner is heuristic and line-based. It can miss macro-expanded clones, - multi-line ownership patterns, or clones hidden behind helper methods. -- Some expensive-looking clones are required for async task ownership, fan-out, - retries, or API clarity. -- Performance impact is inferred unless backed by benchmarks or allocation - profiles. diff --git a/.agents/skills/dynamo-clone-hotpath-audit/scripts/clone_inventory.py b/.agents/skills/dynamo-clone-hotpath-audit/scripts/clone_inventory.py deleted file mode 100755 index ce955619f5a9..000000000000 --- a/.agents/skills/dynamo-clone-hotpath-audit/scripts/clone_inventory.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Rank Rust .clone() call sites for Dynamo hot-path clone audits.""" - -from __future__ import annotations - -import argparse -import json -import re -from dataclasses import asdict, dataclass -from pathlib import Path -from typing import Iterable - -DEFAULT_PATHS = ("lib", "crates", "components") - -CLONE_RE = re.compile(r"(?:\bArc::clone\s*\(|\.clone\s*\()") - -TEST_PATH_MARKERS = ( - "/tests/", - "/benches/", - "/examples/", - "/bench/", - "_test.rs", - "tests.rs", -) - -HOT_PATH_RULES = ( - ("lib/llm/src/backend", 10, "llm request backend"), - ("lib/llm/src/preprocessor", 9, "llm request preprocessing"), - ("lib/llm/src/http", 8, "http request path"), - ("lib/llm/src/grpc", 8, "grpc request path"), - ("lib/llm/src/protocols", 7, "protocol conversion"), - ("lib/llm/src/migration", 7, "request retry/migration"), - ("lib/llm/src/kv_router", 8, "llm kv routing"), - ("lib/llm/src/block_manager", 8, "block manager path"), - ("lib/bindings/kvbm/src/block_manager", 8, "kvbm binding block manager"), - ("lib/kvbm-engine/src/offload", 8, "kvbm offload path"), - ("lib/kvbm-logical/src", 7, "kvbm logical manager"), - ("lib/kvbm-physical/src", 7, "kvbm physical manager"), - ("lib/kv-router/src/scheduling", 9, "kv-router scheduler"), - ("lib/kv-router/src/sequences", 8, "kv-router sequence tracker"), - ("lib/kv-router/src/indexer", 8, "kv-router indexer"), - ("lib/runtime/src/component", 7, "runtime component path"), - ("lib/runtime/src/pipeline", 7, "runtime pipeline path"), - ("lib/runtime/src/transports", 7, "runtime transport path"), - ("lib/runtime/src/storage", 6, "runtime storage path"), -) - -CHEAP_HINTS = ( - "Arc::clone", - "CancellationToken", - "cancel_token", - "shutdown_token", - "sender", - "receiver", - "_tx", - "_rx", - "watch::", - "Handle", - "span.clone", - "metrics", -) - -DEEP_VALUE_HINTS = ( - "tokens", - "token_ids", - "token_block", - "token_chunks", - "request", - "response", - "event", - "payload", - "metadata", - "blocks", - "block_hashes", - "scores", - "overlap", - "runtime_data", - "annotations", - "messages", - "content", - "schema", - "config", -) - -BOUNDARY_HINTS = ( - "tokio::spawn", - "spawn_blocking", - ".send(", - ".try_send(", - ".instrument(", - ".map_err(", - "async move", -) - - -@dataclass(frozen=True) -class CloneCandidate: - score: int - tier: str - category: str - path: str - line: int - hot_path: str - reasons: list[str] - text: str - - -def require_under_root(root: Path, path: Path) -> Path: - resolved_root = root.resolve() - resolved_path = path.resolve() - try: - resolved_path.relative_to(resolved_root) - except ValueError as exc: - msg = f"refusing to scan path outside repository root: {path}" - raise SystemExit(msg) from exc - return resolved_path - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "--root", - default=".", - help="Repository root. Defaults to current working directory.", - ) - parser.add_argument( - "--paths", - nargs="+", - default=DEFAULT_PATHS, - help="Files or directories to scan, relative to --root.", - ) - parser.add_argument( - "--limit", - type=int, - default=120, - help="Maximum candidates to print. Use 0 for all.", - ) - parser.add_argument( - "--format", - choices=("markdown", "json"), - default="markdown", - help="Output format.", - ) - parser.add_argument( - "--only-actionable", - action="store_true", - help="Hide cheap shared-ownership and test-only clones.", - ) - parser.add_argument( - "--include-tests", - action="store_true", - help="Keep test, benchmark, and example clone sites in normal scoring.", - ) - return parser.parse_args() - - -def rust_files(root: Path, paths: Iterable[str]) -> list[Path]: - files: set[Path] = set() - for raw_path in paths: - path = require_under_root(root, root / raw_path) - if path.is_file() and path.suffix == ".rs": - files.add(path) - elif path.is_dir(): - files.update(p for p in path.rglob("*.rs") if p.is_file()) - return sorted(files) - - -def rel_path(root: Path, path: Path) -> str: - try: - return path.resolve().relative_to(root.resolve()).as_posix() - except ValueError: - return path.as_posix() - - -def is_test_path(path: str) -> bool: - normalized = f"/{path}" - return any(marker in normalized for marker in TEST_PATH_MARKERS) - - -def hot_path_score(path: str) -> tuple[int, str]: - for prefix, score, label in HOT_PATH_RULES: - if path.startswith(prefix): - return score, label - return 2, "not a known hot path" - - -def context_window(lines: list[str], index: int) -> str: - start = max(0, index - 5) - end = min(len(lines), index + 3) - return "\n".join(lines[start:end]) - - -def has_loop_context(window: str) -> bool: - return bool(re.search(r"^\s*(for|while|loop)\b", window, re.MULTILINE)) - - -def score_candidate( - path: str, line: str, window: str, include_tests: bool -) -> CloneCandidate: - base_score, hot_path = hot_path_score(path) - score = base_score - reasons = [hot_path] - - stripped = line.strip() - path_is_test = is_test_path(path) - is_cold = path_is_test and not include_tests - is_cheap = any(hint in stripped for hint in CHEAP_HINTS) - is_loop = has_loop_context(window) - is_clone_into_arc = "Arc::new(" in stripped and ".clone" in stripped - - if is_cold: - score -= 8 - reasons.append("test, benchmark, or example path") - - if is_cheap: - score -= 5 - reasons.append("looks like shared-handle or control-plane clone") - - if any(hint in stripped for hint in DEEP_VALUE_HINTS): - score += 3 - reasons.append("name suggests non-trivial owned data") - - if is_loop: - score += 4 - reasons.append("clone appears inside or near a loop") - - if is_clone_into_arc: - score += 5 - reasons.append("owned value may be cloned before Arc wrapping") - - if ".clone()" in stripped and any(hint in window for hint in BOUNDARY_HINTS): - score -= 2 - reasons.append("near async/task/channel boundary; ownership may be required") - - if re.search(r"let\s+\w+\s*=\s*\w+\.clone\(\)\s*;", stripped): - score += 2 - reasons.append("simple clone assignment; check whether final use can move") - - if ".clone()." in stripped or ( - ".clone()" in stripped and "unwrap_or_else" in stripped - ): - score += 1 - reasons.append("clone participates in expression chain") - - if is_cold: - category = "cold_or_test" - elif is_cheap: - category = "cheap_shared" - elif is_clone_into_arc: - category = "clone_into_arc" - elif is_loop: - category = "loop_clone" - elif score >= 8: - category = "suspicious_hotpath" - else: - category = "needs_review" - - tier = "high" if score >= 12 else "medium" if score >= 8 else "low" - return CloneCandidate( - score=score, - tier=tier, - category=category, - path=path, - line=0, - hot_path=hot_path, - reasons=reasons, - text=stripped, - ) - - -def scan_file(root: Path, path: Path, include_tests: bool) -> list[CloneCandidate]: - rel = rel_path(root, path) - text = path.read_text(encoding="utf-8", errors="replace") - lines = text.splitlines() - candidates: list[CloneCandidate] = [] - - for index, line in enumerate(lines): - stripped = line.strip() - if not stripped or stripped.startswith("//"): - continue - if not CLONE_RE.search(line): - continue - - candidate = score_candidate( - rel, line, context_window(lines, index), include_tests - ) - candidates.append( - CloneCandidate( - score=candidate.score, - tier=candidate.tier, - category=candidate.category, - path=rel, - line=index + 1, - hot_path=candidate.hot_path, - reasons=candidate.reasons, - text=candidate.text, - ) - ) - - return candidates - - -def scan(root: Path, paths: Iterable[str], include_tests: bool) -> list[CloneCandidate]: - candidates: list[CloneCandidate] = [] - for path in rust_files(root, paths): - candidates.extend(scan_file(root, path, include_tests)) - return sorted(candidates, key=lambda c: (-c.score, c.path, c.line)) - - -def filtered( - candidates: list[CloneCandidate], only_actionable: bool -) -> list[CloneCandidate]: - if not only_actionable: - return candidates - ignored = {"cheap_shared", "cold_or_test"} - return [candidate for candidate in candidates if candidate.category not in ignored] - - -def limited(candidates: list[CloneCandidate], limit: int) -> list[CloneCandidate]: - if limit <= 0: - return candidates - return candidates[:limit] - - -def summarize(candidates: list[CloneCandidate]) -> dict[str, object]: - by_tier: dict[str, int] = {} - by_category: dict[str, int] = {} - for candidate in candidates: - by_tier[candidate.tier] = by_tier.get(candidate.tier, 0) + 1 - by_category[candidate.category] = by_category.get(candidate.category, 0) + 1 - return { - "total": len(candidates), - "by_tier": dict(sorted(by_tier.items())), - "by_category": dict(sorted(by_category.items())), - } - - -def markdown_escape(value: str) -> str: - return value.replace("|", "\\|").replace("\n", " ") - - -def print_markdown( - candidates: list[CloneCandidate], all_candidates: list[CloneCandidate] -) -> None: - summary = summarize(all_candidates) - print("# Rust Clone Hotpath Inventory") - print() - print(f"Total candidates after filters: {summary['total']}") - print(f"Tier counts: `{summary['by_tier']}`") - print(f"Category counts: `{summary['by_category']}`") - print() - print("| Score | Tier | Category | Location | Reasons | Code |") - print("|---:|---|---|---|---|---|") - for candidate in candidates: - location = f"{candidate.path}:{candidate.line}" - reasons = "; ".join(candidate.reasons) - print( - "| " - f"{candidate.score} | " - f"{candidate.tier} | " - f"{candidate.category} | " - f"`{markdown_escape(location)}` | " - f"{markdown_escape(reasons)} | " - f"`{markdown_escape(candidate.text)}` |" - ) - - -def print_json( - candidates: list[CloneCandidate], all_candidates: list[CloneCandidate] -) -> None: - payload = { - "summary": summarize(all_candidates), - "candidates": [asdict(candidate) for candidate in candidates], - } - print(json.dumps(payload, indent=2, sort_keys=True)) - - -def main() -> int: - args = parse_args() - root = Path(args.root).resolve() - all_candidates = filtered( - scan(root, args.paths, args.include_tests), - only_actionable=args.only_actionable, - ) - candidates = limited(all_candidates, args.limit) - - if args.format == "json": - print_json(candidates, all_candidates) - else: - print_markdown(candidates, all_candidates) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.agents/skills/dynamo-docs/SKILL.md b/.agents/skills/dynamo-docs/SKILL.md deleted file mode 100644 index 5e86f1115689..000000000000 --- a/.agents/skills/dynamo-docs/SKILL.md +++ /dev/null @@ -1,407 +0,0 @@ ---- -name: dynamo-docs -description: Add, update, move, or remove content on the Dynamo Fern docs site — standard docs pages, catalog-driven recipe and feature-benchmark pages, examples, recipes, and translations — keeping everything in line with the documentation style guide. Use for any change under docs/, recipes/, or examples/ (new page, edit, section move, rename, removal, recipe/benchmark page, .zh-CN translation, version cut) and whenever content needs its frontmatter, headings, links, callouts, or terminology fixed. ---- - -# Dynamo Docs Maintenance - - - -Unified skill for adding, updating, moving, and removing content on the Dynamo Fern documentation -site, in line with the project's authoring guides. - -Two authoring guides govern this work; read whichever applies before writing: - -- [`docs/documentation-style-guide.md`](https://github.com/ai-dynamo/dynamo/blob/main/docs/documentation-style-guide.md) — the standard for **every** page: frontmatter, headings, prose, terminology, links, callouts. The must-fix subset is distilled in [Style Guide Is the Standard](#style-guide-is-the-standard) and [Content Rules](#content-rules) below. -- [`docs/recipes/_catalog/README.md`](https://github.com/ai-dynamo/dynamo/blob/main/docs/recipes/_catalog/README.md) — the standard for **recipe and feature-benchmark pages** (the catalog contract, the `.mdx` page blueprint, and the pure-CSS target picker). See [Add a Recipe or Feature Benchmark Page](#add-a-recipe-or-feature-benchmark-page). - -## Branch Rule - -**ALL edits happen on `main` (or a feature branch based on `main`).** -The `docs-website` branch is CI-managed and must **never** be edited by hand. - -## Style Guide Is the Standard - -Every page under `docs/` (and the READMEs under `examples/` and `recipes/`) follows the -[Documentation Style Guide](https://github.com/ai-dynamo/dynamo/blob/main/docs/documentation-style-guide.md) -(`docs/documentation-style-guide.md`). Read it before writing content. The docs bot enforces a -**must-fix** subset on every PR — get these right or the checks fail: - -- **SPDX header** on every file, copyright range `2025-2026`. Fern pages put the two `#` lines - *inside* the `---` frontmatter; plain READMEs use an HTML-comment block. -- **Frontmatter with at least one metadata key** (`title`/`subtitle`/`sidebar-title`) and **no body - `# H1`**. Fern renders the page H1 from the nav `page:` value, so a body `# H1` produces a - duplicate title — and a bare `#` SPDX line left in the body also renders as an H1. Start the body - at `##`. -- **A nav entry** in `docs/index.yml` for every new page — a page not in the nav is unreachable. -- **Links**: relative path *with extension* within `docs/` (`[Routing](router-concepts.md)`); - absolute `https://github.com/ai-dynamo/dynamo/blob/main/` URL for targets outside `docs/` - (examples, recipes, source; `/tree/main/` for a directory). No `../` path that escapes `docs/`, and - never a hardcoded `https://docs.nvidia.com/...` link to a page in this repo. Link text names the - destination, never "click here". -- **No internal or sensitive references**: NVBug/JIRA/Linear IDs, internal hostnames, secrets, - `TODO`/`FIXME`. - -Everything else in the style guide (page types, heading case, terminology, list and code-fence -formatting, the pre-merge checklist) is guidance — the high-value rules are distilled in -[Content Rules](#content-rules) below; apply them and deviate only with a reason. - -## Content Rules - -Apply these on every page so the result reads like a person wrote it and passes review without a -round-trip to the style guide. These are defaults; deviate with a reason. - -- **Page type (Diátaxis).** Each page serves one need — *tutorial* (`getting-started/`), *how-to* - (`backends//`, `kubernetes/`), *reference* (flags/APIs/config), or *explanation* - (`design-docs/`). Don't blend a how-to into a flag reference; split and cross-link. -- **Headings.** Title Case for short label / noun-phrase headings ("Routing Behavior"); sentence - case for full-phrase headings ("Choosing a checkpoint flow"). Be consistent within a page. No end - punctuation. Logical `##` → `###` hierarchy, no skipped levels. Renaming a heading breaks inbound - `#anchor` links — rename deliberately. -- **Terminology, exact casing.** Backends: **vLLM**, **SGLang**, **TensorRT-LLM** (or **TRT-LLM**) — - never "vllm", "Sglang", "TensorRT LLM". **NVIDIA Dynamo** on first mention, then **Dynamo**; **KV - router**, **NIXL**, **GPU**; **Kubernetes**, not "k8s", in prose. Expand acronyms on first use - ("Time To First Token (TTFT)"). Use one word per concept. -- **Inclusive terms.** "denylist"/"allowlist", not "blacklist"/"whitelist"; "primary"/"replica", not - "master"/"slave". -- **Cut marketing and bombast.** Remove "seamless, robust, powerful, blazing-fast, cutting-edge, - effortless, unlock, leverage, delve, comprehensive, rich ecosystem, world-class, game-changing". - Cut filler ("it's important to note", "simply", "just", "in order to") and difficulty words - ("easy", "easily"). Start sentences with a verb; active voice; present tense; second-person - imperative. Name the flag/default/command, not "configure the appropriate settings". Avoid the - em-dash-aside tic. -- **Procedures.** Condition before instruction ("To enable KV-aware routing, set `--router-mode - kv`", not the reverse). One action per numbered step. -- **Links.** Follow the must-fix Links rule in - [Style Guide Is the Standard](#style-guide-is-the-standard) (relative + extension inside `docs/`, - absolute GitHub URL outside, no `../` escape, no `docs.nvidia.com` self-link). -- **Code fences** always tag a language (`bash`, not `sh`); no `$`/`#` prompt prefixes; put output in - its own `text` block. Wrap flags, paths, and `DYN_*` env vars in backticks in prose. -- **Lifecycle.** Mark preview features **Experimental.** and legacy ones **Deprecated.** (with a - `> [!WARNING]`); note availability for new features ("Available since v0.X"). - -## Operations - -Pick your operation: - -- Standard `.md` doc page → [Add a Page](#add-a-page) -- Rendered recipe / feature-benchmark page (`.mdx` + catalog triple) → [Add a Recipe or Feature Benchmark Page](#add-a-recipe-or-feature-benchmark-page) -- Code under `examples/` or `recipes/` → [Add an Example or Recipe (code)](#add-an-example-or-recipe-code) -- Edit, move, or remove existing content → [Update a Page](#update-a-page), [Remove a Page](#remove-a-page) (recipes: [Move, defer, or remove a recipe](#move-defer-or-remove-a-recipe)) -- Chinese translation or version cut → [Translations and Versioned Navs](#translations-and-versioned-navs) - -### Add a Page - -1. **Choose placement from the live nav.** Open `docs/index.yml` and find the existing page closest - in topic to yours — your page joins **that** section, and its file goes in that sibling's - subdirectory under `docs/`. Page *type* narrows the field (tutorial → `getting-started/`, how-to → - `backends//` or `kubernetes/`, reference → flags/APIs/config, explanation → - `design-docs/`), but the nearest existing page is the tie-breaker — don't guess from the section - names in [Navigation](#navigation-tabs-and-sections), read the file. Note the section, the - subdirectory, a kebab-case `.md` filename, and the page title. -2. Create `docs//.md`. Frontmatter carries the SPDX header plus at least one - metadata key; the body starts at `##` with a short intro — **no body `# H1`**: - -```markdown ---- -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -title: -subtitle: ---- - -Short intro paragraph stating what the page covers. - -## -``` - -3. Add a nav entry in `docs/index.yml` under the section you chose in step 1 — a `- page:` in that - section's `contents:`, 2-space indent, `path:` relative to `docs/` (see - [Navigation](#navigation-tabs-and-sections) for the grammar): - -```yaml -- page: - path: /.md -``` - -### Update a Page - -1. Locate by file path, page title, or keyword search (`grep -rn` in `docs/`). -2. **Content only** -- edit the markdown file directly; keep it within the style guide. -3. **Title/label change** -- update the frontmatter (`title`/`sidebar-title`) and the `- page:` name - in `docs/index.yml`. -4. **Section move** -- `git mv` the file when the subdirectory changes, move the nav entry to the new - section, and update every incoming link. - -> [!IMPORTANT] -> A page's URL is `/`, where the page-name slug comes from the nav -> `page:` label. Moving a page to another section **or** renaming its label changes that URL. Add a -> **dev-scoped** redirect to the `redirects:` list in `fern/docs.yml`: `/dynamo/dev/` → -> `/dynamo/dev/`. Editing `docs/index.yml` regenerates only the `dev` nav, so do **not** redirect -> the unversioned (`/dynamo/`) or `/dynamo/latest/` forms — those serve **Latest**, a frozen -> release snapshot that `main` edits don't touch, and a redirect there would break a working URL. See -> [Redirects and the version model](#redirects-and-the-version-model). - -### Remove a Page - -1. Find incoming links: `grep -rn "" docs/`. -2. `git rm docs//.md`. -3. Remove the `- page:` block from `docs/index.yml`. If it was the last page in a section, remove the - whole `- section:` block. -4. Fix or remove every incoming link found in step 1, and add a `fern/docs.yml` redirect if the page - had a stable URL. - -### Add a Recipe or Feature Benchmark Page - -Recipe and feature-benchmark pages are **catalog-driven** and use `.mdx` (they embed a pure-CSS -target picker). Authoritative guide: -[`docs/recipes/_catalog/README.md`](https://github.com/ai-dynamo/dynamo/blob/main/docs/recipes/_catalog/README.md). -Each page is a triple — page + catalog entry + nav: - -1. **Write the `.mdx`** at `docs/recipes/.mdx` (or `docs/benchmarks/.mdx`). Frontmatter - carries SPDX + `title` + one-sentence `subtitle`; body starts with a short intro, then the target - picker — multi-target pages use the radio picker, single-target pages use the **static** form - (exact classes under [Target picker](#target-picker) below) — then the fixed section order: - `## Prerequisites` → `## Deploy` → `## Smoke Test` → `## Benchmark` → `## Expected Performance` - (omit if no numbers) → `## Compare All Targets` (multi-target only) → `## Related Feature - Benchmarks` → `## Notes` → `## Source`. **MDX rule:** blank line after `
` and before - `
`; keep code fences at column 0. -2. **Add a catalog entry** — one file at `docs/recipes/_catalog/recipes/.yaml` (or - `docs/benchmarks/_catalog/benchmarks/.yaml`), SPDX header, exactly one object. **Read the - sibling `schema.json` first for the exact field set** (`docs/recipes/_catalog/schema.json` for - recipes, `docs/benchmarks/_catalog/schema.json` for benchmarks — they are **different** schemas) — - each is `additionalProperties: false`, so an invented or misspelled key fails validation; don't - guess the shape. A **recipe** entry requires `id`, - `title`, `provider`, `model`, `status`, `targets`, `maintainer`, and each `targets[]` item - requires `id`, `recommended`, `hardware`, `runtime`, `topology`, `techniques`, `workload`, - `deploy`, `expected_performance`. Internal `id:` **must equal the filename**; active entries carry - `page:`, deferred ones carry `deferred_reason` and omit `page:`. Add the `` to the matching - `_catalog/index.yaml` (`recipes:` for active, `deferred_recipes:` for deferred — it controls - sidebar/landing order). -3. **Wire navigation** in `docs/index.yml`: a `- page:` under `- tab: recipes` for recipes, or under - the **Feature Benchmarks** section (`- tab: docs`) for benchmarks. Per-benchmark pages are usually - `hidden: true` (surfaced from the landing page). -4. **Patch `fern/main.css` only if** the page introduces a picker axis value not already supported - (`recipe-sku`: `b200`/`h200`/`h100`/`gb200`/`hopper`/`blackwell`; `recipe-usecase`: - `chat`/`agentic`; `recipe-variant`: `agg`/`disagg`/…). A value missing from CSS renders but - filters nothing. -5. **Add the landing card** in `docs/recipes/README.mdx` and update the model/target counts. -6. **Validate**: `python3 docs/recipes/_catalog/validate.py` (covers both catalogs), then `fern - check` and `fern docs broken-links`. - -#### Catalog entry shape - -`schema.json` is authoritative for the field set; this skeleton just anchors the **nested shapes and -enums** that are easy to get wrong (`model`/`hardware`/`runtime`/`workload`/`deploy`/ -`expected_performance` are **objects**, not scalars; `status` and `topology` are **enums**). Minimal -valid active entry: - -```yaml -id: llama-3-1-8b # == filename; pattern ^[a-z0-9][a-z0-9-]*$ -title: Llama 3.1 8B -provider: meta # landing-page filter key (meta, qwen, nvidia, …) -model: - name: Llama 3.1 8B - hf_id: Meta-Llama/Llama-3.1-8B - precision: BF16 -status: validated # enum: validated | experimental (NOT "active") -page: recipes/llama-3-1-8b.mdx # active only; deferred → omit page:, add deferred_reason: -maintainer: Jane Doe # or null (null is tracked as a gap) -targets: # >= 1 item - - id: vllm-agg-h100 - recommended: true # bool - hardware: { gpu: H100, count: 1 } - runtime: { framework: vllm } - topology: aggregated # enum: aggregated | disaggregated - techniques: [bf16] - workload: { type: chat } - deploy: { asset: recipes/llama-3-1-8b/vllm/agg/deploy.yaml } - expected_performance: { available: false } # add summary: when numbers exist -``` - -**Benchmarks use a different schema.** A `docs/benchmarks/_catalog/benchmarks/.yaml` entry -validates against `docs/benchmarks/_catalog/schema.json`, whose required set is `id`, `title`, `page`, -`claim`, `subtype` (enum: `ab-test`/`feature-stack`/`topology`/`provider-comparison`/`hands-on`), -`features`, `model`, `hardware`, `traffic`, `arms`, `results`, `maintainer` — **no** `provider`, -`status`, or `targets`. The skeleton above is recipe-only; read the benchmark schema for that shape. - -#### Target picker - -The picker is pure CSS under the `dynamo-*` namespace — **MDX uses `className`, not `class`**, and the -exact class names matter (a wrong class name, or a `class=`-spelled wrapper, renders but filters nothing). A -**multi-target** page renders `
` containing a -`dynamo-target-picker-title`, one `dynamo-target-picker-row` per dimension (a `dynamo-target-picker-dim` -label plus radio `` + `