Merge "veyron/services/store/stored: Add viewer to stored itself (enabled by default), and update mdb example accordingly, with a proper Makefile and full usage instructions in a README.md file."
diff --git a/runtimes/google/rt/signal.go b/runtimes/google/rt/signal.go
index bbd5e55..d0ef7d4 100644
--- a/runtimes/google/rt/signal.go
+++ b/runtimes/google/rt/signal.go
@@ -8,6 +8,10 @@
 )
 
 func (r *vrt) initSignalHandling() {
+	// TODO(caprita): Given that our node manager implementation is to
+	// kill all child apps when the node manager dies, we should
+	// enable SIGHUP on apps by default.
+
 	// Automatically handle SIGHUP to prevent applications started as
 	// daemons from being killed.  The developer can choose to still listen
 	// on SIGHUP and take a different action if desired.
diff --git a/runtimes/google/vsync/vsync.vdl.go b/runtimes/google/vsync/vsync.vdl.go
index 3b4602e..459e9da 100644
--- a/runtimes/google/vsync/vsync.vdl.go
+++ b/runtimes/google/vsync/vsync.vdl.go
@@ -342,36 +342,29 @@
 			{Name: "Err", Type: 68},
 		},
 
-		OutStream: 82,
+		OutStream: 79,
 	}
 
 	result.TypeDefs = []_gen_vdlutil.Any{
-		_gen_wiretype.NamedPrimitiveType{Type: 0x3, Name: "veyron/runtimes/google/vsync.DeviceID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.GenID", Tags: []string(nil)}, _gen_wiretype.MapType{Key: 0x41, Elem: 0x42, Name: "veyron/runtimes/google/vsync.GenVector", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.LSN", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.ArrayType{Elem: 0x46, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x48, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/storage.TagOp", Tags: []string(nil)}, _gen_wiretype.StructType{
-			[]_gen_wiretype.FieldType{
-				_gen_wiretype.FieldType{Type: 0x4b, Name: "Op"},
-				_gen_wiretype.FieldType{Type: 0x47, Name: "ACL"},
-			},
-			"veyron2/storage.Tag", []string(nil)},
-		_gen_wiretype.SliceType{Elem: 0x4c, Name: "veyron2/storage.TagList", Tags: []string(nil)}, _gen_wiretype.StructType{
+		_gen_wiretype.NamedPrimitiveType{Type: 0x3, Name: "veyron/runtimes/google/vsync.DeviceID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.GenID", Tags: []string(nil)}, _gen_wiretype.MapType{Key: 0x41, Elem: 0x42, Name: "veyron/runtimes/google/vsync.GenVector", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.LSN", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.ArrayType{Elem: 0x46, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x48, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
 				_gen_wiretype.FieldType{Type: 0x3, Name: "Name"},
 				_gen_wiretype.FieldType{Type: 0x47, Name: "ID"},
 			},
 			"veyron2/storage.DEntry", []string(nil)},
-		_gen_wiretype.SliceType{Elem: 0x4e, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
+		_gen_wiretype.SliceType{Elem: 0x4b, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
 				_gen_wiretype.FieldType{Type: 0x47, Name: "ID"},
 				_gen_wiretype.FieldType{Type: 0x48, Name: "PriorVersion"},
 				_gen_wiretype.FieldType{Type: 0x48, Name: "Version"},
 				_gen_wiretype.FieldType{Type: 0x2, Name: "IsRoot"},
 				_gen_wiretype.FieldType{Type: 0x4a, Name: "Value"},
-				_gen_wiretype.FieldType{Type: 0x4d, Name: "Tags"},
-				_gen_wiretype.FieldType{Type: 0x4f, Name: "Dir"},
+				_gen_wiretype.FieldType{Type: 0x4c, Name: "Dir"},
 			},
 			"veyron/services/store/raw.Mutation", []string(nil)},
 		_gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
-				_gen_wiretype.FieldType{Type: 0x50, Name: "Mutation"},
+				_gen_wiretype.FieldType{Type: 0x4d, Name: "Mutation"},
 				_gen_wiretype.FieldType{Type: 0x25, Name: "SyncTime"},
 				_gen_wiretype.FieldType{Type: 0x2, Name: "Delete"},
 				_gen_wiretype.FieldType{Type: 0x2, Name: "Continued"},
@@ -386,7 +379,7 @@
 				_gen_wiretype.FieldType{Type: 0x47, Name: "ObjID"},
 				_gen_wiretype.FieldType{Type: 0x48, Name: "CurVers"},
 				_gen_wiretype.FieldType{Type: 0x49, Name: "Parents"},
-				_gen_wiretype.FieldType{Type: 0x51, Name: "Value"},
+				_gen_wiretype.FieldType{Type: 0x4e, Name: "Value"},
 			},
 			"veyron/runtimes/google/vsync.LogRec", []string(nil)},
 	}
diff --git a/runtimes/google/vsync/watcher_test.go b/runtimes/google/vsync/watcher_test.go
index 64b5feb..dee40cf 100644
--- a/runtimes/google/vsync/watcher_test.go
+++ b/runtimes/google/vsync/watcher_test.go
@@ -166,7 +166,6 @@
 				PriorVersion: 0x0,
 				Version:      0x4d65822107fcfd52,
 				Value:        "value-root",
