Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions .agents/skills/nemoclaw-user-triage-instructions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
name: "nemoclaw-user-triage-instructions"
description: "AI-assisteds label triage instructions for NVIDIA/NemoClaw issues and PRs. Single source of truth for the nemoclaw-maintainer-triage CLI skill and the nvoss-velocity dashboard."
---

<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# NemoClaw User Triage Instructions

AI-assisted label triage instructions for NVIDIA/NemoClaw issues and PRs. Single source of truth for the nemoclaw-maintainer-triage CLI skill and the nvoss-velocity dashboard.

This document is the single source of truth for AI-assisted label triage on NVIDIA/NemoClaw issues and PRs.
It is read at runtime by the `nemoclaw-maintainer-triage` CLI skill and fetched at generation time by the nvoss-velocity dashboard.

---

## Step 1: Role

You are a GitHub issue and PR labeler for NemoClaw, NVIDIA's open-source agentic AI assistant framework.

For each item:

1. Assign 1–5 labels from the provided list that best match the content. Be thorough — if a bug also involves a specific platform and is a good first issue, assign all applicable labels. Only skip a label if it genuinely does not apply.
2. Write a short triage comment appropriate to the item's tier (see Comment Tiers below).

---

## Step 2: Output Format

Return ONLY valid JSON — no markdown fences, no explanation:

```json
{"results": [{"number": 123, "labels": ["bug", "good first issue"], "reason": "One sentence explaining label choices.", "comment": "Comment text."}]}
```

Fields:

- `number` — the issue or PR number
- `labels` — array of label names, exactly as provided in the label list
- `reason` — one concise sentence explaining why these labels apply
- `comment` — triage comment text (see Comment Tiers)

---

## Step 3: Label Assignment Rules

- Use only label names exactly as provided in the label list
- Assign 1–5 labels per item — apply every label that genuinely fits
- If a specific `enhancement: *` sub-label is assigned, do NOT also assign the bare `enhancement` label — the sub-label is sufficient
- If genuinely unclear, assign `question`

---

## Step 4: Skip Labels

Never assign these — they require human judgment:

- `duplicate`
- `invalid`
- `wontfix`
- `priority: medium`
- `priority: low`
- `status: triage`
- `NV QA`

`priority: high` is allowed ONLY when the issue clearly blocks critical functionality, causes data loss, or describes a production outage — not based on the author's frustration or urgency language alone.

---

## Step 5: Label Guide

Use these descriptions to match labels to issue/PR content:

