diff --git a/t_functional_duffy_runner/.gitignore b/t_functional_duffy_runner/.gitignore
new file mode 100644
index 0000000..1dbbe6f
--- /dev/null
+++ b/t_functional_duffy_runner/.gitignore
@@ -0,0 +1,4 @@
+# Distribution / packaging
+build/
+dist/
+*.egg-info/
diff --git a/t_functional_duffy_runner/README.md b/t_functional_duffy_runner/README.md
new file mode 100644
index 0000000..85e570c
--- /dev/null
+++ b/t_functional_duffy_runner/README.md
@@ -0,0 +1,3 @@
+# t_functional duffy runner 
+
+Simple script to run these tests on new duffy.
diff --git a/t_functional_duffy_runner/pyproject.toml b/t_functional_duffy_runner/pyproject.toml
new file mode 100644
index 0000000..9787c3b
--- /dev/null
+++ b/t_functional_duffy_runner/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/t_functional_duffy_runner/requirements.txt b/t_functional_duffy_runner/requirements.txt
new file mode 100644
index 0000000..6d61d41
--- /dev/null
+++ b/t_functional_duffy_runner/requirements.txt
@@ -0,0 +1,2 @@
+duffy
+fabric
diff --git a/t_functional_duffy_runner/setup.cfg b/t_functional_duffy_runner/setup.cfg
new file mode 100644
index 0000000..96c7c99
--- /dev/null
+++ b/t_functional_duffy_runner/setup.cfg
@@ -0,0 +1,10 @@
+[metadata]
+name = t_functional_duffy_runner
+version = 0.0.1
+
+[options]
+packages = src
+
+[options.entry_points]
+console_scripts =
+    app = src.cli:main
diff --git a/t_functional_duffy_runner/setup.py b/t_functional_duffy_runner/setup.py
new file mode 100644
index 0000000..6068493
--- /dev/null
+++ b/t_functional_duffy_runner/setup.py
@@ -0,0 +1,3 @@
+from setuptools import setup
+
+setup()
diff --git a/t_functional_duffy_runner/src/__init__.py b/t_functional_duffy_runner/src/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/t_functional_duffy_runner/src/__init__.py
diff --git a/t_functional_duffy_runner/src/__main__.py b/t_functional_duffy_runner/src/__main__.py
new file mode 100644
index 0000000..6403ad8
--- /dev/null
+++ b/t_functional_duffy_runner/src/__main__.py
@@ -0,0 +1,140 @@
+from argparse import ArgumentParser
+import io
+import os
+import tempfile
+
+from duffy.client import DuffyClient
+from fabric import Connection
+from invoke import run as local
+
+class DuffyWrapper:
+    def __init__(self, auth_name, auth_key):
+        self.c = DuffyClient(url="https://duffy.ci.centos.org/api/v1", auth_name=auth_name, auth_key=auth_key)
+        self.last_session = None 
+        
+    def get_hostnames(self, *query):
+        if not self.last_session:
+            self.request_session(*query)
+        
+        nodes = [n for n in self.last_session.session.nodes
+                if all(q in n.pool for q in query)]
+        return [n 
+                 for n in [n.data.get('provision',{}).get('public_hostname',None) for n in nodes]
+                 if n]
+     
+    def find_hostnames(self, *query):
+        ls = self.c.list_sessions()
+        nodes = [n for s in ls.sessions for n in s.nodes
+                if all(q in n.pool for q in query)]
+        return [n 
+                 for n in [n.data.get('provision',{}).get('public_hostname',None) for n in nodes]
+                 if n]
+
+    def find_pool_name(self, *query):
+        return [p.name for p in self.c.list_pools().pools if all(q in p.name for q in query)]
+    
+    def request_session(self, *query):
+        pool = self.find_pool_name(*query)
+        session = self.c.request_session([{"pool":pool[0], "quantity":"1"}])
+        self.last_session = session
+        return session
+    
+class TmuxWrapper:
+    def __init__(self, host, session='default-session', **rest):
+        self.host = host
+        self.c = Connection(host, **rest)
+        self.c.run("dnf -y install git tmux")
+        self.ensure_session(session)
+    
+    def ensure_session(self,session='default-session'):
+        return self.c.run("tmux has-session -t {session} || tmux new -s {session} -d".format(session=session))
+    
+    def run(self, cmd, session='default-session'):
+        return self.c.run("tmux send-keys -t {session}:0 '{cmd}' ENTER".format(session=session, cmd=cmd))
+    
+    def run_and_notify(self, cmd, session='default-session'):
+        return self.c.run("tmux send-keys -t {session}:0 '{cmd}; tmux wait-for -S {session}' ENTER".format(session=session, cmd=cmd))
+
+    def wait_for(self, session='default-session'):
+        return self.c.run("tmux wait-for {session}".format(session=session))
+    
+    def send_folder(self, path, remote):
+        with  tempfile.NamedTemporaryFile() as t:
+            local("tar cf {to} -C {path} .".format(path=path, to=t.name))
+            print(t.name)
+            self.c.run("mkdir -p {}".format(remote))
+            self.c.put(local=t.name, remote=remote)
+            self.c.run("cd {path} && tar xf {name}".format(path=remote, name=t.name.split('/')[-1]))
+    def get_pane(self):
+        return tmux.c.run("tmux capture-pane -p -t default-session")
+    def set_repo(self, baseurl="https://composes.stream.centos.org/development/latest-CentOS-Stream/compose/"):
+        repo = io.StringIO("""
+[baseos-compose]
+name=CentOS Stream $releasever - BaseOS
+baseurl={baseurl}/BaseOS/$arch/os/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
+gpgcheck=0
+repo_gpgcheck=0
+metadata_expire=6h
+countme=1
+enabled=1
+
+[appstream-compose]
+name=CentOS Stream $releasever - AppStream
+baseurl={baseurl}/AppStream/$arch/os/
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
+gpgcheck=0
+repo_gpgcheck=0
+metadata_expire=6h
+countme=1
+enabled=1
+        """.format(baseurl=baseurl))
+        self.c.put(repo, "/tmp/compose.repo")
+        self.c.run("dnf repolist | awk '(NR>1) {print $1}' | xargs dnf config-manager --disable")
+        self.c.run("dnf config-manager --add-repo /tmp/compose.repo")
+        self.c.run("dnf config-manager --enable baseos-compose")
+        self.c.run("dnf config-manager --enable appstream-compose")
+        self.c.run("dnf clean all")
+        
+
+
+def runtests(auth_name, auth_key, query, path=None, compose=None):
+    d = DuffyWrapper(auth_name=auth_name, auth_key=auth_key)
+    print("Getting", *query)
+    hostnames = d.get_hostnames(*query)
+    if not hostnames:
+        raise Exception("Didn't manage to find or provision matching machine.")
+
+    hostname = hostnames[0] 
+    print("root@"+hostname)
+    tmux = TmuxWrapper("root@"+hostname)
+    if compose:
+        print("Setting compose", compose)
+        tmux.set_repo(compose)
+    if path:
+        print("Sending local repo",path )
+        tmux.send_folder(path, "/opt/t_functional")
+    else:
+        tmux.c.run("git clone https://github.com/CentOS/sig-core-t_functional /opt/t_functional")
+    tmux.ensure_session()
+    tmux.run("cd /opt/t_functional")
+    tmux.run("export SYSTEMD_PAGER=")
+    tmux.run("ls -l")
+    tmux.c.run("cd /opt//t_functional && /bin/bash runtests.sh 0_common")
+    return tmux
+
+
+def main():
+    parser = ArgumentParser(prog='My App')
+    parser.add_argument('--arch', help="The duffy query.")
+    parser.add_argument('--path', help="", default=None, required=False)
+    parser.add_argument('--release', help="" )
+    parser.add_argument('--compose', help="", default=None, required=False)
+    args = parser.parse_args()
+    auth_name=os.getenv("DUFFY_AUTH_NAME")
+    auth_key=os.getenv("DUFFY_AUTH_KEY")
+    if auth_name and auth_key:
+        runtests(auth_name, auth_key, ['virt', args.arch, args.release], path=args.path, compose=args.compose)
+
+if __name__ == "__main__":
+    main()