diff --git a/guix-jupyter-container.scm b/guix-jupyter-container.scm
index 29ad0578c0908efdd554240846b907615c3270fc..92def48bcee08701d7d5cc640282827eb2877b90 100644
--- a/guix-jupyter-container.scm
+++ b/guix-jupyter-container.scm
@@ -83,13 +83,14 @@
 ;; Execution.
 ;;
 
-(define (socket->notebook socket key)
-  "Create a \"fake\" notebook that's reachable over SOCKET and uses the given
+(define (socket->kernel socket key)
+  "Create a \"fake\" kernel that's reachable over SOCKET and uses the given
 KEY for signing."
   ;; XXX: This is really a hack.
-  (notebook key
-            #:shell socket
-            #:iopub socket))
+  (kernel "fake-kernel" (getpid)
+          #:key key
+          #:shell socket
+          #:iosub socket))
 
 (define (local-eval socket message count)
   (let* ((scontent (json-string->scm (message-content message)))
@@ -171,10 +172,10 @@ KEY for signing."
         counter))))
 
 (define (reply-html-to-kernel socket message html count)
-  ;; XXX: Here we have to create a fake notebook.  bah.
-  (let* ((notebook (socket->notebook socket notebook-key))
-         (count    (reply-html notebook message html count)))
-    (send-message notebook
+  ;; XXX: Here we have to create a fake kernel.  bah.
+  (let* ((kernel (socket->kernel socket notebook-key))
+         (count  (reply-html kernel message html count)))
+    (send-message kernel
                   (reply message "guix-end-of-eval"
                          (scm->json-string `(("status" . "ok")))))
     count))
@@ -235,15 +236,15 @@ KEY for signing."
         (if kernel
             (begin
               (relay-message (kernel-shell kernel)
-                             (socket->notebook kernel notebook-key)
+                             (socket->kernel kernel notebook-key)
                              (string->utf8 name) message)
               (values kernels (+ count 1)))
             (let* ((new-kernel  (run-kernel context name
                                             (string->utf8 name)
                                             notebook-key))
-                   (notebook    (socket->notebook socket notebook-key))
+                   (kernel      (socket->kernel socket notebook-key))
                    (new-kernels (register-kernel kernels new-kernel)))
-              (relay-message (kernel-shell new-kernel) notebook
+              (relay-message (kernel-shell new-kernel) kernel
                              (string->utf8 name) message)
               (values new-kernels (+ count 1))))))
      ((magic-html? code)
diff --git a/guix-jupyter-kernel.scm b/guix-jupyter-kernel.scm
index 149d83cd526e6aae0348b05eb559a7c633e6f538..fa5476ed36247c9e142842b4d528c657bfad616c 100644
--- a/guix-jupyter-kernel.scm
+++ b/guix-jupyter-kernel.scm
@@ -36,6 +36,7 @@
              (guix-kernel hmac)
              (guix-kernel magic)
              (guix-kernel environ)
+             (guix-kernel jupyter-server)
              (guix-kernel jupyter-client))
 
 (define session-id (random (* 255 255)
@@ -91,33 +92,33 @@
 ;; Handlers.
 ;;
 
-(define (heartbeat-handler notebook)
-  (let ((message (zmq-message-receive-bytevector (notebook-heartbeat notebook)
+(define (heartbeat-handler kernel)
+  (let ((message (zmq-message-receive-bytevector (kernel-heartbeat kernel)
                                                  (zmq-msg-init))))
-    (zmq-message-send-bytevector (notebook-heartbeat notebook) message))
-  (heartbeat-handler notebook))
+    (zmq-message-send-bytevector (kernel-heartbeat kernel) message))
+  (heartbeat-handler kernel))
 
 
-(define (general-handler notebook containers count)
-  (let* ((message (read-message notebook))
+(define (general-handler kernel containers count)
+  (let* ((message (read-message kernel))
          (handler (dispatch (message-type message))))
-    (pub-busy notebook message)
+    (pub-busy kernel message)
     (let-values (((containers count)
-                  (handler notebook message containers count)))
-      (pub-idle notebook message)
-      (general-handler notebook containers count))))
+                  (handler kernel message containers count)))
+      (pub-idle kernel message)
+      (general-handler kernel containers count))))
 
 ;; Unknown request type, ignore it.
-(define (ignore-request notebook message containers count)
+(define (ignore-request kernel message containers count)
   (values containers count))
 
 ;; Send kernel-info.
-(define (reply-kernel-info-request notebook message containers count)
-  (send-message notebook (reply message "kernel_info_reply"
+(define (reply-kernel-info-request kernel message containers count)
+  (send-message kernel (reply message "kernel_info_reply"
                                 (scm->json-string KERNEL-INFO)))
   (values containers count))
 
-(define (reply-execute-request notebook message containers count)
+(define (reply-execute-request kernel message containers count)
   (let* ((content (message-content message))
          (code    (hash-ref (json-string->scm content) "code"))
          (magic   (get-magic-line code)))
@@ -130,16 +131,16 @@
                  (env      (list-cdr-ref list 4)))
             (match (proxy-by-name containers env-name)
               (#f
-               (let* ((id        (start-container notebook env-name env))
+               (let* ((id        (start-container kernel env-name env))
                       (container (new-container-connect env-name id))
                       (new       (register-proxy containers env-name
                                                  container)))
-                 (relay-message (proxy-socket container) notebook
+                 (relay-message (proxy-socket container) kernel
                                 (string->utf8 env-name)
                                 message)
                  (values new (+ count 1))))
               ((? proxy? proxy)
-               (relay-message (proxy-socket proxy) notebook
+               (relay-message (proxy-socket proxy) kernel
                               (string->utf8 env-name)
                               message)
                (values containers (+ count 1))))))
@@ -148,28 +149,28 @@
           (let* ((list     (string-split magic #\ ))
                  (env-name (list-ref list 2))
                  (proxy    (proxy-by-name containers env-name)))
-            (relay-message (proxy-socket proxy) notebook
+            (relay-message (proxy-socket proxy) kernel
                            (string->utf8 env-name)
                            message)
             (values containers (+ count 1))))
          ((magic-html? code)
           (values containers
-                  (reply-html notebook message
+                  (reply-html kernel message
                               (delete-magic code)
                               count)))
          (else
           (relay-message (proxy-socket
                           (proxy-by-name containers "default"))
-                         notebook (string->utf8 "default")
+                         kernel (string->utf8 "default")
                          message)
           (values containers (+ count 1)))))
       (λ error
         (values containers
-                (reply-html notebook message
+                (reply-html kernel message
                             (error->html error) count))))))
 
-(define (shutdown notebook message containers count)
-  (kill-containers containers notebook message)
+(define (shutdown kernel message containers count)
+  (kill-containers containers kernel message)
   (exit #t))
 
 ;;
@@ -194,7 +195,7 @@
 ;; Process exit and signals handler.
 ;;
 
-(define (kill-containers containers notebook message)
+(define (kill-containers containers kernel message)
   "Terminate all the proxies listed in the CONTAINERS vhash, sending them
 MESSAGE."
   (vlist-for-each (match-lambda
@@ -202,7 +203,7 @@ MESSAGE."
                      (format (current-error-port)
                              "terminating proxy ~s (PID ~s)...~%"
                              name (proxy-pid proxy))
-                     (relay-message (proxy-socket proxy) notebook
+                     (relay-message (proxy-socket proxy) kernel
                                     (string->utf8 name) message)
                      (zmq-close-socket (proxy-socket proxy))
                      (false-if-exception (kill (proxy-pid proxy) SIGTERM))))
@@ -238,7 +239,7 @@ MESSAGE."
                        fs)))
               %network-file-mappings))
 
-(define (start-container notebook name env)
+(define (start-container kernel name env)
   (let* ((guile-version (if (null? env)
                             (guile-current-version->path)
                             (if (not (package-in-list->path env "guile"))
@@ -259,7 +260,7 @@ MESSAGE."
                                  (list "--no-auto-compile" "-s"
                                        container-path
                                        name (number->string session-id)
-                                       (notebook-key notebook))))))
+                                       (utf8->string (kernel-key kernel)))))))
          (root (string-append "/tmp/guix-kernel/container/"
                               name "-" (number->string session-id)))
          (fs   (cons* %immutable-store
@@ -276,22 +277,22 @@ MESSAGE."
                    %namespaces 1
                    exec)))
 
-(define (start-kernel pid notebook)
+(define (start-kernel pid kernel)
   (let ((default-container (alist->vhash
                             `(("default" .
                                ,(new-container-connect "default" pid))))))
-    (general-handler notebook default-container 0)))
+    (general-handler kernel default-container 0)))
 
 
-(define (exit-handler notebook)
+(define (exit-handler kernel)
   (lambda _
-    (close-notebook notebook)
+    (close-kernel kernel)
     (exit 1)))
 
 ;; Start!
-(let ((notebook (call-with-input-file (car (last-pair (command-line)))
-                  json->notebook)))
-  (sigaction SIGTERM (exit-handler notebook))
-  (sigaction SIGINT  (exit-handler notebook))
-  (start-kernel (start-container notebook "default" '())
-                notebook))
+(let ((kernel (call-with-input-file (car (last-pair (command-line)))
+                json->kernel)))
+  (sigaction SIGTERM (exit-handler kernel))
+  (sigaction SIGINT  (exit-handler kernel))
+  (start-kernel (start-container kernel "default" '())
+                kernel))
diff --git a/guix-kernel/jupyter-client.scm b/guix-kernel/jupyter-client.scm
index bdce0e8f877da4616cd942cffe9cc6215822a7f8..f543d1da497b84cf68cf8ba46aa07c57d0b54ba0 100644
--- a/guix-kernel/jupyter-client.scm
+++ b/guix-kernel/jupyter-client.scm
@@ -18,6 +18,7 @@
 (define-module (guix-kernel jupyter-client)
   #:use-module (guix-kernel hmac)
   #:use-module (guix-kernel tools)
+  #:use-module (guix-kernel jupyter-server)
   #:use-module (simple-zmq)
   #:use-module (json)
   #:use-module (srfi srfi-9)
@@ -27,17 +28,7 @@
   #:export (guile-version
             KERNEL-INFO
 
-            json->notebook
-            notebook?
-            notebook
-            notebook-key
-            notebook-control
-            notebook-shell
-            notebook-standard-input
-            notebook-heartbeat
-            notebook-iopub
-
-            close-notebook
+            json->kernel
 
             message make-message
             reply
@@ -73,24 +64,10 @@
 ;; Kernel information.
 ;;
 
-;; Notebook connections.
-(define-record-type <notebook>
-  (%notebook key control shell stdin heartbeat iopub)
-  notebook?
-  (key         notebook-key)                    ;bytevector
-  (control     notebook-control)                ;zmq socket
-  (shell       notebook-shell)                  ;zmq socket
-  (stdin       notebook-standard-input)         ;zmq socket
-  (heartbeat   notebook-heartbeat)              ;zmq socket
-  (iopub       notebook-iopub))                 ;zmq socket
-
-(define* (notebook key #:key control shell standard-input heartbeat iopub)
-  "Return a new notebook with the given key and the given sockets."
-  (%notebook key control shell standard-input heartbeat iopub))
-
-(define (json->notebook port)
-  "Read metadata from PORT, typically in the format passed to kernels on the
-command line, and return a <notebook> record."
+(define* (json->kernel port #:key (name "guix"))
+  "Read the contents of a Jupyter \"connection file\" from PORT and return a
+<kernel> record."
+  ;; See <https://jupyter-client.readthedocs.io/en/stable/kernels.html#connection-files>.
   (let* ((table   (json->scm port))
          (ip      (hash-ref table "ip"))
          (scheme  (hash-ref table "transport"))
@@ -106,26 +83,18 @@ command line, and return a <notebook> record."
         (zmq-bind-socket socket uri)
         socket))
 
-    (notebook key
-              #:control
-              (make-socket ZMQ_ROUTER (hash-ref table "control_port"))
-              #:shell
-              (make-socket ZMQ_ROUTER (hash-ref table "shell_port"))
-              #:standard-input
-              (make-socket ZMQ_ROUTER (hash-ref table "stdin_port"))
-              #:heartbeat
-              (make-socket ZMQ_REP (hash-ref table "hb_port"))
-              #:iopub
-              (make-socket ZMQ_PUB (hash-ref table "iopub_port")))))
-
-(define (close-notebook notebook)
-  "Close all the open connections of NOTEBOOK."
-  (for-each zmq-close-socket
-            (map (lambda (proc)
-                   (proc notebook))
-                 (list notebook-control notebook-shell
-                       notebook-standard-input
-                       notebook-heartbeat notebook-iopub))))
+    (kernel name (getpid)
+            #:key (string->utf8 key)
+            #:control
+            (make-socket ZMQ_ROUTER (hash-ref table "control_port"))
+            #:shell
+            (make-socket ZMQ_ROUTER (hash-ref table "shell_port"))
+            #:standard-input
+            (make-socket ZMQ_ROUTER (hash-ref table "stdin_port"))
+            #:heartbeat
+            (make-socket ZMQ_REP (hash-ref table "hb_port"))
+            #:iosub
+            (make-socket ZMQ_PUB (hash-ref table "iopub_port")))))
 
 ;; Jupyter message header as defined at
 ;; <https://jupyter-client.readthedocs.io/en/stable/messaging.html#general-message-format>.
@@ -257,8 +226,8 @@ This is a low-level procedure for internal use."
               #:parent-header (string->header parent-header)
               #:metadata metadata))))
 
-(define (read-message notebook)
-  "Read a message for NOTEBOOK--i.e., a message sent by Jupyter--and return
+(define (read-message kernel)
+  "Read a message for KERNEL--i.e., a message sent by Jupyter--and return
 it or #f if nothing is available."
   ;; FIXME Use 'zmq_poll'.
   (define (waiting-data socket next)
@@ -271,8 +240,8 @@ it or #f if nothing is available."
           (and next (waiting-data next socket)))
          (else #f)))))
 
-  (and=> (waiting-data (notebook-shell notebook)
-                       (notebook-control notebook))
+  (and=> (waiting-data (kernel-shell kernel)
+                       (kernel-control kernel))
          parts->message))
 
 (define guile-version (effective-version))
@@ -299,23 +268,23 @@ it or #f if nothing is available."
 ;; Send procedures.
 ;;
 
-(define* (send-message notebook message
+(define* (send-message kernel message
                        #:key
-                       (notebook-socket notebook-shell)
+                       (kernel-socket kernel-shell)
                        (recipient (and=> (message-parent-header message)
                                          header-sender)))
-  "Send message over the shell socket of NOTEBOOK to RECIPIENT, a ZeroMQ
+  "Send message over the shell socket of KERNEL to RECIPIENT, a ZeroMQ
 identity (bytevector)."
-  (zmq-send-msg-parts-bytevector (notebook-socket notebook)
+  (zmq-send-msg-parts-bytevector (kernel-socket kernel)
                                  (message-parts message
-                                                (notebook-key notebook)
+                                                (kernel-key kernel)
                                                 #:recipient recipient)))
 
-(define* (relay-message socket notebook name request)
+(define* (relay-message socket kernel name request)
   "Relay REQUEST, a message, over SOCKET, with recipient NAME (a bytevector
 representing a ZeroMQ identity), and return its reply.  Send replies to
-NOTEBOOK."
-  (let ((parts (message-parts request (notebook-key notebook)
+KERNEL."
+  (let ((parts (message-parts request (kernel-key kernel)
                               #:recipient name)))
     (zmq-send-msg-parts-bytevector socket parts))
 
@@ -325,19 +294,19 @@ NOTEBOOK."
            (sender (message-sender request)))
       (match (message-type reply)
         ("execute_reply"
-         (send-message notebook reply
+         (send-message kernel reply
                        #:recipient sender)
          (loop))
         ((or "execute_result" "error")
-         (send-message notebook reply #:notebook-socket notebook-iopub
+         (send-message kernel reply #:kernel-socket kernel-iopub
                        #:recipient sender)
          (loop))
         ("execute_input"
-         (send-message notebook reply #:notebook-socket notebook-iopub
+         (send-message kernel reply #:kernel-socket kernel-iopub
                        #:recipient sender)
          (loop))
         ("stream"
-         (send-message notebook reply #:notebook-socket notebook-iopub
+         (send-message kernel reply #:kernel-socket kernel-iopub
                        #:recipient sender)
          (loop))
         ("status"
@@ -348,23 +317,23 @@ NOTEBOOK."
         (_
          reply)))))
 
-(define (pub notebook message status)
+(define (pub kernel message status)
   (let ((content `(("execution_state" . ,status))))
-    (send-message notebook (reply message "status"
+    (send-message kernel (reply message "status"
                                   (scm->json-string content))
-                  #:notebook-socket notebook-iopub)))
+                  #:kernel-socket kernel-iopub)))
 
-(define (pub-busy notebook message)
-  (pub notebook message "busy"))
+(define (pub-busy kernel message)
+  (pub kernel message "busy"))
 
-(define (pub-idle notebook message)
-  (pub notebook message "idle"))
+(define (pub-idle kernel message)
+  (pub kernel message "idle"))
 
 ;;
 ;; Reply.
 ;;
 
-(define (reply-html notebook message html count)
+(define (reply-html kernel message html count)
   "Reply to MESSAGE with HTML."
   (let ((code (hash-ref (json-string->scm (message-content message))
                         "code"))
@@ -372,19 +341,19 @@ NOTEBOOK."
 	(counter      (+ count 1))                ;execution counter
 
         (send (lambda (socket type content)
-                (send-message notebook
+                (send-message kernel
                               (reply message type
                                      (scm->json-string content))
-                              #:notebook-socket socket))))
+                              #:kernel-socket socket))))
 
-    (send notebook-iopub
+    (send kernel-iopub
           "execute_input" `(("code"            . ,code)
                             ("execution_count" . ,counter)))
-    (send notebook-iopub
+    (send kernel-iopub
           "execute_result" `(("data"     . (("text/html" . ,html)))
                              ("metadata" . ,empty-object)
                              ("execution_count" . ,counter)))
-    (send notebook-shell
+    (send kernel-shell
           "execute_reply" `(("status"           . "ok")
                             ("execution_count"  . ,counter)
                             ("payload"          . [])
diff --git a/guix-kernel/jupyter-server.scm b/guix-kernel/jupyter-server.scm
index 423a09c6e40423cd7cfa86ee3fc0a0c5eece099a..a9bb4e9a540474045cc2df6ac43cf30d83fda405 100644
--- a/guix-kernel/jupyter-server.scm
+++ b/guix-kernel/jupyter-server.scm
@@ -35,31 +35,48 @@
             kernel?
             kernel-name
             kernel-pid
+            kernel-key
             kernel-control
             kernel-shell
             kernel-standard-input
             kernel-heartbeat
-            kernel-iosub))
+            kernel-iosub
+            kernel-iopub                          ;alias
+
+            close-kernel))
 
 ;;
 ;; Kernel execution.
 ;;
 
-;; Type of a proxied Jupyter kernel.
+;; Type of a Jupyter kernel.
 (define-record-type <kernel>
-  (%kernel name pid control shell stdin heartbeat iosub)
+  (%kernel name pid key control shell stdin heartbeat iosub)
   kernel?
   (name        kernel-name)                       ;string
   (pid         kernel-pid)                        ;integer
+  (key         kernel-key)                        ;bytevector
   (control     kernel-control)                    ;zmq socket
   (shell       kernel-shell)                      ;zmq socket
   (stdin       kernel-standard-input)             ;zmq socket
   (heartbeat   kernel-heartbeat)                  ;zmq socket
   (iosub       kernel-iosub))                     ;zmq socket
 
-(define* (kernel name pid #:key control shell
+(define-syntax kernel-iopub (identifier-syntax kernel-iosub))
+
+(define* (kernel name pid #:key key control shell
                  standard-input heartbeat iosub)
-  (%kernel name pid control shell standard-input heartbeat iosub))
+  (%kernel name pid key
+           control shell standard-input heartbeat iosub))
+
+(define (close-kernel kernel)
+  "Close all the open connections of KERNEL."
+  (for-each zmq-close-socket
+            (map (lambda (proc)
+                   (proc kernel))
+                 (list kernel-control kernel-shell
+                       kernel-standard-input
+                       kernel-heartbeat kernel-iopub))))
 
 (define (jupyter-kernel-path)
   "Return the default search path for Jupyter kernels."