-				Tags:         nil,
 				Dir: []storage.DEntry{
 					storage.DEntry{
 						Name: "a",
@@ -187,7 +186,6 @@
 				PriorVersion: 0x0,
 				Version:      0x57e9d1860d1d68d8,
 				Value:        "value-a",
-				Tags:         nil,
 				Dir: []storage.DEntry{
 					storage.DEntry{
 						Name: "b",
@@ -208,7 +206,6 @@
 				PriorVersion: 0x0,
 				Version:      0x55104dc76695721d,
 				Value:        "value-b",
-				Tags:         nil,
 				Dir:          nil,
 			},
 			ResumeMarker: nil,
@@ -225,7 +222,6 @@
 				PriorVersion: 0x57e9d1860d1d68d8,
 				Version:      0x365a858149c6e2d1,
 				Value:        "value-a",
-				Tags:         nil,
 				Dir: []storage.DEntry{
 					storage.DEntry{
 						Name: "b",
@@ -251,7 +247,6 @@
 				PriorVersion: 0x0,
 				Version:      0x380704bb7b4d7c03,
 				Value:        "value-c",
-				Tags:         nil,
 				Dir:          nil,
 			},
 			ResumeMarker: nil,
diff --git a/services/mgmt/lib/exec/doc.go b/services/mgmt/lib/exec/doc.go
index f0b8168..953965a 100644
--- a/services/mgmt/lib/exec/doc.go
+++ b/services/mgmt/lib/exec/doc.go
@@ -7,9 +7,7 @@
 // for the child to terminate, and to terminate the child cleaning up any state
 // associated with it.
 //
-// A child process uses the NewChildHandle function to complete the initial
-// authentication handshake and must then call the Run() function to run a
-// goroutine to handle the process rendezvous. The child must call SetReady to
-// indicate that it is fully initialized and ready for whatever purpose it is
-// intended to fulfill.
+// A child process uses the GetChildHandle function to complete the initial
+// authentication handshake. The child must call SetReady to indicate that it is
+// fully initialized and ready for whatever purpose it is intended to fulfill.
 package exec
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index 33abf91..c735952 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -2,7 +2,6 @@
 
 import (
 	"fmt"
-	"sync"
 
 	"veyron/services/mgmt/node"
 	"veyron/services/mgmt/node/config"
@@ -27,10 +26,8 @@
 	return &dispatcher{
 		auth: auth,
 		internal: &internalState{
-			channels:      make(map[string]chan string),
-			channelsMutex: new(sync.Mutex),
-			updating:      false,
-			updatingMutex: new(sync.Mutex),
+			channels: make(map[string]map[string]chan string),
+			updating: false,
 		},
 		config: config,
 	}, nil
@@ -39,6 +36,10 @@
 // DISPATCHER INTERFACE IMPLEMENTATION
 
 func (d *dispatcher) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
+	// TODO(caprita): Split out the logic that operates on the node manager
+	// from the logic that operates on the applications that the node
+	// manager runs.  We can have different invoker implementations,
+	// dispatching based on the suffix ("nm" vs. "apps").
 	return ipc.ReflectInvoker(node.NewServerNode(&invoker{
 		internal: d.internal,
 		config:   d.config,
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 79d6c2b..16988ec 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -1,6 +1,8 @@
 package impl_test
 
 import (
+	"crypto/md5"
+	"encoding/base64"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -16,9 +18,11 @@
 	"veyron/services/mgmt/node/config"
 	"veyron/services/mgmt/node/impl"
 
+	"veyron2/ipc"
 	"veyron2/naming"
 	"veyron2/rt"
 	"veyron2/services/mgmt/application"
+	"veyron2/services/mgmt/node"
 	"veyron2/verror"
 	"veyron2/vlog"
 )
@@ -33,8 +37,9 @@
 	// create it here.
 	rt.Init()
 
-	blackbox.CommandTable["nodeManager"] = nodeManager
 	blackbox.CommandTable["execScript"] = execScript
+	blackbox.CommandTable["nodeManager"] = nodeManager
+	blackbox.CommandTable["app"] = app
 }
 
 // execScript launches the script passed as argument.
@@ -123,6 +128,38 @@
 	}
 }
 
+// appService defines a test service that the test app should be running.
+// TODO(caprita): Use this to make calls to the app and verify how Suspend/Stop
+// interact with an active service.
+type appService struct{}
+
+func (appService) Echo(_ ipc.ServerCall, message string) (string, error) {
+	return message, nil
+}
+
+func app(args []string) {
+	if expected, got := 1, len(args); expected != got {
+		vlog.Fatalf("Unexpected number of arguments: expected %d, got %d", expected, got)
+	}
+	publishName := args[0]
+
+	defer rt.R().Cleanup()
+	server, _ := newServer()
+	defer server.Stop()
+	if err := server.Serve(publishName, ipc.SoloDispatcher(new(appService), nil)); err != nil {
+		vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
+	}
+	if call, err := rt.R().Client().StartCall(rt.R().NewContext(), "pingserver", "Ping", nil); err != nil {
+		vlog.Fatalf("StartCall failed: %v", err)
+	} else if err = call.Finish(); err != nil {
+		vlog.Fatalf("Finish failed: %v", err)
+	}
+	<-signals.ShutdownOnSignals()
+	if err := ioutil.WriteFile("testfile", []byte("goodbye world"), 0600); err != nil {
+		vlog.Fatalf("Failed to write testfile: %v", err)
+	}
+}
+
 // generateScript is very similar in behavior to its namesake in invoker.go.
 // However, we chose to re-implement it here for two reasons: (1) avoid making
 // generateScript public; and (2) how the test choses to invoke the node manager
@@ -145,22 +182,28 @@
 	return path
 }
 
+// nodeEnvelopeFromCmd returns a node manager application envelope that
+// describes the given command object.
+func nodeEnvelopeFromCmd(cmd *goexec.Cmd) *application.Envelope {
+	return envelopeFromCmd(application.NodeManagerTitle, cmd)
+}
+
 // envelopeFromCmd returns an envelope that describes the given command object.
-func envelopeFromCmd(cmd *goexec.Cmd) *application.Envelope {
+func envelopeFromCmd(title string, cmd *goexec.Cmd) *application.Envelope {
 	return &application.Envelope{
-		Title:  application.NodeManagerTitle,
+		Title:  title,
 		Args:   cmd.Args[1:],
 		Env:    cmd.Env,
 		Binary: "br",
 	}
 }
 
-// TestUpdateAndRevert makes the node manager go through the motions of updating
+// TestNodeManagerUpdateAndRevert makes the node manager go through the motions of updating
 // itself to newer versions (twice), and reverting itself back (twice).  It also
 // checks that update and revert fail when they're supposed to.  The initial
 // node manager is started 'by hand' via a blackbox command.  Further versions
 // are started through the soft link that the node manager itself updates.
-func TestUpdateAndRevert(t *testing.T) {
+func TestNodeManagerUpdateAndRevert(t *testing.T) {
 	// Set up mount table, application, and binary repositories.
 	defer setupLocalNamespace(t)()
 	envelope, cleanup := startApplicationRepository()
@@ -226,7 +269,7 @@
 	resolve(t, "factoryNM") // Verify the node manager has published itself.
 
 	// Simulate an invalid envelope in the application repository.
-	*envelope = *envelopeFromCmd(nm.Cmd)
+	*envelope = *nodeEnvelopeFromCmd(nm.Cmd)
 	envelope.Title = "bogus"
 	updateExpectError(t, "factoryNM", verror.BadArg)   // Incorrect title.
 	revertExpectError(t, "factoryNM", verror.NotFound) // No previous version available.
@@ -239,7 +282,7 @@
 	// node manager to stage the next version.
 	nmV2 := blackbox.HelperCommand(t, "nodeManager", "v2NM")
 	defer setupChildCommand(nmV2)()
-	*envelope = *envelopeFromCmd(nmV2.Cmd)
+	*envelope = *nodeEnvelopeFromCmd(nmV2.Cmd)
 	update(t, "factoryNM")
 
 	// Current link should have been updated to point to v2.
@@ -288,7 +331,7 @@
 	// Create a third version of the node manager and issue an update.
 	nmV3 := blackbox.HelperCommand(t, "nodeManager", "v3NM")
 	defer setupChildCommand(nmV3)()
-	*envelope = *envelopeFromCmd(nmV3.Cmd)
+	*envelope = *nodeEnvelopeFromCmd(nmV3.Cmd)
 	update(t, "v2NM")
 
 	scriptPathV3 := evalLink()
@@ -360,3 +403,104 @@
 	runNM.Expect("ready")
 	resolve(t, "factoryNM") // Current link should have been launching factory version.
 }
+
+type pingServerDisp chan struct{}
+
+func (p pingServerDisp) Ping(ipc.ServerCall) { close(p) }
+
+// TestAppStartStop installs an app, starts it, and then stops it.
+func TestAppStartStop(t *testing.T) {
+	// Set up mount table, application, and binary repositories.
+	defer setupLocalNamespace(t)()
+	envelope, cleanup := startApplicationRepository()
+	defer cleanup()
+	defer startBinaryRepository()()
+
+	// This is the local filesystem location that the node manager is told
+	// to use.
+	root := filepath.Join(os.TempDir(), "nodemanager")
+	defer os.RemoveAll(root)
+
+	// Set up the node manager.  Since we won't do node manager updates,
+	// don't worry about its application envelope and current link.
+	nm := blackbox.HelperCommand(t, "nodeManager", "nm", root, "unused app repo name", "unused curr link")
+	defer setupChildCommand(nm)()
+	if err := nm.Cmd.Start(); err != nil {
+		t.Fatalf("Start() failed: %v", err)
+	}
+	defer nm.Cleanup()
+	nm.Expect("ready")
+
+	// Create the local server that the app uses to let us know it's ready.
+	server, _ := newServer()
+	defer server.Stop()
+	pingCh := make(chan struct{})
+	if err := server.Serve("pingserver", ipc.SoloDispatcher(pingServerDisp(pingCh), nil)); err != nil {
+		t.Errorf("Failed to set up ping server")
+	}
+
+	// Create an envelope for an app.
+	app := blackbox.HelperCommand(t, "app", "app1")
+	defer setupChildCommand(app)()
+	appTitle := "google naps"
+	*envelope = *envelopeFromCmd(appTitle, app.Cmd)
+
+	appsName := "nm//apps"
+	stub, err := node.BindApplication(appsName)
+	if err != nil {
+		t.Fatalf("BindApplication(%v) failed: %v", appsName, err)
+	}
+	appID, err := stub.Install(rt.R().NewContext(), "ar")
+	if err != nil {
+		t.Fatalf("Install failed: %v", err)
+	}
+	appName := naming.Join(appsName, appID)
+	stub, err = node.BindApplication(appName)
+	if err != nil {
+		t.Fatalf("BindApplication(%v) failed: %v", appName, err)
+	}
+	var instanceID string
+	if instanceIDs, err := stub.Start(rt.R().NewContext()); err != nil {
+		t.Fatalf("Start failed: %v", err)
+	} else {
+		if want, got := 1, len(instanceIDs); want != got {
+			t.Fatalf("Expected %v instance ids, got %v instead", want, got)
+		}
+		instanceID = instanceIDs[0]
+	}
+	// Wait until the app pings us that it's ready.
+	<-pingCh
+
+	instanceName := naming.Join(appName, instanceID)
+	stub, err = node.BindApplication(instanceName)
+	if err != nil {
+		t.Fatalf("BindApplication(%v) failed: %v", instanceName, err)
+	}
+	if err := stub.Stop(rt.R().NewContext(), 5); err != nil {
+		t.Errorf("Stop failed: %v", err)
+	}
+
+	// HACK ALERT: for now, we peek inside the node manager's directory
+	// structure (which ought to be opaque) to check for what the app has
+	// written to its local root.
+	//
+	// TODO(caprita): add support to node manager to browse logs/app local
+	// root.
+	applicationDirName := func(title string) string {
+		h := md5.New()
+		h.Write([]byte(title))
+		hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+		return "app-" + hash
+	}
+	components := strings.Split(appID, "/")
+	appTitle, installationID := components[0], components[1]
+	instanceDir := filepath.Join(root, applicationDirName(appTitle), "installation-"+installationID, "instances", "stopped-instance-"+instanceID)
+	rootDir := filepath.Join(instanceDir, "root")
+	testFile := filepath.Join(rootDir, "testfile")
+	if read, err := ioutil.ReadFile(testFile); err != nil {
+		t.Errorf("Failed to read %v: %v", testFile, err)
+	} else if want, got := "goodbye world", string(read); want != got {
+		t.Errorf("Expected to read %v, got %v instead", want, got)
+	}
+	// END HACK
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index b7b4611..4e95dff 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -3,14 +3,13 @@
 // The implementation of the node manager expects that the node manager
 // installations are all organized in the following directory structure:
 //
-// VEYRON_NM_ROOT/
-//   workspace-1/
-//     noded - the node manager binary
-//     noded.sh - a shell script to start the binary
-//  ...
-//   workspace-n/
-//     noded - the node manager binary
-//     noded.sh - a shell script to start the binary
+// <config.Root>/
+//   node-manager/
+//     <version 1 timestamp>/  - timestamp of when the version was downloaded
+//       noded                 - the node manager binary
+//       noded.sh              - a shell script to start the binary
+//     <version 2 timestamp>
+//     ...
 //
 // The node manager is always expected to be started through the symbolic link
 // passed in as config.CurrentLink, which is monitored by an init daemon. This
@@ -20,25 +19,88 @@
 // the symlink is updated to the new noded.sh script. Similarly, to revert the
 // node manager to a previous version, all that is required is to update the
 // symlink to point to the previous noded.sh script.
+//
+//
+// The node manager manages the applications it installs and runs using the
+// following directory structure:
+//
+// TODO(caprita): Not all is yet implemented.
+//
+// <config.Root>/
+//   app-<hash 1>/                  - the application dir is named using a hash of the application title
+//     installation-<id 1>/         - installations are labelled with ids
+//       <version 1 timestamp>/     - timestamp of when the version was downloaded
+//         bin                      - application binary
+//         previous                 - symbolic link to previous version directory (TODO)
+//         origin                   - object name for application envelope
+//         envelope                 - application envelope (JSON-encoded)
+//       <version 2 timestamp>
+//       ...
+//       current                    - symbolic link to the current version
+//       instances/
+//         instance-<id a>/         - instances are labelled with ids
+//           root/                  - workspace that the instance is run from
+//           logs/                  - stderr/stdout and log files generated by instance
+//           info                   - app manager name and process id for the instance (if running)
+//           version                - symbolic link to installation version for the instance
+//         instance-<id b>
+//         ...
+//         stopped-instance-<id c>  - stopped instances have their directory name prepended by 'stopped-'
+//         ...
+//     installation-<id 2>
+//     ...
+//   app-<hash 2>
+//   ...
+//
+// When node manager starts up, it goes through all instances and resumes the
+// ones that are not suspended.  If the application was still running, it
+// suspends it first.  If an application fails to resume, it stays suspended.
+//
+// When node manager shuts down, it suspends all running instances.
+//
+// Start starts an instance.  Suspend kills the process but leaves the workspace
+// untouched. Resume restarts the process. Stop kills the process and prevents
+// future resumes (it also eventually gc's the workspace).
+//
+// If the process dies on its own, it stays dead and is assumed suspended.
+// TODO(caprita): Later, we'll add auto-restart option.
+//
+// Concurrency model: installations can be created independently of one another;
+// installations can be removed at any time (any running instances will be
+// stopped). The first call to Uninstall will rename the installation dir as a
+// first step; subsequent Uninstalls will fail. Instances can be created
+// independently of one another, as long as the installation exists (if it gets
+// Uninstalled during an instance Start, the Start may fail). When an instance
+// is stopped, the first call to Stop renames the instance dir; subsequent Stop
+// calls will fail. Resume will attempt to create an info file; if one exists
+// already, Resume fails. Suspend will attempt to rename the info file; if none
+// present, Suspend will fail.
+//
+// TODO(caprita): There is room for synergy between how node manager organizes
+// its own workspace and that for the applications it runs.  In particular,
+// previous, origin, and envelope could be part of a single config.  We'll
+// refine that later.
 
 import (
-	"bytes"
-	"errors"
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
 	"fmt"
+	"hash/crc64"
 	"io/ioutil"
-	"math/rand"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"reflect"
 	"regexp"
-	"runtime"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
 
 	"veyron/lib/config"
-	blib "veyron/services/mgmt/lib/binary"
+	binlib "veyron/services/mgmt/lib/binary"
 	vexec "veyron/services/mgmt/lib/exec"
 	iconfig "veyron/services/mgmt/node/config"
 	"veyron/services/mgmt/profile"
@@ -47,30 +109,40 @@
 	"veyron2/mgmt"
 	"veyron2/naming"
 	"veyron2/rt"
+	"veyron2/services/mgmt/appcycle"
 	"veyron2/services/mgmt/application"
-	"veyron2/services/mgmt/binary"
-	"veyron2/services/mgmt/build"
+	binapi "veyron2/services/mgmt/binary"
 	"veyron2/services/mgmt/node"
 	"veyron2/services/mgmt/repository"
 	"veyron2/verror"
 	"veyron2/vlog"
 )
 
+// instanceInfo holds state about a running instance.
+type instanceInfo struct {
+	AppCycleMgrName string
+	Pid             int
+}
+
 // internalState wraps state shared between different node manager
 // invocations.
 type internalState struct {
-	// channels maps callback identifiers to channels that are used to
-	// communicate information from child processes.
-	channels map[string]chan string
+	// channels maps callback identifiers and config keys to channels that
+	// are used to communicate corresponding config values from child
+	// processes.
+	channels map[string]map[string]chan string
+	// nextCallbackID provides the next callback identifier to use as key
+	// for the channels map.
+	nextCallbackID int64
 	// channelsMutex is a lock for coordinating concurrent access to
 	// <channels>.
-	channelsMutex *sync.Mutex
+	channelsMutex sync.Mutex
 	// updating is a flag that records whether this instance of node
 	// manager is being updated.
 	updating bool
 	// updatingMutex is a lock for coordinating concurrent access to
 	// <updating>.
-	updatingMutex *sync.Mutex
+	updatingMutex sync.Mutex
 }
 
 // invoker holds the state of a node manager invocation.
@@ -94,217 +166,42 @@
 	errUpdateInProgress   = verror.Existsf("update in progress")
 	errIncompatibleUpdate = verror.BadArgf("update failed: mismatching app title")
 	errUpdateNoOp         = verror.NotFoundf("no different version available")
+	errNotExist           = verror.NotFoundf("object does not exist")
+	errInvalidOperation   = verror.BadArgf("invalid operation")
 )
 
 // NODE INTERFACE IMPLEMENTATION
 
-// computeNodeProfile generates a description of the runtime
-// environment (supported file format, OS, architecture, libraries) of
-// the host node.
-//
-// TODO(jsimsa): Avoid computing the host node description from
-// scratch if a recent cached copy exists.
-func (i *invoker) computeNodeProfile() (*profile.Specification, error) {
-	result := profile.Specification{}
-
-	// Find out what the supported file format, operating system, and
-	// architecture is.
-	switch runtime.GOOS {
-	case "darwin":
-		result.Format = build.MACH
-		result.OS = build.Darwin
-	case "linux":
-		result.Format = build.ELF
-		result.OS = build.Linux
-	case "windows":
-		result.Format = build.PE
-		result.OS = build.Windows
-	default:
-		return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
-	}
-	switch runtime.GOARCH {
-	case "amd64":
-		result.Arch = build.AMD64
-	case "arm":
-		result.Arch = build.ARM
-	case "x86":
-		result.Arch = build.X86
-	default:
-		return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
-	}
-
-	// Find out what the installed dynamically linked libraries are.
-	switch runtime.GOOS {
-	case "linux":
-		// For Linux, we identify what dynamically linked libraries are
-		// install by parsing the output of "ldconfig -p".
-		command := exec.Command("ldconfig", "-p")
-		output, err := command.CombinedOutput()
-		if err != nil {
-			return nil, err
-		}
-		buf := bytes.NewBuffer(output)
-		// Throw away the first line of output from ldconfig.
-		if _, err := buf.ReadString('\n'); err != nil {
-			return nil, errors.New("Could not identify libraries.")
-		}
-		// Extract the library name and version from every subsequent line.
-		result.Libraries = make(map[profile.Library]struct{})
-		line, err := buf.ReadString('\n')
-		for err == nil {
-			words := strings.Split(strings.Trim(line, " \t\n"), " ")
-			if len(words) > 0 {
-				tokens := strings.Split(words[0], ".so")
-				if len(tokens) != 2 {
-					return nil, errors.New("Could not identify library: " + words[0])
-				}
-				name := strings.TrimPrefix(tokens[0], "lib")
-				major, minor := "", ""
-				tokens = strings.SplitN(tokens[1], ".", 3)
-				if len(tokens) >= 2 {
-					major = tokens[1]
-				}
-				if len(tokens) >= 3 {
-					minor = tokens[2]
-				}
-				result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
-			}
-			line, err = buf.ReadString('\n')
-		}
-	case "darwin":
-		// TODO(jsimsa): Implement.
-	case "windows":
-		// TODO(jsimsa): Implement.
-	default:
-		return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
-	}
-	return &result, nil
-}
-
-// getProfile gets a profile description for the given profile.
-//
-// TODO(jsimsa): Avoid retrieving the list of known profiles from a
-// remote server if a recent cached copy exists.
-func (i *invoker) getProfile(name string) (*profile.Specification, error) {
-	// TODO(jsimsa): This function assumes the existence of a profile
-	// server from which the profiles can be retrieved. The profile
-	// server is a work in progress. When it exists, the commented out
-	// code below should work.
-	var profile profile.Specification
-	/*
-			client, err := r.NewClient()
-			if err != nil {
-				vlog.Errorf("NewClient() failed: %v", err)
-				return nil, err
-			}
-			defer client.Close()
-		  server := // TODO
-			method := "Specification"
-			inputs := make([]interface{}, 0)
-			call, err := client.StartCall(server + "/" + name, method, inputs)
-			if err != nil {
-				vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
-				return nil, err
-			}
-			if err := call.Finish(&profiles); err != nil {
-				vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
-				return nil, err
-			}
-	*/
-	return &profile, nil
-}
-
-// getKnownProfiles gets a list of description for all publicly known
-// profiles.
-//
-// TODO(jsimsa): Avoid retrieving the list of known profiles from a
-// remote server if a recent cached copy exists.
-func (i *invoker) getKnownProfiles() ([]profile.Specification, error) {
-	// TODO(jsimsa): This function assumes the existence of a profile
-	// server from which a list of known profiles can be retrieved. The
-	// profile server is a work in progress. When it exists, the
-	// commented out code below should work.
-	knownProfiles := make([]profile.Specification, 0)
-	/*
-			client, err := r.NewClient()
-			if err != nil {
-				vlog.Errorf("NewClient() failed: %v\n", err)
-				return nil, err
-			}
-			defer client.Close()
-		  server := // TODO
-			method := "List"
-			inputs := make([]interface{}, 0)
-			call, err := client.StartCall(server, method, inputs)
-			if err != nil {
-				vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
-				return nil, err
-			}
-			if err := call.Finish(&knownProfiles); err != nil {
-				vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
-				return nil, err
-			}
-	*/
-	return knownProfiles, nil
-}
-
-// matchProfiles inputs a profile that describes the host node and a
-// set of publicly known profiles and outputs a node description that
-// identifies the publicly known profiles supported by the host node.
-func (i *invoker) matchProfiles(p *profile.Specification, known []profile.Specification) node.Description {
-	result := node.Description{Profiles: make(map[string]struct{})}
-loop:
-	for _, profile := range known {
-		if profile.Format != p.Format {
-			continue
-		}
-		if profile.OS != p.OS {
-			continue
-		}
-		if profile.Arch != p.Arch {
-			continue
-		}
-		for library := range profile.Libraries {
-			// Current implementation requires exact library name and version match.
-			if _, found := p.Libraries[library]; !found {
-				continue loop
-			}
-		}
-		result.Profiles[profile.Label] = struct{}{}
-	}
-	return result
-}
-
 func (i *invoker) Describe(call ipc.ServerContext) (node.Description, error) {
 	vlog.VI(1).Infof("%v.Describe()", i.suffix)
 	empty := node.Description{}
-	nodeProfile, err := i.computeNodeProfile()
+	nodeProfile, err := computeNodeProfile()
 	if err != nil {
 		return empty, err
 	}
-	knownProfiles, err := i.getKnownProfiles()
+	knownProfiles, err := getKnownProfiles()
 	if err != nil {
 		return empty, err
 	}
-	result := i.matchProfiles(nodeProfile, knownProfiles)
+	result := matchProfiles(nodeProfile, knownProfiles)
 	return result, nil
 }
 
-func (i *invoker) IsRunnable(call ipc.ServerContext, description binary.Description) (bool, error) {
+func (i *invoker) IsRunnable(call ipc.ServerContext, description binapi.Description) (bool, error) {
 	vlog.VI(1).Infof("%v.IsRunnable(%v)", i.suffix, description)
-	nodeProfile, err := i.computeNodeProfile()
+	nodeProfile, err := computeNodeProfile()
 	if err != nil {
 		return false, err
 	}
 	binaryProfiles := make([]profile.Specification, 0)
 	for name, _ := range description.Profiles {
-		profile, err := i.getProfile(name)
+		profile, err := getProfile(name)
 		if err != nil {
 			return false, err
 		}
 		binaryProfiles = append(binaryProfiles, *profile)
 	}
-	result := i.matchProfiles(nodeProfile, binaryProfiles)
+	result := matchProfiles(nodeProfile, binaryProfiles)
 	return len(result.Profiles) > 0, nil
 }
 
@@ -316,13 +213,13 @@
 
 // APPLICATION INTERFACE IMPLEMENTATION
 
-func downloadBinary(workspace, name string) error {
-	data, err := blib.Download(name)
+func downloadBinary(workspace, fileName, name string) error {
+	data, err := binlib.Download(name)
 	if err != nil {
 		vlog.Errorf("Download(%v) failed: %v", name, err)
 		return errOperationFailed
 	}
-	path, perm := filepath.Join(workspace, "noded"), os.FileMode(755)
+	path, perm := filepath.Join(workspace, fileName), os.FileMode(755)
 	if err := ioutil.WriteFile(path, data, perm); err != nil {
 		vlog.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
 		return errOperationFailed
@@ -347,13 +244,13 @@
 	return &envelope, nil
 }
 
-func generateBinary(workspace string, envelope *application.Envelope, newBinary bool) error {
+func generateBinary(workspace, fileName string, envelope *application.Envelope, newBinary bool) error {
 	if newBinary {
 		// Download the new binary.
-		return downloadBinary(workspace, envelope.Binary)
+		return downloadBinary(workspace, fileName, envelope.Binary)
 	}
 	// Link the current binary.
-	path := filepath.Join(workspace, "noded")
+	path := filepath.Join(workspace, fileName)
 	if err := os.Link(os.Args[0], path); err != nil {
 		vlog.Errorf("Link(%v, %v) failed: %v", os.Args[0], path, err)
 		return errOperationFailed
@@ -374,7 +271,7 @@
 	output += filepath.Join(workspace, "noded") + " "
 	output += strings.Join(envelope.Args, " ")
 	path = filepath.Join(workspace, "noded.sh")
-	if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
+	if err := ioutil.WriteFile(path, []byte(output), 0700); err != nil {
 		vlog.Errorf("WriteFile(%v) failed: %v", path, err)
 		return errOperationFailed
 	}
@@ -419,10 +316,20 @@
 	return nil
 }
 
-func (i *invoker) registerCallback(id string, channel chan string) {
+func (i *invoker) generateCallbackID() string {
 	i.internal.channelsMutex.Lock()
 	defer i.internal.channelsMutex.Unlock()
-	i.internal.channels[id] = channel
+	i.internal.nextCallbackID++
+	return strconv.FormatInt(i.internal.nextCallbackID-1, 10)
+}
+
+func (i *invoker) registerCallback(id, key string, channel chan string) {
+	i.internal.channelsMutex.Lock()
+	defer i.internal.channelsMutex.Unlock()
+	if _, ok := i.internal.channels[id]; !ok {
+		i.internal.channels[id] = make(map[string]chan string)
+	}
+	i.internal.channels[id][key] = channel
 }
 
 func (i *invoker) revertNodeManager() error {
@@ -439,25 +346,30 @@
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
 	// Setup up the child process callback.
-	id := fmt.Sprintf("%d", rand.Int())
+	id := i.generateCallbackID()
 	cfg := config.New()
 	cfg.Set(mgmt.ParentNodeManagerConfigKey, naming.MakeTerminal(naming.Join(i.config.Name, id)))
 	handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
-	callbackChan := make(chan string)
-	i.registerCallback(id, callbackChan)
-	defer i.unregisterCallback(id)
+	// Make the channel buffered to avoid blocking the Set method when
+	// nothing is receiving on the channel.  This happens e.g. when
+	// unregisterCallbacks executes before Set is called.
+	callbackChan := make(chan string, 1)
+	i.registerCallback(id, mgmt.ChildNodeManagerConfigKey, callbackChan)
+	defer i.unregisterCallbacks(id)
 	// Start the child process.
 	if err := handle.Start(); err != nil {
 		vlog.Errorf("Start() failed: %v", err)
 		return errOperationFailed
 	}
+	defer func() {
+		if err := handle.Clean(); err != nil {
+			vlog.Errorf("Clean() failed: %v", err)
+		}
+	}()
 	// Wait for the child process to start.
 	testTimeout := 10 * time.Second
 	if err := handle.WaitForReady(testTimeout); err != nil {
 		vlog.Errorf("WaitForReady(%v) failed: %v", testTimeout, err)
-		if err := cmd.Process.Kill(); err != nil {
-			vlog.Errorf("Kill() failed: %v", err)
-		}
 		return errOperationFailed
 	}
 	// Wait for the child process to invoke the Callback().
@@ -468,16 +380,10 @@
 		nmClient, err := node.BindNode(childName)
 		if err != nil {
 			vlog.Errorf("BindNode(%v) failed: %v", childName, err)
-			if err := handle.Clean(); err != nil {
-				vlog.Errorf("Clean() failed: %v", err)
-			}
 			return errOperationFailed
 		}
 		linkOld, pathOld, err := i.getCurrentFileInfo()
 		if err != nil {
-			if err := handle.Clean(); err != nil {
-				vlog.Errorf("Clean() failed: %v", err)
-			}
 			return errOperationFailed
 		}
 		// Since the resolution of mtime for files is seconds,
@@ -485,16 +391,10 @@
 		// check whether the current symlink is updated.
 		time.Sleep(time.Second)
 		if err := nmClient.Revert(rt.R().NewContext()); err != nil {
-			if err := handle.Clean(); err != nil {
-				vlog.Errorf("Clean() failed: %v", err)
-			}
 			return errOperationFailed
 		}
 		linkNew, pathNew, err := i.getCurrentFileInfo()
 		if err != nil {
-			if err := handle.Clean(); err != nil {
-				vlog.Errorf("Clean() failed: %v", err)
-			}
 			return errOperationFailed
 		}
 		// Check that the new node manager updated the current symbolic
@@ -512,15 +412,12 @@
 		}
 	case <-time.After(testTimeout):
 		vlog.Errorf("Waiting for callback timed out")
-		if err := handle.Clean(); err != nil {
-			vlog.Errorf("Clean() failed: %v", err)
-		}
 		return errOperationFailed
 	}
 	return nil
 }
 
-func (i *invoker) unregisterCallback(id string) {
+func (i *invoker) unregisterCallbacks(id string) {
 	i.internal.channelsMutex.Lock()
 	defer i.internal.channelsMutex.Unlock()
 	delete(i.internal.channels, id)
@@ -541,57 +438,175 @@
 		return errUpdateNoOp
 	}
 	// Create new workspace.
-	workspace := filepath.Join(i.config.Root, fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano)))
-	perm := os.FileMode(0755)
+	workspace := filepath.Join(i.config.Root, "node-manager", generateVersionDirName())
+	perm := os.FileMode(0700)
 	if err := os.MkdirAll(workspace, perm); err != nil {
 		vlog.Errorf("MkdirAll(%v, %v) failed: %v", workspace, perm, err)
 		return errOperationFailed
 	}
+	deferrer := func() {
+		if err := os.RemoveAll(workspace); err != nil {
+			vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
+		}
+	}
+	defer func() {
+		if deferrer != nil {
+			deferrer()
+		}
+	}()
 	// Populate the new workspace with a node manager binary.
 	// TODO(caprita): match identical binaries on binary metadata
 	// rather than binary object name.
 	sameBinary := i.config.Envelope != nil && envelope.Binary == i.config.Envelope.Binary
-	if err := generateBinary(workspace, envelope, !sameBinary); err != nil {
-		if err := os.RemoveAll(workspace); err != nil {
-			vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
-		}
+	if err := generateBinary(workspace, "noded", envelope, !sameBinary); err != nil {
 		return err
 	}
 	// Populate the new workspace with a node manager script.
 	configSettings, err := i.config.Save(envelope)
 	if err != nil {
-		if err := os.RemoveAll(workspace); err != nil {
-			vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
-		}
 		return errOperationFailed
 	}
 	if err := generateScript(workspace, configSettings, envelope); err != nil {
-		if err := os.RemoveAll(workspace); err != nil {
-			vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
-		}
 		return err
 	}
 	if err := i.testNodeManager(workspace, envelope); err != nil {
-		if err := os.RemoveAll(workspace); err != nil {
-			vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
-		}
 		return err
 	}
 	// If the binary has changed, update the node manager symlink.
 	if err := i.updateLink(filepath.Join(workspace, "noded.sh")); err != nil {
-		if err := os.RemoveAll(workspace); err != nil {
-			vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
-		}
 		return err
 	}
 	rt.R().Stop()
+	deferrer = nil
 	return nil
 }
 
-func (i *invoker) Install(call ipc.ServerContext, von string) (string, error) {
-	vlog.VI(1).Infof("%v.Install(%q)", i.suffix, von)
-	// TODO(jsimsa): Implement.
-	return "", nil
+func saveEnvelope(dir string, envelope *application.Envelope) error {
+	jsonEnvelope, err := json.Marshal(envelope)
+	if err != nil {
+		vlog.Errorf("Marshal(%v) failed: %v", envelope, err)
+		return errOperationFailed
+	}
+	envelopePath := filepath.Join(dir, "envelope")
+	if err := ioutil.WriteFile(envelopePath, jsonEnvelope, 0600); err != nil {
+		vlog.Errorf("WriteFile(%v) failed: %v", envelopePath, err)
+		return errOperationFailed
+	}
+	return nil
+}
+
+func loadEnvelope(dir string) (*application.Envelope, error) {
+	envelopePath := filepath.Join(dir, "envelope")
+	envelope := new(application.Envelope)
+	if envelopeBytes, err := ioutil.ReadFile(envelopePath); err != nil {
+		vlog.Errorf("ReadFile(%v) failed: %v", envelopePath, err)
+		return nil, errOperationFailed
+	} else if err := json.Unmarshal(envelopeBytes, envelope); err != nil {
+		vlog.Errorf("Unmarshal(%v) failed: %v", envelopeBytes, err)
+		return nil, errOperationFailed
+	}
+	return envelope, nil
+}
+
+func saveOrigin(dir, originVON string) error {
+	path := filepath.Join(dir, "origin")
+	if err := ioutil.WriteFile(path, []byte(originVON), 0600); err != nil {
+		vlog.Errorf("WriteFile(%v) failed: %v", path, err)
+		return errOperationFailed
+	}
+	return nil
+}
+
+// generateID returns a new unique id string.  The uniqueness is based on the
+// current timestamp.  Not cryptographically secure.
+func generateID() string {
+	timestamp := fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano))
+	h := crc64.New(crc64.MakeTable(crc64.ISO))
+	h.Write([]byte(timestamp))
+	b := make([]byte, 8)
+	binary.LittleEndian.PutUint64(b, uint64(h.Sum64()))
+	return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
+}
+
+// TODO(caprita): Nothing prevents different applications from sharing the same
+// title, and thereby being installed in the same app dir.  Do we want to
+// prevent that for the same user or across users?
+
+// applicationDirName generates a cryptographic hash of the application title,
+// to be used as a directory name for installations of the application with the
+// given title.
+func applicationDirName(title string) string {
+	h := md5.New()
+	h.Write([]byte(title))
+	hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+	return "app-" + hash
+}
+
+func installationDirName(installationID string) string {
+	return "installation-" + installationID
+}
+
+func instanceDirName(instanceID string) string {
+	return "instance-" + instanceID
+}
+
+func stoppedInstanceDirName(instanceID string) string {
+	return "stopped-instance-" + instanceID
+}
+
+func generateVersionDirName() string {
+	return time.Now().Format(time.RFC3339Nano)
+}
+
+func (i *invoker) Install(call ipc.ServerContext, applicationVON string) (string, error) {
+	vlog.VI(1).Infof("%v.Install(%q)", i.suffix, applicationVON)
+	if i.suffix != "apps" {
+		return "", errInvalidSuffix
+	}
+	envelope, err := fetchEnvelope(applicationVON)
+	if err != nil {
+		return "", err
+	}
+	if envelope.Title == application.NodeManagerTitle {
+		// Disallow node manager apps from being installed like a
+		// regular app.
+		return "", errInvalidOperation
+	}
+	installationID := generateID()
+	installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID))
+	versionDir := filepath.Join(installationDir, generateVersionDirName())
+	perm := os.FileMode(0700)
+	if err := os.MkdirAll(versionDir, perm); err != nil {
+		vlog.Errorf("MkdirAll(%v, %v) failed: %v", versionDir, perm, err)
+		return "", errOperationFailed
+	}
+	deferrer := func() {
+		if err := os.RemoveAll(versionDir); err != nil {
+			vlog.Errorf("RemoveAll(%v) failed: %v", versionDir, err)
+		}
+	}
+	defer func() {
+		if deferrer != nil {
+			deferrer()
+		}
+	}()
+	// TODO(caprita): Share binaries if already existing locally.
+	if err := generateBinary(versionDir, "bin", envelope, true); err != nil {
+		return "", err
+	}
+	if err := saveEnvelope(versionDir, envelope); err != nil {
+		return "", err
+	}
+	if err := saveOrigin(versionDir, applicationVON); err != nil {
+		return "", err
+	}
+	link := filepath.Join(installationDir, "current")
+	if err := os.Symlink(versionDir, link); err != nil {
+		vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, link, err)
+		return "", errOperationFailed
+	}
+	deferrer = nil
+	return naming.Join(envelope.Title, installationID), nil
 }
 
 func (i *invoker) Refresh(call ipc.ServerContext) error {
@@ -634,15 +649,221 @@
 	return err
 }
 
-func (i *invoker) Start(call ipc.ServerContext) ([]string, error) {
-	vlog.VI(1).Infof("%v.Start()", i.suffix)
-	// TODO(jsimsa): Implement.
-	return make([]string, 0), nil
+func splitName(name string) (ret []string) {
+	components := strings.Split(name, "/")
+	for _, c := range components {
+		if len(c) > 0 {
+			ret = append(ret, c)
+		}
+	}
+	return
 }
 
-func (i *invoker) Stop(call ipc.ServerContext, deadline uint64) error {
+func generateCommand(envelope *application.Envelope, binPath, instanceDir string) (*exec.Cmd, error) {
+	// TODO(caprita): For the purpose of isolating apps, we should run them
+	// as different users.  We'll need to either use the root process or a
+	// suid script to be able to do it.
+	cmd := exec.Command(binPath)
+	// TODO(caprita): Also pass in configuration info like NAMESPACE_ROOT to
+	// the app (to point to the device mounttable).
+	cmd.Env = envelope.Env
+	rootDir := filepath.Join(instanceDir, "root")
+	perm := os.FileMode(0700)
+	if err := os.MkdirAll(rootDir, perm); err != nil {
+		vlog.Errorf("MkdirAll(%v, %v) failed: %v", rootDir, perm, err)
+		return nil, err
+	}
+	cmd.Dir = rootDir
+	logDir := filepath.Join(instanceDir, "logs")
+	if err := os.MkdirAll(logDir, perm); err != nil {
+		vlog.Errorf("MkdirAll(%v, %v) failed: %v", logDir, perm, err)
+		return nil, err
+	}
+	timestamp := time.Now().UnixNano()
+	var err error
+	perm = os.FileMode(0600)
+	cmd.Stdout, err = os.OpenFile(filepath.Join(logDir, fmt.Sprintf("STDOUT-%d", timestamp)), os.O_WRONLY|os.O_CREATE, perm)
+	if err != nil {
+		return nil, err
+	}
+
+	cmd.Stderr, err = os.OpenFile(filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp)), os.O_WRONLY|os.O_CREATE, perm)
+	if err != nil {
+		return nil, err
+	}
+	// Set up args and env.
+	cmd.Args = append(cmd.Args, "--log_dir=../logs")
+	cmd.Args = append(cmd.Args, envelope.Args...)
+	return cmd, nil
+}
+
+func (i *invoker) Start(call ipc.ServerContext) ([]string, error) {
+	vlog.VI(1).Infof("%v.Start()", i.suffix)
+	if !strings.HasPrefix(i.suffix, "apps") {
+		return nil, errInvalidSuffix
+	}
+	components := splitName(strings.TrimPrefix(i.suffix, "apps"))
+	if nComponents := len(components); nComponents < 2 {
+		return nil, fmt.Errorf("Start all installations / all applications not yet implemented (%v)", i.suffix)
+	} else if nComponents > 2 {
+		return nil, errInvalidSuffix
+	}
+	app, installation := components[0], components[1]
+	installationDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation))
+	if _, err := os.Stat(installationDir); err != nil {
+		if os.IsNotExist(err) {
+			return nil, errNotExist
+		}
+		vlog.Errorf("Stat(%v) failed: %v", installationDir, err)
+		return nil, errOperationFailed
+	}
+	currLink := filepath.Join(installationDir, "current")
+	envelope, err := loadEnvelope(currLink)
+	if err != nil {
+		return nil, err
+	}
+	binPath := filepath.Join(currLink, "bin")
+	if _, err := os.Stat(binPath); err != nil {
+		vlog.Errorf("Stat(%v) failed: %v", binPath, err)
+		return nil, errOperationFailed
+	}
+	instanceID := generateID()
+	// TODO(caprita): Clean up instanceDir upon failure.
+	instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID))
+	cmd, err := generateCommand(envelope, binPath, instanceDir)
+	if err != nil {
+		vlog.Errorf("generateCommand(%v, %v, %v) failed: %v", envelope, binPath, instanceDir, err)
+		return nil, errOperationFailed
+	}
+	// Setup up the child process callback.
+	id := i.generateCallbackID()
+	cfg := config.New()
+	cfg.Set(mgmt.ParentNodeManagerConfigKey, naming.MakeTerminal(naming.Join(i.config.Name, id)))
+	handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
+	// Make the channel buffered to avoid blocking the Set method when
+	// nothing is receiving on the channel.  This happens e.g. when
+	// unregisterCallbacks executes before Set is called.
+	callbackChan := make(chan string, 1)
+	i.registerCallback(id, mgmt.AppCycleManagerConfigKey, callbackChan)
+	defer i.unregisterCallbacks(id)
+	// Start the child process.
+	if err := handle.Start(); err != nil {
+		vlog.Errorf("Start() failed: %v", err)
+		return nil, errOperationFailed
+	}
+	// Wait for the child process to start.
+	testTimeout := 10 * time.Second
+	if err := handle.WaitForReady(testTimeout); err != nil {
+		vlog.Errorf("WaitForReady(%v) failed: %v", testTimeout, err)
+		if err := handle.Clean(); err != nil {
+			vlog.Errorf("Clean() failed: %v", err)
+		}
+		return nil, errOperationFailed
+	}
+	select {
+	case childName := <-callbackChan:
+		instanceInfo := &instanceInfo{
+			AppCycleMgrName: childName,
+			Pid:             handle.Pid(),
+		}
+		if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil {
+			if err := handle.Clean(); err != nil {
+				vlog.Errorf("Clean() failed: %v", err)
+			}
+			return nil, err
+		}
+		// TODO(caprita): Spin up a goroutine to reap child status upon
+		// exit and transition it to suspended state if it exits on its
+		// own.
+	case <-time.After(testTimeout):
+		vlog.Errorf("Waiting for callback timed out")
+		if err := handle.Clean(); err != nil {
+			vlog.Errorf("Clean() failed: %v", err)
+		}
+		return nil, errOperationFailed
+	}
+	return []string{instanceID}, nil
+}
+
+func saveInstanceInfo(dir string, info *instanceInfo) error {
+	jsonInfo, err := json.Marshal(info)
+	if err != nil {
+		vlog.Errorf("Marshal(%v) failed: %v", info, err)
+		return errOperationFailed
+	}
+	infoPath := filepath.Join(dir, "info")
+	if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil {
+		vlog.Errorf("WriteFile(%v) failed: %v", infoPath, err)
+		return errOperationFailed
+	}
+	return nil
+}
+
+func loadInstanceInfo(dir string) (*instanceInfo, error) {
+	infoPath := filepath.Join(dir, "info")
+	info := new(instanceInfo)
+	if infoBytes, err := ioutil.ReadFile(infoPath); err != nil {
+		vlog.Errorf("ReadFile(%v) failed: %v", infoPath, err)
+		return nil, errOperationFailed
+	} else if err := json.Unmarshal(infoBytes, info); err != nil {
+		vlog.Errorf("Unmarshal(%v) failed: %v", infoBytes, err)
+		return nil, errOperationFailed
+	}
+	return info, nil
+}
+
+func (i *invoker) Stop(call ipc.ServerContext, deadline uint32) error {
+	// TODO(caprita): implement deadline.
 	vlog.VI(1).Infof("%v.Stop(%v)", i.suffix, deadline)
-	// TODO(jsimsa): Implement.
+	if !strings.HasPrefix(i.suffix, "apps") {
+		return errInvalidSuffix
+	}
+	components := splitName(strings.TrimPrefix(i.suffix, "apps"))
+	if nComponents := len(components); nComponents < 3 {
+		return fmt.Errorf("Stop all instances / all installations / all applications not yet implemented (%v)", i.suffix)
+	} else if nComponents > 3 {
+		return errInvalidSuffix
+	}
+	app, installation, instance := components[0], components[1], components[2]
+	instancesDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation), "instances")
+	instanceDir := filepath.Join(instancesDir, instanceDirName(instance))
+	stoppedInstanceDir := filepath.Join(instancesDir, stoppedInstanceDirName(instance))
+	if err := os.Rename(instanceDir, stoppedInstanceDir); err != nil {
+		vlog.Errorf("Rename(%v, %v) failed: %v", instanceDir, stoppedInstanceDir, err)
+		if os.IsNotExist(err) {
+			return errNotExist
+		}
+		vlog.Errorf("Rename(%v, %v) failed: %v", instanceDir, stoppedInstanceDir, err)
+		return errOperationFailed
+	}
+	// TODO(caprita): restore the instance to unstopped upon failure?
+
+	info, err := loadInstanceInfo(stoppedInstanceDir)
+	if err != nil {
+		return errOperationFailed
+	}
+	appStub, err := appcycle.BindAppCycle(info.AppCycleMgrName)
+	if err != nil {
+		vlog.Errorf("BindAppCycle(%v) failed: %v", info.AppCycleMgrName, err)
+		return errOperationFailed
+	}
+	stream, err := appStub.Stop(rt.R().NewContext())
+	if err != nil {
+		vlog.Errorf("Got error: %v", err)
+		return errOperationFailed
+	}
+	rstream := stream.RecvStream()
+	for rstream.Advance() {
+		vlog.VI(2).Infof("%v.Stop(%v) task update: %v", i.suffix, deadline, rstream.Value())
+	}
+	if err := rstream.Err(); err != nil {
+		vlog.Errorf("Stream returned an error: %v", err)
+		return errOperationFailed
+	}
+	if err := stream.Finish(); err != nil {
+		vlog.Errorf("Got error: %v", err)
+		return errOperationFailed
+	}
 	return nil
 }
 
