Local-first NemoClaw/OpenShell integration for Jetson AGX Thor (SM110a / Blackwell).
For LLMs (Claude, ChatGPT, agentic IDEs): read
AGENTS.mdbefore making any change. It tells you what this repo is, what it does NOT own (specs live inmanyforge_specs; orchestration lives inmanyforge), and the branch + commit workflow that applies to LLM contributors. Full contributor reference isCONTRIBUTING.md. For the full doc map seeINDEX.md; for plan/report status (live + archived) seePLANS_INDEX.md.
📖 Operator manual: this README is a landing page / quickstart. For the full step-by-step procedure — swap setup, image rebuild, JIT compile expectations, sandbox workflows, cleanup procedure, troubleshooting — see USER_QUICKSTART_MANUAL.md. Additional deep-dive docs: serving/docs/KV-CACHE-BUDGET.md, serving/docs/DFLASH-INVESTIGATION.md, serving/docs/TOOL-EVAL-BENCH-THOR.md, serving/docker/NOTES.md.
⚠ Benchmark methodology: DFlash throughput numbers in this repo were all measured with coding prompts,
enable_thinking: false, temperature ≈ 0.2, and ~1200-token outputs. Running naive prompts with default thinking mode produces ~30 tps on a profile that can do 45+ tps under the intended workload. Always record the drafter SHA (z-lab/Qwen3.6-35B-A3B-DFlashis unpinned, so numbers drift with upstream).
For Qwen3.8-Flash-Next, use the
recommended Thor recipe:
complete pinned image-build steps, local checkpoint setup, and foreground
launcher. It uses fused SM110a GDN CUDA, optimized PLE reads, MTP=3, BF16 KV,
0.92 GPU memory, 256K context and three scheduler slots. The default linked
hybrid keeps routed experts NVFP4 and KV BF16 while converting frequently read
dense side weights to FP8; its initial public sgbench C1 result was 46.7 tok/s
versus 34.8 for the original side weights. Its full 88-scenario Tool Eval
matched the original at 94/100 overall, reached 100/100 on Hard Mode, and
reduced median turn time from 4.3 to 4.1 seconds. A new Thor uses the recipe's build, base
download, Docker-only hybrid-preparation and foreground-start helpers.
This is a dedicated model recipe, not a change to the default ManyForge
profile below.
From scratch, using the default ManyForge assistant profile
(gemma4-12b-it-gguf, Gemma 4 12B GGUF served by llama.cpp):
# Terminal 1: start the default model
cd ~/workspaces/dev_ws/src/NemoClaw-Thor
./serving/start-model.sh
# Terminal 2: wire the sandbox + sanity-check
./setup/configure-local-provider.sh
./setup/status.sh
nemoclaw my-assistant connect # inside sandbox: `openclaw tui`./serving/start-model.sh with no args picks up the default profile
gemma4-12b-it-gguf. This matches the ManyForge launcher default and the
current OpenClaw-lane evidence: 52/66 on the 2026-06-07 model sweep and 51/66
in the 2026-06-09 three-lane head-to-head (75 total / 66 scored). See
manyforge/docs/PHASE-5-PRODUCTION-DECISION.md
for the decision record.
For higher single-stream throughput at the cost of a less-reliable OpenClaw lane, the 35B Qwen variant remains the documented alternative from the PrismaQuant + DFlash-15 benchmark set:
./serving/start-model.sh qwen3.6-35b-a3b-nvfp4-tq-mtp-manyforge
./setup/configure-local-provider.sh qwen3.6-35b-a3b-nvfp4-tq-mtp-manyforgeThe Qwen profile is faster on direct/single-prompt workloads, but default selection is based on the ManyForge assistant smoke corpus, not raw token throughput.
For many concurrent sequences or huge context, use the TQ-MTP variant:
./serving/start-model.sh qwen3.6-35b-a3b-nvfp4-tq-mtp
./setup/configure-local-provider.sh qwen3.6-35b-a3b-nvfp4-tq-mtpTrade-off: 28.6 tok/s single but 2.22M KV tokens, 29× concurrency at 256K
context, and 154.7 tok/s aggregate at 8-concurrent. Requires the
fix-pr39931-turboquant runtime mod (auto-applied). See
Model profiles below for the full comparison.
Prerequisites: 32 GiB swap active, HF token at ~/.cache/huggingface/token
(for the gated DFlash drafter), NemoClaw+OpenShell installed (see
Usage for install commands).
| Component | Pin source |
|---|---|
| NemoClaw, OpenShell CLI, OpenShell cluster, OpenClaw | See VERSIONS.md § A. |
| vLLM image | Custom SM110 build owned by this repo. See VERSIONS.md § B for the canonical image generation and per-pin status; serving/docker/Dockerfile.vllm header carries the per-pin rationale. |
| Sandbox | Created via nemoclaw onboard. Landlock + seccomp + netns. |
| Provider | vllm-local route on the OpenShell gateway. Direct HTTP to host vLLM (:8000); see setup/configure-local-provider.sh. |
| ManyForge integration | See VERSIONS.md § C for the active phase and artifacts. |
Authoritative version reference: VERSIONS.md is the single source of truth across all three scopes. Reproduction commands for the control-plane bring-up live in USER_QUICKSTART_MANUAL.md.
| Script | Purpose |
|---|---|
serving/start-model.sh <profile> |
Launch vLLM with a model profile |
setup/configure-local-provider.sh [OPTIONS] [profile] |
Wire OpenShell provider + patch sandbox config |
setup/status.sh [profile] |
System health checks |
cd ~/workspaces/dev_ws/src/NemoClaw-Thor
# Terminal 1: start the default local model
./serving/start-model.sh # loads gemma4-12b-it-gguf
# Terminal 2: configure and verify
./setup/configure-local-provider.sh # picks up the same default
./setup/status.sh
nemoclaw my-assistant connectPass a profile name to either script to pick a non-default (e.g.
./serving/start-model.sh qwen3.6-35b-a3b-nvfp4-tq-mtp-manyforge for the
35B Qwen profile — slower OpenClaw lane on this model, see comparison doc).
The Composer-assistant lane (openclaw provider, in-sandbox gateway,
manyforge MCP bridge) is the supported user-facing path. Bring it up
end-to-end after the local model server is running:
# 1. Provision the sandbox (idempotent — policy + skill + MCP register +
# agent profile + workspace AGENTS.md). Uses the model that's currently
# being served by vLLM.
./manyforge/setup-manyforge-assistant.sh my-assistant
# 2. Start the OpenClaw assistant bridge on :8200
./manyforge/start-openclaw-assistant-bridge.sh &
# 3. Start Composer in OpenClaw mode (defaults to ASSISTANT_PROVIDER=openclaw)
cd ~/workspaces/dev_ws/src/manyforge
./scripts/demo-assistant-known-good.sh startProduction default = OpenClaw + gemma4-12b-it-gguf. The decision and benchmark data are in manyforge/docs/PHASE-5-PRODUCTION-DECISION.md; the operational runbook is manyforge/docs/COMPOSER-ASSISTANT-RUNBOOK.md; the lane parity debug tooling lives at manyforge/scripts/debug/.
To swap to the direct lane (fast-path for simple prompts; sandboxed bypass — bridge runs its own loop with a tool_choice pin):
# manyforge's unified launcher tears the assistant stack down
# and brings it back up with the new provider.
ASSISTANT_PROVIDER=direct ./scripts/demo-assistant-known-good.sh restartSame sequence: serving/start-model.sh, then setup/configure-local-provider.sh,
then setup/status.sh. To restore the Composer-assistant lane on top of
that, re-run the three steps in the previous block (the provisioner and
bridge are idempotent; Composer's container is recreated by the launcher).
Stop vLLM (Ctrl-C), drop caches, start new model, reconfigure:
sudo sync && sudo sysctl -w vm.drop_caches=3
./serving/start-model.sh <new-profile>
./setup/configure-local-provider.sh <new-profile>Always drop caches between model switches — Thor's unified memory is not automatically freed.
Tok/s columns are single peak / aggregate at N-conc under matched
methodology (coding prompts, enable_thinking: false, temp 0.2, 1200-tok
outputs). Numbers are against the 2026-04-22 drafter main — drafter is
unpinned so these shift with upstream z-lab releases; always re-measure
after a version bump.
| Profile | Tok/s | KV Tokens | Seqs | Spec Method | Notes |
|---|---|---|---|---|---|
qwen3.6-35b-a3b-prismaquant-dflash |
50.7 / 142.4@5 | 938K | 5 | DFlash-15 | Best raw throughput; mixed-precision 4.75 bpp, claimed −0.56 pp vs BF16 |
qwen3.6-35b-a3b-nvfp4-dflash |
44.6 / 140.2@5 | 678K | 5 | DFlash-15 | Uniform NVFP4 (lighter weights, max concurrent seqs can stretch to 8) |
qwen3.6-35b-a3b-fp8-dflash |
47.6 | ~700K | 4 | DFlash-15 | Best FP8 (historical — re-measure) |
qwen3.6-35b-a3b-nvfp4-tq-mtp |
28.6 | 2.22M | 8 | MTP N=4 | MAX CONTEXT, 153 tok/s @ 8-conc |
qwen3.6-35b-a3b-fp8-mtp-fp8kv |
25.7 | 1.44M | 8 | MTP N=4 | FP8+FP8 KV |
qwen3.6-35b-a3b-fp8-turboquant |
26.2 | 1.89M | 6 | MTP N=4 | FP8+TQ KV |
| Profile | Model | Seqs | Notes |
|---|---|---|---|
qwen3.5-9b-claude-distilled-nvfp4 |
9B VLM | 8 | Multimodal, Claude-distilled |
gemma4-e4b-it |
8B MoE | 12 | Vision+text+audio |
gemma4-26b-a4b-it |
26B MoE | 17 | Vision+text, BF16 |
Default profile: gemma4-12b-it-gguf — what
./serving/start-model.sh (no args) loads on a clean config. Gemma 4 12B GGUF
is served by llama.cpp with the E2B speculative draft and is the current
ManyForge assistant default. Footprint is much smaller than the historical
Cosmos/vLLM anchor; persisted THOR_MODEL_PROFILE or an explicit CLI profile
still overrides the clean-start default.
If you need higher single-stream throughput (and don't depend on the OpenClaw lane's reliability — e.g. direct-bridge usage), fall back to the 35B Qwen profile:
./serving/start-model.sh qwen3.6-35b-a3b-nvfp4-dflashIf you can't use NVFP4 at all (no HF token, or prefer FP8 weights), run:
./serving/start-model.sh qwen3.6-35b-a3b-fp8-dflash.
DS4 is an alternative, isolated Docker service rather than a vLLM model
profile. It preserves the active vLLM/ManyForge service on :8000 and serves
the matching 0731 base + DSpark drafter on 127.0.0.1:8050 by default:
./serving/start-ds4.sh start
./serving/start-ds4.sh logsFor a LAN client, bind only Thor's intended LAN address:
DS4_BIND_ADDRESS=192.168.1.136 ./serving/start-ds4.sh startThe first start builds the sm_110 image inside Docker and resumes the model
download into ~/thor-hf-cache/ds4/; later starts reuse the persistent weights
and KV cache. The current tested settings, container files, operations, and
Thor-specific safety limits are in
serving/docs/DS4-ON-THOR.md.
Host (Jetson AGX Thor)
├── Local model server (Docker, --network host, port 8000)
│ └── Model serving: Gemma GGUF default, plus vLLM profiles for bake-offs
├── OpenShell gateway (K3s, openshell-cluster-nemoclaw)
│ ├── L7 proxy (10.200.0.1:3128) — TLS termination, policy enforcement
│ └── Inference route:
│ • direct mode → vllm-local → host:8000
│ • ManyForge default → vllm-local → host:8000; Composer talks to
│ openclaw_assistant_bridge(:8200) or direct bridge(:8100)
│ • legacy mux diagnostics → vllm-local → host:8888
└── Sandbox pod (thor-v5)
├── OpenClaw gateway (port 18789) — agent orchestration
├── OpenClaw agent — LLM-powered task execution
└── Workspace (/sandbox/workspace → /sandbox/.openclaw-data/workspace)
nemoclaw onboard bakes defaults that don't work for our local runtime modes:
| Setting | Onboard default | What we need | Why |
|---|---|---|---|
baseUrl |
https://inference.local/v1 |
Keep https://inference.local/v1 in the sandbox, but repoint the OpenShell provider target to http://host.openshell.internal:8000/v1 by default |
In this build the sandbox/OpenClaw client works through the proxy route; Composer reaches ManyForge through the assistant-provider bridge, not the old mux |
contextWindow |
131072 | 262144 | Models support 256K context |
maxTokens |
4096 | 16384 | Agent needs long outputs for code generation |
timeoutSeconds |
(unset) | 1800 | Long reasoning sessions need 30min timeout |
| Concurrency | (unset) | Per-profile | Matches vLLM max_num_seqs budget |
The setup/configure-local-provider.sh script patches these via kubectl exec into
the sandbox. This bypasses Landlock (kubectl exec starts a new process, not a
child of the sandbox entrypoint) and DAC restrictions (runs as root).
Legacy mux diagnostics can still persist mux state in
~/.config/nemoclaw-thor/config.env, but the current ManyForge assistant path
does not require mux.
The repo produces two independent runtime images (vLLM and TRT-Edge-LLM) plus a production bundle of the vLLM image with baked-in JIT caches.
| Goal | Command | Dockerfile | Output image |
|---|---|---|---|
| Build/rebuild vLLM | cd serving/docker && ./build-vllm.sh |
Dockerfile.vllm |
nemoclaw-thor/vllm:<tag> + :latest |
| Build/rebuild TRT-Edge-LLM | cd serving/docker && ./build-trt.sh |
Dockerfile.trt |
nemoclaw-thor/trt-edge-llm:<tag> + :latest |
| Build vLLM production bundle | cd serving/docker && ./bundle.sh |
Dockerfile.bundle |
nemoclaw-thor/vllm:<tag>-bundled |
| Add a package without full rebuild | cd serving/docker && docker build -f Dockerfile.overlay -t nemoclaw-thor/vllm:latest . |
Dockerfile.overlay |
overrides :latest |
| Build DS4 alternative service | ./serving/start-ds4.sh build |
serving/docker/Dockerfile.ds4 |
nemoclaw-thor/ds4:v0.5.6.2-sm110-thor |
Each build-*.sh accepts --help for arg reference. Both vLLM and TRT
builds share apt cache (id=apt-cache-thor) and pip cache mounts so package
downloads done by either build are reused by the other on subsequent runs.
vLLM and TRT-Edge-LLM images are independent — no inheritance — so you can delete or rebuild either without affecting the other. They co-exist on disk fine; the host filesystem deduplicates layers where possible.
For the runtime tradeoff between vLLM and TRT-Edge-LLM (memory, throughput,
which model classes work best with which runtime), see serving/docs/PERFORMANCE-V7.md.
NemoClaw-Thor/
├── README.md # This file (entrypoint)
├── AGENTS.md # Agent/LLM instructions for working in this repo
├── VERSIONS.md # Single source of truth for current versions across all scopes
├── USER_QUICKSTART_MANUAL.md # Operator quickstart
│
├── setup/ # Scope A: NemoClaw / OpenShell / OpenClaw control plane
│ ├── configure-local-provider.sh # OpenShell provider + sandbox config sync
│ ├── status.sh # System health checks
│ ├── checks.sh # Diagnostic check functions (sourced)
│ ├── sandbox-runtime.sh # sync_sandbox_runtime_config(), sandbox helpers
│ ├── policies/ # Egress policy presets (dynamic/, static/)
│ └── NEMOCLAW-OPENCLAW-WORKFLOW.md
│
├── serving/ # Scope B: vLLM model serving + benchmarks
│ ├── start-model.sh # vLLM launcher (picks profile, mounts caches)
│ ├── start-duo.sh # Two-model co-serving (Qwen3.6 + Cosmos-2B)
│ ├── config.sh # Model profiles, concurrency math, runtime config
│ ├── launch.sh # Docker run logic, cache mounts, env vars
│ ├── docker/ # Image builds: vLLM, TRT-Edge-LLM, bundle, overlay
│ ├── benchmarks/ # Loose perf probes (bench-throughput.py, etc.)
│ ├── agentic-bench/ # lm-eval-harness wrappers + tool-call proxy
│ ├── templates/ # Tokenizer chat templates (qwen3 tool-call jinjas)
│ ├── scripts/ # Model-serving prep scripts (patch-minimax-w4a16-config.sh)
│ └── docs/ # KV-CACHE-BUDGET, PERFORMANCE-V*, TOOL-EVAL-BENCH-THOR,
│ # *-INVESTIGATION.md, COSMOS-REASON2-32B-QUANTIZATION
│
└── manyforge/ # Scope C: ManyForge ↔ NemoClaw integration
├── setup-manyforge-assistant.sh# Idempotent provisioner (skill + preset + MCP register)
├── policies/ # manyforge-composer.preset.yaml (egress preset)
├── bridge/ # Audit-log mount point (bridge service lives in manyforge repo)
└── docs/ # MANYFORGE-MCP-INTEGRATION, -ASSISTANT-DEPLOYMENT-PLAN,
# -PROFILE-CALIBRATION
- NemoClaw — sandbox framework
- OpenShell — container orchestration
- OpenClaw — agent runtime
- Historical v5 transition notes now live in git history.
- serving/docs/DFLASH-INVESTIGATION.md — DFlash speculative decoding investigation and results
- Current version: see
VERSION(single line, SemVer). Per-component pins (vLLM container, FlashInfer, NemoClaw CLI, OpenClaw, model profiles) live inVERSIONS.mdand move on external upstream releases, independent of the repo SemVer. - What changed:
CHANGELOG.md(Keep-a-Changelog format). - What's next:
ROADMAP.md. - License: MIT — see
LICENSE. Upstream NemoClaw has its own license; NemoClaw-Thor is a sibling project, not a fork. - Reporting a security vulnerability: see
SECURITY.md. Do NOT use public issues for security topics.
Both human contributors and LLM agents are first-class. The branch
workflow (main = released, dev = integration where work lands,
squash-merge dev → main on release) and the LLM-specific commit/push
rules are documented in CONTRIBUTING.md and
reinforced in AGENTS.md. Special rule for this repo:
model-profile changes in serving/config.sh require operator approval
and a smoke-corpus retest before merge.
Compatible with manyforge 0.1.x for the assistant lane — see the companion repo at https://github.com/pastoriomarco/manyforge.
Originally forked from jetsonhacks/NemoClaw-Thor
in March 2026. That project has since been archived (2026-04-17), and this
repository's scope has grown well beyond the original installer-script set —
adding a vLLM container build pipeline, an agentic evaluation harness, and the
ManyForge integration layer. As of May 2026 the fork relationship has been
detached on GitHub; this is now an independent project. Credit to the
original maintainer for the initial Thor onboarding scaffolding that made the
first weeks easier.