---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.18.1
kernelspec:
  language: python
  display_name: Python 3 (ipykernel)
  name: python3
---

---
title: Hermes Agent
abstract: |
    This guide explains how to set up and use Hermes Agent — an open-source AI coding agent — in two interfaces: JupyterLab (via JupyterAI) and VSCode/code-server (via the ACP Client extension). It includes a hands-on tutorial that uses Hermes to build a Python package, incorporating git for safety, conda environments for isolation, and pytest for validation.
---

+++

[Hermes Agent](https://github.com/NousResearch/hermes-agent) is an open-source AI agent framework by Nous Research that runs in your terminal, IDEs, and messaging platforms. It belongs to the same category as Claude Code (Anthropic), Codex (OpenAI), and OpenCode — autonomous coding and task-execution agents that use tool calling to interact with your system. Hermes works with any LLM provider (OpenRouter, Anthropic, OpenAI, DeepSeek, local models, and 15+ others).

What makes Hermes different from a chatbot:

- **Self-improving through skills** — learns from experience by saving reusable procedures as skills that accumulate over time.
- **Persistent memory across sessions** — remembers who you are, your preferences, and environment details.
- **Full system access** — terminal, file system, web browsing, code execution, and more.
- **Provider-agnostic** — swap models and providers without changing anything else.

This guide covers two ways to access Hermes Agent in the course environment:

1. **JupyterLab** — via the JupyterAI chat panel using the `jupyter-ai-hermes` persona.
2. **VSCode (code-server)** — via the ACP Client extension connecting to the `hermes acp` subprocess.

+++

## Skills and Memory

+++

Two of Hermes's most distinctive capabilities are **skills** and **persistent memory**. Together, they let the agent improve over time and remember context across sessions.

+++

### Skills: Procedural Memory on Disk

+++

Skills are reusable procedure documents stored as Markdown files in `~/.hermes/skills/`. They capture _how_ to do complex tasks — workflows, commands, pitfalls, and verification steps. Unlike memory (see below), skills are **unlimited in size** and **never forgotten** — they live as files on disk.

How skills work:

- **Trigger conditions** — each skill declares when it should be loaded (e.g., "debugging Python", "managing GitHub PRs", "deploying to Docker").
- **On-demand loading** — at the start of each conversation, Hermes scans its available skills and loads only the ones relevant to your current task via `skill_view()`.
- **Accumulate over time** — after solving a complex problem or receiving a correction, Hermes can save the approach as a new skill for future reuse.
- **Persistent** — skills survive session resets, container restarts, and profile switches. They are versioned files you can edit, review, or version-control.

You can manage skills directly:

```sh
hermes skills list          # see all installed skills
hermes skills search QUERY  # search the skills hub
hermes skills install ID    # install from the hub
hermes skills browse        # browse all available skills
```

+++

::::{note} Skills vs Memory

- **Storage** — Memory: ~2,200 chars injected into every prompt. Skills: Unlimited files on disk.
- **Content** — Memory: User preferences, environment facts, corrections. Skills: Reusable procedures, workflows, scripts.
- **Example** — Memory: "Headers must be in separate cells". Skills: "How to set up a Telegram gateway with tmux autostart".
- **Can Hermes forget it?** — Memory: Yes, if full older entries are pruned. Skills: No, files persist forever.

::::

:::{caution} Skills are living documents

Skills can become outdated — if Hermes uses a skill and finds an error, tell it to update the skill immediately. Over time, review your skills folder (`~/.hermes/skills/`) and delete or merge skills that are no longer useful. A stale skill is worse than no skill.

:::

+++

### Asking Hermes to Create New Skills

+++

Hermes will suggest saving a workflow as a skill after completing complex tasks. You can also **ask directly**:

- *`Save this as a skill`* — after Hermes completes a multi-step workflow
- *`Remember this procedure for next time`* — after solving a tricky problem
- *`Create a skill for setting up [X]`* — to proactively capture a reusable workflow

What gets saved: trigger conditions, numbered steps, exact commands, pitfalls, and verification steps. What does **not** get saved: temporary task progress or one-off results.

:::{tip} Good vs. bad skill candidates

- **Good:** "How to debug failing pytest tests with a conda env", "Setting up Telegram gateway with auto-restart" — reusable across sessions
- **Bad:** "Install pandas for Lab 3" — one-time, context-specific task

:::

+++

### Managing Skills

+++

You can also manage skills from the command line:

```sh
hermes skills list          # see all installed skills
hermes skills search QUERY  # search the skills hub
hermes skills install ID    # install from the hub
hermes skills browse        # browse all available skills
```

Skills live as Markdown files in `~/.hermes/skills/` — you can browse, edit, or delete them directly.

:::{caution} Deleting skills

Before deleting a skill, ask yourself: is this truly obsolete, or have you merged its content into another skill? Hermes can consolidate skills for you — just say *`Merge the X skill into Y`*.

:::

+++

### Persistent Memory: Cross-Session Context

+++

Memory is a compact note-taking system that survives across sessions. It stores facts about **who you are** and **what Hermes should know** — your name, role, preferences, coding style, environment details, and lessons learned from corrections.

Memory is intentionally small (~2,200 characters) so it can be injected into every conversation without bloating context. Think of it as **sticky notes**, not a database. It captures what reduces the need for you to repeat yourself.

What is **not** saved to memory: task progress, completed work logs, or temporary session state. Those live in conversation history and can be searched with `session_search`.

+++

## Architecture Overview

+++

The integration consists of three packages installed in the course environment:

- [`hermes-agent`](https://github.com/NousResearch/hermes-agent) — The core Hermes CLI agent (v0.15.2)
- [`jupyter-ai`](https://github.com/jupyterlab/jupyter-ai) — JupyterAI extension framework providing the chat panel, persona manager, and ACP client (v3.0.0)
- [`jupyter-ai-hermes`](https://github.com/dive4dec/jupyter-ai-hermes) — The custom Hermes persona that injects Jupyter notebook context and MCP tool documentation before forwarding messages to Hermes (v0.1.2)

The data flow:

1. **User** types a message in the JupyterAI chat panel or VSCode ACP extension.
2. **JupyterAI** (or ACP Client) passes the message to the `jupyter-ai-hermes` persona.
3. **Hermes persona** gathers notebook context (active notebook, current cell) via `jupyter-mcp-cli` → MCP server, and enriches the message with MCP tool documentation.
4. **Hermes agent** runs as a subprocess (`hermes acp`) and processes the enriched message using its tools (terminal, file, code execution, etc.).
5. **Response** flows back through the same channel to the chat panel.

+++

## JupyterLab Interface

+++

### Using the Chat Panel

+++

Hermes Agent is available as a persona in the JupyterAI chat panel. Personas are routed via `@mention` rather than a settings dropdown:

1. Click the chat icon on the left menu bar to open the chat panel.
2. In the message input, type `@Hermes` and select **Hermes Agent** from the dropdown.
3. Hermes is now the **active persona** and will reply to all subsequent messages in that chat.
4. To switch back to another persona (e.g., Jupyternaut), `@mention` it instead.

+++

### How Notebook Context Injection Works

+++

When you select Hermes Agent as your persona, the `jupyter-ai-hermes` package automatically enriches every message with Jupyter notebook context before forwarding it to the Hermes agent. This means Hermes can see:

- **Open documents** — list of all currently open notebooks/files in JupyterLab.
- **Active notebook** — the path of the notebook you're currently viewing.
- **Active cell** — the ID and content of the cell where your cursor is.

This context is gathered via the `jupyter-mcp-cli` CLI bridge, which communicates with Jupyter's built-in MCP server (`jupyter_server_mcp`) at `http://localhost:3001/mcp`. The MCP server provides real-time access to the in-memory YDoc representation of open notebooks, ensuring collaborative editing safety.

+++

### Available MCP Tools

+++

The Hermes persona injects documentation for `jupyter-mcp-cli` tools into every prompt, so Hermes knows it should prefer these over raw `nbformat` scripts. These tools handle collaborative editing, preserve cell metadata/tags, and update the JupyterLab UI instantly:

- `jupyter-mcp-cli get_open_documents` — List all open documents
- `jupyter-mcp-cli get_active_notebook` — Get currently active notebook path
- `jupyter-mcp-cli get_active_cell_id --arg notebook_path=X` — Get active cell ID
- `jupyter-mcp-cli read_notebook_cells --arg notebook_path=X` — Read all cells
- `jupyter-mcp-cli add_cell --arg file_path=X ...` — Add cell above/below target
- `jupyter-mcp-cli edit_cell --arg file_path=X ...` — Modify cell content
- `jupyter-mcp-cli run_cell --arg cell_id=X` — Execute one cell
- `jupyter-mcp-cli set_cell_metadata ...` — Set cell metadata (e.g., slideshow type)

Changes are applied collaboratively via YDoc — the UI updates instantly.

+++

### In-Notebook: the `%%hermes` Magic (`jupyter-ai-hermes-magics`)

+++

Beyond the chat panel, the **`jupyter-ai-hermes-magics`** package lets you call Hermes **directly from a notebook cell** — no chat panel, no separate tab. It runs a persistent `hermes acp` subprocess *inside your kernel* and exposes it as an IPython magic:

```{code-cell} ipython3
:editable: true

# Load the magic once per kernel
%load_ext jupyter_ai_hermes_magics
```

Then use the **`%%hermes`** cell magic (option before the prompt), or the **`%hermes`** line magic for subcommands:

```{code-cell} ipython3
:editable: true

%%hermes
Briefly explain what the cell above this one does.
```

```{code-cell} ipython3
:editable: true

# Line-magic subcommands
%hermes list       # show the session tree
%hermes version    # show the magics package version
%hermes reset      # clear all sessions (and the ACP connection)
```

The magic injects the live notebook context (the cells at/above it) into each prompt, the same way the chat persona does, so Hermes can reason about the current notebook.

**Do you need to restart the kernel when you change the provider/model?** **Yes — for `%%hermes`.** The `hermes acp` subprocess is spawned **once per kernel** and kept alive; it loads `~/.hermes/config.yaml` at spawn time. After you change the provider (via `hermes model` or by editing the config), the running subprocess is still on the old model, so you must **restart the kernel** (or run **`%hermes reset`** to drop the ACP connection so the next `%%hermes` re-spawns with the new config). This is different from the JupyterAI chat panel, where a *new chat* picks up the current config without a kernel restart.

+++

## VSCode Interface (via ACP Client)

+++

### What is ACP?

+++

[ACP (Agent Client-Server Protocol)](https://agentclientprotocol.com/) is an open protocol that allows AI agents to integrate with editors. In the course environment, Hermes Agent exposes an ACP server via the `hermes acp` subcommand, which the VSCode ACP Client extension connects to — giving you full agent capabilities directly in your editor.

+++

### Using the ACP Client Extension in code-server

+++

The [ACP Client](https://marketplace.visualstudio.com/items?itemName=formulahendry.acp-client) extension (by `formulahendry`) is pre-installed in the course code-server instance. To use it:

1. Open VSCode from JupyterLab: **File → New Launcher → VSCode**, or use the `vscode()` link generator in any lab notebook.
2. In the **ACP Client** view (activity bar icon), click the **Connect** button next to **Hermes Agent** in the **Agents** panel. The extension will start a `hermes acp` subprocess and connect to it. You can verify the connection by running:

```sh
hermes acp --check
```

Which should output `Hermes ACP check OK`.

3. Once connected, open the **Chat** panel within the ACP Client view (usually on the left side) and start chatting with Hermes.

+++

### VSCode-Specific Workflow

+++

In VSCode, Hermes has native awareness of your editor context:

- **Active file** — Hermes can see the file you have open and the cursor position.
- **Selected text** — Highlight code and ask Hermes to explain, refactor, or debug it.
- **Terminal access** — Hermes can run shell commands, install packages, and execute scripts.
- **File editing** — Hermes can read, create, and edit files across your workspace.

Example prompts to try:

- `Explain the current file`
- `Refactor the selected function to use list comprehensions`
- `Find all uses of the function \`gcd\` in this project`

+++

::::{note} ACP vs JupyterAI

Both interfaces run the same Hermes Agent subprocess, but they differ in what context they provide:

- **JupyterAI (JupyterLab):** Notebook cells, YDoc collaboration
- **ACP Client (VSCode):** Editor files, cursor position, selections
- **JupyterLab best for:** Working inside notebooks
- **VSCode best for:** Working with source code, scripts, config files

::::

+++

## Messaging Platforms (Telegram)

+++

Beyond JupyterLab and VSCode, Hermes Agent also runs on messaging platforms like **Telegram**, letting you interact with the agent from your phone or desktop Telegram app. The gateway uses **outbound long polling** to connect to Telegram — no inbound ports need to be open.

+++

### Setting Up a Telegram Bot

+++

1. Open Telegram and search for **`@BotFather`**.
2. Send `/newbot` and follow the prompts to create a bot (choose a name and username).
3. BotFather will give you an **HTTP API token** — save it.
4. Configure the token in Hermes:

```sh
hermes gateway setup
```

Select **Telegram** from the platform list and paste the bot token when prompted. This writes the token to `~/.hermes/.env`.

Alternatively, set it directly:

```sh
hermes config set gateway.telegram.token "YOUR_BOT_TOKEN_HERE"
```

+++

### Connecting Your Telegram Account

+++

After setting the token, Hermes needs to know your Telegram user ID to route messages. Start a chat with your bot and send any message — the gateway logs will show your user ID:

```sh
tail -f ~/.hermes/logs/gateway.log
```

Look for a line like:

```
 inbound message: platform=telegram user=YourName chat=123456789
```

Your chat ID (`123456789` above) is used for message routing.

+++

### Starting the Gateway

+++

To start the gateway manually:

```sh
hermes gateway run
```

This runs in the foreground and stays active until you press `Ctrl+C`. To run it in the background within a tmux session:

```sh
tmux new-session -d -s hermes-gateway 'hermes gateway run'
```

Check the tmux session:

```sh
tmux has-session -t hermes-gateway && echo "Gateway running" || echo "Gateway stopped"
```

+++

### Auto-Starting the Gateway on Container Restart

+++

In the JupyterHub course environment, the container may restart (e.g., after idle timeout or server maintenance). To ensure the gateway comes back automatically, add a startup hook to your Jupyter server config:

Create `~/.jupyter/jupyter_server_config.py` with:

```python
# Auto-start Hermes gateway on Jupyter server startup
import subprocess

def _ensure_hermes_gateway():
    """Start Hermes gateway in a detached tmux session if not already running."""
    result = subprocess.run(
        ["tmux", "has-session", "-t", "hermes-gateway"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    if result.returncode != 0:
        subprocess.Popen(
            [
                "tmux", "new-session", "-d",
                "-s", "hermes-gateway",
                "-x", "120", "-y", "40",
                "hermes gateway run",
            ],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            start_new_session=True,
        )

_ensure_hermes_gateway()
```

This config is imported when the Jupyter server starts (before you even open a browser tab). It:

1. Checks if a tmux session named `hermes-gateway` already exists.
2. If not, spawns the gateway in a detached tmux session via `subprocess.Popen` (non-blocking).
3. If the session exists, skips silently — no duplicate gateways.

+++

### Using Hermes via Telegram

+++

Once the gateway is running, you can chat with Hermes directly from your Telegram app. Hermes supports **slash commands** — special commands that start with `/` — for controlling the agent and inspecting its state.

**Essential commands:**

- `/help` — List all available slash commands. Send this anytime you're not sure what Hermes can do.
- `/commands` — Browse every command with descriptions (paginated).
- `/status` — Show the current session info, model, and platform connection status.
- `/restart` — Restart the Hermes gateway. Use this if Hermes seems stuck, unresponsive, or is giving repeated errors.
- `/new` — Start a fresh conversation (clears the current session context).

Try `/help` now and see the full list:

```
/help
```

You'll see responses like:

```
Here are the available slash commands:
  /help     Show this help message
  /commands Browse all commands
  /restart  Restart gateway
  ...
```

+++

### Try These Example Prompts

Here are some prompts you can copy-paste to experiment with Hermes via Telegram:

**Code generation:**

```
Write a Python function that converts a temperature from Celsius to Fahrenheit and Kelvin.
```

**Explain code:**

```
Explain what this Python code does:

for i in range(len(arr)-1, 0, -1):
    if arr[i] < arr[i-1]:
        arr[i], arr[i-1] = arr[i-1], arr[i]
```

**Math and data:**

```
What is the derivative of x^3 * sin(x)? Show the steps.
```

**File and terminal access:**

```
List the files in my home directory.
```

**Creative:**

```
Write a haiku about Jupyter notebooks.
```

Hermes has full access to its tools (terminal, file system, code execution, web search) even from Telegram — so it can run commands, read files, and execute Python code just as it does in JupyterLab or VSCode.

::::{tip} Tips for Telegram use

- Use `` `code` `` backticks for inline code snippets in your prompts.
- For longer code, wrap it in triple backticks: `` ```python ... ``` ``.
- Hermes remembers context within a session. Use `/new` to start fresh if the conversation gets off track.
- Hermes responds asynchronously — for code execution or web searches, it may take 10–30 seconds.

::::

+++

::::{seealso} Troubleshooting Telegram

- **Hermes not responding:** Send `/restart` to the bot in Telegram. This restarts the gateway without touching your container.
- **Still not responding?** In JupyterLab, open a terminal and check the gateway:

  ```sh
  tmux has-session -t hermes-gateway && echo "Gateway running" || echo "Gateway stopped"
  tail -f ~/.hermes/logs/gateway.log
  ```

- **Gateway stopped or stuck:** Kill and restart it:

  ```sh
  tmux kill-session -t hermes-gateway
  tmux new-session -d -s hermes-gateway 'hermes gateway run'
  ```

- **Nothing works?** Restart your Jupyter server entirely. In JupyterLab go to **File → Hub Control Panel → Restart Server**. This recreates the container and re-runs `jupyter_server_config.py`, which brings the gateway back automatically.
- **Multiple gateways running:** Run `ps aux | grep 'hermes gateway' | grep -v grep` and kill duplicates with `kill <PID>`.
- **Telegram bot unreachable from container:** The gateway uses fallback IPs automatically — look for `Telegram fallback IPs active` in the gateway logs.

::::

+++

## Hands-On Tutorial: Building a Python Package with Hermes

+++

In this tutorial you will use Hermes Agent to build a small Python package `pybmi` that computes the [Body Mass Index](https://en.wikipedia.org/wiki/Body_mass_index) and classifies it. You will learn not only how to ask an agent to write code, but also how to use professional software engineering practices alongside it:

- **Git version control** — commit before each agent change so you can always revert mistakes.
- **Conda environments** — isolate build and test environments so experiments never break your kernel.
- **Pytest** — write tests before the agent generates code, validating its output automatically.
- **`pyproject.toml`** — use the modern Python packaging standard.

This exercise demonstrates how Hermes remembers context, improves through iteration, and how good practices (git, environments, tests) tame the unpredictability of AI-generated code.

+++

### Prerequisites: Git Safety Net

+++

Before letting an agent write code, set up a git repository so every change is tracked and reversible. Open a terminal in VSCode and run:

```sh
mkdir -p ~/projects/pybmi && cd ~/projects/pybmi
git init
git config user.name "Your Name"
git config user.email "your@email.com"
```

:::{important} Golden rule: commit before every agent prompt

This gives you a checkpoint to revert to if Hermes makes a mistake. Combined with tests written first, you can always catch and undo unwanted changes.
:::

```sh
touch .gitkeep
git add .gitkeep
git commit -m "Initial empty repo"
```

If Hermes introduces a bug, you can inspect and undo:

```sh
git diff                  # see what changed
git restore <file>       # revert a single file
git revert HEAD          # undo the last commit
```

We will practice this throughout the tutorial.

+++

### Prerequisites: Conda Environments

+++

Agents have full system access — they can install packages, modify configs, and run experiments in your default environment. To prevent this, create **isolated conda environments**:

```sh
# Build environment: for building and installing your package
conda create -n pybmi-build python=3.12 pip build setuptools -y

# Test environment: for running tests against the installed package
conda create -n pybmi-test python=3.12 pip pytest -y
```

Why two environments?

- **Build env** has the tools to compile and package your code (`build`, `setuptools`).
- **Test env** simulates a clean install — you install your package here and run tests, catching missing dependencies the build env might have masked.

Activate an environment with `conda activate pybmi-build` and return to your default kernel with `conda deactivate`.

Hermes will use these environments when we ask it to build and test, ensuring experiments stay contained.

+++

### Part 1: Build the Package in VSCode (with Hermes + Git + Tests)

+++

#### Step 1: Scaffold the Project

Open VSCode from JupyterLab (**File → New Launcher → VSCode**), navigate to `~/projects/pybmi`, and connect Hermes via the ACP Client. In the Chat panel, send:

```
Create a minimal Python package structure in the current directory:

1. pyproject.toml with [build-system] (setuptools), project metadata
   (name="pybmi", version="0.1.0", description="BMI calculator"),
   and optional dependencies [project.optional-dependencies] with
   a "test" extra that requires pytest.

2. pybmi/__init__.py — empty for now with a docstring.

3. tests/__init__.py — empty.

4. tests/test_bmi.py — with a placeholder test that always passes:
   import pytest
   def test_placeholder(): assert True

5. README.md with a brief description and usage placeholder.

Use pyproject.toml (not setup.py or setup.cfg).
```

#### Step 2: Commit Before Continuing

```sh
git add -A
git commit -m "Scaffold pybmi package with pyproject.toml"
```

+++

#### Step 3: Write Tests First (Agent-Assisted TDD)

Ask Hermes to write tests *before* implementing the code. This teaches you to think about expected behavior and gives the agent a target to hit:

```
In tests/test_bmi.py, write pytest tests for a BMI package:

1. test_bmi_basic — bmi(70, 1.75) should return 22.857... (70 / 1.75^2)
2. test_bmi_rounding — bmi(80, 1.80) should be approximately 24.7
3. test_classify_underweight — classify(18.5) returns "underweight"
4. test_classify_normal — classify(22.0) returns "normal"
5. test_classify_overweight — classify(27.0) returns "overweight"
6. test_classify_obese — classify(32.0) returns "obese"
7. test_profile — profile(70, 1.75) returns {"bmi": 22.9, "category": "normal"}

Use pytest.approx() for float comparisons. Import from pybmi.
```

Then run the tests to confirm they **fail** (since the code doesn't exist yet):

```sh
conda activate pybmi-build
pip install -e ".[test]"
python -m pytest tests/ -v
# Expected: all tests FAIL with ImportError
```

This is the "Red" phase of Test-Driven Development. The agent showed you what to build.

```sh
git add -A
git commit -m "Add tests for bmi, classify, and profile functions"
```

+++

#### Step 4: Implement the Code

Now ask Hermes to make the tests pass:

```
Implement the pybmi package in pybmi/__init__.py:

1. def bmi(weight_kg, height_m) — returns weight / height^2 as float
2. def classify(bmi_value) — returns WHO category string:
   "underweight" (< 18.5), "normal" (18.5–24.9),
   "overweight" (25–29.9), "obese" (>= 30)
3. def profile(weight_kg, height_m) — returns dict with
   "bmi" (rounded to 1 decimal) and "category"

Keep it simple and well-documented.
```

Run the tests again:

```sh
python -m pytest tests/ -v
# Expected: all tests PASS
```

This is the "Green" phase. Commit your working code:

```sh
git add -A
git commit -m "Implement bmi, classify, and profile functions"
```

+++

#### Step 5: Practice Reverting (Deliberate Mistake)

Now let's practice the safety net. Ask Hermes to add a feature — but intentionally give a bad prompt:

```
Update classify() to also return "severe thinness" for BMI below 16
and "obesity class III" for BMI above 40.
```

Run the tests — they should still pass for existing cases, but if Hermes changed the thresholds for existing categories, tests would catch it. Now simulate a bigger mistake:

```
Rewrite __init__.py to use OOP instead of functions — make a BMI class
with methods calculate(), get_category(), and get_profile().
```

This changes the public API, breaking your tests. Run them:

```sh
python -m pytest tests/ -v
# Expected: tests FAIL because the API changed
```

Now recover with git:

```sh
git diff pybmi/__init__.py    # see what broke
git restore pybmi/__init__.py # revert to last commit
python -m pytest tests/ -v    # confirm all PASS again
```

This demonstrates why you commit before each agent prompt and write tests first.

```sh
git add -A
git commit -m "Demonstrate git revert after API-breaking change"
```

+++

#### Step 6: Build and Test in Isolated Environments

Now demonstrate why separate conda environments matter. Build the package in the build env and test it in the test env:

```sh
# Build in the build environment
conda activate pybmi-build
python -m build
# Produces dist/pybmi-0.1.0.tar.gz and dist/pybmi-0.1.0-py3-none-any.whl
```

Then switch to the clean test environment and install the wheel:

```sh
# Test in the clean test environment
conda activate pybmi-test
pip install dist/pybmi-0.1.0-py3-none-any.whl
pip install pytest

# Copy tests and run them in the clean environment
cp tests/test_bmi.py /tmp/
python -m pytest /tmp/test_bmi.py -v
# Expected: all tests PASS in a clean environment!
```

This proves your package works outside the development environment — a critical step before sharing or submitting code.

::::{exercise} Reflection: Why Environments Matter

Without isolated environments:

- Hermes might install a dependency globally that conflicts with your Jupyter kernel.
- You might accidentally test against a library version not listed in your dependencies.
- A student grading script might fail because your code relies on packages not declared in `pyproject.toml`.

Conda environments make all of these problems visible and contained.

::::

```sh
git add -A
git commit -m "Add build output and verify isolated test env"
```

+++

### Part 2: Demonstrate the Package in JupyterLab

+++

#### Step 1: Create a Demo Notebook with Hermes

Return to JupyterLab. Open the chat panel and `@mention` **Hermes Agent** so it becomes the active persona.

```
Create a new notebook called pybmi_demo.ipynb that demonstrates the
pybmi package. Include:

1. A markdown cell with a title and brief introduction to BMI.
2. A code cell that imports the package and shows basic usage.
3. A code cell that plots a BMI classification chart using matplotlib
   — show the four zones (underweight, normal, overweight, obese) as
   horizontal color bands for BMI values from 15 to 40.
4. A code cell that computes and displays the BMI for sample heights
   and weights in a nicely formatted output.

Save the notebook in ~/projects/pybmi/.
```

#### Step 2: Run and Verify

Open `pybmi_demo.ipynb` and run each cell to verify the output.

#### Step 3: Use Notebook Context Injection

Place your cursor inside any cell and send:

```
Explain the current cell and suggest one improvement.
```

Because Hermes receives the active notebook context automatically, it knows exactly which cell you're pointing at.

+++

::::{note} Why Hermes is powerful in notebooks

The `jupyter-ai-hermes` persona sends the **active cell content** and **notebook path** to Hermes with every message. You can:

- Ask Hermes to explain, debug, or extend the current cell
- Highlight text in a cell, then use **Send message with selection** to focus the agent
- Ask Hermes to add new cells, run cells, or set cell metadata (e.g., slideshow tags)

::::

+++

::::{exercise} Extend on your own

Try these prompts in the Hermes chat to see how the agent iterates:

- `Add a code cell that lets me input my own weight and height using ipywidgets sliders and displays my BMI and category in real time.`
- `Add a cell that compares my BMI against the average BMI for different countries using a bar chart.`

Each prompt builds on the previous context. Watch how Hermes preserves earlier cells while adding new ones.

::::