@@ -697,16 +918,16 @@
 
 func (i *invoker) Set(_ ipc.ServerContext, key, value string) error {
 	vlog.VI(1).Infof("%v.Set(%v, %v)", i.suffix, key, value)
-	// For now, only handle the child node manager name.  We'll add handling
-	// for the child's app cycle manager name later on.
-	if key != mgmt.ChildNodeManagerConfigKey {
-		return nil
-	}
+	id := i.suffix
 	i.internal.channelsMutex.Lock()
-	channel, ok := i.internal.channels[i.suffix]
+	if _, ok := i.internal.channels[id]; !ok {
+		i.internal.channelsMutex.Unlock()
+		return errInvalidSuffix
+	}
+	channel, ok := i.internal.channels[id][key]
 	i.internal.channelsMutex.Unlock()
 	if !ok {
-		return errInvalidSuffix
+		return nil
 	}
 	channel <- value
 	return nil
diff --git a/services/mgmt/node/impl/profile.go b/services/mgmt/node/impl/profile.go
new file mode 100644
index 0000000..d522784
--- /dev/null
+++ b/services/mgmt/node/impl/profile.go
@@ -0,0 +1,191 @@
+package impl
+
+import (
+	"bytes"
+	"errors"
+	"os/exec"
+	"runtime"
+	"strings"
+
+	"veyron/services/mgmt/profile"
+
+	"veyron2/services/mgmt/build"
+	"veyron2/services/mgmt/node"
+)
+
+// computeNodeProfile generates a description of the runtime
+// environment (supported file format, OS, architecture, libraries) of
+// the host node.
+//
+// TODO(jsimsa): Avoid computing the host node description from
+// scratch if a recent cached copy exists.
+func computeNodeProfile() (*profile.Specification, error) {
+	result := profile.Specification{}
+
+	// Find out what the supported file format, operating system, and
+	// architecture is.
+	switch runtime.GOOS {
+	case "darwin":
+		result.Format = build.MACH
+		result.OS = build.Darwin
+	case "linux":
+		result.Format = build.ELF
+		result.OS = build.Linux
+	case "windows":
+		result.Format = build.PE
+		result.OS = build.Windows
+	default:
+		return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
+	}
+	switch runtime.GOARCH {
+	case "amd64":
+		result.Arch = build.AMD64
+	case "arm":
+		result.Arch = build.ARM
+	case "x86":
+		result.Arch = build.X86
+	default:
+		return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
+	}
+
+	// Find out what the installed dynamically linked libraries are.
+	switch runtime.GOOS {
+	case "linux":
+		// For Linux, we identify what dynamically linked libraries are
+		// install by parsing the output of "ldconfig -p".
+		command := exec.Command("ldconfig", "-p")
+		output, err := command.CombinedOutput()
+		if err != nil {
+			return nil, err
+		}
+		buf := bytes.NewBuffer(output)
+		// Throw away the first line of output from ldconfig.
+		if _, err := buf.ReadString('\n'); err != nil {
+			return nil, errors.New("Could not identify libraries.")
+		}
+		// Extract the library name and version from every subsequent line.
+		result.Libraries = make(map[profile.Library]struct{})
+		line, err := buf.ReadString('\n')
+		for err == nil {
+			words := strings.Split(strings.Trim(line, " \t\n"), " ")
+			if len(words) > 0 {
+				tokens := strings.Split(words[0], ".so")
+				if len(tokens) != 2 {
+					return nil, errors.New("Could not identify library: " + words[0])
+				}
+				name := strings.TrimPrefix(tokens[0], "lib")
+				major, minor := "", ""
+				tokens = strings.SplitN(tokens[1], ".", 3)
+				if len(tokens) >= 2 {
+					major = tokens[1]
+				}
+				if len(tokens) >= 3 {
+					minor = tokens[2]
+				}
+				result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
+			}
+			line, err = buf.ReadString('\n')
+		}
+	case "darwin":
+		// TODO(jsimsa): Implement.
+	case "windows":
+		// TODO(jsimsa): Implement.
+	default:
+		return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
+	}
+	return &result, nil
+}
+
+// getProfile gets a profile description for the given profile.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func getProfile(name string) (*profile.Specification, error) {
+	// TODO(jsimsa): This function assumes the existence of a profile
+	// server from which the profiles can be retrieved. The profile
+	// server is a work in progress. When it exists, the commented out
+	// code below should work.
+	var profile profile.Specification
+	/*
+			client, err := r.NewClient()
+			if err != nil {
+				vlog.Errorf("NewClient() failed: %v", err)
+				return nil, err
+			}
+			defer client.Close()
+		  server := // TODO
+			method := "Specification"
+			inputs := make([]interface{}, 0)
+			call, err := client.StartCall(server + "/" + name, method, inputs)
+			if err != nil {
+				vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
+				return nil, err
+			}
+			if err := call.Finish(&profiles); err != nil {
+				vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
+				return nil, err
+			}
+	*/
+	return &profile, nil
+}
+
+// getKnownProfiles gets a list of description for all publicly known
+// profiles.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func getKnownProfiles() ([]profile.Specification, error) {
+	// TODO(jsimsa): This function assumes the existence of a profile
+	// server from which a list of known profiles can be retrieved. The
+	// profile server is a work in progress. When it exists, the
+	// commented out code below should work.
+	knownProfiles := make([]profile.Specification, 0)
+	/*
+			client, err := r.NewClient()
+			if err != nil {
+				vlog.Errorf("NewClient() failed: %v\n", err)
+				return nil, err
+			}
+			defer client.Close()
+		  server := // TODO
+			method := "List"
+			inputs := make([]interface{}, 0)
+			call, err := client.StartCall(server, method, inputs)
+			if err != nil {
+				vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
+				return nil, err
+			}
+			if err := call.Finish(&knownProfiles); err != nil {
+				vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
+				return nil, err
+			}
+	*/
+	return knownProfiles, nil
+}
+
+// matchProfiles inputs a profile that describes the host node and a
+// set of publicly known profiles and outputs a node description that
+// identifies the publicly known profiles supported by the host node.
+func matchProfiles(p *profile.Specification, known []profile.Specification) node.Description {
+	result := node.Description{Profiles: make(map[string]struct{})}
+loop:
+	for _, profile := range known {
+		if profile.Format != p.Format {
+			continue
+		}
+		if profile.OS != p.OS {
+			continue
+		}
+		if profile.Arch != p.Arch {
+			continue
+		}
+		for library := range profile.Libraries {
+			// Current implementation requires exact library name and version match.
+			if _, found := p.Libraries[library]; !found {
+				continue loop
+			}
+		}
+		result.Profiles[profile.Label] = struct{}{}
+	}
+	return result
+}
diff --git a/services/store/memstore/acl/cache.go b/services/store/memstore/acl/cache.go
deleted file mode 100644
index cd3f5b9..0000000
--- a/services/store/memstore/acl/cache.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package acl
-
-import (
-	"bytes"
-
-	"veyron/runtimes/google/lib/functional"
-	"veyron/runtimes/google/lib/functional/rb"
-	"veyron2/storage"
-)
-
-// FindFunc is used to fetch the ACL given its ID.
-type FindFunc func(id storage.ID) *storage.ACL
-
-// Cache keeps a map from ID -> ACL, based on the contents of the store.
-//
-// Cache is thread-compatible -- mutating methods require the caller to
-// have exclusive access to the object.
-//
-// TODO(jyh): Currently, the cache can grow without bound.  Implement a
-// replacement policy, for a cache with a bounded number of entries.
-type Cache struct {
-	contents functional.Set // *cacheEntry
-	find     FindFunc
-}
-
-// cacheEntry is an entry in the cache, mapping storage.ID -> storage.ACL.
-type cacheEntry struct {
-	id  storage.ID
-	acl *storage.ACL
-}
-
-var (
-	emptyCacheContents = rb.NewSet(compareCacheEntriesByID)
-)
-
-// compareCacheEntriesByID compares the two cells' IDs.
-func compareCacheEntriesByID(a, b interface{}) bool {
-	return bytes.Compare(a.(*cacheEntry).id[:], b.(*cacheEntry).id[:]) < 0
-}
-
-// NewCache returns an empty cache.
-func NewCache(find FindFunc) Cache {
-	return Cache{contents: emptyCacheContents, find: find}
-}
-
-// Clear resets the cache.
-func (c *Cache) Clear() {
-	c.contents = emptyCacheContents
-	c.find = nil
-}
-
-// Invalidate removes cache entry.  It should be called whenever the ACL
-// associated with the ID is changed.
-func (c *Cache) Invalidate(id storage.ID) {
-	c.contents = c.contents.Remove(&cacheEntry{id: id})
-}
-
-// UpdateFind changes the find function.
-func (c *Cache) UpdateFinder(find FindFunc) {
-	c.find = find
-}
-
-// get fetches a cache entry.  If the entry is not in the cache, the function
-// <find> is used to fetch the value, the result is saved in the cache, and
-// returned.
-func (c *Cache) get(id storage.ID) *storage.ACL {
-	e := &cacheEntry{id: id}
-	x, ok := c.contents.Get(e)
-	if ok {
-		return x.(*cacheEntry).acl
-	}
-	acl := c.find(id)
-	if acl == nil {
-		return nil
-	}
-	e.acl = acl
-	c.contents = c.contents.Put(e)
-	return acl
-}
diff --git a/services/store/memstore/acl/checker.go b/services/store/memstore/acl/checker.go
deleted file mode 100644
index c3921e7..0000000
--- a/services/store/memstore/acl/checker.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package acl
-
-import (
-	"bytes"
-	"fmt"
-
-	"veyron2/security"
-	"veyron2/storage"
-)
-
-// Entry includes an ACL and flags to indicate whether the ACL should be inherited.
-type Entry struct {
-	ACL       *storage.ACL
-	Inherited bool
-}
-
-// Set is a set of Entries, indexed by their IDs.
-type Set map[storage.ID]Entry
-
-// Checker is used to check if a principal matches ACLs extracted from the tags
-// applied to objects in a store.
-//
-// While walking through a path as part of resolving a cell, call Update for each
-// component of the path.  Checker will then keep track of the inherited ACLs
-// for that path.
-type Checker struct {
-	cache     *Cache
-	principal security.PublicID
-	acls      Set
-}
-
-// NewChecker constructs a new Checker and returns it.
-func NewChecker(cache *Cache, clientID security.PublicID, acls Set) *Checker {
-	// Copy the Set.
-	cp := make(Set)
-	for id, acl := range acls {
-		cp[id] = acl
-	}
-	return &Checker{cache: cache, principal: clientID, acls: cp}
-}
-
-// Copy, so that updates do not affect the original.
-func (c Checker) Copy() *Checker {
-	acls := make(Set)
-	for id, acl := range c.acls {
-		acls[id] = acl
-	}
-	c.acls = acls
-	return &c
-}
-
-// Update is called for each step in a path traversal to update the
-// Checker using the TagList associated with a value in the store.
-func (c *Checker) Update(tags storage.TagList) {
-	// The caller has just made one step deeper into the path.  The non-inherited
-	// ACLs are no longer relevant, so prune them.
-	for id, entry := range c.acls {
-		if !entry.Inherited {
-			delete(c.acls, id)
-		}
-	}
-
-	// Add the new ACLc.
-	for _, tag := range tags {
-		switch tag.Op {
-		case storage.RemoveACL:
-			delete(c.acls, tag.ACL)
-		case storage.AddACL:
-			if acl := c.cache.get(tag.ACL); acl != nil {
-				c.acls[tag.ACL] = Entry{ACL: acl}
-			}
-		case storage.AddInheritedACL:
-			if acl := c.cache.get(tag.ACL); acl != nil {
-				c.acls[tag.ACL] = Entry{ACL: acl, Inherited: true}
-			}
-		}
-	}
-}
-
-// IsAllowed returns true iff the current acls allow the principal to use a
-// label.
-func (c *Checker) IsAllowed(label security.Label) bool {
-	for _, entry := range c.acls {
-		for key, labels := range entry.ACL.Contents {
-			if labels.HasLabel(label) {
-				if c.principal.Match(key) {
-					return true
-				}
-			}
-		}
-	}
-	return false
-}
-
-// IsEqual returns true iff the checkers are exactly equivalent, containing the same ACLs.
-func (c1 *Checker) IsEqual(c2 *Checker) bool {
-	if c1.cache != c2.cache || c1.principal != c2.principal || len(c1.acls) != len(c2.acls) {
-		return false
-	}
-
-	for id, _ := range c1.acls {
-		if _, ok := c2.acls[id]; !ok {
-			return false
-		}
-	}
-	return true
-}
-
-func (c Checker) String() string {
-	var buf bytes.Buffer
-	fmt.Fprintf(&buf, "Checker{principal:%q", c.principal)
-	for p, l := range c.acls {
-		fmt.Fprintf(&buf, ", %s:%s", p, l)
-	}
-	buf.WriteRune('}')
-	return buf.String()
-}
-
-func (e Entry) String() string {
-	if e.Inherited {
-		return "[Inherited]" + e.ACL.String()
-	}
-	return e.ACL.String()
-}
diff --git a/services/store/memstore/refs/builder.go b/services/store/memstore/refs/builder.go
index 05f12a9..18582f2 100644
--- a/services/store/memstore/refs/builder.go
+++ b/services/store/memstore/refs/builder.go
@@ -6,7 +6,6 @@
 
 	"veyron/runtimes/google/lib/functional"
 
