aipoch/open-science/resources/skills/remote-compute-ssh/SKILL.md
remote-compute-ssh
Discover and use SSH compute hosts. Load when you need to run remote commands or submit long-running jobs with automatic harvest and analysis.
- Source repository stars
- 1,536
- Declared platforms
- 0
- Static risk flags
- 1
- Last source update
- 2026-08-04
- Source checked
- 2026-08-04
Decision brief
What it does—and where it fits
Run await host.compute.list() to see all registered hosts.
Not for
- Tasks that require unconfirmed production actions or broad system permissions.
- Environments where the pinned source and install steps cannot be inspected.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/aipoch/open-science --skill "resources/skills/remote-compute-ssh"Inspect the Agent Skill "remote-compute-ssh" from https://github.com/aipoch/open-science/blob/e6d42f837606061f8a0fc13dc64bf9189c7c79e5/resources/skills/remote-compute-ssh/SKILL.md at commit e6d42f837606061f8a0fc13dc64bf9189c7c79e5. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
Workflow: the analysis turn
When the app initiates the analysis turn, it provides the jobid, status, and featuredfiles (workspace-relative paths under hpc//featured/). In this turn:
Call attachjob(jobid).result() to get the full result dict.Inspect the outputs, run any analysis needed.Call writeartifactfile to publish outputs worth keeping as artifacts. - 02
Typical first-contact workflow
1. await host.compute.details(providerid, { mode: 'read' }) — a Resources skeleton means first contact; populated sections mean prior sessions did the legwork, trust them. 2. Bind once: const c = host.compute.create(providerid). 3. Run one batched probe: await c.callcommand('id;…
await host.compute.details(providerid, { mode: 'read' }) — a Resources skeleton meansBind once: const c = host.compute.create(providerid).Run one batched probe: await c.callcommand('id; module avail 2&1 | head -40', ''). - 03
Registered hosts
Run await host.compute.list() to see all registered hosts.
Display nameProvider ID (e.g., ssh:biowulf, ssh:192.168.1.100)Shape (e.g., directssh, slurm, pbs) - 04
Session-active host
The user may enable one host for this conversation via the ≡ panel in the composer. Always check which host is active before creating a handle:
The user may enable one host for this conversation via the ≡ panel in the composer. Always check which host is active before creating a handle: - 05
API reference
With loginshell: true, the remote Bash login profiles run first and then Open Science attempts to source /.bashrc when it is readable. A .bashrc can deliberately return early for non-interactive shells, so variables declared after such a guard are not available. A missing .bashr…
With loginshell: true, the remote Bash login profiles run first and then Open Science attempts to source /.bashrc when it is readable. A .bashrc can deliberately return early for non-interactive shells, so variables dec…
Permission review
Static risk signals and limitations
Runs scripts
The documentation asks the agent to run terminal commands or scripts.
// Run a short remote command (throws on approval_denied / host_unreachable / timeout)Runs scripts
The documentation asks the agent to run terminal commands or scripts.
Set `login_shell: false` to run the command without either initialization step. Initialization failuresEvidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 87/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 1,536 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- aipoch/open-science
- Skill path
- resources/skills/remote-compute-ssh/SKILL.md
- Commit
- e6d42f837606061f8a0fc13dc64bf9189c7c79e5
- License
- Apache-2.0
- Collected
- 2026-08-04
- Default branch
- main
View the original SKILL.md
This skill covers remote compute over SSH: listing hosts, creating handles, running short remote commands (call_command), reading/writing host knowledge docs, and the full async job lifecycle — submit → harvest → analysis turn → publish artifacts.
Where host.compute runs: host.compute lives ONLY on the control-plane REPL kernel — run
every example below with the repl_execute tool (JavaScript), the same kernel that hosts
host.mcp. The python/r data kernels have NO host.compute (SSH and approvals stay outside
the sandbox workspace); calling it from a python/r cell will fail with host.compute is undefined.
Registered hosts
Run await host.compute.list() to see all registered hosts.
Each host entry shows:
- Display name
- Provider ID (e.g.,
ssh:biowulf,ssh:192.168.1.100) - Shape (e.g.,
direct_ssh,slurm,pbs) - Connection status
Session-active host
The user may enable one host for this conversation via the ≡ panel in the composer.
Always check which host is active before creating a handle:
// Returns the session-enabled host provider_ids (a string[] subset of all registered hosts).
// Empty array means the user hasn't chosen a host for this conversation yet.
const activeHosts = await host.compute.list_compute()
const c = activeHosts[0] ? host.compute.create(activeHosts[0]) : null
API reference
// List ALL registered hosts
const hosts = await host.compute.list()
// List session-enabled hosts (user's active selection for this conversation)
const activeHosts = await host.compute.list_compute()
// Create a handle to a specific host (no network call)
const c = host.compute.create('ssh:<alias>')
// Run a short remote command (throws on approval_denied / host_unreachable / timeout)
const result = await c.call_command('<shell command>', '<one-line intent for the approval card>', {
login_shell: true, // default: true — runs login profiles, then readable ~/.bashrc, before this command
timeout_seconds: 60 // optional — the host applies its own default (60s) when omitted
})
// result → { exit_code, stdout, stderr, truncated }
// Read the host knowledge doc (returns { doc, isSkeleton })
const info = await host.compute.details('ssh:<alias>', { mode: 'read' })
// Append a note to the host knowledge doc (agent writes; 32 KB cap enforced)
await host.compute.details('ssh:<alias>', {
mode: 'append',
text: '\n## Note\nlearned X on <date>'
})
// Replace the entire host knowledge doc (old_text must match the current doc exactly)
await host.compute.details('ssh:<alias>', {
mode: 'replace',
text: '<new full doc>',
old_text: info.doc // from the read above
})
With login_shell: true, the remote Bash login profiles run first and then Open Science attempts to
source ~/.bashrc when it is readable. A .bashrc can deliberately return early for non-interactive
shells, so variables declared after such a guard are not available. A missing .bashrc is a no-op.
Set login_shell: false to run the command without either initialization step. Initialization failures
are reported through the normal command result/error behavior.
API reference (async jobs)
Use submit_job for long-running computations (minutes to hours). It returns immediately with a
job_id; the job runs on the remote host in the background. When the job finishes, the app
automatically harvests the outputs and initiates a new analysis turn — you never poll or block.
// List the session's active compute hosts (set via the ≡ host selector)
const activeHosts = await host.compute.list_compute()
// returns ['ssh:<alias>', ...] — the provider_ids currently enabled for this session
// Submit a non-blocking job — returns immediately after the user approves
const c = host.compute.create('ssh:<alias>')
const job = await c.submit_job(
'<one-line intent for the approval card>', // shown in the approval card
'<shell command>', // command to run remotely
{
timeout_seconds: 3600, // optional; default 24 h, max 7 days
inputs: [
{ src: 'in.dat', dst_filename: 'in.dat' }, // stage a workspace file
{ remote_path: 'ssh:<alias>/<abs_path>' } // link a remote file (no transfer)
],
outputs: [
'*.result', // featured (default visibility)
{ glob: '*.json', visibility: 'featured' }, // explicitly featured
{ glob: '*.log', visibility: 'hidden' }, // hidden (diagnostic, not shown in card)
{ glob: 'checkpoints/**', residency: 'remote' } // leave on remote — recorded in left_on_remote
],
harvest: {
exclude: ['work/**'], // never harvest these paths
max_file_mb: 100, // single-file cap (default 100 MB)
max_total_mb: 500 // total-harvest cap (default 500 MB)
}
}
)
// job → { job_id, provider_id, status: 'submitted', remote_workdir }
print(job.job_id) // end the cell — kernel never blocks on compute
End the cell here. Do NOT write a polling loop. The app runs the poller and harvest in the background. When the job finishes, the app automatically starts a new analysis turn in this conversation — the conversation is NOT locked while the job runs, so the user can keep chatting.
Behavior boundaries
- While the job runs: the conversation is open. The user can send messages; you can handle other tasks. No blocking wait.
- When the job finishes: the app initiates a new analysis turn automatically. You do not trigger this — it happens without any action on your part.
- Do NOT write a loop calling
attach_job().status()to wait for completion. That is the app's job, not yours. Writing such a loop would block the conversation for the entire job duration.
Check job status (non-blocking read, for informational use)
// Non-blocking DB read — no SSH. Use if you need a status snapshot mid-conversation.
const handle = c.attach_job(job.job_id)
const s = await handle.status()
// s → { job_id, status, exit_code, stdout_tail, stderr_tail, remote_workdir }
// status: 'submitted' | 'running' | 'success' | 'failed' | 'timeout' | 'error'
submit_job status values
| status | meaning |
|---|---|
submitted | accepted; background dispatch in progress |
running | remote process confirmed alive (pid recorded) |
success | exit code 0 |
failed | non-zero exit (job_failed) or process vanished (process_vanished) |
timeout | exceeded timeout_seconds |
error | never reached the remote host (host_unreachable / dispatch_failed) |
Workflow: the analysis turn
When the app initiates the analysis turn, it provides the job_id, status, and
featured_files (workspace-relative paths under hpc/<job_id>/featured/). In this turn:
- Call
attach_job(job_id).result()to get the full result dict. - Inspect the outputs, run any analysis needed.
- Call
write_artifact_fileto publish outputs worth keeping as artifacts.
// In the analysis turn — read the full harvested result (non-blocking DB + directory scan)
const c = host.compute.create('ssh:<alias>')
const r = await c.attach_job(job_id).result()
// r → {
// job_id, status, exit_code,
// featured_files: ['hpc/<job_id>/featured/out.result', ...], // workspace-relative
// hidden_files: ['hpc/<job_id>/hidden/run.log', ...],
// output_files: [...featured_files, ...hidden_files], // featured first
// left_on_remote: [{ uri: 'ssh:<alias>/<abs_path>', size_mb: 420, reason: 'residency:remote' }],
// remote_workdir: '.openscience/jobs/<job_id>',
// stdout_tail: '...last 64 KB...',
// stderr_tail: '...last 64 KB...'
// }
Files land in the workspace at hpc/<job_id>/ and are readable directly:
# python cell — files are in the workspace; open() works with workspace-relative paths
import pandas as pd
df = pd.read_csv('hpc/<job_id>/featured/results.csv')
Publish artifacts
Harvest only lands files in the workspace — it does NOT publish artifacts automatically.
Call write_artifact_file in the analysis turn to publish outputs worth keeping:
// In the analysis turn — publish featured outputs as artifacts (bound to this turn)
for (const path of r.featured_files) {
await host.mcp('artifacts', 'write_artifact_file', { path })
}
// Artifacts appear in the artifact panel with provenance tied to this analysis turn.
When the job fails
Read r.exit_code and r.stderr_tail. An infrastructure failure (wrong partition, env not
activated, missing module, OOM, walltime) is yours to fix — adjust command, record the fix,
fresh c.submit_job(). A harvest failure (r.stderr_tail notes it, r.remote_workdir is
preserved) means some files were not downloaded — the remote workdir is kept so you can
c.call_command('ls ...', intent='...') to inspect what's there.
Chaining jobs via left_on_remote
Large outputs declared with residency: 'remote' or files that exceed the size threshold stay
on the remote host and appear in r.left_on_remote. Use their URIs directly as remote_path
inputs to the next job — no local round-trip:
// In the analysis turn — chain a left_on_remote output into the next job
const big_output_uri = r.left_on_remote[0].uri // e.g. 'ssh:biowulf//scratch/jobs/<id>/big.h5'
const job2 = await c.submit_job(
'process big.h5 output from job 1',
'python process.py --input big.h5 --out summary.csv',
{
inputs: [
{ remote_path: big_output_uri } // symlinked in job workdir, no transfer
],
outputs: ['summary.csv']
}
)
Submitting several jobs
Submit a batch and let each job's analysis turn handle its results independently. The app triggers a separate analysis turn for each job as it finishes (or merges simultaneous completions into one turn with multiple job_ids):
// Submit multiple jobs — end the cell after all submits
const c = host.compute.create('ssh:gpu-cluster')
const jobs = []
for (const seed of [0, 1, 2, 3, 4]) {
const job = await c.submit_job(
`AlphaFold seed ${seed}`,
`python fold.py --seed ${seed} --in input.fasta --out ranked.pdb`,
{
inputs: [{ src: 'input.fasta', dst_filename: 'input.fasta' }],
outputs: [{ glob: '*.pdb', visibility: 'featured' }],
timeout_seconds: 3600
}
)
jobs.push(job.job_id)
}
print(jobs) // end the cell — no waiting, no loop
The app triggers one analysis turn per job completion (or a merged turn for simultaneous completions). Do NOT write a loop collecting all results — each analysis turn handles its job independently.
Session concurrency control
Cap how many non-terminal jobs run at once across all providers in this conversation. Jobs that
would exceed the cap enter a queued state and auto-dispatch when a slot frees up. These two
methods live on the handle returned by create(), but they are session-scoped — they act on
the whole conversation, not on the handle's bound provider.
const c = host.compute.create('ssh:<alias>')
// Set the conversation-wide limit (positive integer 1..500).
await c.set_concurrency_limit(2)
// Read the session's concurrency status (non-blocking DB read, no SSH).
const s = await c.status()
// s → {
// session_limit: number | null, // the cap you set, or null if unset
// active_count: number, // non-terminal jobs running now
// queued_count: number, // jobs waiting for a slot
// provider_ceilings: Record<string, number> // per-host hard limits (host config)
// }
call_command error handling
try {
const r = await c.call_command('cmd', '<intent>')
} catch (e) {
const code = e.error_code || ''
if (code === 'host_unreachable') {
// SSH connectivity issue — needs user action (VPN, key, etc.); e.retry_after_user_action is true
} else if (code === 'approval_denied') {
// User declined the approval card
} else if (code === 'timeout') {
// Command exceeded timeout_seconds
}
}
Typical first-contact workflow
await host.compute.details(provider_id, { mode: 'read' })— a## Resourcesskeleton means first contact; populated sections mean prior sessions did the legwork, trust them.- Bind once:
const c = host.compute.create(provider_id). - Run one batched probe:
await c.call_command('id; module avail 2>&1 | head -40', '<intent>'). - Append what you learned via
await host.compute.details(..., { mode: 'append' }).
What to record in the knowledge doc
The knowledge doc is the only state that survives across sessions. Record:
- Scheduler type and any known partition/account combinations that worked.
- Environment activation commands (e.g.
module load X/<ver>,conda activate <env>). - Verified invocations tagged
verified <date>; user-provided info taggedper user <date>. - Gotchas specific to this host or provider.
Do NOT record per-job state, transient errors, or facts about your project — those belong elsewhere. When a session ends without new host-specific learnings, write nothing.
Alternatives
Compare before choosing
K-Dense-AI/scientific-agent-skills
simpy
Build, inspect, test, and analyze bounded process-based discrete-event simulations with SimPy, including events, resources, interrupts, monitoring, replications, warm-up, and reproducible output analysis.
mgiovani/cc-arsenal
ci-local
Run the checks a GitHub Actions workflow would run, locally, when Actions is unavailable or out of quota. Parses .github/workflows/*.yml, extracts the jobs/steps that gate merges (lint, typecheck, test, build), translates them to local commands respecting the workflow's pinned node/python versions and env, executes them sequentially, and reports a parity table of what passed locally vs. what can't be replicated (service containers, secrets, matrix dimensions) and why. Activates on "CI quota", "A
PramodDutta/qaskills
Allure Report Generator
Configure and generate rich Allure test reports with test categorization, historical trends, environment details, and CI/CD integration for comprehensive test visibility
muend/geoai-skills
swe-devops-standards
Always invoke to review, repair, or deliver geospatial or GeoAI code, including contract compliance, security, error handling, transactions, tests, scripts, functions, notebooks, packages, CI/CD, and repository changes, even when deployment is not requested. Pair with the domain skill for ETL and other production code. Covers CRS/data invariants, dependencies, cross-platform reproducibility, automation, and shipping. Do not trigger for unrelated software or analysis requesting no code or reposit