Merge "veyron/examples/stfortune: Bug fix to handle same fortune being put repeatedly."
diff --git a/examples/boxes/android/src/boxesp2p/main.go b/examples/boxes/android/src/boxesp2p/main.go
index b157517..8b0b724 100644
--- a/examples/boxes/android/src/boxesp2p/main.go
+++ b/examples/boxes/android/src/boxesp2p/main.go
@@ -68,14 +68,16 @@
 	"veyron/examples/boxes"
 	inaming "veyron/runtimes/google/naming"
 	vsync "veyron/runtimes/google/vsync"
-	"veyron/services/store/raw"
-	storage "veyron/services/store/server"
+	sstore "veyron/services/store/server"
 
 	"veyron2"
 	"veyron2/ipc"
 	"veyron2/naming"
 	"veyron2/rt"
 	"veyron2/security"
+	istore "veyron2/services/store"
+	iwatch "veyron2/services/watch"
+	"veyron2/storage"
 	"veyron2/storage/vstore"
 	"veyron2/storage/vstore/primitives"
 	"veyron2/vom"
@@ -95,7 +97,6 @@
 
 type goState struct {
 	runtime       veyron2.Runtime
-	store         *storage.Server
 	ipc           ipc.Server
 	disp          boxesDispatcher
 	drawStream    boxes.DrawInterfaceServiceDrawStream
@@ -192,14 +193,16 @@
 func (gs *goState) monitorStore() {
 	ctx := gs.runtime.NewContext()
 
+	vst, err := vstore.New(gs.storeEndpoint)
+	if err != nil {
+		panic(fmt.Errorf("Failed to init veyron store:%v", err))
+	}
+	root := vst.Bind("/")
+
 	// Watch for any box updates from the store
 	go func() {
-		rst, err := raw.BindStore(naming.JoinAddressName(gs.storeEndpoint, raw.RawStoreSuffix))
-		if err != nil {
-			panic(fmt.Errorf("Failed to raw.Bind Store:%v", err))
-		}
-		req := raw.Request{}
-		stream, err := rst.Watch(ctx, req, veyron2.CallTimeout(ipc.NoTimeout))
+		req := iwatch.GlobRequest{Pattern: "*"}
+		stream, err := root.WatchGlob(ctx, req)
 		if err != nil {
 			panic(fmt.Errorf("Can't watch store: %s: %s", gs.storeEndpoint, err))
 		}
@@ -209,20 +212,16 @@
 				panic(fmt.Errorf("Can't receive watch event: %s: %s", gs.storeEndpoint, err))
 			}
 			for _, change := range cb.Changes {
-				if mu, ok := change.Value.(*raw.Mutation); ok && len(mu.Dir) == 0 {
-					if box, ok := mu.Value.(boxes.Box); ok && box.DeviceId != gs.myIPAddr {
+				if entry, ok := change.Value.(*storage.Entry); ok {
+					if box, ok := entry.Value.(boxes.Box); ok && box.DeviceId != gs.myIPAddr {
 						nativeJava.addBox(&box)
 					}
 				}
 			}
 		}
 	}()
+
 	// Send any box updates to the store
-	vst, err := vstore.New(gs.storeEndpoint)
-	if err != nil {
-		panic(fmt.Errorf("Failed to init veyron store:%v", err))
-	}
-	root := vst.Bind("/")
 	tr := primitives.NewTransaction(ctx)
 	if _, err := root.Put(ctx, tr, ""); err != nil {
 		panic(fmt.Errorf("Put for %s failed:%v", root, err))
@@ -321,10 +320,10 @@
 	}
 
 	// Create a new store server
-	var err error
 	storeDBName := storePath + "/" + storeDatabase
-	if gs.store, err = storage.New(storage.ServerConfig{Admin: publicID, DBName: storeDBName}); err != nil {
-		panic(fmt.Errorf("storage.New() failed:%v", err))
+	store, err := sstore.New(sstore.ServerConfig{Admin: publicID, DBName: storeDBName})
+	if err != nil {
+		panic(fmt.Errorf("store.New() failed:%v", err))
 	}
 
 	// Create ACL Authorizer with read/write permissions for the identity
@@ -333,7 +332,7 @@
 		panic(fmt.Errorf("LoadACL failed:%v", err))
 	}
 	auth := security.NewACLAuthorizer(acl)
-	gs.disp.storeDispatcher = storage.NewStoreDispatcher(gs.store, auth)
+	gs.disp.storeDispatcher = sstore.NewStoreDispatcher(store, auth)
 
 	// Create an endpoint and start listening
 	if _, err = gs.ipc.Listen("tcp", gs.myIPAddr+storeServicePort); err != nil {
@@ -360,7 +359,10 @@
 }
 
 func init() {
-	vom.Register(&raw.Mutation{})
+	// Register *store.Entry for WatchGlob.
+	// TODO(tilaks): store.Entry is declared in vdl, vom should register the
+	// pointer automatically.
+	vom.Register(&istore.Entry{})
 	runtime.GOMAXPROCS(runtime.NumCPU())
 }
 
diff --git a/examples/pipetobrowser/Makefile b/examples/pipetobrowser/Makefile
index 2acdaf0..22c9feb 100644
--- a/examples/pipetobrowser/Makefile
+++ b/examples/pipetobrowser/Makefile
@@ -43,9 +43,9 @@
 	$(VEYRON_IDENT) --name=veyron_p2b_identity > $(VEYRON_IDENTITY_PATH)
 
 	export VEYRON_IDENTITY=$(VEYRON_IDENTITY_PATH) ; \
-	$(VEYRON_IDENTITYD) --port=$(VEYRON_IDENTITY_PORT) & \
+	$(VEYRON_IDENTITYD) --address=:$(VEYRON_IDENTITY_PORT) & \
 	$(VEYRON_MOUNTTABLE) --address=:$(VEYRON_MOUNTTABLE_PORT) & \
-	export NAMESPACE_ROOT=/localhost:$(VEYRON_MOUNTTABLE_PORT)/mt ; \
+	export NAMESPACE_ROOT=/localhost:$(VEYRON_MOUNTTABLE_PORT) ; \
 	$(VEYRON_PROXY) -address=$(VEYRON_PROXY_ADDR) & \
 	$(VEYRON_WSPR) --v=1 -logtostderr=true -vproxy=$(VEYRON_PROXY_ADDR) --port $(VEYRON_WSPR_PORT) & \
 	$(VEYRON_STORE) --address=:$(VEYRON_STORE_PORT) --name=global/$(USER)/store &
diff --git a/examples/pipetobrowser/browser/actions/add-pipe-viewer.js b/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
index 1919ec2..ae07987 100644
--- a/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
+++ b/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
@@ -14,6 +14,7 @@
 
 import { displayError } from 'actions/display-error'
 import { navigatePipesPage } from 'actions/navigate-pipes-page'
+import { redirectPipe } from 'actions/redirect-pipe'
 
 import { LoadingView } from 'views/loading/view'
 
@@ -52,15 +53,16 @@
   var count = (pipesPerNameCounter[name] || 0) + 1;
   pipesPerNameCounter[name] = count;
   var tabKey = name + count;
-  var tabName = name + ' #' + count;
+  var tabName = 'Loading...';
 
   // Get the plugin that can render the stream, ask it to play it and display
   // the element returned by the pipeViewer.
   getPipeViewer(name).then((pipeViewer) => {
+    tabName = pipeViewer.name + ' #' + count
     return pipeViewer.play(stream);
   }).then((pipeViewerView) => {
     // replace the loading view with the actual viewerView
-    pipesViewInstance.replaceTabView(tabKey, pipeViewerView);
+    pipesViewInstance.replaceTabView(tabKey, tabName, pipeViewerView);
   }).catch((e) => { displayError(e); });
 
   // Add a new tab and show a loading indicator for now,
@@ -68,6 +70,12 @@
   var loadingView = new LoadingView();
   pipesViewInstance.addTab(tabKey, tabName, loadingView);
 
+  // Add the redirect stream action
+  var icon = 'hardware:cast';
+  pipesViewInstance.addToolbarAction(tabKey, icon, () => {
+    redirectPipe(stream, name);
+  });
+
   // Take the user to the pipes view.
   navigatePipesPage();
-}
\ No newline at end of file
+}
diff --git a/examples/pipetobrowser/browser/actions/navigate-home-page.js b/examples/pipetobrowser/browser/actions/navigate-home-page.js
index 16f6406..ea90138 100644
--- a/examples/pipetobrowser/browser/actions/navigate-home-page.js
+++ b/examples/pipetobrowser/browser/actions/navigate-home-page.js
@@ -7,7 +7,7 @@
 import { Logger } from 'libs/logs/logger'
 import { register, trigger } from 'libs/mvc/actions'
 
-import { publish, stopPublishing, state as publishState } from 'services/pipe-to-browser'
+import { publish, stopPublishing, state as publishState } from 'services/pipe-to-browser-server'
 
 import { displayError } from 'actions/display-error'
 import { addPipeViewer } from 'actions/add-pipe-viewer'
diff --git a/examples/pipetobrowser/browser/actions/navigate-neighborhood.js b/examples/pipetobrowser/browser/actions/navigate-neighborhood.js
new file mode 100644
index 0000000..998e345
--- /dev/null
+++ b/examples/pipetobrowser/browser/actions/navigate-neighborhood.js
@@ -0,0 +1,49 @@
+/*
+ * Navigates to neighborhood page displaying list of P2B names that are online
+ * @fileoverview
+ */
+
+import { Logger } from 'libs/logs/logger'
+import { register, trigger } from 'libs/mvc/actions'
+
+import { displayError } from 'actions/display-error'
+import { page } from 'runtime/context'
+
+import { NeighborhoodView } from 'views/neighborhood/view'
+import { getAll as getAllPublishedP2BNames } from 'services/pipe-to-browser-namespace'
+
+var log = new Logger('actions/navigate-neighborhood');
+const ACTION_NAME = 'neighborhood';
+
+/*
+ * Registers the action
+ */
+export function registerNavigateNeigbourhoodAction() {
+  register(ACTION_NAME, actionHandler);
+}
+
+/*
+ * Triggers the action
+ */
+export function navigateNeigbourhood() {
+  return trigger(ACTION_NAME);
+}
+
+/*
+ * Handles the action.
+ *
+ * @private
+ */
+function actionHandler() {
+  log.debug('navigate neighborhood triggered');
+
+  // create an neighborhood view
+  var neighborhoodView = new NeighborhoodView();
+
+  // get all the online names and set it on the view
+  getAllPublishedP2BNames().then((allNames) => {
+    neighborhoodView.existingNames = allNames;
+  }).catch((e) => { displayError(e); });
+
+  page.setSubPageView('neighborhood', neighborhoodView);
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/actions/redirect-pipe.js b/examples/pipetobrowser/browser/actions/redirect-pipe.js
new file mode 100644
index 0000000..5d37832
--- /dev/null
+++ b/examples/pipetobrowser/browser/actions/redirect-pipe.js
@@ -0,0 +1,69 @@
+/*
+ * Redirects a stream to another veyron name. It prompts the user to pick
+ * a Veyron name before redirecting and allows the user to chose between
+ * redirecting all the data or just new incoming data.
+ * @fileoverview
+ */
+import { Logger } from 'libs/logs/logger'
+import { register, trigger } from 'libs/mvc/actions'
+
+import { page } from 'runtime/context'
+
+import { RedirectPipeDialogView } from 'views/redirect-pipe-dialog/view'
+import { pipe } from 'services/pipe-to-browser-client'
+import { getAll as getAllPublishedP2BNames } from 'services/pipe-to-browser-namespace'
+
+var log = new Logger('actions/redirect-pipe');
+const ACTION_NAME = 'redirect-pipe';
+
+/*
+ * Registers the redirect pipe action
+ */
+export function registerRedirectPipeAction() {
+  register(ACTION_NAME, actionHandler);
+}
+
+/*
+ * Triggers the redirect pipe action
+ * @param {stream} stream Stream object to redirect
+ * @param {string} currentPluginName name of the current plugin
+ */
+export function redirectPipe(stream, currentPluginName) {
+  return trigger(ACTION_NAME, stream, currentPluginName);
+}
+
+/*
+ * Handles the redirect pipe action.
+ *
+ * @private
+ */
+function actionHandler(stream, currentPluginName) {
+  log.debug('redirect pipe action triggered');
+
+  // display a dialog asking user where to redirect and whether to redirect
+  // all the data or just new data.
+  var dialog = new RedirectPipeDialogView();
+  dialog.open();
+
+  // if user decides to redirect, copy the stream and pipe it.
+  dialog.onRedirectAction((name, newDataOnly) => {
+    var copyStream = stream.copier.copy(newDataOnly);
+
+    pipe(name, copyStream).then(() => {
+      page.showToast('Redirected successfully to ' + name);
+    }).catch((e) => {
+      page.showToast('FAILED to redirect to ' + name + '. Please see console for error details.');
+      log.debug('FAILED to redirect to', name, e);
+    });
+  });
+
+  // also get the list of all existing P2B names in the namespace and supply it to the dialog
+  getAllPublishedP2BNames().then((allNames) => {
+    // append current plugin name to the veyron names for better UX
+    dialog.existingNames = allNames.map((n) => {
+      return n + '/pipe/' + (currentPluginName || ''); //TODO(aghassemi) publish issue
+    });
+  }).catch((e) => {
+    log.debug('getAllPublishedP2BNames failed', e);
+  });
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/config.js b/examples/pipetobrowser/browser/config.js
index 0240c14..2fcb266 100644
--- a/examples/pipetobrowser/browser/config.js
+++ b/examples/pipetobrowser/browser/config.js
@@ -11,5 +11,6 @@
     proxy: 'http://localhost:5165',
     logLevel: veyronLogLevels.INFO
   },
+  namespaceRoot: '/localhost:5167',
   publishNamePrefix: 'google/p2b'
 }
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/index.css b/examples/pipetobrowser/browser/index.css
new file mode 100644
index 0000000..3b0dd96
--- /dev/null
+++ b/examples/pipetobrowser/browser/index.css
@@ -0,0 +1,4 @@
+body {
+  font-family: 'Roboto', sans-serif;
+  color: rgba(0,0,0,0.87);
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/index.html b/examples/pipetobrowser/browser/index.html
index 9315525..e37405b 100644
--- a/examples/pipetobrowser/browser/index.html
+++ b/examples/pipetobrowser/browser/index.html
@@ -10,20 +10,24 @@
 
   <script src="libs/vendor/polymer/platform/platform.js"></script>
 
-  <!-- TODO(aghassemi) For Production - Use Volcanize to combine all components into a single import at build time -->
-  <link rel="import" href="views/page/component.html">
-  <link rel="import" href="views/publish/component.html">
-  <link rel="import" href="views/status/component.html">
-  <link rel="import" href="views/error/component.html">
-  <link rel="import" href="views/loading/component.html">
-  <link rel="import" href="views/pipes/component.html">
+  <link rel="stylesheet" type="text/css" href="index.css"/>
 
-  <link rel="import" href="libs/ui-components/blackhole/component.html">
+  <!-- TODO(aghassemi) For Production - Use Volcanize to combine all components into a single import at build time -->
+  <link rel="import" href="views/page/component.html"/>
+  <link rel="import" href="views/publish/component.html"/>
+  <link rel="import" href="views/status/component.html"/>
+  <link rel="import" href="views/error/component.html"/>
+  <link rel="import" href="views/loading/component.html"/>
+  <link rel="import" href="views/pipes/component.html"/>
+  <link rel="import" href="views/redirect-pipe-dialog/component.html"/>
+  <link rel="import" href="views/neighborhood/component.html"/>
+
+  <link rel="import" href="libs/ui-components/blackhole/component.html"/>
 
   <!-- TODO(aghassemi) Dynamic loading of plugins. Plugins should be able to use a provided module to load web components if they need to. We don't want to load all plugins for no reason! There could be hundreds of them -->
-  <link rel="import" href="pipe-viewers/builtin/console/component.html">
-  <link rel="import" href="pipe-viewers/builtin/git/status/component.html">
-  <link rel="import" href="pipe-viewers/builtin/vlog/component.html">
+  <link rel="import" href="pipe-viewers/builtin/console/component.html"/>
+  <link rel="import" href="pipe-viewers/builtin/git/status/component.html"/>
+  <link rel="import" href="pipe-viewers/builtin/vlog/component.html"/>
 
 </head>
 <body>
diff --git a/examples/pipetobrowser/browser/libs/css/common-style.css b/examples/pipetobrowser/browser/libs/css/common-style.css
index 62bc38c..cc263ea 100644
--- a/examples/pipetobrowser/browser/libs/css/common-style.css
+++ b/examples/pipetobrowser/browser/libs/css/common-style.css
@@ -2,6 +2,10 @@
   display: none !important;
 }
 