-	"veyron2/security"
 	"veyron2/storage"
 )
 
@@ -45,7 +44,7 @@
 // AddDEntries adds the references contained in the DEntry list.
 func (b *Builder) AddDEntries(d []*storage.DEntry) {
 	for _, de := range d {
-		b.refs = b.refs.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name), Label: security.ReadLabel})
+		b.refs = b.refs.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name)})
 	}
 }
 
@@ -58,20 +57,13 @@
 	b.addRefs(nil, reflect.ValueOf(v))
 }
 
-// AddTags adds the references contained in the TagList.
-func (b *Builder) AddTags(v storage.TagList) {
-	for i, tag := range v {
-		b.refs.Put(&Ref{ID: tag.ACL, Path: tagsDir.Append(strconv.Itoa(i)), Label: security.AdminLabel})
-	}
-}
-
 func (b *Builder) addRefs(path *Path, v reflect.Value) {
 	if !v.IsValid() {
 		return
 	}
 	ty := v.Type()
 	if ty == tyID {
-		b.refs = b.refs.Put(&Ref{ID: v.Interface().(storage.ID), Path: path, Label: security.ReadLabel})
+		b.refs = b.refs.Put(&Ref{ID: v.Interface().(storage.ID), Path: path})
 		return
 	}
 	switch ty.Kind() {
diff --git a/services/store/memstore/refs/path.go b/services/store/memstore/refs/path.go
index 5cb86db..4a151ee 100644
--- a/services/store/memstore/refs/path.go
+++ b/services/store/memstore/refs/path.go
@@ -21,15 +21,9 @@
 	table map[Path]*Path
 }
 
-const (
-	TagsDirName = ".tags"
-)
-
 var (
 	pathTable = &pathHashConsTable{table: make(map[Path]*Path)}
 	nilPath   *Path
-
-	tagsDir = NewSingletonPath(TagsDirName)
 )
 
 // ComparePaths defines a total order over *Path values, based on pointer
diff --git a/services/store/memstore/refs/refs.go b/services/store/memstore/refs/refs.go
index 2576c73..c7f791b 100644
--- a/services/store/memstore/refs/refs.go
+++ b/services/store/memstore/refs/refs.go
@@ -5,7 +5,6 @@
 	"veyron/runtimes/google/lib/functional"
 	"veyron/runtimes/google/lib/functional/rb"
 
-	"veyron2/security"
 	"veyron2/storage"
 )
 
