diff --git a/reviewboard/reviews/models.py b/reviewboard/reviews/models.py
index 56ee445ab05031170cd28436b7fdb040fa33eeb9..9830fee06c564cd3dab18bdde5db7284d88a6b0f 100644
--- a/reviewboard/reviews/models.py
+++ b/reviewboard/reviews/models.py
@@ -1911,6 +1911,10 @@ class Action(models.Model):
         else:
             return 'review'
 
+    @property
+    def display_verb(self):
+        return self.get_verb_display()
+
     class Meta:
         ordering = ['-timestamp']
         get_latest_by = 'timestamp'
diff --git a/reviewboard/reviews/views.py b/reviewboard/reviews/views.py
index c9b72fb15c22bca4918446249e4b6ab40982a5b0..5f45d70830b687f0258fe7e0decf3f57790a4f51 100644
--- a/reviewboard/reviews/views.py
+++ b/reviewboard/reviews/views.py
@@ -836,8 +836,7 @@ def dashboard(request,
         # review requests.
         grid = WatchedGroupDataGrid(request, local_site=local_site)
     elif view == "action-feed":
-        view = "action-feed"
-        grid = ActionFeed(request, local_site=local_site)
+        grid = ActionFeed(request)
     else:
         grid = DashboardDataGrid(request, local_site=local_site)
 
@@ -848,32 +847,12 @@ def dashboard(request,
 
 
 class ActionFeed(object):
-    def __init__(self, request, local_site):
-        self.request = request
-        self.local_site = local_site
+    def __init__(self, request):
+        self.request = request;
 
     def render_to_response(self, template_name, extra_context={}):
-        """
-        The action feed for any particular user should display all actions
-        performed on requests the user has previously visited.
-        """
-        actions = Action.objects.extra(
-            tables=['accounts_reviewrequestvisit'],
-            where=['accounts_reviewrequestvisit.user_id = %s' %
-                   self.request.user.pk,
-                   'accounts_reviewrequestvisit.review_request_id = '
-                   'reviews_action.review_request_id'])
-
-        if self.local_site:
-            local_site_id = self.local_site.id
-            extra_context['local_site'] = self.local_site.name
-        else:
-            local_site_id = None
-
-        actions = actions.filter(local_site_id=local_site_id)
         context = {
             'user': self.request.user,
-            'stream': actions,
         }
         context.update(extra_context)
 
diff --git a/reviewboard/static/rb/css/action-feed.less b/reviewboard/static/rb/css/action-feed.less
index 52fca9bd0309a3011847625bd0b7983d8e6d4f49..b8cc70af6d6aa92be511c57c9f3c20358aa37fb3 100644
--- a/reviewboard/static/rb/css/action-feed.less
+++ b/reviewboard/static/rb/css/action-feed.less
@@ -1,6 +1,6 @@
 @import "defs.less";
 
-.container_action_feed {
+.container-action-feed {
   padding: 3px;
 }
 
@@ -103,6 +103,21 @@
     padding-right: 5px;
     width: 15px;
   }
+
+  .load-more {
+    margin-left: auto;
+    margin-right: auto;
+    width: 30%;
+
+    .load-more-button {
+      width: 20em;
+      border-width: 1px;
+      border-style: solid;
+      text-align: center;
+      font-size: 1.3em;
+      background-color: #C6DCF3;
+    }
+  }
 }
 
 .issue-state {
diff --git a/reviewboard/static/rb/js/action-feed.js b/reviewboard/static/rb/js/action-feed.js
index 2ab07f0e55b25c19006ec9f9fbffd27895204b4e..5bedb7705278567e042b7cb344c9d4df17874136 100644
--- a/reviewboard/static/rb/js/action-feed.js
+++ b/reviewboard/static/rb/js/action-feed.js
@@ -129,7 +129,7 @@ function with_change(collapse_button, change, callback) {
             }
 
             if (!multiline) {
-                changeitem.append(' changed from');
+                changeitem.append(' changed from ');
                 changeitem.append($('<i/>').text(oldValue));
                 changeitem.append(' to ');
                 changeitem.append($('<i/>').text(newValue));
@@ -204,7 +204,7 @@ function get_comment_text(comment) {
 function get_comment_anchor(comment) {
     return $('<a/>')
         .addClass("comment-anchor")
-        attr('name', comment.anchor_prefix + comment.id);
+        .attr('name', comment.anchor_prefix + comment.id);
 }
 
 function with_review(collapse_button, review, callback) {
@@ -308,39 +308,140 @@ function with_review(collapse_button, review, callback) {
     callback();
 }
 
-$(document).ready(function() {
-    $('.collapse-button').click(function() {
-        var self = $(this),
-            action_id = self.attr('data-action-id'),
-            action_type = self.attr('data-object-type'),
-            action, expand;
-
-        function toggle_collapsed() {
-            $(self).closest('.box').toggleClass('collapsed');
-        }
+function append_actions(action_list) {
+    var container = $('#action-feed-content'),
+        site_prefix = SITE_ROOT + gSitePrefix,
+        button = $('#load-more'),
+        last_id = button.data('last_id'),
+        i, action_list_length = action_list.actions.length,
+        action, box_inner, action_summary, collapse_button, reviewer,
+        posted_time, header, posted_text;
+
+    for (i = 0; i < action_list_length; i++) {
+        action = action_list.actions[i];
+        box_inner = $('<div/>')
+            .addClass('box-inner')
+            .appendTo($('<div/>')
+                .addClass('box')
+                .addClass('action_feed')
+                .addClass('collapsed')
+                .addClass(action.type)
+                .appendTo($('<div/>')
+                    .addClass('box-container')
+                    .appendTo(container)
+                )
+            );
+        action_summary = $('<div/>')
+            .addClass('action-summary')
+            .text('Review Request ')
+            .append($('<a/>')
+                .attr('href', site_prefix + 'r/' + action.request_display_id +
+                      '/')
+                .text('#' + action.request_display_id)
+            )
+            .append(': ')
+            .append($('<b/>')
+                .text(action.summary)
+            );
+        collapse_button = $('<div/>')
+            .addClass('collapse-button')
+            .click(on_collapse)
+            .attr('data-object-type', action.type)
+            .attr('data-action-id', action.id)
+            .attr('data-review-display-id', action.request_display_id);
+        reviewer = $('<div/>')
+            .append($('<a/>')
+                .addClass('user')
+                .attr('href', site_prefix + action.submitter.url.substring(1))
+                .text(action.submitter.username)
+            )
+            .append(' ' + action.display_verb);
 
-        if (action_type === 'review') {
-            expand = 'review,diff_comments,file_attachment_comments,' +
-                'screenshot_comments';
+        if (action.type === 'change') {
+            posted_text = 'Updated';
         } else {
-            expand = 'changedesc';
+            posted_text = 'Posted';
         }
 
-        action = self.data(action_id);
+        posted_time = $('<div/>')
+            .addClass('posted_time')
+            .text(action.timesince + ' ago');
+        header = $('<div/>')
+            .addClass('header')
+            .append(action_summary)
+            .append(collapse_button)
+            .append(reviewer)
+            .append(posted_time);
+        $('<div/>')
+            .addClass('main')
+            .append(header)
+            .append($('<div/>')
+                .addClass('body')
+            )
+            .appendTo(box_inner);
+        last_id = action.id;
+    }
 
-        if (!action) {
-            action = new RB.Action(action_id, expand, gSitePrefix);
-            action.ready(function() {
-                self.data(action_id, action);
+    button.data('last_id', last_id);
+}
+
+function load_more() {
+    var button = $('#load-more'),
+        last_id = button.data('last_id'),
+        action_list;
+
+    if (!last_id) {
+        last_id = 0;
+        button.data('last_id', 0);
+    }
+
+    action_list = new RB.ActionList(last_id, 'submitter', button, gSitePrefix);
+    action_list.ready(function() {
+        append_actions(action_list);
+    });
+    return false;
+}
+
+function on_collapse(){
+    var self = $(this),
+        action_id = self.attr('data-action-id'),
+        action_type = self.attr('data-object-type'),
+        action, expand;
+
+    function toggle_collapsed() {
+        $(self).closest('.box').toggleClass('collapsed');
+    }
+
+    if (action_type === 'review') {
+        expand = 'review,diff_comments,file_attachment_comments,' +
+                 'screenshot_comments';
+    } else {
+        expand = 'changedesc';
+    }
+
+    action = self.data(action_id);
+
+    if (!action) {
+        action = new RB.Action(action_id, expand, gSitePrefix);
+        action.ready(function() {
+            self.data(action_id, action);
                 if (action_type === 'review') {
                     with_review(self, action.review, toggle_collapsed);
                 } else {
                     with_change(self, action.changedesc, toggle_collapsed);
                 }
             });
-        } else {
-            toggle_collapsed();
-        }
+    } else {
+        toggle_collapsed();
+    }
+};
+
+
+$(document).ready(function() {
+    load_more();
+
+    $('#load-more').click(function(){
+        load_more();
     });
 });
 
diff --git a/reviewboard/static/rb/js/datastore.js b/reviewboard/static/rb/js/datastore.js
index cde83fc15aeb56d2ec632c7107356c713848435e..f7f255a73893a93bbfb62f22bbbcb24fb43f66bf 100644
--- a/reviewboard/static/rb/js/datastore.js
+++ b/reviewboard/static/rb/js/datastore.js
@@ -48,6 +48,53 @@ $.extend(RB.Action.prototype, {
     }
 });
 
+RB.ActionList = function(last_id, expand, disable, prefix) {
+    this.prefix = prefix;
+    this.last_id = last_id;
+    this.expand = expand;
+    this.loaded = false;
+    this.disable = disable;
+    this.max_results = 15;
+    return this;
+};
+
+$.extend(RB.ActionList.prototype, {
+
+    ready: function(on_ready) {
+
+        if (this.loaded) {
+            on_ready.apply(this, arguments);
+        } else {
+            this._load(on_ready);
+        }
+    },
+
+    _load: function(on_done) {
+        var self = this;
+        console.log(self);
+        RB.apiCall({
+            type: "GET",
+            buttons: self.disable,
+            prefix: self.prefix,
+            path: "/session/actions?last_id=" + self.last_id +
+                  "&max-results=" + self.max_results +
+                  "&expand=" + this.expand,
+            success: function(rsp, status) {
+                if (status != 404) {
+                    self._loadDataFromResponse(rsp);
+                }
+
+                on_done.apply(this, arguments);
+            }
+        });
+    },
+
+    _loadDataFromResponse: function(rsp) {
+        this.actions = rsp.actions;
+        this.loaded = true;
+    }
+});
+
 RB.DiffComment = function(review, id, filediff, interfilediff, beginLineNum,
                           endLineNum) {
     this.id = id;
diff --git a/reviewboard/templates/reviews/action_feed.html b/reviewboard/templates/reviews/action_feed.html
deleted file mode 100644
index 33952e52f99840049874646773ccbdda545cb764..0000000000000000000000000000000000000000
--- a/reviewboard/templates/reviews/action_feed.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% load compressed %}
-{% load djblets_deco %}
-{% load djblets_utils %}
-{% load i18n %}
-{% load reviewtags %}
-{% load staticfiles %}
-{% load tz %}
-
-<div class="container_action_feed">
-{% for action in stream %}
-
-{%  definevar "boxclass" %}{{action.type}} action_feed collapsed {% enddefinevar %}
-{%  box boxclass %}
- <div class="main">
-  <div class="header">
-   <div class="action-summary">
-    {% trans "Review Request" %}<a href={% url review-request-detail action.request_display_id %}> #{{action.request_display_id}}</a>:
-    <b>{{action.summary}}</b>
-</div>
-   <div class="collapse-button" data-object-type="{{action.type}}" data-action-id="{{action.id}}"
-                                data-review-display-id="{{action.request_display_id}}"></div>
-   <div class="reviewer"><a href="{% url user action.submitter %}" class="user">{{action.submitter|user_displayname}}</a> {{action.get_verb_display}} </div>
-     <div class="posted_time">
-{%   if action.review %}
-     {% trans "Posted" %}
-{%   else %}
-     {% trans "Updated" %}
-{%   endif %}
-{% localtime on %}{% blocktrans with action.timestamp|timesince as timestamp_since  and action.timestamp|date:"F jS, Y, P" as timestamp_date %}
-    {{timestamp_since}} ago ({{timestamp_date}}){% endblocktrans %}{% endlocaltime %}</div>
-    </div>
-   <div class="body">
-   </div><!-- body -->
- </div><!-- main -->
-{%  endbox %}
-{% endfor %}
- </div>
-</div>
-</div>
diff --git a/reviewboard/templates/reviews/dashboard.html b/reviewboard/templates/reviews/dashboard.html
index 15c7bc94d88ee8e178f5e5d5caf7ea67be0248f0..c4698649e97521e6cd495ef3b721125368976601 100755
--- a/reviewboard/templates/reviews/dashboard.html
+++ b/reviewboard/templates/reviews/dashboard.html
@@ -68,7 +68,12 @@
  </table>
  <div id="dashboard-main" class="clearfix">
 {%  if view == 'action-feed' %}
-{%   include "reviews/action_feed.html" %}
+ <div class="container-action-feed">
+  <div id="action-feed-content"></div>
+  <div class="load-more">
+   <input type="button" class="load-more-button" id="load-more" value="Load more"></input>
+  </div>
+ </div>
 {%  else %}
     {{datagrid.render_listview}}
 {%  endif %}
diff --git a/reviewboard/webapi/resources.py b/reviewboard/webapi/resources.py
index c2f0dcfa485a3fa42d6ff1ce2bf238f526068531..2bb0491c8fbd06f5c546b130add700441d1eb73c 100644
--- a/reviewboard/webapi/resources.py
+++ b/reviewboard/webapi/resources.py
@@ -240,6 +240,14 @@ class ActionResource(WebAPIResource):
             'type': 'reviewboard.webapi.resources.ChangeResource',
             'description': 'The change description.',
         },
+        'request_display_id': {
+            'type': int,
+            'description': 'The local site review request id.',
+        },
+        'related_id': {
+            'type': int,
+            'description': 'The related object (review or changedesc) id.',
+        },
         'type': {
             'type': str,
             'description': 'The type of the action. Either "change", "review" or "reply"',
@@ -248,16 +256,33 @@ class ActionResource(WebAPIResource):
             'type': str,
             'description': 'Verb that describes the action.'
         },
+        'display_verb': {
+            'type': str,
+            'description': 'Human readable version of the verb.'
+        },
         'timestamp': {
             'type': str,
             'description': 'The date and time that the change was made '
                            '(in YYYY-MM-DD HH:MM:SS format).',
         },
+        'timesince': {
+            'type': str,
+            'descriptions': 'The timestamp in timesince format.',
+        }
     }
     uri_object_key = 'action_id'
     last_modified_field = 'timestamp'
     allowed_methods = ('GET',)
 
+    @webapi_request_fields(
+        optional={
+            'last_id': {
+                'type': int,
+                'description': 'The returned ids are smaller than last_id.'
+            },
+        },
+        allow_unknown=True
+    )
     @webapi_check_local_site
     @augment_method_from(WebAPIResource)
     def get_list(self, *args, **kwargs):
@@ -272,15 +297,23 @@ class ActionResource(WebAPIResource):
         pass
 
     def get_queryset(self, request, is_list=False, local_site_name=None,
-                     *args, **kwargs):
+                    *args, **kwargs):
         actions = self.model.objects.extra(
             tables=['accounts_reviewrequestvisit'],
             where=['accounts_reviewrequestvisit.user_id = %s' %
-                    request.user.pk,
+                   request.user.pk,
                    'accounts_reviewrequestvisit.review_request_id = '
                    'reviews_action.review_request_id'])
 
         try:
+            last_id = int(request.GET.get('last_id', '0'))
+        except ValueError:
+            last_id = 0
+
+        if last_id:
+            actions = actions.filter(id__lt=last_id)
+
+        try:
             local_site = LocalSite.objects.get(name=local_site_name)
             local_site_id = local_site.pk
         except LocalSite.DoesNotExist:
@@ -288,6 +321,9 @@ class ActionResource(WebAPIResource):
 
         return actions.filter(local_site_id=local_site_id)
 
+    def serialize_timesince_field(self, obj):
+        return timesince(obj.timestamp)
+
 action_resource= ActionResource()
 
 