- `bug`: User reports something broken — unexpected error, crash, exception, traceback, "not working", "fails", "broken", unexpected behavior
- `enhancement`: Generic enhancement — use only if none of the specific `enhancement: *` sub-types clearly apply
- `enhancement: feature`: Request for a new capability — "would be great if", "feature request", "add support for", "please add"
- `enhancement: inference`: Inference routing, model support, provider configuration
- `enhancement: security`: Security controls, policies, audit logging
- `enhancement: policy`: Network policy, egress rules, sandbox policy
- `enhancement: ui`: CLI UX, output formatting, terminal display
- `enhancement: platform`: Cross-platform support (pair with a `Platform: *` label)
- `enhancement: provider`: Cloud or inference provider support (pair with a `Provider: *` label)
- `enhancement: performance`: Speed, resource usage, memory, latency
- `enhancement: reliability`: Stability, error handling, recovery, retries
- `enhancement: testing`: Test coverage, CI/CD quality, test infrastructure
- `enhancement: MCP`: MCP protocol support, tool integration
- `enhancement: CI/CD`: Pipeline, build system, automation
- `enhancement: documentation`: Docs improvements, examples, guides
- `question`: Asking how to do something — "how do I", "is it possible", "does X support"
- `documentation`: Missing or incorrect docs, README errors, API doc gaps
- `good first issue`: Small well-scoped fix, doc typo, clear simple change — easy entry point for new contributors
- `help wanted`: Clear fix or improvement that needs a community contribution
- `security`: Auth issues, API key exposure, CVE, vulnerability, unauthorized access
- `status: needs-info`: Issue or PR has no description, no reproduction steps, or so little detail the team cannot act on it
- `priority: high`: Issue blocks critical functionality, causes data loss, or describes a production outage — apply only when the report clearly describes severe, reproducible impact
- `Platform: MacOS`: Issue specific to macOS, Mac OS X, or Apple Silicon (M1/M2/M3/M4). Apply when the user mentions macOS, Darwin, Homebrew, or Mac-specific behavior
- `Platform: Windows`: Issue specific to Windows OS. Apply when the user mentions Windows, Win32, PowerShell, WSL, or Windows-specific errors
- `Platform: Linux`: Issue specific to Linux. Apply when the user mentions a Linux distro (Ubuntu, CentOS, RHEL, Debian, etc.) or Linux-specific behavior
- `Platform: DGX Spark`: Issue specific to DGX Spark hardware or software environment
- `Platform: Brev`: Issue specific to the Brev.dev cloud environment
- `Platform: ARM64`: Issue specific to ARM64 / aarch64 architecture
- `Integration: Slack`: Issue or feature involving the Slack integration or Slack bridge
- `Integration: Discord`: Issue or feature involving the Discord integration
- `Integration: Telegram`: Issue or feature involving the Telegram integration
- `Integration: GitHub`: Issue or feature involving GitHub-specific behavior (not the repo itself)
- `Provider: NVIDIA`: Issue or feature specific to NVIDIA inference endpoints or NIM
- `Provider: OpenAI`: Issue or feature specific to OpenAI API or models
- `Provider: Anthropic`: Issue or feature specific to Anthropic / Claude models
- `Provider: Azure`: Issue or feature specific to Azure OpenAI or Azure cloud
- `Provider: AWS`: Issue or feature specific to AWS Bedrock or AWS cloud
- `Provider: GCP`: Issue or feature specific to Google Cloud / Vertex AI

---

## Step 6: Comment Tiers

Items are classified as `quality_tier` or `standard_tier` before generation. This is passed in the item metadata.

- **quality_tier** (influencer author, company-affiliated author, or body > 800 chars): Write 2–3 sentences. Start with "Thanks," then naturally reference specific details from the body. Avoid "I've taken a look at", "I've reviewed", "it appears to", "I can see that" — these sound bot-generated. Write like a human maintainer giving a warm, specific response.
- **standard_tier**: Write 1 sentence acknowledging the report and mentioning the labels applied.

---

## Step 7: Tone Rules (strictly enforced)

- Use "could" not "should"; use "may" not "will" — this is a first response, not a commitment
- Never say "Thanks for fixing" — say "Thanks for the proposed fix" or "Thanks for submitting this"
- Never say "Thanks for adding" — say "Thanks for the suggested addition"
- Never claim the submission accomplishes something before review
- Do not say "I'll" or "we'll"
- For issues (bugs, questions, enhancements): use "this identifies a..." or "this reports a..."
- For PRs: use "this proposes a way to..."
- For security-related items: never confirm a vulnerability is real; use neutral language
- Do NOT open with praise about detail or thoroughness. Only reference the quality of the report if the body is genuinely exceptional — multiple reproduction steps, version info, logs, and clear expected vs actual behavior. For most reports, skip the praise entirely and go straight to the triage acknowledgment.
- Do not add generic closing filler phrases
- If a "Spam signal:" line is present in the item metadata, assign only `status: needs-info` and ask for more detail politely
- If a "Note: Author also opened..." line is present, briefly acknowledge if the relationship is plausible

---

## Related Skills

- `nemoclaw-user-skills-coding` — Agent Skills — all available maintainer and user skills
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "nemoclaw-blueprint/router/llm-router"]
path = nemoclaw-blueprint/router/llm-router
url = https://github.com/NVIDIA-AI-Blueprints/llm-router.git
branch = v3
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,58 @@ Alternatively, send a single message and print the response:
openclaw agent --agent main --local -m "hello" --session-id test
```

### Model Router (Complexity-Based Routing)

NemoClaw includes an optional model router that automatically picks the most efficient model for each query. Instead of sending every request to a single large model, the router uses a lightweight encoder to predict which model in a pool can handle each query correctly, then routes to the cheapest one that meets an accuracy threshold.

The router uses the [NVIDIA LLM Router v3](https://github.com/NVIDIA-AI-Blueprints/llm-router/tree/v3) prefill routing engine and runs on the host as a LiteLLM proxy. The sandbox reaches it through the OpenShell gateway and continues to call `https://inference.local/v1`; do not probe `localhost:4000` or `host.openshell.internal` directly from inside the sandbox.

