diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py
index e4ed7cb5587..a2857a9aebc 100644
--- a/lib/ansible/runner/__init__.py
+++ b/lib/ansible/runner/__init__.py
@@ -612,6 +612,20 @@ class Runner(object):
 
     # *****************************************************
 
+    def _execute_assemble(self, conn, tmp):
+        ''' handler for assemble operations '''
+        module_name = 'assemble'
+        options = utils.parse_kv(self.module_args)
+        module = self._transfer_module(conn, tmp, module_name)
+        exec_rc = self._execute_module(conn, tmp, module, self.module_args)
+
+        if exec_rc.is_successful():
+            return self._chain_file_module(conn, tmp, exec_rc, options)
+        else:
+            return exec_rc
+
+    # *****************************************************
+
     def _executor(self, host):
         try:
             exec_rc = self._executor_internal(host)
@@ -656,6 +670,8 @@ class Runner(object):
             result = self._execute_template(conn, tmp)
         elif self.module_name == 'raw':
             result = self._execute_raw(conn, tmp)
+        elif self.module_name == 'assemble':
+            result = self._execute_assemble(conn, tmp)
         else:
             if self.background == 0:
                 result = self._execute_normal_module(conn, tmp, module_name)
diff --git a/library/assemble b/library/assemble
new file mode 100755
index 00000000000..fcfd6bf52c0
--- /dev/null
+++ b/library/assemble
@@ -0,0 +1,111 @@
+#!/usr/bin/python
+
+# (c) 2012, Stephen Fromm <sfromm@gmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+import os
+import os.path
+import md5
+import sys
+import shlex
+import shutil
+import syslog
+import tempfile
+
+# Since hashlib is only available in 2.5 and onwards, this module
+# uses md5 which is available in 2.4.
+
+# ===========================================
+# Support methods
+
+def exit_json(rc=0, **kwargs):
+   print json.dumps(kwargs)
+   sys.exit(rc)
+
+def fail_json(**kwargs):
+   kwargs['failed'] = True
+   exit_json(rc=1, **kwargs)
+
+def assemble_from_fragments(path):
+    ''' assemble a file from a directory of fragments '''
+    assembled = []
+    for f in sorted(os.listdir(path)):
+        fragment = "%s/%s" % (path, f)
+        if os.path.isfile(fragment):
+            assembled.append(file(fragment).read())
+    return "".join(assembled)
+
+def write_temp_file(data):
+    fd, path = tempfile.mkstemp()
+    os.write(fd, data)
+    os.close(fd)
+    return path
+
+# ===========================================
+
+if len(sys.argv) == 1:
+   fail_json(msg="the assemble module requires arguments (-a)")
+
+argfile = sys.argv[1]
+if not os.path.exists(argfile):
+   fail_json(msg="Argument file not found")
+
+args = open(argfile, 'r').read()
+items = shlex.split(args)
+syslog.openlog('ansible-%s' % os.path.basename(__file__))
+syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args)
+
+if not len(items):
+   fail_json(msg="the assemble module requires arguments (-a)")
+
+params = {}
+for x in items:
+    (k, v) = x.split("=")
+    params[k] = v
+
+changed = False
+pathmd5 = None
+destmd5 = None
+src  = params.get('src', None)
+dest = params.get('dest', None)
+
+if src:
+    src = os.path.expanduser(src)
+if dest:
+    dest = os.path.expanduser(dest)
+
+if not os.path.exists(src):
+    fail_json(msg="Source (%s) does not exist" % src)
+
+if not os.path.isdir(src):
+    fail_json(msg="Source (%s) is not a directory" % src)
+
+path = write_temp_file(assemble_from_fragments(src))
+pathmd5 = md5.new(file(path).read()).hexdigest()
+
+if os.path.exists(dest):
+    destmd5 = md5.new(file(dest).read()).hexdigest()
+
+if pathmd5 != destmd5:
+    shutil.copy(path, dest)
+    changed = True
+
+exit_json(md5sum=pathmd5, changed=changed)
diff --git a/test/TestRunner.py b/test/TestRunner.py
index e97e9d6ce4c..76c85f7af0a 100644
--- a/test/TestRunner.py
+++ b/test/TestRunner.py
@@ -231,5 +231,28 @@ class TestRunner(unittest.TestCase):
     def test_service(self):
         # TODO: tests for the service module
         pass
+    def test_assemble(self):
+        input = self._get_test_file('assemble.d')
+        metadata = self._get_test_file('metadata.json')
+        output = self._get_stage_file('sample.out')
+        result = self._run('assemble', [
+            "src=%s" % input,
+            "dest=%s" % output,
+            "metadata=%s" % metadata
+        ])
+        assert os.path.exists(output)
+        out = file(output).read()
+        assert out.find("first") != -1
+        assert out.find("second") != -1
+        assert out.find("third") != -1
+        assert result['changed'] == True
+        assert 'md5sum' in result
+        assert 'failed' not in result
+        result = self._run('assemble', [
+            "src=%s" % input,
+            "dest=%s" % output,
+            "metadata=%s" % metadata
+        ])
+        assert result['changed'] == False
 
 
diff --git a/test/assemble.d/00-source.txt b/test/assemble.d/00-source.txt
new file mode 100644
index 00000000000..1b3985419f4
--- /dev/null
+++ b/test/assemble.d/00-source.txt
@@ -0,0 +1,2 @@
+# This is the first comment.
+[somegroup]
diff --git a/test/assemble.d/01-source.txt b/test/assemble.d/01-source.txt
new file mode 100644
index 00000000000..fc88cb924e9
--- /dev/null
+++ b/test/assemble.d/01-source.txt
@@ -0,0 +1,2 @@
+# This is the second comment.
+127.0.0.2
diff --git a/test/assemble.d/02.source.txt b/test/assemble.d/02.source.txt
new file mode 100644
index 00000000000..a3f1d96b48e
--- /dev/null
+++ b/test/assemble.d/02.source.txt
@@ -0,0 +1,4 @@
+# This is the third comment.
+[somegroup:vars]
+foo=bar
+# 無 可 無 非 常 無