A session requires three files:
| File | Runs on | Purpose |
|---|---|---|
controller-v3.sh |
Controller (login) node | Install software, download dependencies |
start-template-v3.sh |
Controller or compute node | Start the web service |
| Workflow YAML | Platform | Define the UI form, generate inputs.sh, call session_runner |
The controller node always has internet access. The compute node may not.
Use this prompt template when instructing an AI code assistant to add a new session:
Using the DeveloperGuide.md and the AIPromptAddingNewWorkflow.md files, create a new interactive session workflow for [deployment] to [Describe your workflow here].
Replace [deployment] with your target deployment (general/emed/hsp/noaa) and [Describe your workflow here] with your specific service requirements, for example "a hello world nginx service". The AI assistant will use both reference files to understand the architecture and requirements for creating the new workflow.
File: my-session/controller-v3.sh
This script runs before the service starts. Use it to install dependencies that require internet access. All variables from inputs.sh are available.
#!/usr/bin/env bash
set -o pipefail
if [ -z ${service_parent_install_dir} ]; then
service_parent_install_dir=${HOME}/pw/software
fi
# Install your software if not already present
if ! [ -f "${service_parent_install_dir}/my-server" ]; then
echo "Installing my-server..."
mkdir -p ${service_parent_install_dir}
wget https://example.com/my-server.tar.gz -O /tmp/my-server.tar.gz
tar -xzf /tmp/my-server.tar.gz -C ${service_parent_install_dir}
fiKeep it idempotent -- check if software exists before installing.
File: my-session/start-template-v3.sh
This script starts the web service. The session_runner subworkflow provides the service_port variable -- your service must listen on this port. All variables from inputs.sh are available.
#!/bin/bash
# service_port is provided by the session_runner subworkflow
if [ -z ${service_parent_install_dir} ]; then
service_parent_install_dir=${HOME}/pw/software
fi
# Create a cancel.sh script so the platform can stop the service
echo '#!/bin/bash' > cancel.sh
chmod +x cancel.sh
# Start your service
${service_parent_install_dir}/my-server --port=${service_port} &
pid=$!
echo "kill ${pid}" >> cancel.sh
sleep infKey requirements:
- Use
service_portas the listening port. It is allocated automatically. - Write a
cancel.shscript that kills your service process. - End with
sleep infto keep the job alive (or run the service in the foreground).
File: workflow/yamls/my-session/general_v4.yaml
The YAML has three responsibilities:
- Define the user input form (under
on.execute.inputs) - Generate
inputs.shfrom the form values (in thepreprocessingjob) - Call the
session_runnersubworkflow with the paths to your scripts
permissions:
- '*'
sessions:
session:
useTLS: false
redirect: true
jobs:
preprocessing:
ssh:
remoteHost: ${{ inputs.cluster.resource.ip }}
steps:
- name: Checkout
uses: parallelworks/checkout
with:
repo: https://github.com/parallelworks/interactive_session.git
branch: main
sparse_checkout:
- my-session
- name: Create Inputs
run: |
set -x
# Capture PW environment variables
env | grep '^PW_' | grep -v 'PW_API_KEY' > inputs.sh
sed -i 's/=\(.*\)/="\1"/' inputs.sh
# Add your form inputs
cat <<'EOF' >> inputs.sh
basepath=/me/session/${PW_USER}/${{ sessions.session }}
PATH=$HOME/pw:$PATH
service_parent_install_dir="${{ inputs.service.parent_install_dir }}"
EOF
# Clean up and export
sed -i '/=\s*$\|=undefined\s*$/d' inputs.sh
sed -i '/=""/d' inputs.sh
sed -i 's/^/export /' inputs.sh
session_runner:
needs:
- preprocessing
ssh:
remoteHost: ${{ inputs.cluster.resource.ip }}
steps:
- uses: marketplace/session_runner/v1.3
early-cancel: any-job-failed
with:
session: ${{ sessions.session }}
resource: ${{ inputs.cluster.resource }}
cluster:
scheduler: ${{ inputs.cluster.scheduler }}
slurm:
is_disabled: ${{ inputs.cluster.resource.provider == 'existing' && inputs.cluster.resource.schedulerType != 'slurm' || inputs.cluster.scheduler == false }}
partition: ${{ inputs.cluster.slurm.partition }}
scheduler_directives: ${{ inputs.cluster.slurm.scheduler_directives }}
time: ${{ inputs.cluster.slurm.time }}
pbs:
is_disabled: ${{ inputs.cluster.resource.schedulerType != 'pbs' || inputs.cluster.scheduler == false }}
scheduler_directives: ${{ inputs.cluster.pbs.scheduler_directives }}
service:
start_service_script: ${PW_PARENT_JOB_DIR}/my-session/start-template-v3.sh
controller_script: ${PW_PARENT_JOB_DIR}/my-session/controller-v3.sh
inputs_sh: ${PW_PARENT_JOB_DIR}/inputs.sh
slug: ""
rundir: ${PW_PARENT_JOB_DIR}
'on':
execute:
inputs:
cluster:
type: group
label: Compute Cluster Settings
items:
resource:
type: compute-clusters
label: Service host
include-workspace: false
scheduler:
type: boolean
default: false
label: Schedule Job?
hidden: ${{ inputs.cluster.resource.schedulerType == '' }}
ignore: ${{ .hidden }}
slurm:
type: group
label: SLURM Directives
hidden: ${{ inputs.cluster.resource.provider == 'existing' && inputs.cluster.resource.schedulerType != 'slurm' || inputs.cluster.scheduler == false }}
items:
is_disabled:
type: boolean
hidden: true
default: ${{ inputs.cluster.resource.provider == 'existing' && inputs.cluster.resource.schedulerType != 'slurm' || inputs.cluster.scheduler == false }}
partition:
type: slurm-partitions
label: SLURM partition
optional: true
resource: ${{ inputs.cluster.resource }}
time:
label: Walltime
type: string
default: '01:00:00'
scheduler_directives:
type: editor
optional: true
pbs:
type: group
label: PBS Directives
hidden: ${{ inputs.cluster.resource.schedulerType != 'pbs' || inputs.cluster.scheduler == false }}
items:
is_disabled:
type: boolean
hidden: true
default: ${{ inputs.cluster.resource.schedulerType != 'pbs' || inputs.cluster.scheduler == false }}
scheduler_directives:
type: editor
service:
type: group
label: My Session Settings
items:
parent_install_dir:
label: Install Directory
type: string
default: ${HOME}/pw/softwareThe session_runner subworkflow accepts these inputs:
| Parameter | Description |
|---|---|
session |
Reference to the session object defined in sessions: |
resource |
The compute cluster resource |
cluster.scheduler |
true to submit to SLURM/PBS, false to run on controller |
cluster.slurm |
SLURM settings: is_disabled, partition, time, scheduler_directives |
cluster.pbs |
PBS settings: is_disabled, scheduler_directives |
service.start_service_script |
Path to your start script |
service.controller_script |
Path to your controller script |
service.inputs_sh |
Path to the generated inputs.sh |
service.slug |
URL path appended to the session URL (e.g., lab, vnc.html) |
service.rundir |
Working directory for the service |
- Preprocessing -- Combines
inputs.sh+controller-v3.shand runs it on the controller node. Then combinesinputs.sh+start-template-v3.shinto the final start script, injecting port allocation and cleanup traps. - Job submission -- If
scheduler: true, submits the start script viasbatch/qsubto a compute node. Iffalse, runs it directly on the controller node. - Wait for start -- Polls for the
job.startedmarker file. - Create session -- Waits for the service to respond on its port, then registers the session URL with the platform.
- Cleanup -- On workflow cancellation, runs
cancel.shto stop the service.
The session_runner injects these into the start script before your code runs:
| Variable | Description |
|---|---|
service_port |
The allocated port. Your service must listen on this port. |
All variables exported in inputs.sh are also available in both scripts.
To support multiple deployments, create a separate YAML per platform deployment:
workflow/yamls/my-session/
├── general_v4.yaml # Standard SLURM/PBS clusters
├── emed_v4.yaml # EMED clusters
├── noaa_v4.yaml # NOAA clusters
└── hsp_v4.yaml # HSP clusters
Each YAML uses the corresponding session_runner variant (e.g., marketplace/session_runner/v1.3 resolves to the appropriate deployment). The differences are typically in scheduler directives, partition names, and cluster-specific environment setup in the inputs.sh generation.
Look at these for working examples:
- Simplest:
webshell/-- Starts a singlettydprocess, minimal controller setup. - Typical:
jupyterlab-host/-- Conda installation in controller, nginx proxy + JupyterLab in start script. - Complex:
vncserver/-- Multiple desktop environment options, Singularity/Docker containers.