#### Enable during onboard

Select **Model Router (complexity-based routing)** during the onboard wizard, or set `NEMOCLAW_PROVIDER=routed` for non-interactive mode:

```bash
NEMOCLAW_PROVIDER=routed nemoclaw onboard --non-interactive
```

The onboard wizard starts the router, configures the OpenShell provider, and creates the sandbox. The router process runs on the host on port 4000.

#### Configure the model pool

Edit `nemoclaw-blueprint/router/pool-config.yaml` to define which models the router can choose from:

```yaml
routing:
method: prefill
checkpoint: llm-router/checkpoints/prefill_router_qwen08b.pt
tolerance: 0.20
encoder: Qwen/Qwen3.5-0.8B

models:
- name: nano
litellm_model: "openai/nvidia/nvidia/Nemotron-3-Nano-30B-A3B"
cost_per_m_input_tokens: 0.05
api_base: "https://inference-api.nvidia.com"

- name: super
litellm_model: "openai/nvidia/nvidia/nemotron-3-super-v3"
cost_per_m_input_tokens: 0.10
api_base: "https://inference-api.nvidia.com"
```

The `tolerance` parameter controls the accuracy-cost tradeoff: 0.0 always picks the most accurate model, 1.0 always picks the cheapest, and 0.20 (default) allows up to 20 percentage points below the best for a cheaper model.

#### Architecture

The router runs on the host, not inside the sandbox:

```text
Sandbox (OpenClaw) ──> OpenShell Gateway (L7 proxy) ──> Model Router (:4000) ──> NVIDIA API
└── PrefillRouter selects model
```

Credentials flow through the OpenShell provider system. The sandbox never sees raw API keys.

### Uninstall

To remove NemoClaw and all resources created during setup, run the CLI's built-in uninstall command:
Expand Down Expand Up @@ -197,6 +249,9 @@ NemoClaw/
│ ├── commands/ # Slash commands, migration state
│ └── onboard/ # Onboarding config
├── nemoclaw-blueprint/ # Blueprint YAML and network policies
│ └── router/
│ ├── pool-config.yaml # Model pool and routing config
│ └── llm-router/ # LLM Router v3 submodule (prefill routing engine)
├── scripts/ # Install helpers, setup, automation
├── test/ # Integration and E2E tests
└── docs/ # User-facing docs (Sphinx/MyST)
Expand Down
14 changes: 14 additions & 0 deletions nemoclaw-blueprint/blueprint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ profiles:
- ncp
- nim-local
- vllm
- routed

description: |
NemoClaw blueprint: orchestrates OpenClaw sandbox creation, migration,
Expand Down Expand Up @@ -70,6 +71,19 @@ components:
credential_default: "dummy"
timeout_secs: 180

routed:
provider_type: "openai"
provider_name: "nvidia-router"
endpoint: "http://localhost:4000/v1"
model: "nvidia-routed"
credential_env: "NVIDIA_API_KEY"
timeout_secs: 180

router:
enabled: true
port: 4000
pool_config_path: "router/pool-config.yaml"

policy:
base: "sandboxes/openclaw/policy.yaml"
additions:
Expand Down
1 change: 1 addition & 0 deletions nemoclaw-blueprint/router/llm-router
Submodule llm-router added at 2bd8df
36 changes: 36 additions & 0 deletions nemoclaw-blueprint/router/pool-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Model Router Toolkit pool config for complexity-based routing.
# Uses the NVIDIA LLM Router v3 (https://github.com/NVIDIA-AI-Blueprints/llm-router)
# to route requests to the most efficient model that meets an accuracy threshold.
#
# The router runs an encoder (Qwen3.5-0.8B) on each query to predict P(correct)
# per model, then selects the cheapest model above the tolerance threshold.
#
# tolerance controls the accuracy-cost tradeoff:
# 0.0 = always pick the highest-confidence model (most expensive)
# 0.20 = allow up to 20pp below the best for a cheaper model (default)
# 1.0 = always pick the cheapest model

