diff --git a/src/BUILD.gn b/src/BUILD.gn
index 07ea36f447b4be0eac11e642d424e07a6b0a25ab..f134d7f332c318ab3049be1e60ab6843ae134379 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -16,6 +16,7 @@ group("src") {
     "modular",
     "recovery",
     "speech",
+    "sys",
     "testing",
   ]
 }
diff --git a/src/sys/BUILD.gn b/src/sys/BUILD.gn
new file mode 100644
index 0000000000000000000000000000000000000000..a20d0efddd2380c5864b5aee79f6d75d4c74d0d3
--- /dev/null
+++ b/src/sys/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+group("sys") {
+  testonly = true
+  deps = [
+    "component_test_runner",
+  ]
+}
diff --git a/src/sys/component_test_runner/BUILD.gn b/src/sys/component_test_runner/BUILD.gn
new file mode 100644
index 0000000000000000000000000000000000000000..911fde9918cd002abf54efa5ade90ab02f9cd74b
--- /dev/null
+++ b/src/sys/component_test_runner/BUILD.gn
@@ -0,0 +1,66 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("bin") {
+  name = "component_test_runner"
+
+  edition = "2018"
+
+  with_unit_tests = true
+
+  deps = [
+    "//garnet/lib/rust/fuchsia_uri",
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-component",
+    "//garnet/public/rust/fuchsia-runtime",
+    "//garnet/public/rust/fuchsia-syslog",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//sdk/fidl/fuchsia.sys:fuchsia.sys-rustc",
+    "//third_party/rust_crates:failure",
+    "//third_party/rust_crates:futures-preview",
+    "//third_party/rust_crates:serde_json",
+    "//zircon/public/fidl/fuchsia-io:fuchsia-io-rustc",
+  ]
+}
+
+package("component_test_runner") {
+  deps = [
+    ":bin",
+  ]
+
+  binaries = [
+    {
+      name = "component_test_runner"
+    },
+  ]
+
+  meta = [
+    {
+      path = "meta/component_test_runner.cmx"
+      dest = "component_test_runner.cmx"
+    },
+  ]
+}
+
+package("component_test_runner_tests") {
+  deps = [
+    ":bin",
+  ]
+
+  tests = [
+    {
+      name = "component_test_runner_bin_test"
+    },
+  ]
+  meta = [
+    {
+      path = "meta/component_test_runner_tests.cmx"
+      dest = "component_test_runner_tests.cmx"
+    },
+  ]
+}
diff --git a/src/sys/component_test_runner/meta/component_test_runner.cmx b/src/sys/component_test_runner/meta/component_test_runner.cmx
new file mode 100644
index 0000000000000000000000000000000000000000..b2f1f24a0b0ca89062d499197d8fe2fcdc265fe9
--- /dev/null
+++ b/src/sys/component_test_runner/meta/component_test_runner.cmx
@@ -0,0 +1,11 @@
+{
+    "program": {
+        "binary": "bin/component_test_runner"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.sys.Environment",
+            "fuchsia.sys.Loader"
+        ]
+    }
+}
diff --git a/src/sys/component_test_runner/meta/component_test_runner_tests.cmx b/src/sys/component_test_runner/meta/component_test_runner_tests.cmx
new file mode 100644
index 0000000000000000000000000000000000000000..42558c524581281f8a92a1911e686756bdf98ce9
--- /dev/null
+++ b/src/sys/component_test_runner/meta/component_test_runner_tests.cmx
@@ -0,0 +1,6 @@
+{
+    "program": {
+        "binary": "test/component_test_runner_bin_test"
+    },
+    "sandbox": {}
+}
diff --git a/src/sys/component_test_runner/src/main.rs b/src/sys/component_test_runner/src/main.rs
new file mode 100644
index 0000000000000000000000000000000000000000..65aadab42de5e8e3609ff11fed2647d1e69d1915
--- /dev/null
+++ b/src/sys/component_test_runner/src/main.rs
@@ -0,0 +1,270 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![feature(async_await, await_macro, futures_api)]
+
+use failure::{bail, format_err, Error, ResultExt};
+use fidl::endpoints::{create_proxy, ServerEnd};
+use fidl_fuchsia_io::{DirectoryProxy, FileMarker, NodeMarker};
+use fidl_fuchsia_sys::{FlatNamespace, RunnerRequest, RunnerRequestStream};
+use fuchsia_async as fasync;
+use fuchsia_component::server::ServiceFs;
+use fuchsia_syslog::fx_log_info;
+use fuchsia_uri::pkg_uri::PkgUri;
+use fuchsia_zircon as zx;
+use futures::prelude::*;
+
+use std::mem;
+
+fn manifest_path_from_url(url: &str) -> Result<String, Error> {
+    match PkgUri::parse(url) {
+        Ok(uri) => match uri.resource() {
+            Some(r) => Ok(r.to_string()),
+            None => bail!("no resource"),
+        },
+        Err(e) => Err(e),
+    }
+    .map_err(|e| format_err!("parse error {}", e))
+}
+
+fn extract_directory_with_name(ns: &mut FlatNamespace, name: &str) -> Result<zx::Channel, Error> {
+    let handle_ref = ns
+        .paths
+        .iter()
+        .zip(ns.directories.iter_mut())
+        .find(|(n, _)| n.as_str() == name)
+        .ok_or_else(|| format_err!("could not find entry matching {}", name))
+        .map(|x| x.1)?;
+    Ok(mem::replace(handle_ref, zx::Channel::from(zx::Handle::invalid())))
+}
+
+async fn file_contents_at_path(dir: zx::Channel, path: &str) -> Result<Vec<u8>, Error> {
+    let dir_proxy = DirectoryProxy::new(fasync::Channel::from_channel(dir)?);
+
+    let (file, server) = create_proxy::<FileMarker>()?;
+
+    dir_proxy.open(0, 0, path, ServerEnd::<NodeMarker>::new(server.into_channel()))?;
+
+    let attr = await!(file.get_attr())?.1;
+
+    let (_, vec) = await!(file.read(attr.content_size))?;
+    Ok(vec)
+}
+
+#[derive(Default)]
+#[allow(unused)]
+struct TestFacet {
+    component_under_test: String,
+    injected_services: Vec<String>,
+    system_services: Vec<String>,
+}
+
+// TODO(jamesr): Use serde to validate and deserialize the facet directly.
+// See //garnet/bin/cmc/src/validate.rs for reference.
+fn test_facet(meta: &serde_json::Value) -> Result<TestFacet, Error> {
+    let facets = meta.get("facets").ok_or_else(|| format_err!("no facets"))?;
+    if !facets.is_object() {
+        bail!("facet not an object");
+    }
+    let fuchsia_test_facet = match facets.get("fuchsia.test") {
+        Some(v) => v,
+        None => bail!("no fuchsia.test facet"),
+    };
+    if !fuchsia_test_facet.is_object() {
+        bail!("fuchsia.test facet not an object");
+    }
+    let component_under_test = match fuchsia_test_facet.get("component_under_test") {
+        Some(v) => v,
+        None => bail!("no component_under_test definition in fuchsia.test facet"),
+    };
+    let component_under_test = component_under_test
+        .as_str()
+        .ok_or_else(|| format_err!("component_under_test in fuchsia.test facet not a string"))?
+        .to_string();
+    Ok(TestFacet {
+        component_under_test: component_under_test,
+        injected_services: Vec::new(),
+        system_services: Vec::new(),
+    })
+}
+
+async fn run_runner_server(mut stream: RunnerRequestStream) -> Result<(), Error> {
+    while let Some(RunnerRequest::StartComponent {
+        package,
+        mut startup_info,
+        controller: _,
+        control_handle,
+    }) = await!(stream.try_next()).context("error running server")?
+    {
+        fx_log_info!("Received runner request for component {}", package.resolved_url);
+
+        let manifest_path = manifest_path_from_url(&package.resolved_url)?;
+
+        fx_log_info!("Component manifest path {}", manifest_path);
+
+        let pkg_directory_channel =
+            extract_directory_with_name(&mut startup_info.flat_namespace, "/pkg")?;
+
+        fx_log_info!("Found package directory handle");
+
+        let meta_contents = await!(file_contents_at_path(pkg_directory_channel, &manifest_path))?;
+
+        fx_log_info!("Meta contents: {:#?}", std::str::from_utf8(&meta_contents)?);
+
+        let meta = serde_json::from_slice::<serde_json::Value>(&meta_contents)?;
+
+        fx_log_info!("Found metadata: {:#?}", meta);
+
+        let _f = test_facet(&meta)?;
+
+        //TODO(jamesr): Configure realm based on |f| then instantiate and watch
+        //|f.component_under_test| within that realm.
+
+        control_handle.shutdown();
+    }
+    Ok(())
+}
+
+enum IncomingServices {
+    Runner(RunnerRequestStream),
+    // ... more services here
+}
+
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+    fuchsia_syslog::init_with_tags(&["component_test_runner"])?;
+
+    let mut fs = ServiceFs::new_local();
+    fs.dir("public").add_fidl_service(IncomingServices::Runner);
+
+    fs.take_and_serve_directory_handle()?;
+
+    const MAX_CONCURRENT: usize = 10_000;
+    let fut = fs.for_each_concurrent(MAX_CONCURRENT, |IncomingServices::Runner(stream)| {
+        run_runner_server(stream).unwrap_or_else(|e| println!("{:?}", e))
+    });
+
+    await!(fut);
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use fuchsia_zircon::HandleBased;
+    use serde_json::json;
+
+    /// Makes a manifest_path_from_url test
+    /// Arguments:
+    ///     name: name of the test case
+    ///     url: url to parse
+    ///     path: expected path component
+    ///     err: true if an error is expected
+    macro_rules! manifest_path_from_url_test {
+        ( $name:ident, $url:literal, $path:literal, $err:literal ) => {
+            #[test]
+            fn $name() {
+                match manifest_path_from_url($url) {
+                    Ok(path) => {
+                        assert!(!$err);
+                        assert_eq!(path, $path)
+                    }
+                    Err(_) => assert!($err),
+                }
+            }
+        };
+    }
+
+    manifest_path_from_url_test!(empty_string, "", "", true);
+    manifest_path_from_url_test!(no_hash, "fuchsia-pkg://foo/abcdef", "", true);
+    manifest_path_from_url_test!(one_hash, "fuchsia-pkg://foo/abc#def", "def", false);
+    manifest_path_from_url_test!(multiple_hash, "fuchsia-pkg://foo/abc#def#ghi", "def#ghi", false);
+    manifest_path_from_url_test!(last_position_hash, "fuchsia-pkg://foo/abc#", "", true);
+
+    #[test]
+    fn directory_with_name_tests() -> Result<(), Error> {
+        let (a_ch_0, _) = zx::Channel::create()?;
+        let (b_ch_0, _) = zx::Channel::create()?;
+        let mut ns = FlatNamespace {
+            paths: vec![String::from("/a"), String::from("/b")],
+            directories: vec![a_ch_0, b_ch_0],
+        };
+
+        assert!(extract_directory_with_name(&mut ns, "/c/").is_err());
+
+        assert!(extract_directory_with_name(&mut ns, "/b").is_ok());
+        assert!(ns.directories[1].is_invalid_handle());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_facet_missing_facet() -> Result<(), Error> {
+        let meta = json!({});
+        let f = test_facet(&meta);
+        assert!(f.is_err());
+        Ok(())
+    }
+
+    #[test]
+    fn test_facet_missing_fuchsia_test_facet() -> Result<(), Error> {
+        let meta = json!({
+          "facets": []
+        });
+        let f = test_facet(&meta);
+        assert!(f.is_err());
+        Ok(())
+    }
+
+    #[test]
+    fn test_facet_facets_wrong_type() -> Result<(), Error> {
+        let meta = json!({
+          "facets": []
+        });
+        let f = test_facet(&meta);
+        assert!(f.is_err());
+        Ok(())
+    }
+
+    #[test]
+    fn test_facet_fuchsia_test_facet_wrong_type() -> Result<(), Error> {
+        let meta = json!({
+          "facets": {
+            "fuchsia.test": []
+          }
+        });
+        let f = test_facet(&meta);
+        assert!(f.is_err());
+        Ok(())
+    }
+
+    #[test]
+    fn test_facet_component_under_test() -> Result<(), Error> {
+        let meta = json!({
+          "facets": {
+            "fuchsia.test": {
+              "component_under_test": "fuchsia-pkg://fuchsia.com/test#meta/test.cmx"
+            }
+          }
+        });
+        let f = test_facet(&meta);
+        assert!(!f.is_err());
+        assert_eq!(f?.component_under_test, "fuchsia-pkg://fuchsia.com/test#meta/test.cmx");
+        Ok(())
+    }
+
+    #[test]
+    fn test_facet_component_under_test_not_string() -> Result<(), Error> {
+        let meta = json!({
+          "facets": {
+            "fuchsia.test": {
+              "component_under_test": 42
+            }
+          }
+        });
+        let f = test_facet(&meta);
+        assert!(f.is_err());
+        Ok(())
+    }
+}