diff --git a/bootstrap.php b/bootstrap.php
index d16f10feefc17f3e4c462a7f792f553208cf00fb..d2afa2243f7bfa2914da15428c3f8e2c9b197fcb 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -101,6 +101,8 @@ class Bootstrap {
         define('TICKET_STATUS_TABLE', $prefix.'ticket_status');
         define('TICKET_PRIORITY_TABLE',$prefix.'ticket_priority');
 
+        define('TASK_TABLE',$prefix.'task');
+
         define('PRIORITY_TABLE',TICKET_PRIORITY_TABLE);
 
 
diff --git a/include/ajax.draft.php b/include/ajax.draft.php
index 8d264ff486639b94df5622028ea611c656f130a0..2687ad94b53fb28e074d856394489aeae2c435db 100644
--- a/include/ajax.draft.php
+++ b/include/ajax.draft.php
@@ -358,7 +358,7 @@ class DraftAjaxAPI extends AjaxController {
             }
         }
         $field_list = array('response', 'note', 'answer', 'body',
-             'message', 'issue');
+             'message', 'issue', 'description');
         foreach ($field_list as $field) {
             if (isset($vars[$field])) {
                 return urldecode($vars[$field]);
diff --git a/include/class.config.php b/include/class.config.php
index eb0ffca76c6382939274120a346daada44147b0c..ab5d822fab63161344ae796386c8a13172d13b2b 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -673,6 +673,26 @@ class OsticketConfig extends Config {
             array('Ticket', 'isTicketNumberUnique'));
     }
 
+    // Task sequence
+    function getDefaultTaskSequence() {
+        if ($this->get('task_sequence_id'))
+            $sequence = Sequence::lookup($this->get('task_sequence_id'));
+        if (!$sequence)
+            $sequence = new RandomSequence();
+
+        return $sequence;
+    }
+
+    function getDefaultTaskNumberFormat() {
+        return $this->get('task_number_format');
+    }
+
+    function getNewTaskNumber() {
+        $s = $this->getDefaultTaskSequence();
+        return $s->next($this->getDefaultTaskNumberFormat(),
+            array('Task', 'isNumberUnique'));
+    }
+
     /* autoresponders  & Alerts */
     function autoRespONNewTicket() {
         return ($this->get('ticket_autoresponder'));
diff --git a/include/class.forms.php b/include/class.forms.php
index 9b86046926406355a24d70ba72f6a1869cbe7c38..25ee52ce1a427caa6f514abf57dcd65daf710362 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -1459,7 +1459,7 @@ class ThreadEntryField extends FormField {
                 'label'=>__('Enable Attachments'),
                 'default'=>$cfg->allowAttachments(),
                 'configuration'=>array(
-                    'desc'=>__('Enables attachments on tickets, regardless of channel'),
+                    'desc'=>__('Enables attachments, regardless of channel'),
                 ),
                 'validators' => function($self, $value) {
                     if (!ini_get('file_uploads'))
diff --git a/include/class.model.php b/include/class.model.php
index 7b8af26ab93d9056f3eb46fc8807e78b1658fc58..5d41a05d099684b943c9ad754f2b8653b443324d 100644
--- a/include/class.model.php
+++ b/include/class.model.php
@@ -21,6 +21,7 @@ class ObjectModel {
     const OBJECT_TYPE_ORG    = 'O';
     const OBJECT_TYPE_FAQ    = 'K';
     const OBJECT_TYPE_FILE   = 'F';
+    const OBJECT_TYPE_TASK   = 'A';
 
     private function objects() {
         static $objects = false;
@@ -32,6 +33,7 @@ class ObjectModel {
                     self::OBJECT_TYPE_ORG     => 'Organization',
                     self::OBJECT_TYPE_FAQ     => 'FAQ',
                     self::OBJECT_TYPE_FILE    => 'AttachmentFile',
+                    self::OBJECT_TYPE_TASK    => 'Task',
                     );
         }
 
diff --git a/include/class.task.php b/include/class.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..29f947a461e858f9a9fcd5f4fe2256fb73627293
--- /dev/null
+++ b/include/class.task.php
@@ -0,0 +1,358 @@
+<?php
+
+class TaskModel extends VerySimpleModel {
+    static $meta = array(
+        'table' => TASK_TABLE,
+        'pk' => array('id'),
+        'joins' => array(
+            'thread' => array(
+                'reverse' => 'ThreadModel.object',
+            ),
+        )
+    );
+
+    const ISOPEN    = 0x0001;
+    const ISOVERDUE = 0x0002;
+
+
+    protected function hasFlag($flag) {
+        return ($this->get('flags') & $flag) !== 0;
+    }
+
+    protected function clearFlag($flag) {
+        return $this->set('flags', $this->get('flags') & ~$flag);
+    }
+
+    protected function setFlag($flag) {
+        return $this->set('flags', $this->get('flags') | $flag);
+    }
+
+    function getId() {
+        return $this->id;
+    }
+
+    function getNumber() {
+        return $this->number;
+    }
+
+    function getStaffId() {
+        return $this->staff_id;
+    }
+
+    function getTeamId() {
+        return $this->team_id;
+    }
+
+    function getDeptId() {
+        return $this->dept_id;
+    }
+
+    function getCreateDate() {
+        return $this->created;
+    }
+
+    function getDueDate() {
+        return $this->duedate;
+    }
+
+    function isOpen() {
+        return $this->hasFlag(self::ISOPEN);
+    }
+
+    function isClosed() {
+        return !$this->isOpen();
+    }
+
+    function close() {
+        return $this->clearFlag(self::ISOPEN);
+    }
+
+    function reopen() {
+        return $this->setFlag(self::ISOPEN);
+    }
+
+    function isAssigned() {
+        return ($this->isOpen() && ($this->getStaffId() || $this->getTeamId()));
+    }
+
+    function isOverdue() {
+        return $this->hasFlag(self::ISOVERDUE);
+    }
+
+}
+
+class Task extends TaskModel {
+    var $form;
+    var $entry;
+    var $thread;
+
+
+    function getStatus() {
+        return $this->isOpen() ? _('Open') : _('Closed');
+    }
+
+    function getTitle() {
+        return $this->__cdata('title', ObjectModel::OBJECT_TYPE_TASK);
+    }
+
+
+    function getDept() {
+        return "Dept Object";
+    }
+
+    function getThread() {
+
+        if (!$this->thread)
+            $this->thread = TaskThread::lookup(array(
+                        'object_id' => $this->getId(),
+                        'object_type' => ObjectModel::OBJECT_TYPE_TASK)
+                    );
+
+        return $this->thread;
+    }
+
+    function getThreadEntry($id) {
+        return $this->getThread()->getEntry($id);
+    }
+
+    function getThreadEntries($type, $order='') {
+        return $this->getThread()->getEntries(
+                array('type' => $type, 'order' => $order));
+    }
+
+    function getForm() {
+        if (!isset($this->form)) {
+            // Look for the entry first
+            if ($this->form = DynamicFormEntry::lookup(
+                        array('object_type' => ObjectModel::OBJECT_TYPE_TASK))) {
+                return $this->form;
+            }
+            // Make sure the form is in the database
+            elseif (!($this->form = DynamicForm::lookup(
+                            array('type' => ObjectModel::OBJECT_TYPE_TASK)))) {
+                $this->__loadDefaultForm();
+                return $this->getForm();
+            }
+            // Create an entry to be saved later
+            $this->form = $this->form->instanciate();
+            $this->form->object_type = ObjectModel::OBJECT_TYPE_TASK;
+        }
+
+        return $this->form;
+    }
+
+    function addDynamicData($data) {
+
+        $tf = TaskForm::getInstance($this->id, true);
+        foreach ($tf->getFields() as $f)
+            if (isset($data[$f->get('name')]))
+                $tf->setAnswer($f->get('name'), $data[$f->get('name')]);
+
+        $tf->save();
+
+        return $tf;
+    }
+
+    function getDynamicData($create=true) {
+        if (!isset($this->_entries)) {
+            $this->_entries = DynamicFormEntry::forObject($this->id,
+                    ObjectModel::OBJECT_TYPE_TASK)->all();
+            if (!$this->_entries && $create) {
+                $f = TaskForm::getInstance($this->id, true);
+                $f->save();
+                $this->_entries[] = $f;
+            }
+        }
+
+        return $this->_entries ?: array();
+    }
+
+
+    function to_json() {
+
+        $info = array(
+                'id'  => $this->getId(),
+                'title' => $this->getTitle()
+                );
+
+        return JsonDataEncoder::encode($info);
+    }
+
+    function __cdata($field, $ftype=null) {
+
+        foreach ($this->getDynamicData() as $e) {
+            // Make sure the form type matches
+            if (!$e->getForm()
+                    || ($ftype && $ftype != $e->getForm()->get('type')))
+                continue;
+
+            // Get the named field and return the answer
+            if ($f = $e->getForm()->getField($field))
+                return $f->getAnswer();
+        }
+
+        return null;
+    }
+
+    function __toString() {
+        return (string) $this->getTitle();
+    }
+
+    /* util routines */
+    function postNote($vars, &$errors, $poster='', $alert=true) {
+        global $cfg, $thisstaff;
+
+        $vars['staffId'] = 0;
+        $vars['poster'] = 'SYSTEM';
+        if ($poster && is_object($poster)) {
+            $vars['staffId'] = $poster->getId();
+            $vars['poster'] = $poster->getName();
+        } elseif ($poster) { //string
+            $vars['poster'] = $poster;
+        }
+
+        if (!($note=$this->getThread()->addNote($vars, $errors)))
+            return null;
+
+        if (isset($vars['task_status'])) {
+            if ($vars['task_status'])
+                $this->open();
+            else
+                $this->close();
+
+            $this->save(true);
+        }
+
+        return $note;
+    }
+
+    static function lookupIdByNumber($number) {
+        $sql = 'SELECT id FROM '.TASK_TABLE
+              .' WHERE `number`='.db_input($number);
+        list($id) = db_fetch_row(db_query($sql));
+
+        return $id;
+    }
+
+    static function isNumberUnique($number) {
+        return !self::lookupIdByNumber($number);
+    }
+
+    static function create($form, $object) {
+        global $cfg, $thisstaff;
+
+        if (!$thisstaff
+                || !$form
+                // TODO: Make sure it's an instance of ORM Model
+                || !$object)
+            return null;
+
+        if (!$form->isValid())
+            return false;
+
+        try {
+
+            $task = parent::create(array(
+                'flags' => 1,
+                'object_id' => $object->getId(),
+                'object_type' => $object->getObjectType(),
+                'number' => $cfg->getNewTaskNumber(),
+                'created' => new SqlFunction('NOW'),
+                'updated' => new SqlFunction('NOW'),
+            ));
+            $task->save(true);
+        } catch(OrmException $e) {
+            return null;
+        }
+
+        $vars = $form->getClean();
+        $task->addDynamicData($vars);
+        // Create a thread + message.
+        $thread = TaskThread::create($task);
+        $desc = $form->getField('description');
+        if ($desc
+                && $desc->isAttachmentsEnabled()
+                && ($attachments=$desc->getWidget()->getAttachments()))
+            $vars['cannedattachments'] = $attachments->getClean();
+
+        $vars['staffId'] = $thisstaff->getId();
+        $vars['poster'] = $thisstaff;
+        if (!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
+            $vars['ip_address'] = $SERVER['REMOTE_ADDR'];
+
+        $thread->addDescription($vars);
+
+        Signal::send('model.created', $task);
+
+        return $task;
+    }
+
+    static function __loadDefaultForm() {
+
+        require_once INCLUDE_DIR.'class.i18n.php';
+
+        $i18n = new Internationalization();
+        $tpl = $i18n->getTemplate('form.yaml');
+        foreach ($tpl->getData() as $f) {
+            if ($f['type'] == ObjectModel::OBJECT_TYPE_TASK) {
+                $form = DynamicForm::create($f);
+                $form->save();
+                break;
+            }
+        }
+    }
+}
+
+class TaskForm extends DynamicForm {
+    static $instance;
+    static $form;
+
+    static function objects() {
+        $os = parent::objects();
+        return $os->filter(array('type'=>ObjectModel::OBJECT_TYPE_TASK));
+    }
+
+    static function getDefaultForm() {
+        if (!isset(static::$form)) {
+            if (($o = static::objects()) && $o[0])
+                static::$form = $o[0];
+        }
+
+        return static::$form;
+    }
+
+    static function getInstance($object_id=0, $new=false) {
+        if ($new || !isset(static::$instance))
+            static::$instance = static::getDefaultForm()->instanciate();
+
+        static::$instance->object_type = ObjectModel::OBJECT_TYPE_TASK;
+
+        if ($object_id)
+            static::$instance->object_id = $object_id;
+
+        return static::$instance;
+    }
+}
+
+// Task thread class
+class TaskThread extends ObjectThread {
+
+    function addDescription($vars, &$errors=array()) {
+
+        $vars['threadId'] = $this->getId();
+        $vars['message'] = $vars['description'];
+        unset($vars['description']);
+
+        return MessageThreadEntry::create($vars, $errors);
+    }
+
+    static function create($task) {
+        $id = is_object($task) ? $task->getId() : $task;
+        return parent::create(array(
+                    'object_id' => $id,
+                    'object_type' => ObjectModel::OBJECT_TYPE_TASK
+                    ));
+    }
+
+}
+?>
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 43c1ed87593ebfbd3acd76e592396288b3c6ae80..067167f64b2393f507189e51b29203a691ac0227 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -32,6 +32,7 @@ include_once(INCLUDE_DIR.'class.canned.php');
 require_once(INCLUDE_DIR.'class.dynamic_forms.php');
 require_once(INCLUDE_DIR.'class.user.php');
 require_once(INCLUDE_DIR.'class.collaborator.php');
+require_once(INCLUDE_DIR.'class.task.php');
 require_once(INCLUDE_DIR.'class.faq.php');
 
 class TicketModel extends VerySimpleModel {
@@ -188,11 +189,14 @@ class Ticket {
 
         $sql='SELECT  ticket.*, thread.id as thread_id, lock_id, dept.name as dept_name '
             .' ,count(distinct attach.attach_id) as attachments'
+            .' ,count(distinct task.id) as tasks'
             .' FROM '.TICKET_TABLE.' ticket '
             .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.id) '
             .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) '
             .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock
                 ON ( ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()) '
+            .' LEFT JOIN '.TASK_TABLE.' task
+                ON ( task.object_id = ticket.ticket_id AND task.object_type="T" ) '
             .' LEFT JOIN '.THREAD_TABLE.' thread
                 ON ( thread.object_id = ticket.ticket_id AND thread.object_type="T" ) '
             .' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry
@@ -718,6 +722,10 @@ class Ticket {
         return $this->last_message;
     }
 
+    function getNumTasks() {
+        return $this->ht['tasks'];
+    }
+
     function getThreadId() {
         return $this->ht['thread_id'];
     }
diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml
index 363b9abef8642f8587fa85bf4d19854a1c68a746..577dd5539694eea2cf66a02b493e13d56aa50660 100644
--- a/include/i18n/en_US/form.yaml
+++ b/include/i18n/en_US/form.yaml
@@ -181,3 +181,39 @@
       configuration:
         rows: 4
         cols: 40
+- type: A # notrans
+  title: Task Details
+  instructions: Please Describe The Issue
+  notes: |
+      This form is used to create tasks.
+  deletable: false
+  fields:
+    - type: text # notrans
+      name: title # notrans
+      label: Title
+      required: true
+      edit_mask: 15
+      flags: 1
+      sort: 1
+      configuration:
+        size: 40
+        length: 50
+    - type: thread # notrans
+      name: description # notrans
+      label: Description
+      hint: Details on the reason(s) for creating the task.
+      required: true
+      edit_mask: 15
+      flags: 3
+      sort: 2
+    - type: datetime # notrans
+      name: duedate # notrans
+      label: Due Date
+      required: false
+      edit_mask: 15
+      flags: 3
+      sort: 3
+      configuration:
+        time: true
+        gmt: true
+        future: true
diff --git a/include/i18n/en_US/sequence.yaml b/include/i18n/en_US/sequence.yaml
index bb502c3526c287280a2fe664419989e26c9307ef..f67594aa91269309ddad0a470c3a3c07ac85e151 100644
--- a/include/i18n/en_US/sequence.yaml
+++ b/include/i18n/en_US/sequence.yaml
@@ -21,3 +21,10 @@
   padding: '0'
   increment: 1
   flags: 1
+
+- id: 2
+  name: "Tasks Sequence"
+  next: 1
+  padding: '0'
+  increment: 1
+  flags: 1
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 252eb31ba78793be662ab22a616f2973246af4b4..73c2003f8c53fd3a92199db12c39bff375b68149 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-4b4daf9cf5e199673885f5ef58e743d1
+48b4499152a6c11e714d4a40fc33332a
diff --git a/include/upgrader/streams/core/4b4daf9c-48b44991.patch.sql b/include/upgrader/streams/core/4b4daf9c-48b44991.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..7ceb7daed55117866ff95f206adb9eb513133a92
--- /dev/null
+++ b/include/upgrader/streams/core/4b4daf9c-48b44991.patch.sql
@@ -0,0 +1,61 @@
+/**
+ * @version v1.9.5
+ * @signature 48b4499152a6c11e714d4a40fc33332a
+ * @title Add tasks
+ *
+ * This patch introduces the concept of tasks
+ *
+ */
+
+-- create task task
+CREATE TABLE `%TABLE_PREFIX%task` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL DEFAULT '0',
+  `object_type` char(1) NOT NULL,
+  `number` varchar(20) DEFAULT NULL,
+  `dept_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `sla_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `staff_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `team_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `flags` int(10) unsigned NOT NULL DEFAULT '0',
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `dept_id` (`dept_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `team_id` (`team_id`),
+  KEY `created` (`created`),
+  KEY `sla_id` (`sla_id`),
+  KEY `object` (`object_id`,`object_type`)
+) DEFAULT CHARSET=utf8;
+
+-- Add flags field to form field
+ALTER TABLE  `%TABLE_PREFIX%form_field`
+    ADD  `flags` INT UNSIGNED NOT NULL DEFAULT  '1' AFTER  `form_id`;
+
+-- Flag field stored in the system elsewhere as nonstorable locally.
+UPDATE `%TABLE_PREFIX%form_field` A1 JOIN `%TABLE_PREFIX%form` A2 ON(A2.id=A1.form_id)
+    SET A1.`flags` = 3
+    WHERE A2.`type` = 'U' AND A1.`name` IN('name','email');
+
+UPDATE `%TABLE_PREFIX%form_field` A1 JOIN `%TABLE_PREFIX%form` A2 ON(A2.id=A1.form_id)
+    SET A1.`flags`=3
+    WHERE A2.`type`='O' AND A1.`name` IN('name');
+
+-- TODO: add ticket thread entry??
+
+
+-- rename ticket sequence numbering
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `key` = 'ticket_number_format'
+    WHERE `key` = 'number_format'  AND `namespace` = 'core';
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `key` = 'ticket_sequence_id'
+    WHERE `key` = 'sequence_id'  AND `namespace` = 'core';
+
+-- Set new schema signature
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = '48b4499152a6c11e714d4a40fc33332a'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/4b4daf9c-48b44991.task.php b/include/upgrader/streams/core/4b4daf9c-48b44991.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..baf686b4d367228276c08710baa60916d55d47e1
--- /dev/null
+++ b/include/upgrader/streams/core/4b4daf9c-48b44991.task.php
@@ -0,0 +1,38 @@
+<?php
+/*
+ * Import initial form for task
+ *
+ */
+
+class TaskFormLoader extends MigrationTask {
+    var $description = "Loading initial data for tasks";
+
+    function run($max_time) {
+        global $cfg;
+
+        // Load task form
+        require_once INCLUDE_DIR.'class.task.php';
+        Task::__loadDefaultForm();
+        // Load sequence for the task
+        $i18n = new Internationalization($cfg->get('system_language', 'en_US'));
+        $sequences = $i18n->getTemplate('sequence.yaml')->getData();
+        foreach ($sequences as $s) {
+            if ($s['id'] != 2) continue;
+            unset($s['id']);
+            $sq=Sequence::create($s);
+            $sq->save();
+            $sql= 'INSERT INTO '.CONFIG_TABLE
+                .' (`namespace`, `key`, `value`) '
+                .' VALUES
+                    ("core", "task_number_format", "###"),
+                    ("core", "task_sequence_id",'.db_input($sq->id).')';
+            db_query($sql);
+            break;
+        }
+
+    }
+}
+
+return 'TaskFormLoader';
+
+?>
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index d91ce79ab86f5fed2f4d628a84c9a2b5ca1ec40a..b106d286f2a665cf2acc296ae222ffd2298d7aae 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -768,6 +768,28 @@ CREATE TABLE `%TABLE_PREFIX%ticket_collaborator` (
   UNIQUE KEY `collab` (`ticket_id`,`user_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%task`;
+CREATE TABLE `%TABLE_PREFIX%task` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL DEFAULT '0',
+  `object_type` char(1) NOT NULL,
+  `number` varchar(20) DEFAULT NULL,
+  `dept_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `sla_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `staff_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `team_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `flags` int(10) unsigned NOT NULL DEFAULT '0',
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `dept_id` (`dept_id`),
+  KEY `staff_id` (`staff_id`),
+  KEY `team_id` (`team_id`),
+  KEY `created` (`created`),
+  KEY `sla_id` (`sla_id`),
+  KEY `object` (`object_id`,`object_type`)
+) DEFAULT CHARSET=utf8;
+
 -- pages
 CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%content` (
   `id` int(10) unsigned NOT NULL auto_increment,