@@ -15,9 +14,8 @@
 // Ref represents a single reference in a store value.  It includes the
 // storage.ID, and the path to the reference.
 type Ref struct {
-	ID    storage.ID
-	Path  *Path
-	Label security.Label
+	ID   storage.ID
+	Path *Path
 }
 
 // Dir represents a directory, which is a set of *Ref, sorted by path.
@@ -58,7 +56,7 @@
 func BuildDir(l []*storage.DEntry) Dir {
 	d := EmptyDir
 	for _, de := range l {
-		d = d.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name), Label: security.ReadLabel})
+		d = d.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name)})
 	}
 	return d
 }
diff --git a/services/store/memstore/state/cell.go b/services/store/memstore/state/cell.go
index 2913146..f0f9c32 100644
--- a/services/store/memstore/state/cell.go
+++ b/services/store/memstore/state/cell.go
@@ -23,10 +23,7 @@
 	// Implicit directory.
 	Dir refs.Set
 
-	// tags are the access control tags.
-	Tags storage.TagList
-
-	// refs includes the references in the value, the dir, and the tags.
+	// refs includes the references in the value and the dir.
 	//
 	// TODO(jyh): This is simple, but it would be more space efficient to
 	// include only the refs in the value, or drop this field entirely.
@@ -69,7 +66,6 @@
 	r := refs.NewBuilder()
 	r.AddValue(c.Value)
 	r.AddDir(c.Dir)
