Generated banner for Tao Run Inference Service
Generated logo for Tao Run Inference Service

Tao Run Inference Service

NVIDIAOfficialCreated by NVIDIA Corporation

  • Skill
  • Workflow
  • Data and AI
  • Apache-2.0
  • Medium risk

From the author

Start, query, and stop a network-specific TAO inference microservice ({network_arch}-inference-microservice) by delegating container execution to the appropriate platform skill. Handles container image resolution, job-payload JSON construction, and the service registry. Use when the user wants to run inference on a TAO model checkpoint using a microservice container, deploy a TAO inference endpoint, or stop a running inference container.
— the description by NVIDIA Corporation

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The tao-run-inference-service skill provides a standardized workflow for deploying, querying, and terminating TAO model inference microservices. It abstracts platform-specific container management (local-docker, brev, slurm, kubernetes) while handling image resolution, service registry maintenance, and payload construction. The skill ensures consistent interaction with inference endpoints by enforcing strict readiness checks, explicit sampling parameter collection, and secure environment variable handling for model access.

What it does

  • Starts inference microservices for specific TAO network architectures.
  • Manages a local service registry to track active jobs and endpoints.
  • Handles platform-specific container execution and port allocation.
  • Facilitates inference requests with user-defined sampling parameters.
  • Provides a clean shutdown process for running inference containers.
  • Validates model paths and environment configurations before deployment.

When to use it

  • When you need to deploy a TAO model checkpoint as a reachable inference endpoint.
  • When you want to run inference against a model using a consistent API format.
  • When managing multiple concurrent inference services across different compute environments.
  • When you need to stop a specific inference container and clean up its registry entry.

Advantages

  • Platform-agnostic workflow for inference deployment.
  • Prevents port conflicts through automated allocation.
  • Ensures secure handling of API keys and tokens via environment variables.
  • Standardizes request structures across different model architectures.

Usage examples

“Start a cosmos-rl inference service using my local docker.”
The agent resolves the container image, assigns a unique port, starts the container, and registers the service in the local state file.
“Send a request to the cosmos-rl service with temperature 0.7.”
The agent identifies the active cosmos-rl job, prompts for remaining sampling parameters, constructs the JSON payload, and sends the request to the endpoint.
“Stop the inference service with job ID 550e8400-e29b.”
The agent retrieves the service details from the registry, executes the platform-specific stop command, and removes the entry from the registry.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/nvidia/tao-run-inference-service/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip tao-run-inference-service.zip -d .agents/skills

Documentation by NVIDIA Corporation

TAO Inference Microservice

Standalone install? If this session was not initialized by the TAO skill bank plugin, run the tao-setup skill first (host preflight, credentials, cross-skill discovery).

Instructions

To start an inference service:

  1. Collect required inputs (Section 1) and resolve the container image (Section 2).

  2. Build the job payload and inner command (Sections 3–4.1); use references/code-templates.yaml → job_payload_builder.

  3. Read skills/platform/<platform>/SKILL.md and start the container (Section 4.2).

  4. Write the service registry and poll readiness (Section 4.3); use references/code-templates.yaml → registry_write.<platform> and readiness_check.

To send an inference request:

  1. Resolve which service receives the request per Section 6.0 (by job_id, by network_arch, or by explicit user choice when multiple services run — never silently default to "latest" when more than one service exists), then read the endpoint from references/code-templates.yaml → request.registry_read with the resolved job_id.

  2. Before building the request body, prompt the user for the vLLM-style sampling parameters (Section 6.1). Present max_tokens, top_p, temperature (and any per-arch extras) with their defaults; let the user override or skip each one to accept the default. Never silently use defaults.

  3. Build and send the body per Section 6.2; handle the response per Section 6.3.

To stop a service: Read references/code-templates.yaml → stop.registry_read to resolve the job_id, read skills/platform/<platform>/SKILL.md, then follow Section 5.

Reference data (schemas, mappings, valid values — no instructions):

  • references/service.yaml — image mappings, valid network_arch names, job payload schema, env var names, secrets classification.

  • references/request.yaml — endpoint definition, request field schema, response shapes, code examples.

  • references/code-templates.yaml — Python templates for payload building, registry writes, readiness checks, and stop/request flows.


Secrets rule (applies to every generated code block in this skill)