+.invisible {
+  visibility: hidden;
+}
+
 .secondary-text {
   color: rgba(0,0,0,.54);
 }
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
index 34574cf..87cdff3 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
@@ -29,7 +29,8 @@
 }
 
 paper-dialog {
-  width: 50%;
+  max-width: 80em;
+  width: 80vw;
 }
 
 /* hack: wrong z-index in shadow of paper-dialog disables text selection in dialog */
@@ -82,4 +83,18 @@
 
 .more-dialog-content [moreInfoOnly] {
   display: initial;
+}
+
+.paginator {
+  display: inline-block;
+  border: solid 1px rgba(0, 0, 0, 0.05);
+  box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
+  color: rgba(0, 0, 0, 0.54);
+  fill: rgba(0, 0, 0, 0.54);
+  margin: 1em;
+  font-size: 0.9em;
+}
+
+.paginator paper-icon-button {
+  vertical-align: middle;
 }
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
index 55498b0..0824c37 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
@@ -8,13 +8,13 @@
 <link rel="import" href="/libs/ui-components/data-grid/grid/cell/renderer.html">
 <link rel="import" href="/libs/ui-components/data-grid/grid/column/renderer.html">
 
-<polymer-element name="p2b-grid" attributes="summary dataSource defaultSortKey defaultSortAscending">
+<polymer-element name="p2b-grid" attributes="summary dataSource defaultSortKey defaultSortAscending pageSize">
   <template>
     <link rel="stylesheet" href="/libs/css/common-style.css">
     <link rel="stylesheet" href="component.css">
     <div id="templates"></div>
     <core-collapse id="searchTools">
-      <div class="result-count">Showing {{ dataSourceResult.length }} items</div>
+      <div class="result-count">Showing {{ dataSourceResult.length }} items of {{totalNumItems}}</div>
       <div>
         <content select="[grid-search]"></content>
       </div>
@@ -43,15 +43,26 @@
             <template ref="{{ col.cellTemplateId }}" bind></template>
           </td>
           <td class="info-column">
-            <paper-icon-button on-tap="{{ showMoreInfo }}" class="more-icon" icon="more-vert" title="more info"></paper-icon-button
+            <paper-icon-button on-click="{{ showMoreInfo }}" class="more-icon" icon="more-vert" title="more info"></paper-icon-button
             >
           </td>
         </tr>
       </tbody>
     </table>
 
+    <!-- Pagination -->
+    <template if="{{totalNumPages > 1}}">
+      <div class="paginator">
+        <paper-icon-button title="Previous page" icon="hardware:keyboard-arrow-left"
+        class="{{ {invisible : pageNumber == 1} | tokenList }}" on-click="{{ previousPage }}"></paper-icon-button>
+        <span>Page {{ pageNumber }} of {{ totalNumPages }}</span>
+        <paper-icon-button title="Next page" icon="hardware:keyboard-arrow-right"
+        class="{{ {invisible : onLastPage } | tokenList }}" on-click="{{ nextPage }}"></paper-icon-button>
+      </div>
+    </template>
+
     <!-- Dialog that displays all columns and their values when more info icon activated -->
-    <paper-dialog layered id="dialog" heading="Details" transition="paper-dialog-transition-bottom">
+    <paper-dialog id="dialog" heading="Details" transition="paper-dialog-transition-bottom">
       <template id="moreInfoTemplate" bind>
         <div class="more-dialog-content">
           <template repeat="{{ item in selectedItems }}">
@@ -62,6 +73,7 @@
           </template>
         </div>
       </template>
+      <paper-button label="Close" dismissive></paper-button>
     </paper-dialog>
 
   </template>
@@ -143,11 +155,14 @@
        */
       defaultSortAscending: false,
 