-	r.AddTags(c.Tags)
 	c.refs = r.Get()
 }
 
diff --git a/services/store/memstore/state/iterator.go b/services/store/memstore/state/iterator.go
index 70eac5c..e7c5275 100644
--- a/services/store/memstore/state/iterator.go
+++ b/services/store/memstore/state/iterator.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 
-	"veyron/services/store/memstore/acl"
 	"veyron/services/store/memstore/refs"
 
 	"veyron2/security"
@@ -59,12 +58,10 @@
 }
 
 type next struct {
-	// checker is the acl.Checker for the object _containing_ the reference.
-	checker *acl.Checker
-	parent  *refs.FullPath
-	path    *refs.Path
-	id      storage.ID
-	action  action
+	parent *refs.FullPath
+	path   *refs.Path
+	id     storage.ID
+	action action
 }
 
 type action int
@@ -116,8 +113,7 @@
 func (sn *snapshot) NewIterator(pid security.PublicID, path storage.PathName,
 	pathFilter PathFilter, filter IterFilter) Iterator {
 
-	checker := sn.newPermChecker(pid)
-	cell, suffix, v := sn.resolveCell(checker, path, nil)
+	cell, suffix, v := sn.resolveCell(path, nil)
 	if cell == nil {
 		return &errorIterator{snapshot: sn}
 	}
@@ -153,7 +149,7 @@
 	}
 
 	if expand {
-		it.pushVisitAll(checker, it.path, set)
+		it.pushVisitAll(it.path, set)
 	}
 	if !ret {
 		it.Next()
@@ -165,7 +161,7 @@
 func (it *iterator) pushUnvisit(path *refs.Path, id storage.ID) {
 	switch it.pathFilter {
 	case ListPaths:
-		it.next = append(it.next, next{nil, nil, path, id, unvisit})
+		it.next = append(it.next, next{nil, path, id, unvisit})
 	case ListObjects:
 		// Do not unvisit the object, as it is on a path already seen by
 		// it.filter.
@@ -174,14 +170,11 @@
 	}
 }
 
-func (it *iterator) pushVisitAll(checker *acl.Checker,
-	parentPath *refs.FullPath, set refs.Set) {
+func (it *iterator) pushVisitAll(parentPath *refs.FullPath, set refs.Set) {
 
 	set.Iter(func(x interface{}) bool {
 		ref := x.(*refs.Ref)
-		if checker.IsAllowed(ref.Label) {
-			it.next = append(it.next, next{checker, parentPath, ref.Path, ref.ID, visit})
-		}
+		it.next = append(it.next, next{parentPath, ref.Path, ref.ID, visit})
 		return true
 	})
 }
@@ -205,7 +198,6 @@
 func (it *iterator) Next() {
 	var n next
 	var fullPath *refs.FullPath
-	var checker *acl.Checker
 	var c *Cell
 	for {
 		topIndex := len(it.next) - 1
@@ -235,18 +227,11 @@
 			panic(fmt.Sprintf("Dangling reference: %s", n.id))
 		}
 
-		// Check permissions.
-		checker = n.checker.Copy()
-		checker.Update(c.Tags)
-		if !checker.IsAllowed(security.ReadLabel) {
-			continue
-		}
-
 		// Check the filter
 		ret, expand := it.filter(n.parent, n.path)
 		fullPath = n.parent.AppendPath(n.path)
 		if expand {
-			it.pushVisitAll(checker, fullPath, c.refs)
+			it.pushVisitAll(fullPath, c.refs)
 		}
 		if ret {
 			// Found a value.
diff --git a/services/store/memstore/state/iterator_test.go b/services/store/memstore/state/iterator_test.go
index a9f3b2b..ffc4bfc 100644
--- a/services/store/memstore/state/iterator_test.go
+++ b/services/store/memstore/state/iterator_test.go
@@ -163,107 +163,3 @@
 		{"teams/cardinals", "players/matt/team"},
 	})
 }
-
-func TestIteratorSecurity(t *testing.T) {
-	st := state.New(rootPublicID)
-	sn := st.MutableSnapshot()
-
-	// Create /Users/jane and give her RWA permissions.
-	janeACLID := putPath(t, sn, rootPublicID, "/Users/jane/acls/janeRWA", &storage.ACL{
-		Name: "Jane",
-		Contents: security.ACL{
-			janeUser: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
-		},
-	})
-	janeTags := storage.TagList{
-		storage.Tag{Op: storage.AddInheritedACL, ACL: janeACLID},
-		storage.Tag{Op: storage.RemoveACL, ACL: state.EveryoneACLID},
-	}
-	put(t, sn, rootPublicID, "/Users/jane/.tags", janeTags)
-	put(t, sn, rootPublicID, "/Users/jane/aaa", "stuff")
-	sharedID := put(t, sn, rootPublicID, "/Users/jane/shared", "stuff")
-
-	// Create /Users/john and give him RWA permissions.
-	johnACLID := putPath(t, sn, rootPublicID, "/Users/john/acls/johnRWA", &storage.ACL{
-		Name: "John",
-		Contents: security.ACL{
-			johnUser: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
-		},
-	})
-	johnTags := storage.TagList{
-		storage.Tag{Op: storage.AddInheritedACL, ACL: johnACLID},
-		storage.Tag{Op: storage.RemoveACL, ACL: state.EveryoneACLID},
-	}
-	put(t, sn, rootPublicID, "/Users/john/.tags", johnTags)
-	put(t, sn, rootPublicID, "/Users/john/aaa", "stuff")
-	put(t, sn, rootPublicID, "/Users/john/shared", sharedID)
-
-	// Root gets everything.
-	checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
-		"",
-		"Users",
-		"Users/jane",
-		"Users/jane/acls",
-		"Users/jane/acls/janeRWA",
-		"Users/jane/aaa",
-		"Users/john",
-		"Users/john/acls",
-		"Users/john/acls/johnRWA",
-		"Users/john/aaa",
-		"Users/jane/shared",
-		"Users/john/shared",
-	})
-	checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
-		{""},
-		{"Users"},
-		{"Users/jane"},
-		{"Users/jane/acls"},
-		{"Users/jane/acls/janeRWA"},
-		{"Users/jane/aaa"},
-		{"Users/john"},
-		{"Users/john/acls"},
-		{"Users/john/acls/johnRWA"},
-		{"Users/john/aaa"},
-		{"Users/jane/shared", "Users/john/shared"},
-	})
-
-	// Jane sees only her names.
-	checkAcyclicIterator(t, sn, janePublicID, nil, []string{
-		"",
-		"Users",
-		"Users/jane",
-		"Users/jane/acls",
-		"Users/jane/acls/janeRWA",
-		"Users/jane/aaa",
-		"Users/jane/shared",
-	})
-	checkUniqueObjectsIterator(t, sn, janePublicID, nil, [][]string{
-		{""},
-		{"Users"},
-		{"Users/jane"},
-		{"Users/jane/acls"},
-		{"Users/jane/acls/janeRWA"},
-		{"Users/jane/aaa"},
-		{"Users/jane/shared"},
-	})
-
-	// John sees only his names.
-	checkAcyclicIterator(t, sn, johnPublicID, nil, []string{
-		"",
-		"Users",
-		"Users/john",
-		"Users/john/acls",
-		"Users/john/acls/johnRWA",
-		"Users/john/aaa",
-		"Users/john/shared",
-	})
-	checkUniqueObjectsIterator(t, sn, johnPublicID, nil, [][]string{
-		{""},
-		{"Users"},
-		{"Users/john"},
-		{"Users/john/acls"},
-		{"Users/john/acls/johnRWA"},
-		{"Users/john/aaa"},
-		{"Users/john/shared"},
-	})
-}
diff --git a/services/store/memstore/state/mutable_snapshot.go b/services/store/memstore/state/mutable_snapshot.go
index f640eb1..731d6ca 100644
--- a/services/store/memstore/state/mutable_snapshot.go
+++ b/services/store/memstore/state/mutable_snapshot.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 
-	"veyron/services/store/memstore/acl"
 	"veyron/services/store/memstore/field"
 	"veyron/services/store/memstore/refs"
 	"veyron/services/store/raw"
@@ -88,11 +87,6 @@
 	// Value is the new value.
 	Value interface{}
 
-	// Tags are the new tags.
-	//
-	// TODO(jyh): Replace with a delta encoding.
-	Tags storage.TagList
-
 	// Dir is the set of new directory entries.
 	//
 	// TODO(jyh): Replace this with a delta, to support large directories.
@@ -108,8 +102,6 @@
 	errBadValue             = verror.BadArgf("value has the wrong type")
 	errDuplicatePutMutation = verror.BadArgf("duplicate calls to PutMutation for the same ID")
 	errNotFound             = verror.NotFoundf("not found")
-	errNotTagList           = verror.BadArgf("not a TagList")
-	errPermissionDenied     = verror.NotAuthorizedf("")
 	errPreconditionFailed   = verror.Abortedf("precondition failed")
 
 	nullID storage.ID
@@ -168,7 +160,6 @@
 	// Perform a GC to clear out gcRoots.
 	sn.gc()
 	cp := sn.snapshot
-	cp.resetACLCache()
 	return &cp
 }
 
@@ -180,7 +171,6 @@
 	cp := *sn
 	cp.mutations = newMutations()
 	cp.gcRoots = make(map[storage.ID]struct{})
-	cp.resetACLCache()
 	return &cp
 }
 
@@ -206,7 +196,6 @@
 func (sn *MutableSnapshot) delete(c *Cell) {
 	sn.idTable = sn.idTable.Remove(c)
 	sn.deletions[c.ID] = c.Version
-	sn.aclCache.Invalidate(c.ID)
 }
 
 // put adds a cell to the state, also adding the new value to the Mutations set.
@@ -218,44 +207,32 @@
 		m.Value = c.Value
 		m.refs = c.refs
 		m.Dir = d
-		m.Tags = c.Tags
 	} else {
 		mu.Preconditions[c.ID] = c.Version
 		m = &Mutation{
 			Postcondition: storage.NewVersion(),
 			Value:         c.Value,
 			Dir:           d,
-			Tags:          c.Tags,
 			refs:          c.refs,
 		}
 		mu.Delta[c.ID] = m
 	}
 	c.Version = m.Postcondition
 	sn.idTable = sn.idTable.Put(c)
-	sn.aclCache.Invalidate(c.ID)
 }
 
 // add adds a new Value to the state, updating reference counts.  Fails if the
 // new value contains dangling references.