Never ask the user to type a secret value into a prompt. For every secret value:

  1. Tell the user which environment variable to set — export HF_TOKEN=... in their shell, or a KEY=value line in a user-approved env file loaded with set -a; source /path/to/.env; set +a.

  2. Never print, cat, or Read such a file — verify presence only: [ -n "$HF_TOKEN" ] && echo SET || echo UNSET.

  3. Generate code that reads it with os.environ["VAR_NAME"] — never hard-code, interpolate, or prompt for the value.

Secret env vars (full list in references/service.yaml → secrets_handling): HF_TOKEN, WANDB_API_KEY, CLEARML_API_ACCESS_KEY, CLEARML_API_SECRET_KEY, TAO_API_KEY, TAO_USER_KEY.

Safe to collect in the prompt: network_arch, model_path, num_gpus, prompt text, WANDB_* config URLs, CLEARML_*_HOST URLs.


1. What to collect from the user

InputRole
network_archChooses container image, the per-arch inner command shape (references/service.yaml → container_commands.<network_arch>), and neural_network_name in the job JSON when applicable. Must match a basename in valid_network_arch_config_basenames in references/service.yaml (e.g. cosmos-rl, cosmos-predict2.5).
model_pathThe trained model checkpoint. Valid forms: hf_model://<org>/<model> (HuggingFace Hub — set HF_TOKEN for gated models) or a local container filesystem path. Cloud URIs (s3://, gs://, az://) are NOT supported — the inference service has no cloud-storage dependency. Always ask the user; never substitute a placeholder. See references/service.yaml → model_path_protocols.
platformCompute platform: local-docker, brev, slurm, or kubernetes.
num_gpusDefaults to 1; minimum 1 for inference.

2. Image resolution

Each network_arch has a sidecar config file named {network_arch}.config.json. Resolve the container image as follows:

  1. Read {network_arch}.config.json and take api_params.image (e.g. COSMOS_RL). This selects an entry from docker_image_defaults in references/service.yaml.

  2. If the host env var IMAGE_<KEY> is set (e.g. IMAGE_COSMOS_RL), it overrides the packaged default.

  3. For a key under model_skill_defaults, call scripts/resolve_tao_image.py with its model, action, and backend. This keeps the exact Cosmos image in the Cosmos model skill's references/skill_info.yaml.

  4. For a key under mapping, resolve the dotted value against the repo-root versions.yaml with scripts/resolve_versions_key.py. Absolute environment overrides pass through unchanged. The Python examples live in references/code-templates.yaml.

  5. If the config file is missing or api_params.image is empty, fall back to the COSMOS_RL key.

The config file also has spec_params.inference.model_path which drives folder vs file path semantics: if the value contains the substring folder, the container treats the path as a directory.


3. Environment variables (no callbacks)

Set these in env_payload before encoding env_json. Do not set TAO_LOGGING_SERVER_URL or TAO_ADMIN_KEY.

TAO_EXECUTION_BACKEND — must match the platform:

PlatformTAO_EXECUTION_BACKEND value
local-dockerlocal-docker
brevlocal-docker
slurmslurm
kuberneteslocal-k8s

CLOUD_BASED — always "False" for this skill (disables callback posting to TAO_LOGGING_SERVER_URL).

GPU env vars — only needed when the platform skill does not handle GPU injection automatically:

  • Tegra / Jetson: --runtime=nvidia with NVIDIA_DRIVER_CAPABILITIES=all and NVIDIA_VISIBLE_DEVICES=<ids>.

  • Standard x86 + nvidia-container-toolkit: use Docker device_requests. The platform skill handles this.


4. Executing across platforms

The job payload and inner command (Sections 1–3) are platform-agnostic. For each platform, read skills/platform/<name>/SKILL.md for preflight checks and credentials before generating any execution code.

4.1 Build the inner command (per arch)

The inner-command shape is per network_arch — there is no uniform template. Look up the per-arch entry in references/service.yaml → container_commands.<network_arch>; if not present, the arch is unsupported — stop and ask. Pick the matching sub-block in references/code-templates.yaml → job_payload_builder.<network_arch>. Prefix the command with umask 0 && and keep it identical across platforms (local-docker, brev, slurm, kubernetes).

Common across arches:

  • job_id: fresh uuid.uuid4() — becomes the container name and registry key.

  • image: resolve per Section 2.

  • Secrets (access_key, secret_key, HF_TOKEN, etc.) are read from env vars at runtime — never hard-code, never log or print.

