

nteract builds upon the very successful foundations of Jupyter. I think of Jupyter as a brilliantly rich REPL toolkit. A typical REPL (Read-Eval-Print-Loop) is an interpreter that takes input from the user and prints results (on stdout and stderr).
Here’s the standard Python interpreter; a REPL many of us know and love.


The standard terminal’s spartan user interface, while useful, leaves something to be desired. IPython was created in 2001 to refine the interpreter, primarily by extending display hooks in Python. Iterative improvement on the interpreter was a big boon for interactive computing experiences, especially in the sciences.


As the team behind IPython evolved, so did their ambitions to create richer consoles and notebooks. Core to this was crafting the building blocks of the protocol that were established on top of ZeroMQ, leading to the creation of the IPython notebook. It decoupled the REPL from a closed loop in one system to multiple components communicating together.


As IPython came to embrace more than just Python (R, Julia, Node.js, Scala, …), the IPython leads created a home for the language agnostic parts: Jupyter.


Jupyter isn’t just a notebook or a console.


It’s an establishment of well-defined protocols and formats. It’s a community of people who come together to build interactive computing experiences. We share our knowledge across the sciences, academia, and industry — there’s a lot of overlap in vision, goals, and priorities.
That being said, one project alone may not meet with everyone’s specific needs and workflows. Luckily, with strong support by Jupyter’s solid foundation of protocols to communicate with the interpreters (Jupyter kernels) and document formats (e.g. .ipynb), you too can build your ideal interactive computing environment.
In pursuit of this, members of the Jupyter community created nteract, a Jupyter notebook desktop application as well as an ecosystem of JavaScript packages to support it and more.


What is the platform that Jupyter provides to build rich interactive experiences?
To explore this, I will describe the Jupyter protocol with a lightweight (non-compliant) version of the protocol that hopefully helps explain how this works under the hood.


When a user runs this code, a message is formed:
{
"msg_type": "execute_request",
"content": {
"code": "print('hey')"
}
}We send that message and receive replies as JSON:
{
"msg_type": "status",
"content": {
"execution_state": "busy"
}
}{
"msg_type": "stream",
"content": {
"text":"hey\n",
"name":"stdout"
}
}{
"msg_type": "status",
"content": {
"execution_state": "idle"
}
}We’ve received two types of messages so far:
- execution status for the interpreter — busy or idle
- a “stream” of stdout
The status tells us the interpreter is ready for more and the stream data is shown below the editor in the output area of a notebook.
What happens when a longer computation runs?


{
"msg_type": "status",
"content": {
"execution_state": "busy"
}
}{
"msg_type": "stream",
"content": {
"text":"hey\n",
"name":"stdout"
}
}{
"msg_type": "stream",
"content": {
"text":"sup\n",
"name":"stdout"
}
}{
"msg_type": "status",
"content": {
"execution_state": "idle"
}
}As multiple outputs come in, they get appended to the display area below the code editor.
How are tables, plots, and other rich media shown?


Let’s send that code over to see
{
"msg_type": "execute_request",
"content": {
"code": "pd.read_csv('sf-temps.csv').sample(n=5)"
}
}The power and simplicity of the protocol emerges when using the execute_result and display_data message types. They both have a data field with multiple media types for the frontend to choose how to represent. Pandas provides text/plain and text/html for tabular data
{
"msg_type": "execute_result",
"content": {
"data": {
// Full payloads below
"text/plain": ...,
"text/html": ...,
}
}
}text/plain
temp date
4851 56.4 2010/07/22 04:00:00
6232 65.9 2010/09/17 17:00:00
8497 47.0 2010/12/21 02:00:00
5875 60.9 2010/09/02 20:00:00
5625 65.7 2010/08/23 10:00:00
text/html
<div>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>temp</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr>
<th>4851</th>
<td>56.4</td>
<td>2010/07/22 04:00:00</td>
</tr>
<tr>
<th>6232</th>
<td>65.9</td>
<td>2010/09/17 17:00:00</td>
</tr>
<tr>
<th>8497</th>
<td>47.0</td>
<td>2010/12/21 02:00:00</td>
</tr>
<tr>
<th>5875</th>
<td>60.9</td>
<td>2010/09/02 20:00:00</td>
</tr>
<tr>
<th>5625</th>
<td>65.7</td>
<td>2010/08/23 10:00:00</td>
</tr>
</tbody>
</table>
</div>
When the front-end receives the HTML payload, it embeds it directly in the outputs so you get a nice table:


This isn’t limited to HTML and text — we can handle images and any other known transform. The primary currency for display are these bundles of media types to data. In nteract we have a few custom mime types, which are also coming soon to a Jupyter notebook near you!




How do you build a notebook document?
We’ve witnessed how our code gets sent across to the runtime and what we receive on the notebook side. How do we form a notebook? How do we associate messages to the cells they originated from?
We need an ID to identify where an execute_request comes from. Let’s bring in the concept of a message ID and form the cell state over time
We send the execute_request as message 0001
{
"msg_type": "execute_request",
"msg_id": "0001",
"content": {
"code": "print('hey')"
}
}and initialize our state
code: "print('hey')"Each message afterward lists the originating msg_id as parent_id 0001. Responses start flowing in, starting with message 0002
{
"msg_type": "status",
"msg_id": "0002",
"parent_id": "0001",
"content": {
"execution_state": "busy"
}
}Which we can store as part of the state of our cell
code: "print('hey')"
status: "busy"Here comes the plain text output in message 0003
{
"msg_type": "stream",
"msg_id": "0003",
"parent_id": "0001",
"content": {
"text":"hey\n",
"name":"stdout"
}
}Which we fold into an outputs structure of our cell
code: "print('hey')"
status: "busy"
outputs:
- type: "stream"
text: "hey\n"
name: "stdout"Finally, we receive a status to inform us the kernel is no longer busy
{
"msg_type": "status",
"msg_id": "0004",
"parent_id": "0001",
"content": {
"execution_state": "idle"
}
}Resulting in the final state of the cell
code: "print('hey')"
status: "idle"
outputs:
- type: "stream"
text: "hey\n"
name: "stdout"That’s just one cell though —what would an entire notebook structure look like? One way of thinking about a notebook is that it’s a rolling work log of computations. A linear list of cells. Using the same format we’ve constructed above, here’s a lightweight notebook:
cells:
- text: "# Now with markdown!"
- code: "from IPython.display import HTML"
- code: "print('hey')"
outputs:
- type: "stream"
text: "hey\n"
name: "stdout" - code: "HTML('<b>WHOA</b>')"
outputs:
- type: "execute_result"
data:
- "text/html": "<b>WHOA</b>"As well as the rendered version:


As Jupyter messages are sent back and forth, a notebook is formed. We use message IDs to route outputs to cells. Users run code, get results, and view representations of their data:
send code 📨 →
… run 🐍, run …
→ 📨 get result(s)
→ 🎉 📊 🎉
This very synchronous imperative description doesn’t give the Jupyter protocol (and ZeroMQ for that matter) enough credence. In reality, it’s a hyper reactor of asynchronous interactive feedback, enabling you to iterate quickly and explore the space of computation and visualization. These messages come in asynchronously, and there are a lot more messages available within the core protocol.


I encourage you to get involved in both the nteract and jupyter projects. We have plenty to explore and build together, whether you are interested in:
Feel free to reach out on issues or the nteract slack.
Thank you to Safia Abdalla, Lukas Geiger, Paul Ivanov, Peggy Rayzis, and Carol Willing for reviewing, providing feedback, and editing this post.