-func (sn *MutableSnapshot) add(parentChecker *acl.Checker, id storage.ID, v interface{}) (*Cell, error) {
+func (sn *MutableSnapshot) add(id storage.ID, v interface{}) (*Cell, error) {
 	c := sn.Find(id)
 	if c == nil {
 		// There is no current value, so create a new cell for the value and add
 		// it.
-		//
-		// There is no permissions check here because the caller is not modifying a preexisting value.
-		//
-		// TODO(jyh): However, the new value is created with default
-		// permissions, which does not include the ability to set the tags on
-		// the cell.  So the caller can wind up in a odd situation where they
-		// can create a value, but not be able to read it back, and no way to
-		// fix it.  Figure out whether this is a problem.
 		c = &Cell{
 			ID:       id,
 			refcount: 0,
 			Value:    v,
 			Dir:      refs.EmptyDir,
-			Tags:     storage.TagList{},
 			inRefs:   refs.Empty,
 			Version:  storage.NoVersion,
 		}
@@ -269,16 +246,11 @@
 	}
 
 	// There is already a value in the state, so replace it with the new value.
-	checker := parentChecker.Copy()
-	checker.Update(c.Tags)
-	return sn.replaceValue(checker, c, v)
+	return sn.replaceValue(c, v)
 }
 
 // replaceValue updates the cell.value.
-func (sn *MutableSnapshot) replaceValue(checker *acl.Checker, c *Cell, v interface{}) (*Cell, error) {
-	if !checker.IsAllowed(security.WriteLabel) {
-		return nil, errPermissionDenied
-	}
+func (sn *MutableSnapshot) replaceValue(c *Cell, v interface{}) (*Cell, error) {
 	cp := *c
 	cp.Value = v
 	cp.setRefs()
@@ -291,10 +263,7 @@
 }
 
 // replaceDir updates the cell.dir.
-func (sn *MutableSnapshot) replaceDir(checker *acl.Checker, c *Cell, d functional.Set) (*Cell, error) {
-	if !checker.IsAllowed(security.WriteLabel) {
-		return nil, errPermissionDenied
-	}
+func (sn *MutableSnapshot) replaceDir(c *Cell, d functional.Set) (*Cell, error) {
 	cp := *c
 	cp.Dir = d
 	cp.setRefs()
@@ -306,26 +275,9 @@
 	return &cp, nil
 }
 
-// replaceTags replaces the cell.tags.
-func (sn *MutableSnapshot) replaceTags(checker *acl.Checker, c *Cell, tags storage.TagList) (*Cell, error) {
-	if !checker.IsAllowed(security.AdminLabel) {
-		return nil, errPermissionDenied
-	}
-	cp := *c
-	cp.Tags = tags
-	cp.setRefs()
-	if !sn.refsExist(cp.refs) {
-		return nil, errBadRef
-	}
-	sn.put(&cp)
-	sn.updateRefs(c.ID, c.refs, cp.refs)
-	return &cp, nil
-}
-
 // Get returns the value for a path.
 func (sn *MutableSnapshot) Get(pid security.PublicID, path storage.PathName) (*storage.Entry, error) {
-	checker := sn.newPermChecker(pid)
-	cell, suffix, v := sn.resolveCell(checker, path, sn.mutations)
+	cell, suffix, v := sn.resolveCell(path, sn.mutations)
 	if cell == nil {
 		return nil, errNotFound
 	}
@@ -341,21 +293,20 @@
 // Put adds a new value to the state or replaces an existing one.  Returns
 // the *Stat for the enclosing *cell.
 func (sn *MutableSnapshot) Put(pid security.PublicID, path storage.PathName, v interface{}) (*storage.Stat, error) {
-	checker := sn.newPermChecker(pid)
-	c, err := sn.putValueByPath(checker, path, v)
+	c, err := sn.putValueByPath(path, v)
 	if err != nil {
 		return nil, err
 	}
 	return c.getStat(), nil
 }
 
-func (sn *MutableSnapshot) putValueByPath(checker *acl.Checker, path storage.PathName, v interface{}) (*Cell, error) {
+func (sn *MutableSnapshot) putValueByPath(path storage.PathName, v interface{}) (*Cell, error) {
 	v = deepcopy(v)
 
 	if path.IsRoot() {
-		return sn.putRoot(checker, v)
+		return sn.putRoot(v)
 	}
-	return sn.putValue(checker, path, v)
+	return sn.putValue(path, v)
 }
 
 // putValue is called for a normal Put() operation, where a new value is being
@@ -363,15 +314,12 @@
 // There are two cases: 1) the value <v> is written directly into the parent, or
 // 2) the field has type storage.ID.  In the latter case, the <id> is assigned
 // into the parent, and the value id->v is added to the idTable.
-func (sn *MutableSnapshot) putValue(checker *acl.Checker, path storage.PathName, v interface{}) (*Cell, error) {
+func (sn *MutableSnapshot) putValue(path storage.PathName, v interface{}) (*Cell, error) {
 	// Find the parent object.
-	c, suffix, _ := sn.resolveCell(checker, path[:len(path)-1], sn.mutations)
+	c, suffix, _ := sn.resolveCell(path[:len(path)-1], sn.mutations)
 	if c == nil {
 		return nil, errNotFound
 	}
-	if len(suffix) > 0 && suffix[0] == refs.TagsDirName {
-		return sn.putTagsValue(checker, path, suffix[1:], c, v)
-	}
 	value := deepcopy(c.Value)
 	p, s := field.Get(makeInnerReference(value), suffix)
 	if len(s) != 0 {
@@ -386,72 +334,28 @@
 		if len(suffix) != 0 {
 			return nil, errNotFound
 		}
-		if name == refs.TagsDirName {
-			return sn.putTags(checker, c, v)
-		}
-		return sn.putDirEntry(checker, c, name, v)
+		return sn.putDirEntry(c, name, v)
 	case field.SetFailedWrongType:
 		return nil, errBadValue
 	case field.SetAsID:
-		nc, err := sn.add(checker, id, v)
+		nc, err := sn.add(id, v)
 		if err != nil {
 			return nil, err
 		}
 		// The sn.add may have modified the cell, so fetch it again.
-		if _, err = sn.replaceValue(checker, sn.Find(c.ID), value); err != nil {
+		if _, err = sn.replaceValue(sn.Find(c.ID), value); err != nil {
 			return nil, err
 		}
 		return nc, nil
 	case field.SetAsValue:
-		return sn.replaceValue(checker, c, value)
+		return sn.replaceValue(c, value)
 	}
 	panic("not reached")
 }
 
-// putTagsValue modifies the cell.tags value.
-func (sn *MutableSnapshot) putTagsValue(checker *acl.Checker, path, suffix storage.PathName, c *Cell, v interface{}) (*Cell, error) {
-	tags := deepcopy(c.Tags).(storage.TagList)
-	p, s := field.Get(&tags, suffix)
-	if len(s) != 0 {
-		return nil, errNotFound
-	}
-
-	// Add value to the parent.
-	name := path[len(path)-1]
-	result, id := field.Set(p, name, v)
-	switch result {
-	case field.SetFailedNotFound:
-		return nil, errNotFound
-	case field.SetFailedWrongType:
-		return nil, errBadValue
-	case field.SetAsID:
-		nc, err := sn.add(checker, id, v)
-		if err != nil {
-			return nil, err
-		}
-		// The sn.add may have modified the cell, so fetch it again.
-		if _, err = sn.replaceTags(checker, sn.Find(c.ID), tags); err != nil {
-			return nil, err
-		}
-		return nc, nil
-	case field.SetAsValue:
-		return sn.replaceTags(checker, c, tags)
-	}
-	panic("not reached")
-}
-
-// putTags updates the tags.
-func (sn *MutableSnapshot) putTags(checker *acl.Checker, c *Cell, v interface{}) (*Cell, error) {
-	tags, ok := v.(storage.TagList)
-	if !ok {
-		return nil, errNotTagList
-	}
-	return sn.replaceTags(checker, c, tags)
-}
-
 // putDirEntry replaces or adds a directory entry.
-func (sn *MutableSnapshot) putDirEntry(checker *acl.Checker, c *Cell, name string, v interface{}) (*Cell, error) {
-	r := &refs.Ref{Path: refs.NewSingletonPath(name), Label: security.ReadLabel}
+func (sn *MutableSnapshot) putDirEntry(c *Cell, name string, v interface{}) (*Cell, error) {
+	r := &refs.Ref{Path: refs.NewSingletonPath(name)}
 	if id, ok := v.(storage.ID); ok {
 		ncell := sn.Find(id)
 		if ncell == nil {
@@ -459,7 +363,7 @@
 		}
 		r.ID = id
 		dir := c.Dir.Put(r)
-		if _, err := sn.replaceDir(checker, c, dir); err != nil {
+		if _, err := sn.replaceDir(c, dir); err != nil {
 			return nil, err
 		}
 		return ncell, nil
@@ -469,7 +373,7 @@
 	if !ok {
 		// The entry does not exist yet; create it.
 		id := storage.NewID()
-		ncell, err := sn.add(checker, id, v)
+		ncell, err := sn.add(id, v)
 		if err != nil {
 			return nil, err
 		}
@@ -477,22 +381,18 @@
 		// The sn.add may have modified the cell, so fetch it again.
 		c = sn.Find(c.ID)
 		dir := c.Dir.Put(r)
-		if _, err := sn.replaceDir(checker, c, dir); err != nil {
+		if _, err := sn.replaceDir(c, dir); err != nil {
 			return nil, err
 		}
 		return ncell, nil
 	}
 
 	// Replace the existing value.
-	return sn.add(checker, x.(*refs.Ref).ID, v)
+	return sn.add(x.(*refs.Ref).ID, v)
 }
 
 // putRoot replaces the root.
-func (sn *MutableSnapshot) putRoot(checker *acl.Checker, v interface{}) (*Cell, error) {
-	if !checker.IsAllowed(security.WriteLabel) {
-		return nil, errPermissionDenied
-	}
-
+func (sn *MutableSnapshot) putRoot(v interface{}) (*Cell, error) {
 	id := sn.rootID
 	c := sn.Find(id)
 	if c == nil {
@@ -500,7 +400,7 @@
 	}
 
 	// Add the new element.
-	ncell, err := sn.add(checker, id, v)
+	ncell, err := sn.add(id, v)
 	if err != nil {
 		return nil, err
 	}
@@ -517,11 +417,7 @@
 
 // Remove removes a value.
 func (sn *MutableSnapshot) Remove(pid security.PublicID, path storage.PathName) error {
-	checker := sn.newPermChecker(pid)
 	if path.IsRoot() {
-		if !checker.IsAllowed(security.WriteLabel) {
-			return errPermissionDenied
-		}
 		sn.unref(sn.rootID)
 		sn.rootID = nullID
 		sn.mutations.RootID = nullID
@@ -530,20 +426,16 @@
 	}
 
 	// Split the names into directory and field parts.
-	cell, suffix, _ := sn.resolveCell(checker, path[:len(path)-1], sn.mutations)
+	cell, suffix, _ := sn.resolveCell(path[:len(path)-1], sn.mutations)
 	if cell == nil {
 		return errNotFound
 	}
 
 	// Remove the field.
 	name := path[len(path)-1]
-	if name == refs.TagsDirName {
-		_, err := sn.replaceTags(checker, cell, storage.TagList{})
-		return err
-	}
-	r := &refs.Ref{Path: refs.NewSingletonPath(name), Label: security.ReadLabel}
+	r := &refs.Ref{Path: refs.NewSingletonPath(name)}
 	if cell.Dir.Contains(r) {
-		_, err := sn.replaceDir(checker, cell, cell.Dir.Remove(r))
+		_, err := sn.replaceDir(cell, cell.Dir.Remove(r))
 		return err
 	}
 	value := deepcopy(cell.Value)
@@ -552,7 +444,7 @@
 		return errNotFound
 	}
 
-	_, err := sn.replaceValue(checker, cell, value)
+	_, err := sn.replaceValue(cell, value)
 	return err
 }
 
diff --git a/services/store/memstore/state/mutable_snapshot_test.go b/services/store/memstore/state/mutable_snapshot_test.go
index 34366c6..df567ba 100644
--- a/services/store/memstore/state/mutable_snapshot_test.go
+++ b/services/store/memstore/state/mutable_snapshot_test.go
@@ -75,7 +75,7 @@
 
 func expectValue(t *testing.T, sn *MutableSnapshot, path string, v interface{}) {
 	_, file, line, _ := runtime.Caller(1)
-	cell, _, _ := sn.resolveCell(sn.newPermChecker(rootPublicID), storage.ParsePath(path), nil)
+	cell, _, _ := sn.resolveCell(storage.ParsePath(path), nil)
 	if cell == nil {
 		t.Errorf("%s(%d): path does not exist: %s", file, line, path)
 	}
diff --git a/services/store/memstore/state/perm.go b/services/store/memstore/state/perm.go
index 712be36..7bf2df5 100644
--- a/services/store/memstore/state/perm.go
+++ b/services/store/memstore/state/perm.go
@@ -1,67 +1 @@
 package state
-
-import (
-	"veyron/services/store/memstore/acl"
-
-	"veyron2/security"
-	"veyron2/storage"
-)
-
-var (
-	// adminACLID is the storage.ID used for the administrator's default ACL.
-	AdminACLID = storage.ID{0}
-
-	// everyoneACLID is the storage.ID used for the default ACL for non-administrators.
-	EveryoneACLID = storage.ID{1}
-
-	// uidTagList is the storage.TagList for the /uid directory.  It ensures that
-	// /uid/* is accessible only to the administrators of the storage.
-	//
-	// TODO(jyh): Consider having an actual /uid object, so that the
-	// administrator could configure permissions on it.
-	uidTagList = storage.TagList{storage.Tag{Op: storage.RemoveACL, ACL: EveryoneACLID}}
-)
-
-// makeDefaultACLSet returns the default ACL for the store, allowing admin
-// universal access, and everyone else gets readonly access.
-func makeDefaultACLSet(admin security.PublicID) acl.Set {
-	adminContents := security.ACL{}
-	for _, name := range admin.Names() {
-		adminContents[security.PrincipalPattern(name)] = security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel)
-	}
-	adminACL := &storage.ACL{
-		Name:     "admin",
-		Contents: adminContents,
-	}
-	everyoneACL := &storage.ACL{
-		Name:     "everyone",
-		Contents: security.ACL{security.AllPrincipals: security.LabelSet(security.ReadLabel)},
-	}
-	return acl.Set{
-		AdminACLID:    acl.Entry{ACL: adminACL, Inherited: true},
-		EveryoneACLID: acl.Entry{ACL: everyoneACL, Inherited: true},
-	}
-}
-
-// newPermChecker returns a new acl.Checker in the current state.
-func (sn *snapshot) newPermChecker(pid security.PublicID) *acl.Checker {
-	return acl.NewChecker(&sn.aclCache, pid, sn.defaultACLSet)
-}
-
-// makeFindACLFunc returns a function to fetch ACL values from the storage.
-func (sn *snapshot) makeFindACLFunc() acl.FindFunc {
-	return func(id storage.ID) *storage.ACL {
-		v, ok := sn.idTable.Get(&Cell{ID: id})
-		if !ok {
-			return nil
-		}
-		x := v.(*Cell).Value
-		if acl, ok := x.(*storage.ACL); ok {
-			return acl
-		}
-		if acl, ok := x.(storage.ACL); ok {
-			return &acl
-		}
-		return nil
-	}
-}
diff --git a/services/store/memstore/state/refs.go b/services/store/memstore/state/refs.go
index a131339..77c8560 100644
--- a/services/store/memstore/state/refs.go
+++ b/services/store/memstore/state/refs.go
@@ -67,13 +67,13 @@
 
 	// Add the inverse link.
 	c := sn.Find(r.ID)
-	c.inRefs = c.inRefs.Put(&refs.Ref{ID: id, Path: r.Path, Label: r.Label})
+	c.inRefs = c.inRefs.Put(&refs.Ref{ID: id, Path: r.Path})
 }
 
 func (sn *MutableSnapshot) removeRef(id storage.ID, r *refs.Ref) {
 	// Remove the inverse link.
 	c := sn.deref(r.ID)
-	c.inRefs = c.inRefs.Remove(&refs.Ref{ID: id, Path: r.Path, Label: r.Label})
+	c.inRefs = c.inRefs.Remove(&refs.Ref{ID: id, Path: r.Path})
 
 	// Update refcount.
 	sn.unref(r.ID)
diff --git a/services/store/memstore/state/security_test.go b/services/store/memstore/state/security_test.go
deleted file mode 100644
index 1c0ddfb..0000000
--- a/services/store/memstore/state/security_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package state_test
-
-import (
-	"reflect"
-	"testing"
-
-	"veyron/services/store/memstore/state"
-
-	"veyron2/security"
-	"veyron2/storage"
-)
-
-func TestSecurity(t *testing.T) {
-	st := state.New(rootPublicID)
-	sn := st.MutableSnapshot()
-
-	// Create /Users/jane and give her RWA permissions.
-	aclID := putPath(t, sn, rootPublicID, "/Users/jane/acls/janeRWA", &storage.ACL{
-		Name: "Jane",
-		Contents: security.ACL{
-			janeUser: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
-		},
-	})
-	janeTags := storage.TagList{storage.Tag{Op: storage.AddInheritedACL, ACL: aclID}}
-	put(t, sn, rootPublicID, "/Users/jane/.tags", janeTags)
-
-	// John should not be able to do anything.
-	{
-		if _, err := maybePut(sn, johnPublicID, "/", Node{}); err == nil {
-			t.Errorf("Security violation")
-		}
-		if _, err := maybePut(sn, johnPublicID, "/Users/jane", Node{}); err == nil {
-			t.Errorf("Security violation")
-		}
-		if _, err := maybeGet(sn, johnPublicID, "/Users/jane/.tags"); err == nil {
-			t.Errorf("Security violation")
-		}
-	}
-
-	// Jane can access her own directory.
-	{
-		if _, err := maybePut(sn, janePublicID, "/", Node{}); err == nil {
-			t.Errorf("Security violation")
-		}
-		if _, err := maybeGet(sn, janePublicID, "/Users/jane"); err != nil {
-			t.Errorf("Expected /Users/jane to exist: %s", err)
-		}
-		if _, err := maybePut(sn, janePublicID, "/Users/jane", Node{}); err != nil {
-			t.Errorf("Unexpected security error: %s %s", err, janePublicID)
-		}
-		if tags, err := maybeGet(sn, janePublicID, "/Users/jane/.tags"); err == nil {
-			if !reflect.DeepEqual(janeTags, tags) {
-				t.Errorf("Expected %+v, got %+v", janeTags, tags)
-			}
-		} else {
-			t.Errorf("Unexpected security error: %s", err)
-		}
-	}
-
-	// Jane gives John read/write permission.
-	var johnTag storage.Tag
-	{
-		aclID := putPath(t, sn, janePublicID, "/Users/jane/acls/johnRW", storage.ACL{
-			Name: "John",
-			Contents: security.ACL{
-				johnUser: security.LabelSet(security.ReadLabel | security.WriteLabel),
-			},
-		})
-		johnTag = storage.Tag{Op: storage.AddInheritedACL, ACL: aclID}
-		// The @ is a pseudo-index, meaning append the tag to the list of tags.
-		put(t, sn, janePublicID, "/Users/jane/.tags/@", johnTag)
-	}
-
-	// Jane can still access.
-	janeTags = append(janeTags, johnTag)
-	{
-		if _, err := maybeGet(sn, janePublicID, "/Users/jane"); err != nil {
-			t.Errorf("Expected /Users/jane to exist: %s", err)
-		}
-		if _, err := maybePut(sn, janePublicID, "/Users/jane", Node{}); err != nil {
-			t.Errorf("Unexpected security error: %s", err)
-		}
-		if tags, err := maybeGet(sn, janePublicID, "/Users/jane/.tags"); err == nil {
-			if !reflect.DeepEqual(janeTags, tags) {
-				t.Errorf("Expected %+v, got %+v", janeTags, tags)
-			}
-		} else {
-			t.Errorf("Unexpected security error: %s", err)
-		}
-	}
-
-	// John also has access.
-	{
-		if _, err := maybePut(sn, johnPublicID, "/Users/jane", Node{}); err != nil {
-			t.Errorf("Unexpected error: %s", err)
-		}
-		mkdir(t, sn, johnPublicID, "/Users/jane/john")
-
-		// John is still not allowed to access the tags.
-		if _, err := maybeGet(sn, johnPublicID, "/Users/jane/.tags"); err == nil {
-			t.Errorf("Security violation")
-		}
-	}
-}
diff --git a/services/store/memstore/state/snapshot.go b/services/store/memstore/state/snapshot.go
index df7f209..80f6372 100644
--- a/services/store/memstore/state/snapshot.go
+++ b/services/store/memstore/state/snapshot.go
@@ -3,7 +3,6 @@
 import (
 	"reflect"
 
-	"veyron/services/store/memstore/acl"
 	"veyron/services/store/memstore/field"
 	"veyron/services/store/memstore/refs"
 
@@ -53,29 +52,16 @@
 
 	// rootID is the identifier of the root object.
 	rootID storage.ID
-
-	// aclCache caches a set of ACLs.
-	aclCache acl.Cache
-
-	// defaultACLSet is the ACLSet used to access the root directory.
-	defaultACLSet acl.Set
 }
 
 // newSnapshot returns an empty snapshot.
 func newSnapshot(admin security.PublicID) snapshot {
 	sn := snapshot{
-		idTable:       emptyIDTable,
-		defaultACLSet: makeDefaultACLSet(admin),
+		idTable: emptyIDTable,
 	}
-	sn.aclCache = acl.NewCache(sn.makeFindACLFunc())
 	return sn
 }
 
-// resetACLCache resets the aclCache.
-func (sn *snapshot) resetACLCache() {
-	sn.aclCache.UpdateFinder(sn.makeFindACLFunc())
-}
-
 // Find performs a lookup based on storage.ID, returning nil if the cell is not found.
 func (sn *snapshot) Find(id storage.ID) *Cell {
 	v, ok := sn.idTable.Get(&Cell{ID: id})
@@ -87,9 +73,8 @@
 
 // Get implements the Snapshot method.
 func (sn *snapshot) Get(pid security.PublicID, path storage.PathName) (*storage.Entry, error) {
-	checker := sn.newPermChecker(pid)
 	// Pass nil for 'mutations' since the snapshot is immutable.
-	cell, suffix, v := sn.resolveCell(checker, path, nil)
+	cell, suffix, v := sn.resolveCell(path, nil)
 	if cell == nil {
 		return nil, errNotFound
 	}
@@ -107,7 +92,7 @@
 // Returns (cell, suffix, v), where cell contains the value, suffix is the path
 // to the value, v is the value itself.  If the operation failed, the returned
 // cell is nil.
-func (sn *snapshot) resolveCell(checker *acl.Checker, path storage.PathName, mu *Mutations) (*Cell, storage.PathName, interface{}) {
+func (sn *snapshot) resolveCell(path storage.PathName, mu *Mutations) (*Cell, storage.PathName, interface{}) {
 	cell := sn.Find(sn.rootID)
 	if cell == nil {
 		return nil, nil, nil
@@ -116,23 +101,9 @@
 		if mu != nil {
 			mu.addPrecondition(cell)
 		}
-		checker.Update(cell.Tags)
 		var v reflect.Value
 		var suffix storage.PathName
-		if len(path) > 0 && path[0] == refs.TagsDirName {
-			if !checker.IsAllowed(security.AdminLabel) {
-				// Access to .tags requires admin priviledges.
-				return nil, nil, errPermissionDenied
-			}
-			v, suffix = field.Get(cell.Tags, path[1:])
-		} else {
-			if !checker.IsAllowed(security.ReadLabel) {
-				// Do not return errPermissionDenied because that would leak the
-				// existence of the inaccessible value.
-				return nil, nil, nil
-			}
-			v, suffix = field.Get(cell.Value, path)
-		}
+		v, suffix = field.Get(cell.Value, path)
 		x := v.Interface()
 		if id, ok := x.(storage.ID); ok {
 			// Always dereference IDs.
diff --git a/services/store/memstore/state/state.go b/services/store/memstore/state/state.go
index 04f914c..c398b86 100644
--- a/services/store/memstore/state/state.go
+++ b/services/store/memstore/state/state.go
@@ -75,11 +75,6 @@
 }
 
 // ApplyMutations applies a set of mutations atomically.
-//
-// We don't need to check permissions because:
-//    1. Permissions were checked as the mutations were created.
-//    2. Preconditions ensure that all paths to modified values haven't changed.
-//    3. The client cannot fabricate a mutations value.
 func (st *State) ApplyMutations(mu *Mutations) error {
 	// Assign a timestamp.
 	ts := uint64(time.Now().UnixNano())
@@ -125,14 +120,12 @@
 	for id, m := range mu.Delta {
 		d := refs.BuildDir(m.Dir)
 		cl, ok := table.Get(&Cell{ID: id})
-		sn.aclCache.Invalidate(id)
 		if !ok {
 			c := &Cell{
 				ID:      id,
 				Version: m.Postcondition,
 				Value:   m.Value,
 				Dir:     d,
-				Tags:    m.Tags,
 				refs:    m.refs,
 				inRefs:  refs.Empty,
 			}
@@ -144,7 +137,6 @@
 			cp.Version = m.Postcondition
 			cp.Value = m.Value
 			cp.Dir = d
-			cp.Tags = m.Tags
 			cp.refs = m.refs
 			table = table.Put(&cp)
 			updates = append(updates, &refUpdate{id: c.ID, before: c.refs, after: cp.refs})
diff --git a/services/store/memstore/state/state_test.go b/services/store/memstore/state/state_test.go
index 1933631..7a8045f 100644
--- a/services/store/memstore/state/state_test.go
+++ b/services/store/memstore/state/state_test.go
@@ -16,14 +16,6 @@
 
 var (
 	rootPublicID security.PublicID = security.FakePublicID("root")
-	johnPublicID security.PublicID = security.FakePublicID("john")
-	janePublicID security.PublicID = security.FakePublicID("jane")
-	joanPublicID security.PublicID = security.FakePublicID("joan")
-
-	rootUser = security.PrincipalPattern("fake/root")
-	johnUser = security.PrincipalPattern("fake/john")
-	janeUser = security.PrincipalPattern("fake/jane")
-	joanUser = security.PrincipalPattern("fake/joan")
 )
 
 // makeParentNodes creates the parent nodes if they do not already exist.
diff --git a/services/store/memstore/watch/raw_processor.go b/services/store/memstore/watch/raw_processor.go
index 68dc10a..962e0bd 100644
--- a/services/store/memstore/watch/raw_processor.go
+++ b/services/store/memstore/watch/raw_processor.go
@@ -83,7 +83,6 @@
 			Version:      cell.Version,
 			IsRoot:       isRoot,
 			Value:        cell.Value,
-			Tags:         cell.Tags,
 			Dir:          flattenDir(refs.FlattenDir(cell.Dir)),
 		}
 		change := watch.Change{
@@ -148,7 +147,6 @@
 			Version:      mu.Postcondition,
 			IsRoot:       isRoot,
 			Value:        mu.Value,
-			Tags:         mu.Tags,
 			Dir:          flattenDir(mu.Dir),
 		}
 		// TODO(tilaks): don't clone value.
diff --git a/services/store/raw/service.vdl b/services/store/raw/service.vdl
index 3384837..4ec7e2c 100644
--- a/services/store/raw/service.vdl
+++ b/services/store/raw/service.vdl
@@ -38,9 +38,6 @@
   // Value is value stored at this entry.
   Value any
 
-  // Tags specify permissions on this entry.
-  Tags storage.TagList
-
   // Dir is the implicit directory of this entry, and may contain references
   // to other entries in the store.
   Dir []storage.DEntry
diff --git a/services/store/raw/service.vdl.go b/services/store/raw/service.vdl.go
index 0cbbb42..1eb0cfd 100644
--- a/services/store/raw/service.vdl.go
+++ b/services/store/raw/service.vdl.go
@@ -35,8 +35,6 @@
 	IsRoot bool
 	// Value is value stored at this entry.
 	Value _gen_vdlutil.Any
-	// Tags specify permissions on this entry.
-	Tags storage.TagList
 	// Dir is the implicit directory of this entry, and may contain references
 	// to other entries in the store.
 	Dir []storage.DEntry
@@ -479,7 +477,7 @@
 		OutArgs: []_gen_ipc.MethodArgument{
 			{Name: "", Type: 68},
 		},
-		InStream: 80,
+		InStream: 77,
 	}
 	result.Methods["Watch"] = _gen_ipc.MethodSignature{
 		InArgs: []_gen_ipc.MethodArgument{
@@ -512,27 +510,20 @@
 				_gen_wiretype.FieldType{Type: 0x47, Name: "Changes"},
 			},
 			"veyron2/services/watch.ChangeBatch", []string(nil)},
-		_gen_wiretype.ArrayType{Elem: 0x41, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/storage.TagOp", Tags: []string(nil)}, _gen_wiretype.StructType{
-			[]_gen_wiretype.FieldType{
-				_gen_wiretype.FieldType{Type: 0x4b, Name: "Op"},
-				_gen_wiretype.FieldType{Type: 0x49, Name: "ACL"},
-			},
-			"veyron2/storage.Tag", []string(nil)},
-		_gen_wiretype.SliceType{Elem: 0x4c, Name: "veyron2/storage.TagList", Tags: []string(nil)}, _gen_wiretype.StructType{
+		_gen_wiretype.ArrayType{Elem: 0x41, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
 				_gen_wiretype.FieldType{Type: 0x3, Name: "Name"},
 				_gen_wiretype.FieldType{Type: 0x49, Name: "ID"},
 			},
 			"veyron2/storage.DEntry", []string(nil)},
-		_gen_wiretype.SliceType{Elem: 0x4e, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
+		_gen_wiretype.SliceType{Elem: 0x4b, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
 			[]_gen_wiretype.FieldType{
 				_gen_wiretype.FieldType{Type: 0x49, Name: "ID"},
 				_gen_wiretype.FieldType{Type: 0x4a, Name: "PriorVersion"},
 				_gen_wiretype.FieldType{Type: 0x4a, Name: "Version"},
 				_gen_wiretype.FieldType{Type: 0x2, Name: "IsRoot"},
 				_gen_wiretype.FieldType{Type: 0x45, Name: "Value"},
-				_gen_wiretype.FieldType{Type: 0x4d, Name: "Tags"},
-				_gen_wiretype.FieldType{Type: 0x4f, Name: "Dir"},
+				_gen_wiretype.FieldType{Type: 0x4c, Name: "Dir"},
 			},
 			"veyron/services/store/raw.Mutation", []string(nil)},
 	}