Arch-specific notes (full details in references/service.yaml → container_commands):

  • cosmos-rl — single --job '<JOB_JSON>' --docker_env_vars '<ENV_JSON>' blob; json.dumps(...) + shlex.quote(...). env_payload carries TAO_EXECUTION_BACKEND (per Section 3 table), TAO_API_JOB_ID, CLOUD_BASED=False. The inference service has no cloud-storage dependency; HF_TOKEN is the only cred env var that ever applies (for gated HuggingFace models).

  • cosmos-predict2.5 — flag-style cosmos_predict inference_microservice start ... --port 8080 (no setup. prefix; uses tyro.conf.OmitArgPrefixes). --job/--docker_env_vars are not accepted. Translate model_path to --checkpoint-path (local path) or --model <registered_key> (hf_model://); cloud URIs are rejected. The only cred env var that ever applies is HF_TOKEN for gated HuggingFace models. Per-request params (prompt, inference_type, num_output_frames, guidance, seed, num_steps, negative_prompt) go in the request body, not at startup. TAO_EXECUTION_BACKEND/TAO_API_JOB_ID/CLOUD_BASED are unused and may be omitted.

4.2 Delegate execution to the platform skill

Read skills/platform/<platform>/SKILL.md and follow it to start the container.

Base parameters (all platforms):

ParameterValue
imageresolved container image (Section 2)
commandinner — the shell string built in Section 4.1
gpu_countnum_gpus
env_varsenv_payload
job / container namejob_id — must equal the UUID from 4.1 so the registry can reference it
host_port (local-docker, brev)host-side port to bind to container port 8080. Default 8080, but must be unique per concurrent service — see the port-allocation rule below.

Platform-specific additional inputs:

PlatformAdditional inputs
local-dockerNone beyond base
brevinstance_id (optional — reuse an existing instance); on multi-credential / multi-workspace accounts also cloud_cred_id and workspace_group_id for first-create — see skills/platform/tao-run-on-brev/SKILL.md
slurmpartition and account — check SLURM_PARTITION/SLURM_ACCOUNT env vars; ask user if unset
kubernetesnamespace (default: default); image_pull_secret (required for nvcr.io images)

Port binding (local-docker and brev): use direct docker run so that -p <host_port>:8080 can be passed and the container name equals job_id exactly.

Port allocation rule (local-docker and brev, REQUIRED for concurrent services): Before starting a service, read the registry (/tmp/tao-inf-ms-state.json) and collect the set of host_port values from every existing entry on the same platform (and, for brev, the same instance_id). Pick the lowest free port starting from 8080 that is not in that set — e.g. host_port = next(p for p in range(8080, 8200) if p not in used_ports). The default 8080 only applies when no other service is running. This is what makes "start 3 services, each reachable at a distinct host_url" work; without it, services 2 and 3 fail with bind: address already in use. SLURM and kubernetes get distinct endpoints from their own platform mechanisms and do not need this step.

4.3 After start: service registry and endpoint

Write the service registry immediately after the platform confirms the container is running. The registry (/tmp/tao-inf-ms-state.json) is keyed by job_id; "latest" always points to the most recently started service.

See references/code-templates.yaml → registry_write.<platform> for the Python template.

Platformhost_urlplatform_job_idExtra step before writing
local-dockerhttp://localhost:{host_port}—None
brevhttp://{brev_ip}:{host_port}—brev ls → get instance IP (localhost is invalid on remote VM)
slurmhttp://localhost:{host_port}SLURM scheduler job IDWait until Running; SSH port-forward localhost:{host_port}→{node}:8080
kuberneteshttp://{external_ip}:8080k8s job namekubectl expose job … --type=LoadBalancer; wait for external IP

After writing the registry, print the job_id and URL:

print(f"Inference service started.")
print(f"  Job ID : {job_id}")
print(f"  Arch   : {network_arch}")
print(f"  URL    : {state[job_id]['host_url']}/v1/chat/completions")
print(f"Use this Job ID to send requests or stop the service.")

Then poll for readiness — see references/code-templates.yaml → readiness_check. The container loads the model in the background; do not send requests before it returns 200.


5. Stopping the inference service

Ask the user for the job_id to stop. If they don't provide one, default to state["latest"] and confirm which job_id is being stopped. Read the registry using references/code-templates.yaml → stop.registry_read, then read skills/platform/<platform>/SKILL.md and use its cancellation / stop mechanism.

PlatformIdentifier to passExtra cleanup
local-dockerjob_id_to_stop — container nameNone
brevjob_id_to_stop — container nameNone
slurmentry["platform_job_id"] — SLURM job IDpkill -f "ssh.*-L.*{entry['host_port']}"
kubernetesentry["platform_job_id"] — k8s job namekubectl delete svc {entry["platform_job_id"]} -n <namespace>

where entry = state[job_id_to_stop]. After stopping, clean up the registry: references/code-templates.yaml → stop.registry_cleanup.


6. Sending inference requests

6.0 Resolve which service receives this request (REQUIRED)

Each request must be routed to the specific service that runs the matching model. Routing happens by job_id — the registry stores network_arch per entry, so you can resolve a target by arch when the user names a model instead of a job_id. Apply these rules in order:

  1. User provided an explicit job_id → use it. Verify it exists in state.

  2. User named a network_arch (e.g. "send this to the cosmos-rl service") → look up matching entries: candidates = [j for j, e in state.items() if j != "latest" and isinstance(e, dict) and e["network_arch"] == arch].

    • Exactly one match → use it.

    • Multiple matches → prompt the user with the candidate job_ids and their started_at; do not auto-pick.

    • No match → stop and tell the user no service for that arch is running.

  3. No job_id and no network_arch → count non-"latest" entries in state:

    • Exactly one running service → use it.

    • Two or more → do not silently default to state["latest"]. Prompt the user with the full list (job_id, network_arch, host_url) and require an explicit choice. The "latest" pointer is a convenience for single-service workflows, not a routing fallback when multiple services coexist.

    • Zero → stop and tell the user to start a service first.

After resolving, read the endpoint from the registry (references/code-templates.yaml → request.registry_read), passing the resolved job_id as user_provided_job_id. Confirm to the user: "Sending to job_id=… arch=… url=…". If the service may still be loading, poll readiness first (references/code-templates.yaml → readiness_check).

Cross-check before sending: if the user-supplied request body contains arch-specific fields (e.g. guidance / num_steps / seed / negative_prompt → cosmos-predict2.5; required image_url/video_url content items → cosmos-rl), verify they are consistent with state[job_id]["network_arch"]. On mismatch, stop and ask — sending a cosmos-predict2.5 body to a cosmos-rl service will fail at the container with a 4xx/5xx that is harder to diagnose than catching it here.

6.1 Sampling parameters — REQUIRED user prompt before each request

Before constructing the request body, you MUST explicitly prompt the user for the vLLM-style sampling parameters. Do not silently apply defaults. Use a structured prompt, one question per field, that:

  1. Lists every applicable field with its type and default value.

  2. Lets the user skip / accept any field to take that field's default — entering a value is never required.

  3. Collects all fields in one round.

After the prompt, apply each user-entered value verbatim and substitute the default for any skipped field. Do not invent values or silently clamp.

Field list, defaults, and per-arch applicability: references/request.yaml → chat_completions_request_body (base sampling fields: max_tokens, top_p, temperature) and network_arch_constraints.<network_arch> (per-arch overrides and extras such as guidance/num_steps/seed/negative_prompt for cosmos-predict2.5). If a field is marked unsupported for the active arch, do not prompt for it and do not include it in the body.

6.2 Request format

Send a POST to {BASE_URL}/v1/chat/completions with Content-Type: application/json and a timeout of at least 300 s. The body is OpenAI-compatible (vLLM chat completions); see references/request.yaml → chat_completions_request_body for the full field schema and content-item shapes (text / image_url / video_url), and code_examples for ready-to-run Python and curl samples.

Constraints: only the first user message is processed. No secret values in request bodies. Per-network constraints (e.g. cosmos-rl requires every request to include an image or video; cosmos-rl rejects data: URIs) are in references/request.yaml → network_arch_constraints.

6.3 Response handling
HTTP statusMeaningAction
200Success — choices[0].message.content has the generated textRead result
202Server still initializing or model still loadingRetry after a delay
503Initialization failed, model load failed, or model not yet readyInspect error.type: model_not_ready → retry; initialization_error / model_load_error → give up and check logs
400Missing or empty JSON bodyFix request
500Unhandled exception during inferenceCheck container logs

For 202 and 503, the body contains {"error": {"type": "<error_type>", "message": "<reason>"}}. See container_response_shapes in references/request.yaml for error type strings.

Files

references/code-templates.yaml

# Code templates for the TAO Inference Microservice skill (SKILL.md).
# These are reference implementations — not instructions.
# SKILL.md sections 6.1, 6.3, 7, and 8 point here for Python code.

job_payload_builder:
  description: >
    Build the inner shell command and any env_payload entries needed for
    docker run. The shape of the command is per-network_arch — there is no
    generic builder. Pick the block matching the user's network_arch.
    Secret access_key and secret_key are read from env vars at runtime —
    never hard-code.

  cosmos-rl:
    description: >
      cosmos-rl consumes a single --job '<JSON>' blob plus --docker_env_vars
      '<JSON>'. The inference container does NOT depend on cloud storage —
      model weights come from the HuggingFace Hub or a local filesystem path,
      and request inputs must be sent inline (data: URIs) or via http(s) URLs.
    python: |
      import json, uuid, shlex

      job_id       = str(uuid.uuid4())
      network_arch = "cosmos-rl"

      # model_path: HuggingFace repo id (e.g. "nvidia/Cosmos-Reason1-7B"),
      # an hf_model://<org>/<model> URI, or a local container path.
      # Cloud URIs (s3://, gs://, az://) are NOT supported by the inference
      # service. See references/service.yaml → model_path_protocols.
      model_path = "hf_model://nvidia/Cosmos-Reason1-7B"

      job_payload = {
          "job_id":              job_id,
          "specs": {
              "model_path":  model_path,
          },
          "neural_network_name": network_arch,
      }

      env_payload = {
          "TAO_EXECUTION_BACKEND": "local-docker",  # replace with platform value — see Section 4 table
          "TAO_API_JOB_ID":        job_id,
          "CLOUD_BASED":           "False",
      }

      job_json = json.dumps(job_payload, separators=(",", ":"))
      env_json = json.dumps(env_payload, separators=(",", ":"))
      inner    = (
          f"umask 0 && cosmos-rl-inference-microservice "
          f"--job {shlex.quote(job_json)} --docker_env_vars {shlex.quote(env_json)}"
      )

      # Resolve Cosmos-RL through the model skill's backend metadata. No TAO SDK.
      import os, subprocess
      image = os.environ.get("IMAGE_COSMOS_RL")
      if not image:
          resolved = subprocess.check_output(
              [f"{os.environ['TAO_SKILL_BANK_PATH']}/scripts/resolve_tao_image.py",
               "--skill-bank", os.environ["TAO_SKILL_BANK_PATH"],
               "--model", "nvidia/Cosmos3-Nano",
               "--action", "inference_microservice",
               "--backend", "cosmos-rl",
               "--format", "json"],
              text=True,
          )
          image = json.loads(resolved)["image"]

  cosmos-predict2.5:
    description: >
      cosmos-predict2.5 uses flag-style `cosmos_predict inference_microservice start
      --<flag> --port` (no `setup.` prefix; CLI uses tyro.conf.OmitArgPrefixes).
      It does NOT accept --job / --docker_env_vars. The inference container does
      NOT depend on cloud storage — model weights come from the HuggingFace Hub
      (via the registered MODEL_KEYS) or a local container path.
      Per-request params (prompt, inference_type, num_output_frames, guidance,
      seed, num_steps, negative_prompt) are sent in the chat-completions
      request body, NOT at startup.
    python: |
      import shlex, uuid

      job_id       = str(uuid.uuid4())
      network_arch = "cosmos-predict2.5"

      # Collect from user via prompt
      model_path = "hf_model://nvidia/Cosmos-Predict2.5-2B"   # or /local/path
      output_dir = "/tmp/cosmos-predict2.5-output"            # local path inside container

      # Translate model_path → checkpoint flag (see references/service.yaml →
      # container_commands.cosmos-predict2.5.placeholders.CHECKPOINT_FLAG).
      if model_path.startswith("hf_model://"):
          # hf_model://<org>/<model> — cosmos-predict2.5 doesn't accept this URI directly.
          # Map to a registered model key (see cosmos_predict2.config.MODEL_KEYS).
          # Ask the user for the registered key (e.g. "base-2b", "base-14b") —
          # don't try to derive it from the URI.
          registered_model_key = "base-2b"   # replace with user-provided value
          checkpoint_flag = f"--model {shlex.quote(registered_model_key)}"
      elif model_path.startswith("/"):
          checkpoint_flag = f"--checkpoint-path {shlex.quote(model_path)}"
      else:
          raise ValueError(f"Unsupported model_path scheme for cosmos-predict2.5: {model_path}")

      inner = (
          "umask 0 && "
          "cosmos_predict inference_microservice start "
          f"{checkpoint_flag} "
          f"--output-dir {shlex.quote(output_dir)} "
          "--port 8080"
      )

      # env_vars passed at docker run. HF_TOKEN is required for gated HF models;
      # follow the SKILL.md "Secrets rule": read from the named env var at runtime.
      env_payload = {}
      if model_path.startswith("hf_model://"):
          env_payload["HF_TOKEN"] = "<HF_TOKEN env var>"   # export HF_TOKEN=...

      # Resolve image per Section 2 (versions.yaml dotted key → concrete URI). No TAO SDK.
      import os, subprocess
      image = os.environ.get("IMAGE_COSMOS_PREDICT2_5") or subprocess.check_output(
          [f"{os.environ['TAO_SKILL_BANK_PATH']}/scripts/resolve_versions_key.py",
           "images.tao_toolkit.cosmos_predict"], text=True
      ).strip()

  tao-dataservices:
    description: >
      tao-dataservices uses the same --job '<JSON>' --docker_env_vars '<JSON>' shape
      as cosmos-rl, but with a different executable: the HuggingFace inference
      microservice from tao-core. The inference container does NOT depend on
      cloud storage — model weights come from the HuggingFace Hub or a local
      filesystem path; request inputs must be sent inline (data: URIs) or via
      http(s) URLs.
    python: |
      import json, uuid, shlex

      job_id       = str(uuid.uuid4())
      network_arch = "tao-dataservices"

      # model_path: HuggingFace repo id (e.g. "Qwen/Qwen3-VL-8B-Instruct"),
      # an hf_model://<org>/<model> URI, or a local container path. Cloud URIs
      # (s3://, gs://, az://) are NOT supported by the inference service.
      model_path = "hf_model://Qwen/Qwen3-VL-8B-Instruct"

      job_payload = {
          "job_id":              job_id,
          "specs": {
              "model_path":  model_path,
          },
          "neural_network_name": network_arch,
      }

      env_payload = {
          "TAO_EXECUTION_BACKEND": "local-docker",  # replace with platform value — see Section 4 table
          "TAO_API_JOB_ID":        job_id,
          "CLOUD_BASED":           "False",
      }

      job_json = json.dumps(job_payload, separators=(",", ":"))
      env_json = json.dumps(env_payload, separators=(",", ":"))
      inner    = (
          "umask 0 && "
          "python -m nvidia_tao_core.microservices.handlers.huggingface_inference_microservice_server "
          f"--port 8080 "
          f"--job {shlex.quote(job_json)} --docker_env_vars {shlex.quote(env_json)}"
      )

      # Resolve image per Section 2 (versions.yaml dotted key → concrete URI). No TAO SDK.
      import os, subprocess
      image = os.environ.get("IMAGE_TAO_DATASERVICES") or subprocess.check_output(
          [f"{os.environ['TAO_SKILL_BANK_PATH']}/scripts/resolve_versions_key.py",
           "images.tao_toolkit.data_services"], text=True
      ).strip()

registry_write:
  description: >
    Write the service registry entry after the platform confirms the container is running.
    Load the existing state first (shared preamble), then apply the platform-specific block,
    then write atomically.
  shared_preamble: |
    import json, os
    from datetime import datetime, timezone

    STATE_FILE = "/tmp/tao-inf-ms-state.json"

    try:
        with open(STATE_FILE) as f:
            state = json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        state = {}

  atomic_write: |
    # Call this after setting state[job_id] and state["latest"]
    tmp = STATE_FILE + ".tmp"
    with open(tmp, "w") as f:
        json.dump(state, f, indent=2)
    os.replace(tmp, STATE_FILE)

  local_docker:
    python: |
      state[job_id] = {
          "job_id":       job_id,
          "platform":     "local-docker",
          "network_arch": network_arch,
          "host_url":     f"http://localhost:{host_port}",
          "docker_url":   f"http://{job_id}:8080",   # container-to-container on tao_default network
          "host_port":    host_port,
          "started_at":   datetime.now(timezone.utc).isoformat(),
      }
      state["latest"] = job_id

  brev:
    note: >
      brev_instance_ip comes from `brev ls` after the platform skill starts the container.
      localhost is not valid — Brev instances are remote VMs.
    python: |
      brev_instance_ip = "<hostname or IP from brev ls>"
      state[job_id] = {
          "job_id":       job_id,
          "platform":     "brev",
          "network_arch": network_arch,
          "host_url":     f"http://{brev_instance_ip}:{host_port}",
          "docker_url":   f"http://{job_id}:8080",   # container-to-container within the instance
          "host_port":    host_port,
          "started_at":   datetime.now(timezone.utc).isoformat(),
      }
      state["latest"] = job_id

  slurm:
    note: >
      After Running, find the allocated node via squeue and open an SSH port-forward
      so localhost:{host_port} reaches the container on the cluster node.
    python: |
      import subprocess, time, os

      # slurm_job_id is the scheduler id the tao-run-on-slurm platform skill
      # returned when it submitted the container (via ssh + sbatch). No TAO SDK.
      slurm_job_id = "<slurm_job_id from tao-run-on-slurm start step>"
      login = f"{os.environ['SLURM_USER']}@{os.environ['SLURM_HOSTNAME']}"

      # Poll scheduler state over SSH (sacct/squeue), the same SDK-free mechanism
      # tao-run-on-slurm uses. Treat RUNNING/COMPLETING as ready to port-forward.
      while True:
          status = subprocess.check_output(
              ["ssh", login, f"sacct -j {slurm_job_id} -X -n -o State"], text=True
          ).strip().split()[0] if slurm_job_id else ""
          if status in ("RUNNING", "COMPLETING"):
              break
          if status in ("FAILED", "CANCELLED", "TIMEOUT", "NODE_FAIL"):
              raise RuntimeError(f"SLURM job failed: {status}")
          time.sleep(15)

      node = subprocess.check_output(
          ["ssh", login, f"squeue -j {slurm_job_id} -h -o '%N'"],
          text=True
      ).strip()

      subprocess.Popen([
          "ssh", "-N", "-L", f"{host_port}:{node}:8080", login
      ])
      print(f"Port-forward: localhost:{host_port} -> {node}:8080 via SLURM login node")

      state[job_id] = {
          "job_id":          job_id,
          "platform":        "slurm",
          "platform_job_id": slurm_job_id,
          "network_arch":    network_arch,
          "host_url":        f"http://localhost:{host_port}",
          "slurm_node":      node,
          "started_at":      datetime.now(timezone.utc).isoformat(),
      }
      state["latest"] = job_id

  kubernetes:
    note: >
      Expose the job via a LoadBalancer Service, then wait for the external IP.
    python: |
      import subprocess, time

      job_name = subprocess.check_output(
          ["kubectl", "get", "jobs", "-n", namespace, "-o",
           f"jsonpath={{.items[?(@.metadata.annotations.tao-job-id=='{job_id}')].metadata.name}}"],
          text=True
      ).strip() or f"tao-job-{job_id}"

      subprocess.run([
          "kubectl", "expose", "job", job_name,
          "--port=8080", "--target-port=8080", "--type=LoadBalancer",
          f"--namespace={namespace}"
      ], check=True)

      external_ip = ""
      for _ in range(24):  # up to ~4 min
          external_ip = subprocess.check_output(
              ["kubectl", "get", "svc", job_name, "-n", namespace,
               "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}"],
              text=True
          ).strip()
          if external_ip:
              break
          time.sleep(10)

      if not external_ip:
          raise RuntimeError("LoadBalancer IP not assigned. Try NodePort, or check cluster ingress.")

      state[job_id] = {
          "job_id":          job_id,
          "platform":        "kubernetes",
          "platform_job_id": job_name,
          "network_arch":    network_arch,
          "host_url":        f"http://{external_ip}:8080",
          "started_at":      datetime.now(timezone.utc).isoformat(),
      }
      state["latest"] = job_id

readiness_check:
  description: >
    Poll GET /api/v1/health/readiness until 200 (ready), or until the body
    reports a hard-failure error.type. Used at startup (Section 6.3) and
    optionally before each request (Section 8.2). BASE_URL = state[job_id]["host_url"].

    HTTP 503 is returned during normal initialization (model download / load),
    so the status code alone is not a failure signal — inspect body.error.type:
      retry-while-initializing : server_initializing, model_loading, model_not_ready
      hard-fail (give up)      : initialization_error, model_load_error
  python: |
    import time, requests

    RETRY_TYPES    = {"server_initializing", "model_loading", "model_not_ready"}
    HARD_FAIL_TYPES = {"initialization_error", "model_load_error"}

    def wait_for_ready(base_url, timeout=600, interval=10):
        deadline = time.time() + timeout
        while time.time() < deadline:
            try:
                r = requests.get(f"{base_url}/api/v1/health/readiness", timeout=5)
                if r.status_code == 200:
                    print("Service is ready.")
                    return True
                try:
                    err_type = r.json().get("error", {}).get("type", "")
                except ValueError:
                    err_type = ""
                if err_type in HARD_FAIL_TYPES:
                    raise RuntimeError(f"Service initialization failed: {err_type}")
                # 202 + 503/model_not_ready and unknown shapes — keep polling
                print(f"Not ready (HTTP {r.status_code} {err_type or 'unknown'}), retrying in {interval}s...")
            except requests.exceptions.ConnectionError:
                print(f"Container not yet reachable, retrying in {interval}s...")
            time.sleep(interval)
        raise TimeoutError(f"Service did not become ready within {timeout}s")

    wait_for_ready(BASE_URL)
    print(f"Ready. Send requests to: {BASE_URL}/v1/chat/completions")

stop:
  registry_read:
    description: Read the registry and resolve which job_id to stop.
    python: |
      import json

      STATE_FILE = "/tmp/tao-inf-ms-state.json"

      try:
          with open(STATE_FILE) as f:
              state = json.load(f)
      except (FileNotFoundError, json.JSONDecodeError):
          state = {}

      job_id_to_stop = user_provided_job_id or state.get("latest")
      if not job_id_to_stop or job_id_to_stop not in state:
          raise RuntimeError("No running inference service found. Provide a job_id or start a service first.")

      entry = state[job_id_to_stop]
      print(f"Stopping inference service: job_id={job_id_to_stop}  arch={entry['network_arch']}")

  registry_cleanup:
    description: >
      Remove the stopped job from the registry and advance "latest" to the
      next most recently started service, or remove "latest" if none remain.
    python: |
      import json, os

      STATE_FILE = "/tmp/tao-inf-ms-state.json"

      try:
          with open(STATE_FILE) as f:
              state = json.load(f)

          state.pop(job_id_to_stop, None)

          if state.get("latest") == job_id_to_stop:
              remaining = {k: v for k, v in state.items() if k != "latest" and isinstance(v, dict)}
              if remaining:
                  state["latest"] = max(remaining, key=lambda k: remaining[k].get("started_at", ""))
              else:
                  state.pop("latest", None)

          tmp = STATE_FILE + ".tmp"
          with open(tmp, "w") as f:
              json.dump(state, f, indent=2)
          os.replace(tmp, STATE_FILE)
          print(f"Removed job {job_id_to_stop} from registry.")
      except FileNotFoundError:
          pass  # already gone — no-op

request:
  registry_read:
    description: Read the registry and resolve the endpoint for sending a request.
    python: |
      import json

      STATE_FILE = "/tmp/tao-inf-ms-state.json"

      with open(STATE_FILE) as f:
          state = json.load(f)

      job_id = user_provided_job_id or state.get("latest")
      if not job_id or job_id not in state:
          raise RuntimeError("No running inference service found. Start a service or provide a job_id.")

      entry    = state[job_id]
      BASE_URL = entry["host_url"]   # from host — valid for all platforms
      # BASE_URL = entry["docker_url"]  # container-to-container (local-docker / brev only)
      print(f"Sending request to job_id={job_id}  url={BASE_URL}")

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Needs an API key, Needs Python, Needs Node.js

The author says: The inference service has no cloud-storage dependency — model weights come from the HuggingFace Hub (HF_TOKEN env var for gated models) or a local container path. Platform prerequisites are checked by each platform skill.

Security

What our scanner noticed· Medium risk

  • Large base64-encoded blobs

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
NVIDIA Corporation
Publisher
NVIDIA (Official)
Source
NVIDIA/skills · skills/tao-run-inference-service · commit fce3cca
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE-APACHE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by NVIDIA Corporation or NVIDIA.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download tao-run-inference-service.zip