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 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:
JupyterLab — via the JupyterAI chat panel using the
jupyter-ai-hermespersona.VSCode (code-server) — via the ACP Client extension connecting to the
hermes acpsubprocess.
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:
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 skillsAsking 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 workflowRemember this procedure for next time— after solving a tricky problemCreate 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.
Managing Skills¶
You can also manage skills from the command line:
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 skillsSkills live as Markdown files in ~/.hermes/skills/ — you can browse, edit, or delete them directly.
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— The core Hermes CLI agent (v0.15.2)jupyter-ai— JupyterAI extension framework providing the chat panel, persona manager, and ACP client (v3.0.0)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:
User types a message in the JupyterAI chat panel or VSCode ACP extension.
JupyterAI (or ACP Client) passes the message to the
jupyter-ai-hermespersona.Hermes persona gathers notebook context (active notebook, current cell) via
jupyter-mcp-cli→ MCP server, and enriches the message with MCP tool documentation.Hermes agent runs as a subprocess (
hermes acp) and processes the enriched message using its tools (terminal, file, code execution, etc.).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:
Click the chat icon on the left menu bar to open the chat panel.
In the message input, type
@Hermesand select Hermes Agent from the dropdown.Hermes is now the active persona and will reply to all subsequent messages in that chat.
To switch back to another persona (e.g., Jupyternaut),
@mentionit 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 documentsjupyter-mcp-cli get_active_notebook— Get currently active notebook pathjupyter-mcp-cli get_active_cell_id --arg notebook_path=X— Get active cell IDjupyter-mcp-cli read_notebook_cells --arg notebook_path=X— Read all cellsjupyter-mcp-cli add_cell --arg file_path=X ...— Add cell above/below targetjupyter-mcp-cli edit_cell --arg file_path=X ...— Modify cell contentjupyter-mcp-cli run_cell --arg cell_id=X— Execute one celljupyter-mcp-cli set_cell_metadata ...— Set cell metadata (e.g., slideshow type)
Changes are applied collaboratively via YDoc — the UI updates instantly.
VSCode Interface (via ACP Client)¶
What is ACP?¶
ACP (Agent Client-Server Protocol) 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 extension (by formulahendry) is pre-installed in the course code-server instance. To use it:
Open VSCode from JupyterLab: File → New Launcher → VSCode, or use the
vscode()link generator in any lab notebook.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 acpsubprocess and connect to it. You can verify the connection by running:
hermes acp --checkWhich should output Hermes ACP check OK.
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 fileRefactor the selected function to use list comprehensionsFind all uses of the function \gcd` in this project`
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¶
Open Telegram and search for
@BotFather.Send
/newbotand follow the prompts to create a bot (choose a name and username).BotFather will give you an HTTP API token — save it.
Configure the token in Hermes:
hermes gateway setupSelect Telegram from the platform list and paste the bot token when prompted. This writes the token to ~/.hermes/.env.
Alternatively, set it directly:
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:
tail -f ~/.hermes/logs/gateway.logLook for a line like:
inbound message: platform=telegram user=YourName chat=123456789Your chat ID (123456789 above) is used for message routing.
Starting the Gateway¶
To start the gateway manually:
hermes gateway runThis runs in the foreground and stays active until you press Ctrl+C. To run it in the background within a tmux session:
tmux new-session -d -s hermes-gateway 'hermes gateway run'Check the tmux session:
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:
# 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:
Checks if a tmux session named
hermes-gatewayalready exists.If not, spawns the gateway in a detached tmux session via
subprocess.Popen(non-blocking).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:
/helpYou’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.
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 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:
mkdir -p ~/projects/pybmi && cd ~/projects/pybmi
git init
git config user.name "Your Name"
git config user.email "your@email.com"touch .gitkeep
git add .gitkeep
git commit -m "Initial empty repo"If Hermes introduces a bug, you can inspect and undo:
git diff # see what changed
git restore <file> # revert a single file
git revert HEAD # undo the last commitWe 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:
# 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 -yWhy 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¶
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):
conda activate pybmi-build
pip install -e ".[test]"
python -m pytest tests/ -v
# Expected: all tests FAIL with ImportErrorThis is the “Red” phase of Test-Driven Development. The agent showed you what to build.
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:
python -m pytest tests/ -v
# Expected: all tests PASSThis is the “Green” phase. Commit your working code:
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:
python -m pytest tests/ -v
# Expected: tests FAIL because the API changedNow recover with git:
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 againThis demonstrates why you commit before each agent prompt and write tests first.
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:
# 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.whlThen switch to the clean test environment and install the wheel:
# 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.
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.