routing:
method: prefill
checkpoint: llm-router/checkpoints/prefill_router_qwen08b.pt
tolerance: 0.20
encoder: Qwen/Qwen3.5-0.8B
encoder_backend: transformers

models:
- name: gpt-5.2
display_name: "GPT-5.2"
litellm_model: "openai/nvidia/nvidia/Nemotron-3-Nano-30B-A3B"
cost_per_m_input_tokens: 0.05
cost_per_m_output_tokens: 0.20
api_base: "https://inference-api.nvidia.com"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

- name: nemotron-3-super
display_name: "Nemotron 3 Super 120B"
litellm_model: "openai/nvidia/nvidia/nemotron-3-super-v3"
cost_per_m_input_tokens: 0.10
cost_per_m_output_tokens: 0.40
api_base: "https://inference-api.nvidia.com"
69 changes: 69 additions & 0 deletions nemoclaw/src/blueprint/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ function minimalBlueprint(overrides?: Record<string, unknown>): Record<string, u
};
}

function routedBlueprint(): Record<string, unknown> {
return {
version: "1.0",
components: {
inference: {
profiles: {
routed: {
provider_type: "openai",
provider_name: "nvidia-router",
endpoint: "http://localhost:4000/v1",
model: "routed",
credential_env: "NVIDIA_API_KEY",
credential_default: "router-local",
timeout_secs: 180,
},
},
},
sandbox: {
image: "openclaw",
name: "test-sandbox",
forward_ports: [18789],
},
router: {
enabled: true,
port: 4000,
pool_config_path: "router/pool-config.yaml",
},
policy: { additions: {} },
},
};
}

function seedBlueprintFile(bp?: Record<string, unknown>): void {
addFile("blueprint.yaml", YAML.stringify(bp ?? minimalBlueprint()));
}
Expand Down Expand Up @@ -378,6 +410,25 @@ describe("runner", () => {
expect(out).toContain("PROGRESS:10:Validating blueprint");
expect(out).toContain("PROGRESS:100:Plan complete");
});

it("includes router info when router is enabled", async () => {
captureStdout();
mockExeca.mockResolvedValue({ exitCode: 0 });

const plan = await actionPlan("routed", routedBlueprint());
expect(plan.router.enabled).toBe(true);
expect(plan.router.port).toBe(4000);
expect(plan.router.pool_config_path).toBe("router/pool-config.yaml");
});

it("defaults router to disabled when not in blueprint", async () => {
captureStdout();
mockExeca.mockResolvedValue({ exitCode: 0 });

const plan = await actionPlan("default", minimalBlueprint());
expect(plan.router.enabled).toBe(false);
expect(plan.router.port).toBe(4000);
});
});

describe("actionApply", () => {
Expand Down Expand Up @@ -679,6 +730,24 @@ describe("runner", () => {
if (!inferenceCall) throw new Error("inference set call not found");
expect(inferenceCall[1]).not.toContain("--timeout");
});

it("passes endpoint as-is from blueprint (no rewriting)", async () => {
process.env.NVIDIA_API_KEY = "test-key";
try {
await actionApply("routed", routedBlueprint());

const providerCall = mockExeca.mock.calls.find(
(c) => Array.isArray(c[1]) && c[1].includes("provider"),
);
if (!providerCall) throw new Error("provider create call not found");
const configArg = (providerCall[1] as string[]).find((a: string) =>
a.startsWith("OPENAI_BASE_URL="),
);
expect(configArg).toBe("OPENAI_BASE_URL=http://localhost:4000/v1");
} finally {
delete process.env.NVIDIA_API_KEY;
}
});
});

describe("actionStatus", () => {
Expand Down
Loading
Loading