-      showMoreInfo: function(e) {
-        // quirk + hack: cheating here and breaking shadow dom boundaries
-        // since paper-dialog does not support backdrop yet
-        this.$.dialog.shadowRoot.querySelector('#overlay').backdrop = true;
+      /*
+       * Number if items displayed in each page.
+       * Defaults to 30
+       * @type {integer}
+       */
+      pageSize: 30,
 
+      showMoreInfo: function(e) {
         var item = e.target.templateInstance.model.item;
         this.selectedItems = [item];
         this.$.dialog.opened = true;
@@ -158,6 +173,7 @@
         // private property fields
         this.columns = [];
         this.shouldRefetchData = true;
+        this.pageNumber = 1;
         this.dataSource = null;
         this.cachedDataSourceResult = [];
         this.gridState = {
@@ -195,7 +211,7 @@
        * @private
        */
       dataSourceChanged: function() {
-        this.refresh();
+        this.refresh(true);
       },
 
       /*
@@ -209,8 +225,8 @@
         for (key in this.gridState) {
           var observer = new ObjectObserver(this.gridState[key])
           observer.open(function() {
-            // refresh the grid on any mutations
-            self.refresh();
+            // refresh the grid on any mutations and go back to page one
+            self.refresh(true);
           });
         }
       },
@@ -283,10 +299,15 @@
 
       /*
        * Refreshed the grid by fetching the data again and updating the UI in the next render tick
+       * @param {bool} goBackToPageOne Optional parameter indicating that grid should go back
+       * to page 1 after refresh. false by default
        */
-      refresh: function() {
+      refresh: function(goBackToPageOne) {
         var self = this;
         requestAnimationFrame(function() {
+          if (goBackToPageOne) {
+            self.pageNumber = 1;
+          }
           self.shouldRefetchData = true;
         });
       },
@@ -375,12 +396,27 @@
           return this.cachedDataSourceResult;
         }
 
+        // fetch the data
         this.cachedDataSourceResult = this.dataSource.fetch(
           this.gridState.search,
           this.gridState.sort,
           this.gridState.filters
         );
 
+        // page the data
+        this.totalNumItems = this.cachedDataSourceResult.length;
+        // if there less data than current page number, go back to page 1
+        if (this.totalNumItems < (this.pageNumber - 1) * this.pageSize) {
+          this.pageNumber = 1;
+        }
+        this.totalNumPages = Math.ceil(this.totalNumItems / this.pageSize);
+        this.onLastPage = this.totalNumPages == this.pageNumber;
+
+        // skip and take
+        var startIndex = (this.pageNumber - 1) * this.pageSize;
+        var endIndex = startIndex + this.pageSize;
+        this.cachedDataSourceResult = this.cachedDataSourceResult.slice(startIndex, endIndex);
+
         this.shouldRefetchData = false;
 
         return this.cachedDataSourceResult;
@@ -392,6 +428,20 @@
        */
       toggleSearchTools: function() {
         this.$.searchTools.toggle();
+      },
+
+      nextPage: function() {
+        if (!this.onLastPage) {
+          this.pageNumber++;
+          this.refresh();
+        }
+      },
+
+      previousPage: function() {
+        if (this.pageNumber > 1) {
+          this.pageNumber--;
+          this.refresh();
+        }
       }
     });
   </script>
diff --git a/examples/pipetobrowser/browser/libs/utils/stream-copy.js b/examples/pipetobrowser/browser/libs/utils/stream-copy.js
new file mode 100644
index 0000000..7e1a38e
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/utils/stream-copy.js
@@ -0,0 +1,45 @@
+var Transform = require('stream').Transform;
+var PassThrough = require('stream').PassThrough;
+var Buffer = require('buffer').Buffer;
+/*
+ * A through transform stream keep a copy of the data piped to it and provides
+ * functions to create new copies of the stream on-demand
+ * @class
+ */
+export class StreamCopy extends Transform {
+  constructor() {
+    super();
+    this._writableState.objectMode = true;
+    this._readableState.objectMode = true;
+    // TODO(aghassemi) make this a FIFO buffer with reasonable max-size
+    this.buffer = [];
+    this.copies = [];
+  }
+
+  _transform(chunk, encoding, cb) {
+    this.buffer.push(chunk);
+    this.push(chunk);
+    for (var i=0; i < this.copies.length; i++) {
+      this.copies[i].push(chunk);
+    }
+    cb();
+  }
+
+ /*
+  * Create a new copy of the stream
+  * @param {bool} onlyNewData Whether the copy should include
+  * existing data from the stream or just new data.
+  * @return {Stream} Copy of the stream
+  */
+  copy(onlyNewData) {
+    var copy = new PassThrough( { objectMode: true });
+    if (!onlyNewData) {
+      // copy existing data first in the order received
+      for (var i = 0; i < this.buffer.length; i++) {
+        copy.push(this.buffer[i]);
+      }
+    }
+    this.copies.push(copy);
+    return copy;
+  }
+}
diff --git a/examples/pipetobrowser/browser/libs/utils/stream-helpers.js b/examples/pipetobrowser/browser/libs/utils/stream-helpers.js
new file mode 100644
index 0000000..cbf95b9
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/utils/stream-helpers.js
@@ -0,0 +1,6 @@
+var es = require('event-stream');
+export var streamUtil = {
+  split: es.split,
+  map: es.map,
+  writeArray: es.writeArray
+};
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
index 86db6ce..7ac8c63 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
@@ -7,14 +7,13 @@
  */
 import { View } from 'view';
 import { PipeViewer } from 'pipe-viewer';
-import { Logger } from 'logger'
+import { streamUtil } from 'stream-helpers';
+import { Logger } from 'logger';
 import { parse } from './parser';
 import { gitStatusDataSource } from './data-source';
 
 var log = new Logger('pipe-viewers/builtin/git/status');
 
-var streamUtil = require('event-stream');
-
 class GitStatusPipeViewer extends PipeViewer {
   get name() {
     return 'git/status';
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
index 1891d5e..67b1779 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -8,13 +8,12 @@
  */
 import { View } from 'view';
 import { PipeViewer } from 'pipe-viewer';
+import { streamUtil } from 'stream-helpers';
 import { Logger } from 'logger'
 import { vLogDataSource } from './data-source';
 
 var log = new Logger('pipe-viewers/builtin/vlog');
 
-var streamUtil = require('event-stream');
-
 class vLogPipeViewer extends PipeViewer {
   get name() {
     return 'vlog';
diff --git a/examples/pipetobrowser/browser/runtime/app.js b/examples/pipetobrowser/browser/runtime/app.js
index 33072b7..d66ad2b 100644
--- a/examples/pipetobrowser/browser/runtime/app.js
+++ b/examples/pipetobrowser/browser/runtime/app.js
@@ -4,6 +4,8 @@
 import { registerDisplayErrorAction } from 'actions/display-error'
 import { registerAddPipeViewerAction } from 'actions/add-pipe-viewer'
 import { registerNavigatePipesPageAction, navigatePipesPage } from 'actions/navigate-pipes-page'
+import { registerNavigateNeigbourhoodAction, navigateNeigbourhood } from 'actions/navigate-neighborhood'
+import { registerRedirectPipeAction } from 'actions/redirect-pipe'
 
 import { SubPageItem } from 'views/page/view'
 
@@ -40,6 +42,8 @@
   registerDisplayErrorAction();
   registerAddPipeViewerAction();
   registerNavigatePipesPageAction();
+  registerNavigateNeigbourhoodAction();
+  registerRedirectPipeAction();
 }
 
 /*
@@ -62,6 +66,12 @@
   pipesSubPageItem.onActivate = navigatePipesPage;
   page.subPages.push(pipesSubPageItem);
 
+  var neighborhoodSubPageItem = new SubPageItem('neighborhood');
+  neighborhoodSubPageItem.name = 'Neighborhood';
+  neighborhoodSubPageItem.icon = 'social:circles-extended';
+  neighborhoodSubPageItem.onActivate = navigateNeigbourhood;
+  page.subPages.push(neighborhoodSubPageItem);
+
   var helpSubPageItem = new SubPageItem('help');
   helpSubPageItem.name = 'Help';
   helpSubPageItem.icon = 'help';
diff --git a/examples/pipetobrowser/browser/runtime/init.js b/examples/pipetobrowser/browser/runtime/init.js
index b5b1cc3..4180417 100644
--- a/examples/pipetobrowser/browser/runtime/init.js
+++ b/examples/pipetobrowser/browser/runtime/init.js
@@ -10,7 +10,8 @@
     'pipe-viewer': 'pipe-viewers/pipe-viewer.js',
     'pipe-viewer-delegation': 'pipe-viewers/pipe-viewer-delegation.js',
     'view': 'libs/mvc/view.js',
-    'logger': 'libs/logs/logger.js'
+    'logger': 'libs/logs/logger.js',
+    'stream-helpers': 'libs/utils/stream-helpers.js'
   };
 
   System.import('runtime/app').then(function(app) {
diff --git a/examples/pipetobrowser/browser/services/pipe-to-browser-client.js b/examples/pipetobrowser/browser/services/pipe-to-browser-client.js
new file mode 100644
index 0000000..c03e780
--- /dev/null
+++ b/examples/pipetobrowser/browser/services/pipe-to-browser-client.js
@@ -0,0 +1,25 @@
+/*
+ * Implements a veyron client that can talk to a P2B service.
+ * @fileoverview
+ */
+import { Logger } from 'libs/logs/logger'
+import { config } from 'config'
+
+var log = new Logger('services/p2b-client');
+var veyron = new Veyron(config.veyron);
+
+/*
+ * Pipes a stream of data to the P2B service identified
+ * by the given veyron name.
+ * @param {string} name Veyron name of the destination service
+ * @param {Stream} Stream of data to pipe to it.
+ * @return {Promise} Promise indicating if piping was successful or not
+ */
+export function pipe(name, stream) {
+  var client = veyron.newClient();
+  return client.bindTo(name).then((remote) => {
+    var remoteStream = remote.pipe().stream;
+    stream.pipe(remoteStream);
+    return Promise.resolve();
+  });
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/services/pipe-to-browser-namespace.js b/examples/pipetobrowser/browser/services/pipe-to-browser-namespace.js
new file mode 100644
index 0000000..648a528
--- /dev/null
+++ b/examples/pipetobrowser/browser/services/pipe-to-browser-namespace.js
@@ -0,0 +1,31 @@
+/*
+ * Implements a veyron client that talks to the namespace service and finds all
+ * the P2B services that are available.
+ * @fileoverview
+ */
+import { Logger } from 'libs/logs/logger'
+import { config } from 'config'
+
+var log = new Logger('services/p2b-namespace');
+var veyron = new Veyron(config.veyron);
+var client = veyron.newClient();
+
+/*
+ * Finds all the P2B services that are published by querying the namespace.
+ * @return {Promise} Promise resolving to an array of names for all published
+ * P2B services
+ */
+export function getAll() {
+  return client.bindTo(config.namespaceRoot).then((namespace) => {
+    var globResult = namespace.glob('google/p2b/*');
+    var p2bServices = [];
+    globResult.stream.on('data', (p2bServiceName) => {
+      p2bServices.push(p2bServiceName.name);
+    });
+
+    // wait until all the data arrives then return the collection
+    return globResult.then(() => {
+      return p2bServices;
+    });
+  });
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/services/pipe-to-browser.js b/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
similarity index 80%
rename from examples/pipetobrowser/browser/services/pipe-to-browser.js
rename to examples/pipetobrowser/browser/services/pipe-to-browser-server.js
index 3bf580a..5f40da4 100644
--- a/examples/pipetobrowser/browser/services/pipe-to-browser.js
+++ b/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
@@ -4,15 +4,14 @@
  * It also exposes the state of the service.
  * @fileoverview
  */
-
 import { Logger } from 'libs/logs/logger'
 import { config } from 'config'
 import { ByteObjectStreamAdapter } from 'libs/utils/byte-object-stream-adapter'
 import { StreamByteCounter } from 'libs/utils/stream-byte-counter'
+import { StreamCopy } from 'libs/utils/stream-copy'
 
-var log = new Logger('services/p2b');
-var v = new Veyron(config.veyron);
-var server = v.newServer();
+var log = new Logger('services/p2b-server');
+var server;
 
 // State of p2b service
 export var state = {
@@ -47,7 +46,8 @@
  */
 export function publish(name, pipeRequestHandler) {
   log.debug('publishing under name:', name);
-
+  var veyron = new Veyron(config.veyron);
+  server = veyron.newServer();
   /*
    * Veyron pipe to browser service implementation.
    * Implements the p2b IDL.
@@ -55,6 +55,9 @@
   var p2b = {
     pipe($suffix, $stream) {
       return new Promise(function(resolve, reject) {
+        //TODO(aghassemi) publish-issue remove /pipe from the suffix
+        $suffix = $suffix.substr(5);
+
         log.debug('received pipe request for:', $suffix);
         var numBytesForThisCall = 0;
 
@@ -65,7 +68,9 @@
           state.numBytes += numBytesRead;
         });
 
-        var stream = $stream.pipe(bufferStream).pipe(streamByteCounter);
+        var streamCopier = $stream.pipe(new StreamCopy());
+        var stream = streamCopier.pipe(bufferStream).pipe(streamByteCounter);
+        stream.copier = streamCopier;
 
         bufferStream.on('end', () => {
           log.debug('end of stream');
@@ -79,7 +84,6 @@
         });
 
         state.numPipes++;
-
         pipeRequestHandler($suffix, stream);
       });
     }
@@ -87,13 +91,13 @@
 
   state.publishing = true;
 
-  return server.register(name, p2b).then(() => {
-    return server.publish(config.publishNamePrefix).then((endpoint) => {
+  return server.register('pipe', p2b).then(() => { //TODO(aghassemi) publish-issue add pipe for now since we can't register under empty name
+    return server.publish(config.publishNamePrefix + '/' + name).then((endpoint) => { //TODO(aghassemi) publish-issue
       log.debug('published with endpoint:', endpoint);
 
       state.published = true;
       state.publishing = false;
-      state.fullServiceName = config.publishNamePrefix + '/' + name;
+      state.fullServiceName = config.publishNamePrefix + '/' + name + '/pipe'; //TODO(aghassemi) publish-issue
       state.date = new Date();
 
       return endpoint;
diff --git a/examples/pipetobrowser/browser/views/common/common.css b/examples/pipetobrowser/browser/views/common/common.css
index a12d237..af47ae2 100644
--- a/examples/pipetobrowser/browser/views/common/common.css
+++ b/examples/pipetobrowser/browser/views/common/common.css
@@ -22,4 +22,10 @@
   display: flex;
   flex-direction: column;
   flex: 1 1 0px;
+}
+
+[page-title] {
+  font-size: 1.5em;
+  color: #4285f4;
+  margin: 0px;
 }
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/namespace-list/component.css b/examples/pipetobrowser/browser/views/namespace-list/component.css
new file mode 100644
index 0000000..93ea771
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/namespace-list/component.css
@@ -0,0 +1,19 @@
+[selectable] .selected {
+  background-color: #00e5ff;
+  opacity: 0.8;
+}
+
+[selectable] core-item {
+  cursor: pointer;
+}
+
+core-item {
+  box-sizing: border-box;
+  border-bottom: 1px solid rgba(0,0,0,0.05);
+  margin: 0;
+  padding: 0 1em;
+}
+
+core-item:last-child {
+  border-bottom: none;
+}
diff --git a/examples/pipetobrowser/browser/views/namespace-list/component.html b/examples/pipetobrowser/browser/views/namespace-list/component.html
new file mode 100644
index 0000000..e6faa7d
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/namespace-list/component.html
@@ -0,0 +1,55 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/core-list/core-list.html">
+<link rel="import" href="/libs/vendor/polymer/core-item/core-item.html">
+
+<polymer-element name="p2b-namespace-list" attributes="names selectable">
+  <template>
+    <link rel="stylesheet" href="component.css">
+    <template if="{{_items.length > 0}}">
+      <core-list selectable?="{{selectable}}" height="40" on-core-activate="{{fireSelectEvent}}" data="{{_items}}" height="20">
+        <template>
+          <core-item class="{{ {selected: selected} | tokenList }}" label="{{name}}"></core-item>
+        </template>
+      </core-list>
+    </template>
+  </template>
+  <script>
+    Polymer('p2b-namespace-list', {
+     /*
+      * List of names to be displayed
+      * @type {Array<string>}
+      */
+      names: [],
+
+     /*
+      * Whether the names displayed are selectable
+      * if selectable, 'select' event will fire with the name of the selected item
+      * @type {boolean}
+      */
+      selectable: false,
+
+      /*
+       * transformed collection of names to objects
+       * @private
+       */
+      _items: [],
+      namesChanged: function() {
+        // transform from [string] to [object] since core-items expects array of objects
+        this._items = this.names.map( function(n) {
+          return {name: n};
+        });
+      },
+
+      /*
+       * fires the select event pass the name as event argument
+       * @private
+       */
+      fireSelectEvent: function(e) {
+        if (!this.selectable) {
+          return;
+        }
+        this.fire('select', e.detail.data.name);
+      }
+    });
+    </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/namespace-list/view.js b/examples/pipetobrowser/browser/views/namespace-list/view.js
new file mode 100644
index 0000000..e2b0f92
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/namespace-list/view.js
@@ -0,0 +1,25 @@
+import { View } from 'libs/mvc/view'
+
+/*
+ * View showing a list of all p2b services from the namespace
+ * @class
+ * @extends {View}
+ */
+export class NamespaceListView extends View {
+	constructor(items) {
+		var el = document.createElement('p2b-namespace-list');
+		el.items = items;
+		super(el);
+	}
+
+/*
+ * Event that fires when user selects an item from the list.
+ * @event
+ * @type {string} name of the item that was selected
+ */
+  onSelectAction(eventHandler) {
+    this.element.addEventListener('select', (e) => {
+      eventHandler(e.detail);
+    });
+  }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/neighborhood/component.html b/examples/pipetobrowser/browser/views/neighborhood/component.html
new file mode 100644
index 0000000..7c7b797
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/neighborhood/component.html
@@ -0,0 +1,20 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/views/namespace-list/component.html">
+
+<polymer-element name="p2b-neighborhood">
+  <template>
+    <link rel="stylesheet" href="../common/common.css">
+    <h2 page-title>Neighborhood</h2>
+    <p>List of PipeToBrowser instances currently published</p>
+    <p2b-namespace-list names="{{existingNames}}"></p2b-namespace-list>
+  </template>
+  <script>
+    Polymer('p2b-neighborhood', {
+      /*
+       * List of existing names to show
+       * @type {Array<string>}
+       */
+      existingNames: [],
+    });
+    </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/neighborhood/view.js b/examples/pipetobrowser/browser/views/neighborhood/view.js
new file mode 100644
index 0000000..2106ba2
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/neighborhood/view.js
@@ -0,0 +1,21 @@
+import { View } from 'libs/mvc/view'
+
+/*
+ * View displaying a list of currently published PipeToBrowsers instances
+ * @class
+ * @extends {View}
+ */
+export class NeighborhoodView extends View {
+	constructor() {
+		var el = document.createElement('p2b-neighborhood');
+		super(el);
+	}
+
+ /*
+  * List of existing names to show
+  * @type {Array<string>}
+  */
+  set existingNames(val)  {
+    this.element.existingNames = val;
+  }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/page/component.css b/examples/pipetobrowser/browser/views/page/component.css
index cecfd1c..787f3bb 100644
--- a/examples/pipetobrowser/browser/views/page/component.css
+++ b/examples/pipetobrowser/browser/views/page/component.css
@@ -1,7 +1,3 @@
-:host {
-  font-family: 'Roboto', sans-serif;
-  color: rgba(0,0,0,0.87);
-}
 
 [drawer] {
   background-color: #FAFAFA;
@@ -15,10 +11,12 @@
 
 [sidebar] {
   padding: 0.5em;
+  fill: #9e9e9e;
 }
 
 [sidebar] .core-selected {
   color: #00bcd4;
+  fill: #212121;
 }
 
 [main] {
diff --git a/examples/pipetobrowser/browser/views/page/component.html b/examples/pipetobrowser/browser/views/page/component.html
index fdb070b..6e6cc54 100644
--- a/examples/pipetobrowser/browser/views/page/component.html
+++ b/examples/pipetobrowser/browser/views/page/component.html
@@ -7,6 +7,10 @@
 <link rel="import" href="/libs/vendor/polymer/core-selector/core-selector.html">
 <link rel="import" href="/libs/vendor/polymer/paper-appbar/paper-appbar.html">
 <link rel="import" href="/libs/vendor/polymer/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="/libs/vendor/polymer/paper-toast/paper-toast.html">
+<link rel="import" href="/libs/vendor/polymer/core-icons/iconsets/hardware-icons.html">
+<link rel="import" href="/libs/vendor/polymer/core-icons/iconsets/social-icons.html">
+
 <link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
 
 <polymer-element name="p2b-page">
@@ -39,6 +43,7 @@
         </core-selector>
       </core-header-panel>
     </core-drawer-panel>
+     <paper-toast id="toast"></paper-toast>
   </template>
   <script>
     Polymer('p2b-page', {
@@ -86,6 +91,15 @@
         });
       },
 
+     /*
+      * Displays a message toast for a few seconds e.g. "Saved Successfully"
+      * @param {string} text Text of the toast
+      */
+      showToast: function(text) {
+        this.$.toast.text = text;
+        this.$.toast.show();
+      },
+
       /*
        * handler for when a sidebar item is clicked
        * @param {string} key Key of the page
diff --git a/examples/pipetobrowser/browser/views/page/view.js b/examples/pipetobrowser/browser/views/page/view.js
index 7435950..b2e7aa0 100644
--- a/examples/pipetobrowser/browser/views/page/view.js
+++ b/examples/pipetobrowser/browser/views/page/view.js
@@ -23,6 +23,14 @@
 	}
 
  /*
+  * Displayed a message toast for a few seconds e.g. "Saved Successfully"
+  * @type {SubPageItem}
+  */
+  showToast(text) {
+    this.element.showToast(text);
+  }
+
+ /*
   * Collection of sub pages
   * @type {SubPageItem}
   */
diff --git a/examples/pipetobrowser/browser/views/pipes/component.css b/examples/pipetobrowser/browser/views/pipes/component.css
index 5b79a3b..819c741 100644
--- a/examples/pipetobrowser/browser/views/pipes/component.css
+++ b/examples/pipetobrowser/browser/views/pipes/component.css
@@ -26,11 +26,8 @@
 }
 
 .empty-message {
-  font-size: 1.5em;
-  padding: 1.5em;
+  padding: 1em;
   position: absolute;
-  margin: 0px;
-  color: #4285f4;
 }
 
 .container {
diff --git a/examples/pipetobrowser/browser/views/pipes/component.html b/examples/pipetobrowser/browser/views/pipes/component.html
index f72aef3..ad47a07 100644
--- a/examples/pipetobrowser/browser/views/pipes/component.html
+++ b/examples/pipetobrowser/browser/views/pipes/component.html
@@ -14,7 +14,7 @@
     <link rel="stylesheet" href="component.css">
     <div class="container" flex>
       <template if="{{ numTabs == 0 }}">
-        <p class="empty-message">No pipes to show...</p>
+        <h2 page-title class="empty-message">No pipes to show...</h2>
         <div class="no-pipes-bg"></div>
       </template>
 
@@ -94,13 +94,25 @@
         this.pipeTabs[key] = {
           name: name,
           tab: tab,
-          tabContent: tabContent
+          tabContent: tabContent,
+          tabToolbar: tabToolbar
         };
 
         this.selectedTabKey = key;
       },
 
       /*
+       * Adds a new toolbar action for the tab's toolbar
+       * @param {string} tabKey Key of the tab to add action to
+       * @param icon {string} icon Icon name for the action
+       * @param onClick {function} event handler for the action
+       */
+      addToolbarAction: function(tabKey, icon, onClick) {
+        var toolbar = this.pipeTabs[tabKey].tabToolbar;
+        toolbar.add(icon, onClick);
+      },
+
+      /*
        * Removes a tab
        * @param {string} key Key of the tab to remove
        */
@@ -128,11 +140,16 @@
       /*
        * Replaces content of a tab
        * @param {string} key Key of the tab to replace content for
+       * @param {string} newName new name for the tab
        * @param {DOMElement} el New content of the tab
        */
-      replaceTabContent: function(key, newEl) {
+      replaceTabContent: function(key, newName, newEl) {
         var tabContent = this.pipeTabs[key].tabContent;
         tabContent.replaceTabContent(newEl);
+        if (newName) {
+          this.pipeTabs[key].tab.textContent = newName;
+          this.pipeTabs[key].tabToolbar.title = newName;
+        }
       },
 
       /*
@@ -147,4 +164,4 @@
       }
     });
   </script>
-</polymer-element>
\ No newline at end of file
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html b/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html
index 98a930b..f34f1ee 100644
--- a/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html
+++ b/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html
@@ -1,4 +1,5 @@
 <link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-icon-button/paper-icon-button.html">
 
 <polymer-element name="p2b-pipes-tab-toolbar">
   <template>
@@ -7,6 +8,7 @@
       <span flex>
         {{ title }}
       </span>
+      <span id="customActions"></span>
       <paper-icon-button id="fullscreenIcon" icon="fullscreen" on-click="{{ fireFullscreenAction }}"></paper-icon-button>
       <paper-icon-button icon="close" on-click="{{ fireCloseAction }}"></paper-icon-button>
     </core-toolbar>
@@ -34,6 +36,18 @@
        */
       fireFullscreenAction: function() {
         this.fire('fullscreen-action');
+      },
+
+      /*
+       * Adds a new action to the toolbar
+       * @param icon {string} icon Icon for the action
+       * @param onClick {function} event handler for the action
+       */
+      add: function(icon, onClick) {
+        var button = document.createElement('paper-icon-button');
+        button.icon = icon;
+        button.addEventListener('click', onClick);
+        this.$.customActions.appendChild(button);
       }
     });
   </script>
diff --git a/examples/pipetobrowser/browser/views/pipes/view.js b/examples/pipetobrowser/browser/views/pipes/view.js
index 7f94b6b..8c9e2b4 100644
--- a/examples/pipetobrowser/browser/views/pipes/view.js
+++ b/examples/pipetobrowser/browser/views/pipes/view.js
@@ -23,12 +23,23 @@
     this.element.addTab(key, name, view.element);
   }
 
+  /*
+   * Adds a new toolbar action for the tab's toolbar
+   * @param {string} key Key of the tab to add action to
+   * @param icon {string} icon key for the action
+   * @param onClick {function} event handler for the action
+   */
+  addToolbarAction(tabKey, icon, onClick) {
+    this.element.addToolbarAction(tabKey, icon, onClick);
+  }
+
  /*
   * Replaces the content of the tab identified via key by the new view.
   * @param {string} key A string key identifier for the tab.
+  * @param {string} newName New name for the tab
   * @param {View} view View to replace the current tab content
   */
-  replaceTabView(key, newView) {
-    this.element.replaceTabContent(key, newView.element);
+  replaceTabView(key, newName, newView) {
+    this.element.replaceTabContent(key, newName, newView.element);
   }
-}
\ No newline at end of file
+}
diff --git a/examples/pipetobrowser/browser/views/publish/view.js b/examples/pipetobrowser/browser/views/publish/view.js
index bc3843f..66b386d 100644
--- a/examples/pipetobrowser/browser/views/publish/view.js
+++ b/examples/pipetobrowser/browser/views/publish/view.js
@@ -22,57 +22,4 @@
       eventHandler(e.detail.publishName);
     });
   }
-}
-
-
-
-/*veyron/examples/pipetobrowser: Supporting search (in message, file path, threadid),
-filtering (log levels, date), sorting (all columns except message) for
-veyron log viewer. Also adding ability to pause/resume the log viewer.
-
-To support these features for veyron log viewer and other plugins,
-a new reusable data grid component was created (libs/ui-components/data-grid)
-data grid can host search, filters and supports sortable columns and custom cell renderer
-example usage:
-
- <p2b-grid defaultSortKey="firstName"
-  defaultSortAscending
-  dataSource="{{ myContactsDataSource }}"
-  summary="Displays your contacts in a tabular format">
-
-  <!-- Search contacts-->
-  <p2b-grid-search label="Search Contacts"></p2b-grid-search>
-
-  <!-- Filter for circles -->
-  <p2b-grid-filter-select multiple key="circle" label="Circles">
-    <p2b-grid-filter-select-item checked label="Close Friends" value="close"></p2b-grid-filter-select-item>
-    <p2b-grid-filter-select-item label="Colleagues" value="far"></p2b-grid-filter-select-item>
-  </p2b-grid-filter-select>
-
-  <!-- Toggle to allow filtering by online mode-->
-  <p2b-grid-filter-toggle key="online" label="Show online only" checked></p2b-grid-filter-toggle>
-
-  <!-- Columns, sorting and cell templates -->
-  <p2b-grid-column sortable label="First Name" key="firstName" />
-    <template>{{ item.firstName }}</template>
-  </p2b-grid-column>
-
-  <p2b-grid-column sortable label="Last Name" key="lastName" />
-    <template>
-      <span style="text-transform:uppercase;">
-        {{ item.lastName }}
-      </span>
-    </template>
-  </p2b-grid-column>
-
-  <p2b-grid-column label="Circle" key="circle"/>
-    <template>
-      <img src="images\circls\{{ item.circle }}.jpg" alt="in {{ item.circle }} circle"><img>
-    </template>
-  </p2b-grid-column>
-
-</p2b-grid>
-Please see documentation in (libs/ui-components/data-grid/grid/component.html) for more details.
-
-still TODO: UI cleanup
-*/
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
new file mode 100644
index 0000000..a58b136
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
@@ -0,0 +1,27 @@
+paper-input {
+  width: 90%;
+}
+
+paper-input, paper-checkbox {
+  display: block;
+}
+
+paper-dialog {
+  max-width: 40em;
+  width: 80vw;
+}
+
+/* hack: wrong z-index in shadow of paper-dialog disables text selection in dialog */
+paper-dialog /deep/ #shadow {
+  z-index: -1 !important;
+}
+
+paper-button[affirmative] {
+  color: #4285f4;
+}
+
+.label {
+  margin: 0;
+  margin-top: 1em;
+  font-size: 1.1em;
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.html b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.html
new file mode 100644
index 0000000..d23a326
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.html
@@ -0,0 +1,78 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-input/paper-input.html">
+<link rel="import" href="/libs/vendor/polymer/paper-button/paper-button.html">
+<link rel="import" href="/libs/vendor/polymer/paper-dialog/paper-dialog.html">
+<link rel="import" href="/libs/vendor/polymer/paper-dialog/paper-dialog-transition.html">
+<link rel="import" href="/views/namespace-list/component.html">
+
+<polymer-element name="p2b-redirect-pipe-dialog">
+
+  <template id="template">
+    <link rel="stylesheet" href="../common/common.css">
+    <link rel="stylesheet" href="component.css">
+    <paper-dialog id="dialog" heading="Redirect" transition="paper-dialog-transition-bottom">
+      <p>
+        <paper-input focused id="nameInput" label="Name to redirect to" floatinglabel></paper-input>
+        <paper-checkbox id="newDataOnly" label="Only redirect new data"></paper-checkbox>
+        <template if="{{existingNames.length > 0}}">
+          <h2 class="label">Currently online</h2>
+          <p2b-namespace-list selectable on-select="{{updateNameInput}}" names="{{existingNames}}"></p2b-namespace-list>
+        </template>
+      </p>
+      <paper-button label="Cancel" dismissive></paper-button>
+      <paper-button label="Redirect" affirmative default on-tap="{{ fireRedirectActionEvent }}"></paper-button>
+    </paper-dialog>
+  </template>
+  <script>
+    Polymer('p2b-redirect-pipe-dialog', {
+
+      /*
+       * List of existing names to show in the dialog for the user to pick from
+       * @type {Array<string>}
+       */
+      existingNames: [],
+
+      ready: function() {
+        var self = this;
+        var dialog = this.$.dialog;
+        var container = document.querySelector('#redirectDialogContainer');
+        if (!container) {
+          var container = document.createElement('div');
+          container.id = 'redirectDialogContainer';
+          document.body.appendChild(container);
+        }
+        this.container = container;
+      },
+
+      /*
+       * Opens the dialog
+       */
+      open: function() {
+        this.container.innerHTML = ''
+        this.container.appendChild(this);
+        this.$.dialog.toggle();
+      },
+
+      /*
+       * Fires redirect event representing user's intention to redirect
+       * @type {string} Requested name for service to be redirected
+       * @type {boolean} Whether only new data should be redirected
+       */
+      fireRedirectActionEvent: function() {
+        var name = this.$.nameInput.value;
+        this.fire('redirect', {
+          name: name,
+          newDataOnly: this.$.newDataOnly.checked
+        });
+      },
+
+      /*
+       * Updates the input value
+       * @private
+       */
+      updateNameInput: function(e) {
+        this.$.nameInput.value = e.detail;
+      }
+    });
+  </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/view.js b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/view.js
new file mode 100644
index 0000000..d369c49
--- /dev/null
+++ b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/view.js
@@ -0,0 +1,42 @@
+import { View } from 'libs/mvc/view'
+
+/*
+ * View representing a dialog that asks the user where they want to redirect
+ * the current pipe and whether only new data should be redirected
+ * @class
+ * @extends {View}
+ */
+export class RedirectPipeDialogView extends View {
+  constructor() {
+    var el = document.createElement('p2b-redirect-pipe-dialog');
+    super(el);
+  }
+
+  /*
+   * Opens the Redirect Pipe Dialog
+   */
+  open() {
+    this.element.open();
+  }
+
+  /*
+   * List of existing names to show in the dialog for the user to pick from
+   * @type {Array<string>}
+   */
+  set existingNames(val) {
+    this.element.existingNames = val;
+  }
+
+ /*
+  * Event representing user's intention to redirect
+  * @event
+  * @type {string} Requested name for service to be redirected
+  * @type {boolean} Whether only new data should be redirected
+  */
+  onRedirectAction(eventHandler) {
+    this.element.addEventListener('redirect', (e) => {
+      eventHandler(e.detail.name, e.detail.newDataOnly);
+    });
+  }
+
+}
\ No newline at end of file
diff --git a/examples/rockpaperscissors/impl/impl_test.go b/examples/rockpaperscissors/impl/impl_test.go
index 9a2bc32..5a6a856 100644
--- a/examples/rockpaperscissors/impl/impl_test.go
+++ b/examples/rockpaperscissors/impl/impl_test.go
@@ -68,7 +68,7 @@
 // that all the counters are consistent.
 func TestRockPaperScissorsImpl(t *testing.T) {
 	runtime := rt.Init()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 	mtAddress, mtStop := startMountTable(t, runtime)
 	defer mtStop()
 	rpsService, rpsStop := startRockPaperScissors(t, runtime, mtAddress)
diff --git a/examples/rockpaperscissors/rpsbot/main.go b/examples/rockpaperscissors/rpsbot/main.go
index 9e9a011..a29b8fc 100644
--- a/examples/rockpaperscissors/rpsbot/main.go
+++ b/examples/rockpaperscissors/rpsbot/main.go
@@ -29,7 +29,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	server, err := r.NewServer()
 	if err != nil {
 		vlog.Fatalf("NewServer failed: %v", err)
diff --git a/examples/rockpaperscissors/rpsplayercli/main.go b/examples/rockpaperscissors/rpsplayercli/main.go
index 7d0d0d9..074907e 100644
--- a/examples/rockpaperscissors/rpsplayercli/main.go
+++ b/examples/rockpaperscissors/rpsplayercli/main.go
@@ -31,7 +31,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	for {
 		if selectOne([]string{"Initiate Game", "Wait For Challenge"}) == 0 {
 			initiateGame()
diff --git a/examples/rockpaperscissors/rpsscorekeeper/main.go b/examples/rockpaperscissors/rpsscorekeeper/main.go
index aca0293..ff0aca9 100644
--- a/examples/rockpaperscissors/rpsscorekeeper/main.go
+++ b/examples/rockpaperscissors/rpsscorekeeper/main.go
@@ -35,7 +35,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	server, err := r.NewServer()
 	if err != nil {
 		vlog.Fatalf("NewServer failed: %v", err)
diff --git a/examples/runtime/complex_server.go b/examples/runtime/complex_server.go
index c8e5a01..66a0122 100644
--- a/examples/runtime/complex_server.go
+++ b/examples/runtime/complex_server.go
@@ -17,9 +17,9 @@
 func complexServerProgram() {
 	// Initialize the runtime.  This is boilerplate.
 	r := rt.Init()
-	// r.Shutdown is optional, but it's a good idea to clean up, especially
+	// r.Cleanup is optional, but it's a good idea to clean up, especially
 	// since it takes care of flushing the logs before exiting.
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	// Create a couple servers, and start serving.
 	server1 := makeServer()
diff --git a/examples/runtime/simple_server.go b/examples/runtime/simple_server.go
index 3fc9a85..b459d80 100644
--- a/examples/runtime/simple_server.go
+++ b/examples/runtime/simple_server.go
@@ -16,13 +16,13 @@
 	// Initialize the runtime.  This is boilerplate.
 	r := rt.Init()
 
-	// r.Shutdown is optional, but it's a good idea to clean up, especially
+	// r.Cleanup is optional, but it's a good idea to clean up, especially
 	// since it takes care of flushing the logs before exiting.
 	//
 	// We use defer to ensure this is the last thing in the program (to
 	// avoid shutting down the runtime while it may still be in use), and to
 	// allow it to execute even if a panic occurs down the road.
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	// Create a server, and start serving.
 	server := makeServer()
diff --git a/examples/stfortune/stfortune/main.go b/examples/stfortune/stfortune/main.go
index a5127b5..e65a1b0 100644
--- a/examples/stfortune/stfortune/main.go
+++ b/examples/stfortune/stfortune/main.go
@@ -9,6 +9,7 @@
 	"fmt"
 	"log"
 	"math/rand"
+	"os"
 	"strings"
 	"time"
 
@@ -69,6 +70,52 @@
 	return
 }
 
+// runAsWatcher monitors updates to the fortunes in the store and
+// prints out that information.  It does not return.
+func runAsWatcher(store storage.Store, user string) {
+	// TODO(tilaks): remove this when the store.Entry is auto-registered by VOM.
+	vom.Register(&istore.Entry{})
+	ctx := rt.R().NewContext()
+
+	// Monitor all new fortunes or only those of a specific user.
+	var path string
+	if user == "" {
+		path = fortunePath("")
+	} else {
+		path = userPath(user)
+	}
+	fmt.Printf("Running as a Watcher monitoring new fortunes under %s...\n", path)
+
+	req := iwatch.GlobRequest{Pattern: "*"}
+	stream, err := store.Bind(path).WatchGlob(ctx, req)
+	if err != nil {
+		log.Fatalf("watcher WatchGlob %s failed: %v", path, err)
+	}
+
+	for {
+		batch, err := stream.Recv()
+		if err != nil {
+			log.Fatalf("watcher Recv failed: %v", err)
+		}
+
+		for _, change := range batch.Changes {
+			entry, ok := change.Value.(*storage.Entry)
+			if !ok {
+				log.Printf("watcher change Value not a storage Entry: %#v", change.Value)
+				continue
+			}
+
+			fortune, ok := entry.Value.(schema.FortuneData)
+			if !ok {
+				log.Printf("watcher data not a FortuneData Entry: %#v", entry.Value)
+				continue
+			}
+
+			fmt.Printf("watcher: new fortune: %s\n", fortune.Fortune)
+		}
+	}
+}
+
 // pickFortune finds all available fortunes under the input path and
 // chooses one randomly.
 func pickFortune(store storage.Store, ctx context.T, path string) (string, error) {
@@ -199,6 +246,7 @@
 	storeAddress = flag.String("store", "", "the address/endpoint of the Veyron Store")
 	newFortune   = flag.String("new_fortune", "", "an optional, new fortune to add to the server's set")
 	user         = flag.String("user_name", "", "an optional username of the fortune creator to get/add to the server's set")
+	watch        = flag.Bool("watch", false, "run as a watcher reporting new fortunes")
 )
 
 func main() {
@@ -233,4 +281,10 @@
 			log.Fatal("error adding fortune: ", err)
 		}
 	}
+
+	// Run as a watcher if --watch is set.
+	if *watch {
+		runAsWatcher(store, *user)
+		os.Exit(0)
+	}
 }
diff --git a/examples/tunnel/tunneld/main.go b/examples/tunnel/tunneld/main.go
index 4520127..687a284 100644
--- a/examples/tunnel/tunneld/main.go
+++ b/examples/tunnel/tunneld/main.go
@@ -41,7 +41,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	server, err := r.NewServer()
 	if err != nil {
 		vlog.Fatalf("NewServer failed: %v", err)
@@ -68,9 +68,10 @@
 		fmt.Sprintf("tunnel/hwaddr/%s", hwaddr),
 		fmt.Sprintf("tunnel/id/%s", rt.R().Identity().PublicID()),
 	}
+	dispatcher := ipc.SoloDispatcher(tunnel.NewServerTunnel(&impl.T{}), sflag.NewAuthorizerOrDie())
 	published := false
 	for _, n := range names {
-		if err := server.Serve(n, ipc.SoloDispatcher(tunnel.NewServerTunnel(&impl.T{}), sflag.NewAuthorizerOrDie())); err != nil {
+		if err := server.Serve(n, dispatcher); err != nil {
 			vlog.Infof("Serve(%v) failed: %v", n, err)
 			continue
 		}
diff --git a/examples/tunnel/tunneld/test.sh b/examples/tunnel/tunneld/test.sh
new file mode 100755
index 0000000..bc0631a
--- /dev/null
+++ b/examples/tunnel/tunneld/test.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+
+# Test the tunneld binary
+#
+# This test starts a tunnel server and a mounttable server and then verifies
+# that vsh can run commands through it and that all the expected names are
+# in the mounttable.
+
+toplevel=$(git rev-parse --show-toplevel)
+go=${toplevel}/scripts/build/go
+thisscript=$0
+
+echo "Test directory: $(dirname $0)"
+
+builddir=$(mktemp -d --tmpdir=${toplevel}/go)
+trap onexit EXIT
+
+onexit() {
+	cd /
+	exec 2> /dev/null
+	kill -9 $(jobs -p)
+	rm -rf $builddir
+}
+
+FAIL() {
+	[ $# -gt 0 ] && echo "$thisscript $*"
+	echo FAIL
+	exit 1
+}
+
+PASS() {
+	echo PASS
+	exit 0
+}
+
+# Build binaries.
+cd $builddir
+$go build veyron/examples/tunnel/tunneld || FAIL "line $LINENO: failed to build tunneld"
+$go build veyron/examples/tunnel/vsh || FAIL "line $LINENO: failed to build vsh"
+$go build veyron/services/mounttable/mounttabled || FAIL "line $LINENO: failed to build mounttabled"
+$go build veyron/tools/mounttable || FAIL "line $LINENO: failed to build mounttable"
+$go build veyron/tools/identity || FAIL "line $LINENO: failed to build identity"
+
+# Start mounttabled and find its endpoint.
+mtlog=$(mktemp --tmpdir=.)
+./mounttabled --address=localhost:0 > $mtlog 2>&1 &
+
+for i in 1 2 3 4; do
+	ep=$(grep "Mount table service at:" $mtlog | sed -re 's/^.*endpoint: ([^ ]*).*/\1/')
+	if [ -n "$ep" ]; then
+		break
+	fi
+	sleep 1
+done
+[ -z $ep ] && FAIL "line $LINENO: no mounttable server"
+
+tmpid=$(mktemp --tmpdir=.)
+./identity --name=test > $tmpid
+
+export NAMESPACE_ROOT=$ep
+export VEYRON_IDENTITY=$tmpid
+
+# Start tunneld and find its endpoint.
+tunlog=$(mktemp --tmpdir=.)
+./tunneld --address=localhost:0 > $tunlog 2>&1 &
+
+for i in 1 2 3 4; do
+	ep=$(grep "Listening on endpoint" $tunlog | sed -re 's/^.*endpoint ([^ ]*).*/\1/')
+	if [ -n "$ep" ]; then
+		break
+	fi
+	sleep 1
+done
+[ -z $ep ] && FAIL "line $LINENO: no tunnel server"
+
+# Run remote command with the endpoint.
+got=$(./vsh $ep echo HELLO WORLD)
+want="HELLO WORLD"
+
+if [ "$got" != "$want" ]; then
+	FAIL "line $LINENO: unexpected output. Got $got, want $want"
+fi
+
+# Run remote command with the object name.
+got=$(./vsh tunnel/id/test echo HELLO WORLD)
+want="HELLO WORLD"
+
+if [ "$got" != "$want" ]; then
+	FAIL "line $LINENO: unexpected output. Got $got, want $want"
+fi
+
+# Verify that all the published names are there.
+got=$(./mounttable glob $NAMESPACE_ROOT 'tunnel/*/*' |    \
+      sed -e 's/TTL .m..s/TTL XmXXs/'                     \
+          -e 's!hwaddr/[^ ]*!hwaddr/XX:XX:XX:XX:XX:XX!' | \
+      sort)
+want="[$NAMESPACE_ROOT]
+tunnel/hostname/$(hostname) $ep// (TTL XmXXs)
+tunnel/hwaddr/XX:XX:XX:XX:XX:XX $ep// (TTL XmXXs)
+tunnel/id/test $ep// (TTL XmXXs)"
+
+if [ "$got" != "$want" ]; then
+	FAIL "line $LINENO: unexpected output. Got $got, want $want"
+fi
+
+PASS
diff --git a/examples/tunnel/vsh/main.go b/examples/tunnel/vsh/main.go
index 8917472..50f662e 100644
--- a/examples/tunnel/vsh/main.go
+++ b/examples/tunnel/vsh/main.go
@@ -71,7 +71,7 @@
 
 func realMain() int {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	host, cmd, err := veyronNameAndCommandLine()
 	if err != nil {
diff --git a/examples/unresolve/test_util.go b/examples/unresolve/test_util.go
index 7725c67..031258a 100644
--- a/examples/unresolve/test_util.go
+++ b/examples/unresolve/test_util.go
@@ -21,7 +21,7 @@
 )
 
 func initRT(opts ...veyron2.ROpt) func() {
-	return rt.Init(opts...).Shutdown
+	return rt.Init(opts...).Cleanup
 }
 
 func createServer(name string, dispatcher ipc.Dispatcher, opts ...ipc.ServerOpt) (ipc.Server, string) {
diff --git a/examples/wspr_sample/sampled/main.go b/examples/wspr_sample/sampled/main.go
index fce02eb..0860680 100644
--- a/examples/wspr_sample/sampled/main.go
+++ b/examples/wspr_sample/sampled/main.go
@@ -12,7 +12,7 @@
 func main() {
 	// Create the runtime
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	s, endpoint, err := lib.StartServer(r)
 	if err != nil {
diff --git a/lib/bluetooth/bluetooth.go b/lib/bluetooth/bluetooth.go
index 647eddd..208858b 100644
--- a/lib/bluetooth/bluetooth.go
+++ b/lib/bluetooth/bluetooth.go
@@ -18,7 +18,7 @@
 
 // // Explicitly link libbluetooth and other libraries as "go build" cannot
 // // figure out these dependencies..
-// #cgo LDFLAGS: -lbluetooth -ldbus-1 -lusb-1.0 -lusb -lexpat
+// #cgo LDFLAGS: -lbluetooth
 // #include <bluetooth/bluetooth.h>
 // #include <bluetooth/hci.h>
 // #include <bluetooth/hci_lib.h>
diff --git a/lib/bluetooth/listener.go b/lib/bluetooth/listener.go
index 35bad9b..86e99a3 100644
--- a/lib/bluetooth/listener.go
+++ b/lib/bluetooth/listener.go
@@ -11,13 +11,13 @@
 
 // // Explicitly link libbluetooth and other libraries as "go build" cannot
 // // figure out these dependencies..
-// #cgo LDFLAGS: -lbluetooth -ldbus-1 -lusb-1.0 -lusb -lexpat
+// #cgo LDFLAGS: -lbluetooth
 // #include <stdlib.h>
 // #include <unistd.h>
 // #include "bt.h"
 import "C"
 
-// listener waits for incoming RFCOMM connections on the providee socket.
+// listener waits for incoming RFCOMM connections on the provided socket.
 // It implements the net.Listener interface.
 type listener struct {
 	localAddr *addr
diff --git a/lib/signals/signals_test.go b/lib/signals/signals_test.go
index bbd576c..93480f8 100644
--- a/lib/signals/signals_test.go
+++ b/lib/signals/signals_test.go
@@ -52,7 +52,7 @@
 	wait := ShutdownOnSignals(signals...)
 	fmt.Println("ready")
 	fmt.Println("received signal", <-wait)
-	r.Shutdown()
+	r.Cleanup()
 	<-closeStopLoop
 }
 
@@ -69,7 +69,7 @@
 }
 
 func handleDefaultsIgnoreChan([]string) {
-	defer rt.Init().Shutdown()
+	defer rt.Init().Cleanup()
 	closeStopLoop := make(chan struct{})
 	go stopLoop(closeStopLoop)
 	ShutdownOnSignals()
@@ -302,7 +302,7 @@
 // TestCleanRemoteShutdown verifies that remote shutdown works correctly.
 func TestCleanRemoteShutdown(t *testing.T) {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	c := blackbox.HelperCommand(t, "handleDefaults")
 	defer c.Cleanup()
 	// This sets up the child's identity to be derived from the parent's (so
diff --git a/lib/testutil/init.go b/lib/testutil/init.go
index 8ba55c7..188291c 100644
--- a/lib/testutil/init.go
+++ b/lib/testutil/init.go
@@ -11,6 +11,7 @@
 	"os"
 	"runtime"
 	"strconv"
+	"sync"
 	// Need to import all of the packages that could possibly
 	// define flags that we care about. In practice, this is the
 	// flags defined by the testing package, the logging library
@@ -28,8 +29,35 @@
 	SeedEnv = "VEYRON_RNG_SEED"
 )
 
+// Random is a concurrent-access friendly source of randomness.
+type Random struct {
+	mu   sync.Mutex
+	rand *rand.Rand
+}
+
+// Int returns a non-negative pseudo-random int.
+func (r *Random) Int() int {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	return r.rand.Int()
+}
+
+// Intn returns a non-negative pseudo-random int in the range [0, n).
+func (r *Random) Intn(n int) int {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	return r.rand.Intn(n)
+}
+
+// Int63 returns a non-negative 63-bit pseudo-random integer as an int64.
+func (r *Random) Int63() int64 {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	return r.rand.Int63()
+}
+
 var (
-	Rand *rand.Rand
+	Rand *Random
 )
 
 func init() {
@@ -54,5 +82,5 @@
 		}
 	}
 	vlog.Infof("Seeding pseudo-random number generator with %v", seed)
-	Rand = rand.New(rand.NewSource(seed))
+	Rand = &Random{rand: rand.New(rand.NewSource(seed))}
 }
diff --git a/lib/testutil/security/util_test.go b/lib/testutil/security/util_test.go
index 5632aef..3aac88d 100644
--- a/lib/testutil/security/util_test.go
+++ b/lib/testutil/security/util_test.go
@@ -17,7 +17,7 @@
 	if err != nil {
 		t.Fatalf("rt.New failed: %v", err)
 	}
-	defer r.Shutdown()
+	defer r.Cleanup()
 	newID := func(name string) security.PrivateID {
 		id, err := r.NewIdentity(name)
 		if err != nil {
@@ -48,7 +48,7 @@
 	if err != nil {
 		t.Fatalf("rt.New failed: %v", err)
 	}
-	defer r.Shutdown()
+	defer r.Cleanup()
 	acl := security.ACL{
 		"veyron/alice": security.LabelSet(security.ReadLabel | security.WriteLabel),
 		"veyron/bob":   security.LabelSet(security.ReadLabel),
@@ -76,7 +76,7 @@
 	if err != nil {
 		t.Fatalf("rt.New failed: %v", err)
 	}
-	defer r.Shutdown()
+	defer r.Cleanup()
 	id, err := r.NewIdentity("test")
 	if err != nil {
 		t.Fatalf("r.NewIdentity failed: %v", err)
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 49b3915..0aa2458 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -40,6 +40,8 @@
 	vcMapMu sync.Mutex
 	// TODO(ashankar): Additionally, should vcMap be keyed with other options also?
 	vcMap map[string]*vcInfo // map from endpoint.String() to vc info
+
+	dischargeCache dischargeCache
 }
 
 type vcInfo struct {
@@ -50,10 +52,11 @@
 
 func InternalNewClient(streamMgr stream.Manager, ns naming.Namespace, opts ...ipc.ClientOpt) (ipc.Client, error) {
 	c := &client{
-		streamMgr:   streamMgr,
-		ns:          ns,
-		vcMap:       make(map[string]*vcInfo),
-		callTimeout: defaultCallTimeout,
+		streamMgr:      streamMgr,
+		ns:             ns,
+		vcMap:          make(map[string]*vcInfo),
+		callTimeout:    defaultCallTimeout,
+		dischargeCache: dischargeCache{CaveatDischargeMap: make(security.CaveatDischargeMap)},
 	}
 	for _, opt := range opts {
 		// Collect all client opts that are also vc opts.
@@ -152,8 +155,11 @@
 			flow.Close()
 			continue
 		}
+
+		discharges := c.prepareDischarges(ctx, flow.LocalID(), flow.RemoteID(), method, opts)
+
 		lastErr = nil
-		fc := newFlowClient(flow)
+		fc := newFlowClient(flow, &c.dischargeCache, discharges)
 		if verr := fc.start(suffix, method, args, timeout, blessing); verr != nil {
 			return nil, verr
 		}
@@ -172,7 +178,7 @@
 	if server == nil {
 		return nil, fmt.Errorf("server identity cannot be nil")
 	}
-	// TODO(ataly): Fetch third-party discharges from the server.
+	// TODO(ataly,andreser): Check the third-party discharges the server presents
 	// TODO(ataly): What should the label be for the context? Typically the label is the security.Label
 	// of the method but we don't have that information here at the client.
 	authID, err := server.Authorize(isecurity.NewContext(isecurity.ContextArgs{
@@ -236,16 +242,21 @@
 	flow     stream.Flow  // the underlying flow
 	response ipc.Response // each decoded response message is kept here
 
+	discharges     []security.ThirdPartyDischarge // discharges used for this request
+	dischargeCache *dischargeCache                // client-global discharge cache reference type
+
 	sendClosedMu sync.Mutex
 	sendClosed   bool // is the send side already closed? GUARDED_BY(sendClosedMu)
 }
 
-func newFlowClient(flow stream.Flow) *flowClient {
+func newFlowClient(flow stream.Flow, dischargeCache *dischargeCache, discharges []security.ThirdPartyDischarge) *flowClient {
 	return &flowClient{
 		// TODO(toddw): Support different codecs
-		dec:  vom.NewDecoder(flow),
-		enc:  vom.NewEncoder(flow),
-		flow: flow,
+		dec:            vom.NewDecoder(flow),
+		enc:            vom.NewEncoder(flow),
+		flow:           flow,
+		discharges:     discharges,
+		dischargeCache: dischargeCache,
 	}
 }
 
@@ -258,11 +269,12 @@
 
 func (fc *flowClient) start(suffix, method string, args []interface{}, timeout time.Duration, blessing security.PublicID) verror.E {
 	req := ipc.Request{
-		Suffix:      suffix,
-		Method:      method,
-		NumPosArgs:  uint64(len(args)),
-		Timeout:     int64(timeout),
-		HasBlessing: blessing != nil,
+		Suffix:        suffix,
+		Method:        method,
+		NumPosArgs:    uint64(len(args)),
+		Timeout:       int64(timeout),
+		HasBlessing:   blessing != nil,
+		NumDischarges: uint64(len(fc.discharges)),
 	}
 	if err := fc.enc.Encode(req); err != nil {
 		return fc.close(verror.BadProtocolf("ipc: request encoding failed: %v", err))
@@ -272,6 +284,11 @@
 			return fc.close(verror.BadProtocolf("ipc: blessing encoding failed: %v", err))
 		}
 	}
+	for _, d := range fc.discharges {
+		if err := fc.enc.Encode(d); err != nil {
+			return fc.close(verror.BadProtocolf("ipc: failed to encode discharge for %x: %v", d.CaveatID(), err))
+		}
+	}
 	for ix, arg := range args {
 		if err := fc.enc.Encode(arg); err != nil {
 			return fc.close(verror.BadProtocolf("ipc: arg %d encoding failed: %v", ix, err))
@@ -374,6 +391,14 @@
 		}
 	}
 	if fc.response.Error != nil {
+		if verror.Is(fc.response.Error, verror.NotAuthorized) {
+			// In case the error was caused by a bad discharge, we do not want to get stuck
+			// with retrying again and again with this discharge. As there is no direct way
+			// to detect it, we conservatively flush all discharges we used from the cache.
+			// TODO(ataly,andreser): add verror.BadDischarge and handle it explicitly?
+			vlog.VI(3).Infof("Discarding %d discharges as RPC failed with %v", len(fc.discharges), fc.response.Error)
+			fc.dischargeCache.Invalidate(fc.discharges...)
+		}
 		return fc.close(verror.ConvertWithDefault(verror.Internal, fc.response.Error))
 	}
 	if got, want := fc.response.NumPosResults, uint64(len(resultptrs)); got != want {
diff --git a/runtimes/google/ipc/discharges.go b/runtimes/google/ipc/discharges.go
new file mode 100644
index 0000000..75d99f3
--- /dev/null
+++ b/runtimes/google/ipc/discharges.go
@@ -0,0 +1,179 @@
+package ipc
+
+import (
+	"sync"
+	"veyron2"
+	"veyron2/context"
+	"veyron2/ipc"
+	"veyron2/security"
+	"veyron2/vdl"
+	"veyron2/vlog"
+)
+
+// prepareDischarges retrieves the caveat discharges required for using blessing
+// at server. The discharges are either found in the client cache, in the call
+// options, or requested from the discharge issuer indicated on the caveat.
+// Note that requesting a discharge is an ipc call, so one copy of this
+// function must be able to successfully terminate while another is blocked.
+func (c *client) prepareDischarges(ctx context.T, blessing, server security.PublicID,
+	method string, opts []ipc.CallOpt) (ret []security.ThirdPartyDischarge) {
+	// TODO(andreser,ataly): figure out whether this should return an error and how that should be handled
+	// Missing discharges do not necessarily mean the blessing is invalid (e.g., SetID)
+	if blessing == nil {
+		return
+	}
+
+	var caveats []security.ThirdPartyCaveat
+	for _, cav := range blessing.ThirdPartyCaveats() {
+		if server.Match(cav.Service) {
+			caveats = append(caveats, cav.Caveat.(security.ThirdPartyCaveat))
+		}
+	}
+	if len(caveats) == 0 {
+		return
+	}
+
+	discharges := make([]security.ThirdPartyDischarge, len(caveats))
+	dischargesFromOpts(caveats, opts, discharges)
+	c.dischargeCache.Discharges(caveats, discharges)
+	if shouldFetchDischarges(opts) {
+		c.fetchDischarges(ctx, caveats, opts, discharges)
+	}
+	for _, d := range discharges {
+		if d != nil {
+			ret = append(ret, d)
+		}
+	}
+	return
+}
+
+// dischargeCache is a concurrency-safe cache for third party caveat discharges.
+type dischargeCache struct {
+	sync.RWMutex
+	security.CaveatDischargeMap // GUARDED_BY(RWMutex)
+}
+
+// Add inserts the argument to the cache, possibly overwriting previous
+// discharges for the same caveat.
+func (dcc *dischargeCache) Add(discharges ...security.ThirdPartyDischarge) {
+	dcc.Lock()
+	for _, d := range discharges {
+		dcc.CaveatDischargeMap[d.CaveatID()] = d
+	}
+	dcc.Unlock()
+}
+
+// Invalidate removes discharges from the cache.
+func (dcc *dischargeCache) Invalidate(discharges ...security.ThirdPartyDischarge) {
+	dcc.Lock()
+	for _, d := range discharges {
+		if dcc.CaveatDischargeMap[d.CaveatID()] == d {
+			delete(dcc.CaveatDischargeMap, d.CaveatID())
+		}
+	}
+	dcc.Unlock()
+}
+
+// Discharges takes a slice of caveats and a slice of discharges of the same
+// length and fills in nil entries in the discharges slice with discharges
+// from the cache (if there are any).
+// REQUIRES: len(caveats) == len(out)
+func (dcc *dischargeCache) Discharges(caveats []security.ThirdPartyCaveat, out []security.ThirdPartyDischarge) {
+	dcc.Lock()
+	for i, d := range out {
+		if d != nil {
+			continue
+		}
+		out[i] = dcc.CaveatDischargeMap[caveats[i].ID()]
+	}
+	dcc.Unlock()
+}
+
+// dischargesFromOpts fills in the nils in the out argument with discharges in
+// opts that match the caveat at the same index in caveats.
+// REQUIRES: len(caveats) == len(out)
+func dischargesFromOpts(caveats []security.ThirdPartyCaveat, opts []ipc.CallOpt,
+	out []security.ThirdPartyDischarge) {
+	for _, opt := range opts {
+		d, ok := opt.(veyron2.DischargeOpt)
+		if !ok {
+			continue
+		}
+		for i, cav := range caveats {
+			if out[i] == nil && d.CaveatID() == cav.ID() {
+				out[i] = d
+			}
+		}
+	}
+}
+
+// fetchDischarges fills in out by fetching discharges for caveats from the
+// appropriate discharge service. Since there may be dependencies in the
+// caveats, fetchDischarges keeps retrying until either all discharges can be
+// fetched or no new discharges are fetched.
+// REQUIRES: len(caveats) == len(out)
+func (c *client) fetchDischarges(ctx context.T, caveats []security.ThirdPartyCaveat, opts []ipc.CallOpt, out []security.ThirdPartyDischarge) {
+	opts = append([]ipc.CallOpt{dontFetchDischarges{}}, opts...)
+	var wg sync.WaitGroup
+	for {
+		type fetched struct {
+			idx       int
+			discharge security.ThirdPartyDischarge
+		}
+		discharges := make(chan fetched, len(caveats))
+		for i := range caveats {
+			if out[i] != nil {
+				continue
+			}
+			wg.Add(1)
+			go func(i int, cav security.ThirdPartyCaveat) {
+				defer wg.Done()
+				vlog.VI(3).Infof("Fetching discharge for %T from %v", cav.ID(), cav, cav.Location())
+				call, err := c.StartCall(ctx, cav.Location(), "Discharge", []interface{}{cav}, opts...)
+				if err != nil {
+					vlog.VI(3).Infof("Discharge fetch for caveat %T from %v failed: %v", cav, cav.Location(), err)
+					return
+				}
+				var dAny vdl.Any
+				// TODO(ashankar): Retry on errors like no-route-to-service, name resolution failures etc.
+				ierr := call.Finish(&dAny, &err)
+				if ierr != nil || err != nil {
+					vlog.VI(3).Infof("Discharge fetch for caveat %T from %v failed: (%v, %v)", cav, cav.Location(), err, ierr)
+					return
+				}
+				d, ok := dAny.(security.ThirdPartyDischarge)
+				if !ok {
+					vlog.Errorf("fetchDischarges: server at %s sent a %T (%v) instead of a ThirdPartyDischarge", cav.Location(), dAny, dAny)
+				}
+				discharges <- fetched{i, d}
+			}(i, caveats[i])
+		}
+		wg.Wait()
+		close(discharges)
+		var got int
+		for fetched := range discharges {
+			c.dischargeCache.Add(fetched.discharge)
+			out[fetched.idx] = fetched.discharge
+			got++
+		}
+		vlog.VI(2).Infof("fetchDischarges: got %d discharges", got)
+		if got == 0 {
+			return
+		}
+	}
+}
+
+// dontFetchDischares is an ipc.CallOpt that indicates that no extre ipc-s
+// should be done to fetch discharges for the call with this opt.
+// Discharges in the cache and in the call options are still used.
+type dontFetchDischarges struct{}
+
+func (dontFetchDischarges) IPCCallOpt() {}
+func shouldFetchDischarges(opts []ipc.CallOpt) bool {
+	for _, opt := range opts {
+		if _, ok := opt.(dontFetchDischarges); ok {
+			return false
+		}
+	}
+	return true
+}
diff --git a/runtimes/google/ipc/flow_test.go b/runtimes/google/ipc/flow_test.go
index 7009258..8850b21 100644
--- a/runtimes/google/ipc/flow_test.go
+++ b/runtimes/google/ipc/flow_test.go
@@ -117,7 +117,7 @@
 	ipcServer := &server{disp: testDisp{newEchoInvoker}}
 	for _, test := range tests {
 		clientFlow, serverFlow := newTestFlows()
-		client := newFlowClient(clientFlow)
+		client := newFlowClient(clientFlow, nil, nil)
 		server := newFlowServer(serverFlow, ipcServer)
 		err := client.start(test.suffix, test.method, test.args, time.Duration(0), nil)
 		if err != nil {
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 86b5a18..66e1cc7 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -30,17 +30,46 @@
 	"veyron2/ipc/stream"
 	"veyron2/naming"
 	"veyron2/security"
+	"veyron2/vdl"
 	"veyron2/verror"
 	"veyron2/vlog"
+	"veyron2/vom"
 )
 
 var (
+	errAuthorizer = errors.New("ipc: application Authorizer denied access")
 	errMethod = verror.Abortedf("server returned an error")
 	clientID  security.PrivateID
 	serverID  security.PrivateID
+	clock     = new(fakeClock)
 )
 
-var errAuthorizer = errors.New("ipc: application Authorizer denied access")
+type fakeClock struct {
+	sync.Mutex
+	time int
+}
+
+func (c *fakeClock) Now() int {
+	c.Lock()
+	defer c.Unlock()
+	return c.time
+}
+
+func (c *fakeClock) Advance(steps uint) {
+	c.Lock()
+	c.time += int(steps)
+	c.Unlock()
+}
+
+type fakeTimeCaveat int
+
+func (c fakeTimeCaveat) Validate(security.Context) error {
+	now := clock.Now()
+	if now > int(c) {
+		return fmt.Errorf("fakeTimeCaveat expired: now=%d > then=%d", now, c)
+	}
+	return nil
+}
 
 type fakeContext struct{}
 
@@ -99,6 +128,18 @@
 	return "UnauthorizedResult", fmt.Errorf("Unauthorized should never be called")
 }
 
+type dischargeServer struct{}
+
+func (*dischargeServer) Discharge(ctx ipc.ServerCall, caveat vdl.Any) (vdl.Any, error) {
+	c, ok := caveat.(security.ThirdPartyCaveat)
+	if !ok {
+		return nil, fmt.Errorf("discharger: unknown caveat(%T)", caveat)
+	}
+	// Add a fakeTimeCaveat to allow the discharge to expire
+	expiry := fakeTimeCaveat(clock.Now())
+	return serverID.MintDischarge(c, ctx, time.Hour, []security.ServiceCaveat{security.UniversalCaveat(expiry)})
+}
+
 type testServerAuthorizer struct{}
 
 func (testServerAuthorizer) Authorize(c security.Context) error {
@@ -113,18 +154,22 @@
 func (t testServerDisp) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
 	// If suffix is "nilAuth" we use default authorization, if it is "aclAuth" we
 	// use an ACL based authorizer, and otherwise we use the custom testServerAuthorizer.
-	if suffix == "nilAuth" {
-		return ipc.ReflectInvoker(t.server), nil, nil
-	}
-	if suffix == "aclAuth" {
+	var authorizer security.Authorizer
+	switch suffix {
+	case "discharger":
+		return ipc.ReflectInvoker(&dischargeServer{}), testServerAuthorizer{}, nil
+	case "nilAuth":
+		authorizer = nil
+	case "aclAuth":
 		// Only authorize clients matching patterns "client" or "server/*".
-		acl := security.ACL{
+		authorizer = security.NewACLAuthorizer(security.ACL{
 			"server/*": security.LabelSet(security.AdminLabel),
 			"client":   security.LabelSet(security.AdminLabel),
-		}
-		return ipc.ReflectInvoker(t.server), security.NewACLAuthorizer(acl), nil
+		})
+	default:
+		authorizer = testServerAuthorizer{}
 	}
-	return ipc.ReflectInvoker(t.server), testServerAuthorizer{}, nil
+	return ipc.ReflectInvoker(t.server), authorizer, nil
 }
 
 // namespace is a simple partial implementation of naming.Namespace.  In
@@ -228,6 +273,9 @@
 	if err := server.Serve("mountpoint/server", disp); err != nil {
 		t.Errorf("server.Publish failed: %v", err)
 	}
+	if err := server.Serve("mountpoint/discharger", disp); err != nil {
+		t.Errorf("server.Publish for discharger failed: %v", err)
+	}
 	return ep, server
 }
 
@@ -315,6 +363,27 @@
 	return derivedID
 }
 
+// deriveForThirdPartyCaveats creates a SetPrivateID that can be used for
+//  1. talking to the server, if the caveats are fulfilled
+//  2. getting discharges, even if the caveats are not fulfilled
+// As an identity with an unfulfilled caveat is invalid (even for asking for  a
+// discharge), this function creates a set of two identities. The first will
+// have the caveats, the second will always be valid, but only for getting
+// discharges. The client presents both blessings in both cases, the discharger
+// ignores the first if it is invalid.
+func deriveForThirdPartyCaveats(blessor security.PrivateID, name string, caveats ...security.ServiceCaveat) security.PrivateID {
+	id := derive(blessor, name, caveats...)
+	dischargeID, err := id.Derive(bless(blessor, id.PublicID(), name, security.UniversalCaveat(caveat.MethodRestriction{"Discharge"})))
+	if err != nil {
+		panic(err)
+	}
+	id, err = isecurity.NewSetPrivateID(id, dischargeID)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
 func matchesErrorPattern(err error, pattern string) bool {
 	if (len(pattern) == 0) != (err == nil) {
 		return false
@@ -370,20 +439,23 @@
 }
 
 func TestStartCall(t *testing.T) {
-	authorizeErr := "not authorized because"
-	nameErr := "does not match the provided pattern"
+	const (
+		authorizeErr = "not authorized because"
+		nameErr      = "does not match the provided pattern"
+	)
+	var (
+		now        = time.Now()
+		cavOnlyV1  = security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
+		cavExpired = security.ServiceCaveat{
+			Service: security.AllPrincipals,
+			Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
+		}
 
-	cavOnlyV1 := security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
-	now := time.Now()
-	cavExpired := security.ServiceCaveat{
-		Service: security.AllPrincipals,
-		Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
-	}
-
-	clientV1ID := derive(clientID, "v1")
-	clientV2ID := derive(clientID, "v2")
-	serverV1ID := derive(serverID, "v1", cavOnlyV1)
-	serverExpiredID := derive(serverID, "expired", cavExpired)
+		clientV1ID      = derive(clientID, "v1")
+		clientV2ID      = derive(clientID, "v2")
+		serverV1ID      = derive(serverID, "v1", cavOnlyV1)
+		serverExpiredID = derive(serverID, "expired", cavExpired)
+	)
 
 	tests := []struct {
 		clientID, serverID security.PrivateID
@@ -546,21 +618,46 @@
 	}
 }
 
+func mkThirdPartyCaveat(discharger security.PublicID, location string, c security.Caveat) security.ThirdPartyCaveat {
+	tpc, err := caveat.NewPublicKeyCaveat(c, discharger, location)
+	if err != nil {
+		panic(err)
+	}
+	return tpc
+}
+
 func TestRPCAuthorization(t *testing.T) {
-	cavOnlyEcho := security.ServiceCaveat{
-		Service: security.AllPrincipals,
-		Caveat:  caveat.MethodRestriction{"Echo"},
-	}
-	now := time.Now()
-	cavExpired := security.ServiceCaveat{
-		Service: security.AllPrincipals,
-		Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
-	}
+	var (
+		now = time.Now()
+		// First-party caveats
+		cavOnlyEcho = security.ServiceCaveat{
+			Service: security.AllPrincipals,
+			Caveat:  caveat.MethodRestriction{"Echo"},
+		}
+		cavExpired = security.ServiceCaveat{
+			Service: security.AllPrincipals,
+			Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
+		}
+		// Third-party caveats
+		// The Discharge service can be run by any identity, but in our tests the same server runs
+		// a Discharge service as well.
+		dischargerID = serverID.PublicID()
+		cavTPValid   = security.ServiceCaveat{
+			Service: security.PrincipalPattern(serverID.PublicID().Names()[0]),
+			Caveat:  mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", &caveat.Expiry{ExpiryTime: now.Add(24 * time.Hour)}),
+		}
+		cavTPExpired = security.ServiceCaveat{
+			Service: security.PrincipalPattern(serverID.PublicID().Names()[0]),
+			Caveat:  mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", &caveat.Expiry{IssueTime: now, ExpiryTime: now}),
+		}
 
-	blessedByServerOnlyEcho := derive(serverID, "onlyEcho", cavOnlyEcho)
-	blessedByServerExpired := derive(serverID, "expired", cavExpired)
-	blessedByClient := derive(clientID, "blessed")
-
+		// Client blessings that will be tested
+		blessedByServerOnlyEcho  = derive(serverID, "onlyEcho", cavOnlyEcho)
+		blessedByServerExpired   = derive(serverID, "expired", cavExpired)
+		blessedByServerTPValid   = deriveForThirdPartyCaveats(serverID, "tpvalid", cavTPValid)
+		blessedByServerTPExpired = deriveForThirdPartyCaveats(serverID, "tpexpired", cavTPExpired)
+		blessedByClient          = derive(clientID, "blessed")
+	)
 	const (
 		expiredIDErr = "forbids credential from being used at this time"
 		aclAuthErr   = "no matching ACL entry found"
@@ -599,7 +696,6 @@
 		{clientID, "mountpoint/server/aclAuth", "Closure", nil, nil, ""},
 		{blessedByClient, "mountpoint/server/aclAuth", "Closure", nil, nil, aclAuthErr},
 		{serverID, "mountpoint/server/aclAuth", "Closure", nil, nil, ""},
-
 		// All methods except "Unauthorized" are authorized by the custom authorizer.
 		{clientID, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
 		{blessedByClient, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
@@ -611,6 +707,9 @@
 		{clientID, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
 		{blessedByClient, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
 		{serverID, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
+		// Third-party caveat discharges should be fetched and forwarded
+		{blessedByServerTPValid, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
+		{blessedByServerTPExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, "missing discharge"},
 	}
 	name := func(t testcase) string {
 		return fmt.Sprintf("%q RPCing %s.%s(%v)", t.clientID.PublicID(), t.name, t.method, t.args)
@@ -640,6 +739,49 @@
 	}
 }
 
+type alwaysValidCaveat struct{}
+
+func (alwaysValidCaveat) Validate(security.Context) error { return nil }
+
+func TestDischargePurgeFromCache(t *testing.T) {
+	var (
+		dischargerID = serverID.PublicID()
+		caveat       = mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", alwaysValidCaveat{})
+		clientCID    = deriveForThirdPartyCaveats(serverID, "client", security.UniversalCaveat(caveat))
+	)
+	b := createBundle(t, clientCID, serverID, &testServer{})
+	defer b.cleanup(t)
+
+	call := func() error {
+		call, err := b.client.StartCall(&fakeContext{}, "mountpoint/server/suffix", "Echo", []interface{}{"batman"})
+		if err != nil {
+			return fmt.Errorf("client.StartCall failed: %v", err)
+		}
+		var got string
+		if err := call.Finish(&got); err != nil {
+			return fmt.Errorf("client.Finish failed: %v", err)
+		}
+		if want := `method:"Echo",suffix:"suffix",arg:"batman"`; got != want {
+			return fmt.Errorf("Got [%v] want [%v]", got, want)
+		}
+		return nil
+	}
+
+	// First call should succeed
+	if err := call(); err != nil {
+		t.Fatal(err)
+	}
+	// Advance virtual clock, which will invalidate the discharge
+	clock.Advance(1)
+	if err := call(); !matchesErrorPattern(err, "fakeTimeCaveat expired") {
+		t.Errorf("Got error [%v] wanted to match pattern 'fakeTimeCaveat expired'", err)
+	}
+	// But retrying will succeed since the discharge should be purged from cache and refreshed
+	if err := call(); err != nil {
+		t.Fatal(err)
+	}
+}
+
 type cancelTestServer struct {
 	started   chan struct{}
 	cancelled chan struct{}
@@ -1095,4 +1237,6 @@
 
 	blackbox.CommandTable["runServer"] = runServer
 	blackbox.CommandTable["runProxy"] = runProxy
+
+	vom.Register(fakeTimeCaveat(0))
 }
diff --git a/runtimes/google/ipc/jni/jni.go b/runtimes/google/ipc/jni/jni.go
index 627d828..204a9f0 100644
--- a/runtimes/google/ipc/jni/jni.go
+++ b/runtimes/google/ipc/jni/jni.go
@@ -129,8 +129,8 @@
 	}
 }
 
-//export Java_com_veyron_runtimes_google_Runtime_00024Server_nativeRegister
-func Java_com_veyron_runtimes_google_Runtime_00024Server_nativeRegister(env *C.JNIEnv, jServer C.jobject, goServerPtr C.jlong, prefix C.jstring, dispatcher C.jobject) {
+//export Java_com_veyron_runtimes_google_Runtime_00024Server_nativeServe
+func Java_com_veyron_runtimes_google_Runtime_00024Server_nativeServe(env *C.JNIEnv, jServer C.jobject, goServerPtr C.jlong, name C.jstring, dispatcher C.jobject) {
 	s := (*ipc.Server)(ptr(goServerPtr))
 	if s == nil {
 		jThrowV(env, fmt.Errorf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
@@ -142,7 +142,10 @@
 		jThrowV(env, err)
 		return
 	}
-	(*s).Register(goString(env, prefix), d)
+	if err := (*s).Serve(goString(env, name), d); err != nil {
+		jThrowV(env, err)
+		return
+	}
 }
 
 //export Java_com_veyron_runtimes_google_Runtime_00024Server_nativeListen
@@ -160,19 +163,6 @@
 	return jString(env, ep.String())
 }
 
-//export Java_com_veyron_runtimes_google_Runtime_00024Server_nativePublish
-func Java_com_veyron_runtimes_google_Runtime_00024Server_nativePublish(env *C.JNIEnv, server C.jobject, goServerPtr C.jlong, name C.jstring) {
-	s := (*ipc.Server)(ptr(goServerPtr))
-	if s == nil {
-		jThrowV(env, fmt.Errorf("Couldn't find Go server with pointer: %d", int(goServerPtr)))
-		return
-	}
-	if err := (*s).Publish(goString(env, name)); err != nil {
-		jThrowV(env, err)
-		return
-	}
-}
-
 //export Java_com_veyron_runtimes_google_Runtime_00024Server_nativeStop
 func Java_com_veyron_runtimes_google_Runtime_00024Server_nativeStop(env *C.JNIEnv, server C.jobject, goServerPtr C.jlong) {
 	s := (*ipc.Server)(ptr(goServerPtr))
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index f162fdf..1a88570 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -332,9 +332,10 @@
 		server: server,
 		disp:   server.disp,
 		// TODO(toddw): Support different codecs
-		dec:  vom.NewDecoder(flow),
-		enc:  vom.NewEncoder(flow),
-		flow: flow,
+		dec:        vom.NewDecoder(flow),
+		enc:        vom.NewEncoder(flow),
+		flow:       flow,
+		discharges: make(security.CaveatDischargeMap),
 	}
 }
 
@@ -450,7 +451,14 @@
 		// should servers be able to assume that a blessing is something that does not
 		// have the authorizations that the server's own identity has?
 	}
-
+	// Receive third party caveat discharges the client sent
+	for i := uint64(0); i < req.NumDischarges; i++ {
+		var d security.ThirdPartyDischarge
+		if err := fs.dec.Decode(&d); err != nil {
+			return nil, verror.BadProtocolf("ipc: decoding discharge %d of %d failed: %v", i, req.NumDischarges, err)
+		}
+		fs.discharges[d.CaveatID()] = d
+	}
 	// Lookup the invoker.
 	invoker, auth, suffix, verr := fs.lookup(req.Suffix)
 	fs.suffix = suffix // with leading /'s stripped
@@ -461,7 +469,6 @@
 	numArgs := int(req.NumPosArgs)
 	argptrs, label, err := invoker.Prepare(req.Method, numArgs)
 	fs.label = label
-	// TODO(ataly, ashankar): Populate the "discharges" field from the request object req.
 	if err != nil {
 		return nil, verror.Makef(verror.ErrorID(err), "%s: name: %q", err, req.Suffix)
 	}
diff --git a/runtimes/google/naming/namespace/all_test.go b/runtimes/google/naming/namespace/all_test.go
index 4db264c..3925350 100644
--- a/runtimes/google/naming/namespace/all_test.go
+++ b/runtimes/google/naming/namespace/all_test.go
@@ -391,7 +391,7 @@
 
 	sr := rt.Init()
 	r, _ := rt.New() // We use a different runtime for the client side.
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	root, _, _, stopper := createNamespace(t, sr)
 	defer stopper()
@@ -455,7 +455,7 @@
 	t.Skip()
 	sr := rt.Init()
 	r, _ := rt.New() // We use a different runtime for the client side.
-	defer r.Shutdown()
+	defer r.Cleanup()
 	root, mts, jokes, stopper := createNamespace(t, sr)
 	runNestedMountTables(t, sr, mts)
 	defer stopper()
@@ -472,7 +472,7 @@
 	t.Skip()
 	sr := rt.Init()
 	r, _ := rt.New() // We use a different runtime for the client side.
-	defer r.Shutdown()
+	defer r.Cleanup()
 	_, _, _, stopper := createNamespace(t, sr)
 	defer func() {
 		vlog.Infof("%d goroutines:", runtime.NumGoroutine())
@@ -486,7 +486,7 @@
 
 func TestBadRoots(t *testing.T) {
 	r, _ := rt.New()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	if _, err := namespace.New(r); err != nil {
 		t.Errorf("namespace.New should not have failed with no roots")
 	}
diff --git a/runtimes/google/rt/mgmt_test.go b/runtimes/google/rt/mgmt_test.go
index e3dfdf6..bb78645 100644
--- a/runtimes/google/rt/mgmt_test.go
+++ b/runtimes/google/rt/mgmt_test.go
@@ -164,7 +164,7 @@
 	checkNoProgress(t, ch)
 	m.AdvanceGoal(0)
 	checkNoProgress(t, ch)
-	m.Shutdown()
+	m.Cleanup()
 	if _, ok := <-ch; ok {
 		t.Errorf("Expected channel to be closed")
 	}
@@ -197,7 +197,7 @@
 	m.AdvanceGoal(4)
 	checkProgress(t, ch1, 11, 4)
 	checkProgress(t, ch2, 11, 4)
-	m.Shutdown()
+	m.Cleanup()
 	if _, ok := <-ch1; ok {
 		t.Errorf("Expected channel to be closed")
 	}
@@ -216,7 +216,7 @@
 		fmt.Printf("Error creating runtime: %v\n", err)
 		return
 	}
-	defer r.Shutdown()
+	defer r.Cleanup()
 	ch := make(chan string, 1)
 	r.WaitForStop(ch)
 	fmt.Printf("Got %s\n", <-ch)
@@ -279,7 +279,7 @@
 		configServer.Stop()
 		c.Cleanup()
 		os.Remove(idFile)
-		// Don't do r.Shutdown() since the runtime needs to be used by
+		// Don't do r.Cleanup() since the runtime needs to be used by
 		// more than one test case.
 	}
 }
diff --git a/runtimes/google/rt/rt.go b/runtimes/google/rt/rt.go
index a27b496..72470fe 100644
--- a/runtimes/google/rt/rt.go
+++ b/runtimes/google/rt/rt.go
@@ -152,7 +152,7 @@
 	return nil
 }
 
-func (rt *vrt) Shutdown() {
+func (rt *vrt) Cleanup() {
 	// TODO(caprita): Consider shutting down mgmt later in the runtime's
 	// shutdown sequence, to capture some of the runtime internal shutdown
 	// tasks in the task tracker.
diff --git a/runtimes/google/vsync/vsyncd/main.go b/runtimes/google/vsync/vsyncd/main.go
index 506c49d..c49de82 100644
--- a/runtimes/google/vsync/vsyncd/main.go
+++ b/runtimes/google/vsync/vsyncd/main.go
@@ -30,7 +30,7 @@
 
 	// Create the runtime.
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	// Create a new server instance.
 	s, err := r.NewServer()
diff --git a/services/gateway/gatewayd/main.go b/services/gateway/gatewayd/main.go
index 897b542..3025812 100644
--- a/services/gateway/gatewayd/main.go
+++ b/services/gateway/gatewayd/main.go
@@ -37,7 +37,7 @@
 
 	// Get the runtime.
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	// Create a new instance of the gateway service.
 	vlog.Info("Connecting to proximity service: ", *proximity)
diff --git a/services/identity/handlers/handlers_test.go b/services/identity/handlers/handlers_test.go
index e540eb8..4793c73 100644
--- a/services/identity/handlers/handlers_test.go
+++ b/services/identity/handlers/handlers_test.go
@@ -37,7 +37,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer r.Shutdown()
+	defer r.Cleanup()
 	ts := httptest.NewServer(Random{r})
 	defer ts.Close()
 
@@ -55,7 +55,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	ts := httptest.NewServer(http.HandlerFunc(Bless))
 	defer ts.Close()
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index ca2a145..b1832e8 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -33,7 +33,7 @@
 	// Setup flags and logging
 	flag.Usage = usage
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	if len(*generate) > 0 {
 		generateAndSaveIdentity()
diff --git a/services/mgmt/application/applicationd/main.go b/services/mgmt/application/applicationd/main.go
index c42bb7d..397e3c3 100644
--- a/services/mgmt/application/applicationd/main.go
+++ b/services/mgmt/application/applicationd/main.go
@@ -23,7 +23,7 @@
 		vlog.Fatalf("Specify a store using --store=<name>")
 	}
 	runtime := rt.Init()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 	server, err := runtime.NewServer()
 	if err != nil {
 		vlog.Fatalf("NewServer() failed: %v", err)
diff --git a/services/mgmt/application/impl/impl_test.go b/services/mgmt/application/impl/impl_test.go
index 76a0fd6..3321f58 100644
--- a/services/mgmt/application/impl/impl_test.go
+++ b/services/mgmt/application/impl/impl_test.go
@@ -17,7 +17,7 @@
 func TestInterface(t *testing.T) {
 	runtime := rt.Init()
 	ctx := runtime.NewContext()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 
 	// Setup and start the application repository server.
 	server, err := runtime.NewServer()
diff --git a/services/mgmt/binary/binaryd/main.go b/services/mgmt/binary/binaryd/main.go
index 55b4d6a..bb59019 100644
--- a/services/mgmt/binary/binaryd/main.go
+++ b/services/mgmt/binary/binaryd/main.go
@@ -61,7 +61,7 @@
 	}
 	vlog.Infof("Binary repository rooted at %v", root)
 	runtime := rt.Init()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 	server, err := runtime.NewServer()
 	if err != nil {
 		vlog.Errorf("NewServer() failed: %v", err)
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 1e62541..8a4cd47 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -219,7 +219,7 @@
 		}
 	case "parent":
 		runtime := rt.Init()
-		defer runtime.Shutdown()
+		defer runtime.Cleanup()
 		// Set up a mock binary repository, a mock application repository, and a node manager.
 		_, crCleanup := startBinaryRepository()
 		defer crCleanup()
@@ -232,7 +232,7 @@
 		blackbox.WaitForEOFOnStdin()
 	case "child":
 		runtime := rt.Init()
-		defer runtime.Shutdown()
+		defer runtime.Cleanup()
 		// Set up a node manager.
 		name, nmCleanup := startNodeManager()
 		defer nmCleanup()
diff --git a/services/mgmt/node/noded/main.go b/services/mgmt/node/noded/main.go
index af7c5d2..b993371 100644
--- a/services/mgmt/node/noded/main.go
+++ b/services/mgmt/node/noded/main.go
@@ -28,7 +28,7 @@
 		vlog.Fatalf("Specify the node manager origin as environment variable %s=<name>", impl.OriginEnv)
 	}
 	runtime := rt.Init()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 	server, err := runtime.NewServer()
 	if err != nil {
 		vlog.Fatalf("NewServer() failed: %v", err)
diff --git a/services/mgmt/profile/impl/impl_test.go b/services/mgmt/profile/impl/impl_test.go
index 6822260..bb52ef9 100644
--- a/services/mgmt/profile/impl/impl_test.go
+++ b/services/mgmt/profile/impl/impl_test.go
@@ -26,7 +26,7 @@
 // the Profile interface.
 func TestInterface(t *testing.T) {
 	runtime := rt.Init()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 
 	ctx := runtime.NewContext()
 
diff --git a/services/mgmt/profile/profiled/main.go b/services/mgmt/profile/profiled/main.go
index c4ab605..61482b2 100644
--- a/services/mgmt/profile/profiled/main.go
+++ b/services/mgmt/profile/profiled/main.go
@@ -23,7 +23,7 @@
 		vlog.Fatalf("Specify a store using --store=<name>")
 	}
 	runtime := rt.Init()
-	defer runtime.Shutdown()
+	defer runtime.Cleanup()
 	server, err := runtime.NewServer()
 	if err != nil {
 		vlog.Fatalf("NewServer() failed: %v", err)
diff --git a/services/mgmt/root/rootd/main.go b/services/mgmt/root/rootd/main.go
index ae4b33a..c20a76a 100644
--- a/services/mgmt/root/rootd/main.go
+++ b/services/mgmt/root/rootd/main.go
@@ -10,7 +10,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	server, err := r.NewServer()
 	if err != nil {
 		vlog.Errorf("NewServer() failed: %v", err)
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
index 23cf394..658aab0 100644
--- a/services/mounttable/mounttabled/mounttable.go
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -52,7 +52,7 @@
 func main() {
 	flag.Usage = Usage
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	mtServer, err := r.NewServer(veyron2.ServesMountTableOpt(true))
 	if err != nil {
diff --git a/services/proximity/proximityd/main.go b/services/proximity/proximityd/main.go
index fdb3201..43cb723 100644
--- a/services/proximity/proximityd/main.go
+++ b/services/proximity/proximityd/main.go
@@ -30,7 +30,7 @@
 func main() {
 	// Get the runtime.
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	// Create a new server.
 	s, err := r.NewServer()
diff --git a/services/proxy/proxyd/main.go b/services/proxy/proxyd/main.go
index 90fead3..f30b10f 100644
--- a/services/proxy/proxyd/main.go
+++ b/services/proxy/proxyd/main.go
@@ -26,7 +26,7 @@
 	)
 
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	rid, err := naming.NewRoutingID()
 	if err != nil {
diff --git a/services/wspr/wsprd/lib/wspr.go b/services/wspr/wsprd/lib/wspr.go
index 4c76bd0..0147432 100644
--- a/services/wspr/wsprd/lib/wspr.go
+++ b/services/wspr/wsprd/lib/wspr.go
@@ -873,7 +873,7 @@
 }
 
 func (ctx WSPR) Shutdown() {
-	ctx.rt.Shutdown()
+	ctx.rt.Cleanup()
 }
 
 // Creates a new WebSocket Proxy object.
diff --git a/tools/application/main.go b/tools/application/main.go
index 2ac2ee8..a510dcc 100644
--- a/tools/application/main.go
+++ b/tools/application/main.go
@@ -7,6 +7,6 @@
 )
 
 func main() {
-	defer rt.Init().Shutdown()
+	defer rt.Init().Cleanup()
 	impl.Root().Main()
 }
diff --git a/tools/binary/main.go b/tools/binary/main.go
index de795aa..e03d21c 100644
--- a/tools/binary/main.go
+++ b/tools/binary/main.go
@@ -8,7 +8,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	impl.Root().Main()
 }
diff --git a/tools/mounttable/main.go b/tools/mounttable/main.go
index a59a17b..61a93d6 100644
--- a/tools/mounttable/main.go
+++ b/tools/mounttable/main.go
@@ -7,6 +7,6 @@
 )
 
 func main() {
-	defer rt.Init().Shutdown()
+	defer rt.Init().Cleanup()
 	impl.Root().Main()
 }
diff --git a/tools/namespace/main.go b/tools/namespace/main.go
index 4a0ff2d..64f046e 100644
--- a/tools/namespace/main.go
+++ b/tools/namespace/main.go
@@ -7,6 +7,6 @@
 )
 
 func main() {
-	defer rt.Init().Shutdown()
+	defer rt.Init().Cleanup()
 	impl.Root().Main()
 }
diff --git a/tools/playground/compilerd/main.go b/tools/playground/compilerd/main.go
index 82a4e11..b565ae3 100644
--- a/tools/playground/compilerd/main.go
+++ b/tools/playground/compilerd/main.go
@@ -4,8 +4,12 @@
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"math/rand"
 	"net/http"
+	"os"
 	"os/exec"
+	"os/signal"
+	"syscall"
 	"time"
 )
 
@@ -19,6 +23,19 @@
 	Events []Event
 }
 
+// This channel is closed when the server begins shutting down.
+// No values are ever sent to it.
+var lameduck chan bool = make(chan bool)
+
+func healthz(w http.ResponseWriter, r *http.Request) {
+	select {
+	case <- lameduck:
+		w.WriteHeader(http.StatusInternalServerError)
+	default:
+		w.Write([]byte("OK"))
+	}
+}
+
 func handler(w http.ResponseWriter, r *http.Request) {
 	if r.Body == nil || r.Method != "POST" {
 		w.WriteHeader(http.StatusBadRequest)
@@ -68,6 +85,54 @@
 }
 
 func main() {
+	limit_min := 60
+	delay_min := limit_min/2 + rand.Intn(limit_min/2)
+
+	// VMs will be periodically killed to prevent any owned vms from
+	// causing damage. We want to shutdown cleanly before then so
+	// we don't cause requests to fail.
+	go WaitForShutdown(time.Minute * time.Duration(delay_min))
+
 	http.HandleFunc("/compile", handler)
+	http.HandleFunc("/healthz", healthz)
 	http.ListenAndServe(":8181", nil)
 }
+
+func WaitForShutdown(limit time.Duration) {
+	var beforeExit func() error
+
+	// Shutdown if we get a SIGTERM
+	term := make(chan os.Signal, 1)
+	signal.Notify(term, syscall.SIGTERM)
+
+	// Or if the time limit expires
+	deadline := time.After(limit)
+	fmt.Println("Shutting down at", time.Now().Add(limit))
+Loop:
+	for {
+		select {
+		case <-deadline:
+			// Shutdown the vm.
+			fmt.Println("Deadline expired, shutting down.")
+			beforeExit = exec.Command("sudo", "halt").Run
+			break Loop
+		case <-term:
+			fmt.Println("Got SIGTERM, shutting down.")
+			// VM is probably already shutting down, so just exit.
+			break Loop
+		}
+	}
+	// Fail health checks so we stop getting requests.
+	close(lameduck)
+	// Give running requests time to finish.
+	time.Sleep(30 * time.Second)
+
+	// Then go ahead and shutdown.
+	if beforeExit != nil {
+		err := beforeExit()
+		if err != nil {
+			panic(err)
+		}
+	}
+	os.Exit(0)
+}
diff --git a/tools/profile/main.go b/tools/profile/main.go
index 71a015a..da5821b 100644
--- a/tools/profile/main.go
+++ b/tools/profile/main.go
@@ -8,7 +8,7 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 
 	impl.Root().Main()
 }
diff --git a/tools/proximity/main.go b/tools/proximity/main.go
index 07ab805..a5d8826 100644
--- a/tools/proximity/main.go
+++ b/tools/proximity/main.go
@@ -7,6 +7,6 @@
 )
 
 func main() {
-	defer rt.Init().Shutdown()
+	defer rt.Init().Cleanup()
 	impl.Root().Main()
 }
diff --git a/tools/vrpc/impl/impl_test.go b/tools/vrpc/impl/impl_test.go
index ee67bf0..85c9be6 100644
--- a/tools/vrpc/impl/impl_test.go
+++ b/tools/vrpc/impl/impl_test.go
@@ -170,7 +170,7 @@
 
 func TestVRPC(t *testing.T) {
 	runtime := rt.Init()
-	// Skip defer runtime.Shutdown() to avoid messing up other tests in the
+	// Skip defer runtime.Cleanup() to avoid messing up other tests in the
 	// same process.
 	server, endpoint, err := startServer(t, runtime)
 	if err != nil {
diff --git a/tools/vrpc/main.go b/tools/vrpc/main.go
index 6711130..1cf6021 100644
--- a/tools/vrpc/main.go
+++ b/tools/vrpc/main.go
@@ -8,6 +8,6 @@
 
 func main() {
 	r := rt.Init()
-	defer r.Shutdown()
+	defer r.Cleanup()
 	impl.Root().Main()
 }