Skip to content

Add in-notebook Jupyter chat agent with .claude skills support (#52)#53

Open
IzBrain67 wants to merge 6 commits into
mainfrom
feature/52-notebook-chat
Open

Add in-notebook Jupyter chat agent with .claude skills support (#52)#53
IzBrain67 wants to merge 6 commits into
mainfrom
feature/52-notebook-chat

Conversation

@IzBrain67
Copy link
Copy Markdown
Collaborator

Summary

Implements a Jupyter notebook chat agent for the July hackathon (closes #52). The agent runs inside the kernel, so it can run code and edit files in the live workspace, while the OpenAI key and MCP workflow tools stay on the backend and are reached over HTTP. It loads the repo's git-tracked .claude/skills/*.md and applies them as guidance (currently create-node.md).

Backend (reuses existing OpenAI/MCP chat services)

  • ServiceTokenAuthentication: the kernel authenticates to stateless endpoints with the shared JupyterHub token.
  • POST /api/chat/llm/ streams OpenAI completions; GET /api/chat/mcp-tools/ and POST /api/chat/mcp-call/ proxy MCP tools, forwarding the user's Keycloak JWT for per-user workflow access.

Kernel package neuroworkflow.agent

  • %chat / %%chat magics and a ChatPanel ipywidget.
  • Notebook-native tools (run_code, read_file, write_file) merged with MCP workflow tools into one tool loop.
  • Skill loader injects .claude/skills/*.md into the system prompt, plus an "Environment paths" correction so skill paths map to the container layout (/home/jovyan/codes/...).

Wiring

  • Spawner injects PYTHONPATH / backend URL / service token and mounts the repo .claude read-only. Note: does not set JUPYTERHUB_API_TOKEN (reserved by the hub) — uses NEUROWORKFLOW_SERVICE_TOKEN instead.
  • nest image gains httpx and ipywidgets.

Docs & tests

  • docs/NOTEBOOK_CHAT_AGENT.md usage guide and a sample notebook.
  • Backend tests for the new auth + proxy endpoints.

Notes for reviewers

  • The agent package is duplicated in src/neuroworkflow/agent/ and the mounted copy gui/workflow_backend/django-project/codes/neuroworkflow/agent/ (same core//utils/ sync convention) — both must stay in sync.
  • Auth scope: the LLM proxy uses a shared service token (acceptable for the trusted lab/hackathon); per-user identity applies only to the MCP workflow tools via the Keycloak token. A move to per-user JWT can follow once JupyterLab project files are not isolated per user or named per project (incl. output folder) #28 SSO lands.

Test plan

  • docker-compose build && up, open Jupyter, %load_ext neuroworkflow.agent
  • %chat generates and runs code; ChatPanel() shows a single panel
  • Skill check: "Skill: create-node.md" in get_agent().messages[0]["content"] is True; node-creation requests target /home/jovyan/codes/nodes/sandbox/
  • With a Keycloak user_token, MCP tools (e.g. add_node) reflect in the web GUI flow
  • Backend: poetry run pytest tests/test_notebook_agent.py

🤖 Generated with Claude Code

IzBrain67 and others added 3 commits May 30, 2026 00:38
Implements a Jupyter notebook chat agent for the July hackathon. The agent
loop runs in the kernel (so it can run code and edit files in the live
workspace) while the OpenAI key and MCP workflow tools stay on the backend,
reached over HTTP.

Backend (reuses existing OpenAI/MCP chat services):
- ServiceTokenAuthentication: kernel authenticates to stateless endpoints
  with the shared JupyterHub token.
- /api/chat/llm/ streams OpenAI completions; /api/chat/mcp-tools/ and
  /api/chat/mcp-call/ proxy MCP tools, forwarding the user's Keycloak JWT for
  per-user workflow access.

Kernel package neuroworkflow.agent (mounted via codes/ copy):
- %chat / %%chat magics and a ChatPanel ipywidget.
- Notebook-native tools (run_code, read_file, write_file) plus MCP workflow
  tools merged into one tool loop.
- Loads git-tracked .claude/skills/*.md into the system prompt.

Wiring: spawner injects PYTHONPATH/backend URL/service token and mounts the
repo .claude read-only; nest image gains httpx and ipywidgets. Includes
backend tests and a sample notebook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document how to use the in-notebook chat agent: setup/wiring, the magics and
ChatPanel widget, enabling workflow tools with a Keycloak token, skills, the
Python API, troubleshooting, and the src/codes sync note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skills are authored against the repository layout (src/neuroworkflow/nodes/,
NODE_CREATION_GUIDE.md), but the kernel only mounts the codes/ tree. Append an
"Environment paths" section to the system prompt that maps repo paths to their
container equivalents (writable nodes at /home/jovyan/codes/nodes/, sandbox
under it) and tells the agent not to chase repo-root docs that aren't mounted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 29, 2026 16:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an in-notebook Jupyter chat agent (neuroworkflow.agent) that runs inside the kernel, can execute notebook-local tools (run code / read+write files), and can optionally access workflow MCP tools via backend proxy endpoints. It also loads git-tracked .claude/skills/*.md and injects them into the system prompt, with container-path corrections for the Jupyter environment.

Changes:

  • Introduces the in-kernel agent loop, notebook magics (%chat/%%chat), and an ipywidgets chat panel UI.
  • Adds backend proxy endpoints for LLM streaming (/api/chat/llm/) and MCP tool listing/calls (/api/chat/mcp-tools/, /api/chat/mcp-call/) plus service-token authentication.
  • Updates JupyterHub spawner + nest image wiring (mount .claude read-only; inject agent env vars; install httpx/ipywidgets) and adds docs + tests.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/neuroworkflow/agent/__init__.py Public notebook agent entrypoints (singleton agent, %load_ext, ChatPanel).
src/neuroworkflow/agent/client.py Kernel → backend HTTP client for SSE LLM streaming + MCP calls.
src/neuroworkflow/agent/config.py Kernel-side config via spawner-injected environment variables.
src/neuroworkflow/agent/loop.py Synchronous in-kernel tool loop driving streamed LLM + tool calls.
src/neuroworkflow/agent/magic.py %chat / %%chat magics wiring.
src/neuroworkflow/agent/skills.py Loads .claude/skills/*.md and builds the system prompt (+ path mapping).
src/neuroworkflow/agent/tools.py Notebook-native tool implementations (run_code, read_file, write_file).
src/neuroworkflow/agent/widget.py ipywidgets-based persistent chat panel UI.
gui/workflow_backend/django-project/codes/neuroworkflow/agent/* Synced copy of the kernel agent package for the mounted codes/ tree.
gui/workflow_backend/django-project/app/auth/authentication.py Adds ServiceTokenAuthentication for kernel → backend stateless auth.
gui/workflow_backend/django-project/app/chat/views.py Adds notebook LLM/MCP proxy endpoints and SSE streaming.
gui/workflow_backend/django-project/app/chat/urls.py Routes for llm/, mcp-tools/, and mcp-call/.
gui/workflow_backend/django-project/tests/test_notebook_agent.py Backend tests for service-token auth and MCP proxy behavior.
gui/workflow_backend/django-project/neuroworkflow/jupyterhub_config.py Spawner mounts .claude and injects agent env vars (PYTHONPATH, backend URL, service token).
gui/workflow_backend/django-project/neuroworkflow/Dockerfile.nest Installs httpx + ipywidgets into the Jupyter image.
docs/NOTEBOOK_CHAT_AGENT.md Usage + troubleshooting documentation for the notebook agent.
notebooks/notebook_chat_agent.ipynb Example notebook showing %chat and ChatPanel() usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +191 to +192
authentication_classes = [ServiceTokenAuthentication, KeycloakAuthentication]
permission_classes = [IsAuthenticated]
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea: NotebookLLMView.authentication_classes is now [ServiceTokenAuthentication] only, so a Keycloak-authenticated user can no longer reach the LLM proxy. The browser chat uses /api/chat/stream/.

Comment on lines +233 to +239
"""Authenticate trusted internal callers via the shared JupyterHub token.

The notebook chat agent runs inside a Jupyter kernel that has the
``JUPYTERHUB_API_TOKEN`` in its environment but no end-user JWT. It uses
that token to reach stateless backend endpoints (the LLM proxy). A constant
name avoids leaking the secret into the user table. This authenticates the
*call*; per-user MCP actions still forward the user's own Keycloak JWT.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea: docstring now states the kernel sends the token as NEUROWORKFLOW_SERVICE_TOKEN (the per-server JUPYTERHUB_API_TOKEN is a different value) and the backend validates it against its own JUPYTERHUB_API_TOKEN.

Comment thread src/neuroworkflow/agent/widget.py Outdated
Comment on lines +41 to +43
def on_tool(name, args):
with log:
print(f"\n ⚙ {name}({', '.join(args)})")
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea: the widget on_tool callback now prints key=value pairs (matching the magics) instead of just the dict keys.

Comment on lines +41 to +43
def on_tool(name, args):
with log:
print(f"\n ⚙ {name}({', '.join(args)})")
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea (synced copy): same key=value fix as the src widget.

Comment thread src/neuroworkflow/agent/tools.py Outdated
Comment on lines +91 to +93
def _read_file(path: str) -> str:
with open(path, encoding="utf-8") as f:
return _truncate(f.read())
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea: read_file now resolves via _resolve_in_workspace and rejects paths outside NEUROWORKFLOW_WORKSPACE_ROOT (default /home/jovyan/codes). (Note: run_code can still reach anything the kernel can; this guard is defense-in-depth for the file tools.)

Comment thread src/neuroworkflow/agent/tools.py Outdated
Comment on lines +96 to +102
def _write_file(path: str, content: str) -> str:
directory = os.path.dirname(path)
if directory:
os.makedirs(directory, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"Wrote {len(content)} chars to {path}"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea: write_file is confined to NEUROWORKFLOW_WORKSPACE_ROOT via the same _resolve_in_workspace check before creating directories/writing.

Comment on lines +91 to +93
def _read_file(path: str) -> str:
with open(path, encoding="utf-8") as f:
return _truncate(f.read())
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea (synced copy): same workspace-boundary guard on read_file.

Comment on lines +96 to +102
def _write_file(path: str, content: str) -> str:
directory = os.path.dirname(path)
if directory:
os.makedirs(directory, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"Wrote {len(content)} chars to {path}"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9900fea (synced copy): same workspace-boundary guard on write_file.

- LLM proxy now accepts only the service token (drop KeycloakAuthentication)
  so a logged-in user cannot spend the backend OpenAI key.
- Confine read_file/write_file to NEUROWORKFLOW_WORKSPACE_ROOT
  (default /home/jovyan/codes); reject paths outside it.
- Widget tool trace now prints arg values, not just keys.
- Fix ServiceTokenAuthentication docstring to reflect NEUROWORKFLOW_SERVICE_TOKEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@IzBrain67
Copy link
Copy Markdown
Collaborator Author

Hi @carlosengutierrez 👋

Could you help me verify that the new in-notebook chat agent correctly uses the skills we track in the repo? There's a short usage guide to follow: docs/NOTEBOOK_CHAT_AGENT.md (see the Quick start and Skills sections).

1. Get onto this branch

git fetch origin
git switch feature/52-notebook-chat     # or: git checkout feature/52-notebook-chat
git branch --show-current               # should print feature/52-notebook-chat

If git says local changes are blocking the switch, set them aside first and restore them afterwards:

git stash
git switch feature/52-notebook-chat
# ... after testing ...
git switch -        # go back to your previous branch
git stash pop       # restore your changes

When you're done, you can return to the main branch with git switch main.

2. Run it

Start the stack and open a Jupyter notebook (steps are in the guide), then in a cell:

%load_ext neuroworkflow.agent
%chat I want to create a new node that computes the mean firing rate (Hz) per population from NEST spike trains

3. What to confirm

The agent should follow our node-creation guidance — for example, it first looks through the existing nodes, asks you for the details it needs (name, stage, parameters, input/output ports with scientific units like Hz), and proposes creating the file in the nodes "sandbox" folder.

Could you let me know:

  • Does the agent's behavior actually reflect the node-creation guidance (the points above)?
  • Anything that feels off, missing, or confusing while you use it?

The guide also has a short Skills verification step and a Troubleshooting section in case something doesn't load. Thanks so much! 🙏

@carlosengutierrez
Copy link
Copy Markdown
Collaborator

carlosengutierrez commented Jun 2, 2026

@IzBrain67

Thanks for the work on this, the skill loading and notebook design are OK.

I want to clarify the use case I had in mind, because I would like the participants to understand create nodes locally, without dependency on backend and server (I am assuming low connection to internet, and no server at all), first.

First stage:
Participants:

  • Clone the repo on their local machine
  • Run pip install -e .
  • Bring their own existing Python code or notebooks
  • load the skill in an local AI agent (within a notebook)
  • Use an AI agent to help them convert that code into nodes and workflow, test them locally in a
    notebook, and run simple workflows, all without any server running

So the first version of this agent should be fully standalone. Just the repo, a local Python environment, and an API key (OpenAI or Anthropic or Llama?) set as an environment variable. The agent reads the .claude/skills/ files (you already did this), can read/write files and run code in the notebook kernel, and calls the LLM API directly.

Second stage (after the hackathon)
A GUI-integrated version that you almost built here (with the backend proxy, service token, MCP
tools)。

よろしくお願いします。

IzBrain67 and others added 2 commits June 4, 2026 22:22
Let the in-notebook chat agent understand the brain viewer's live state.
The viewer (a standalone browser tab) and the chat agent (in the Jupyter
kernel) have no communication channel, so add an explicit, manual bridge:

- Viewer: a "Copy state for Chat" button serialises the current selection,
  thresholds, display toggles, BOLD playback, stimulated regions, dataset
  summary, and camera into a human-readable Markdown block (with an embedded
  JSON fence) and copies it to the clipboard. The user pastes it into Chat.
  Reuses the existing visible-connection maths; falls back to window.prompt
  outside a secure context. Applied to all four brain_viewer.js copies.

- Chat: a new .claude/skills/brain-viewer.md skill (auto-loaded into the
  agent system prompt) documents how to read a pasted snapshot and how to
  load connectivity_data.json for deeper data questions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The in-kernel notebook agent now runs the Claude Agent SDK instead of the
OpenAI LLM-proxy loop. The browser chat (/api/chat/stream/) is unchanged and
still uses OpenAI.

Backend:
- Add an Anthropic passthrough proxy (/api/chat/anthropic/<path>): it validates
  the shared service token (sent as x-api-key) and injects the real
  ANTHROPIC_API_KEY, so the key never enters user-accessible kernels. Forwards
  the request/response untouched and decompresses the streaming body.

Kernel agent (src + synced codes copy):
- Drive the SDK on a dedicated worker-thread event loop, isolated from the
  kernel's own loop (avoids the nest_asyncio re-entrancy that destroyed kernel
  tasks). Let query()'s async generator finish on its own.
- Keep live run_code as an in-process SDK MCP tool (exec against the notebook
  namespace); wrap the workflow MCP tools via the existing backend proxy; file
  edits use the SDK's Read/Write/Edit confined to the workspace via can_use_tool.
- Reach Anthropic through the backend proxy (ANTHROPIC_BASE_URL) presenting the
  service token as the API key.

Infra:
- Bundle Node.js + the Claude Code CLI + claude-agent-sdk + nest_asyncio in the
  nest kernel image (kept at the end of Dockerfile.nest so the expensive
  source-built layers stay cached).
- Inject ANTHROPIC_BASE_URL/ANTHROPIC_MODEL into kernels via the spawner; add
  ANTHROPIC_API_KEY to the backend and compose/env templates.

Docs/tests:
- Update CLAUDE.md, docs/NOTEBOOK_CHAT_AGENT.md, gui/DOCKER_SETUP.md,
  gui/.env.example and gui/env.template.
- Add tests for the Anthropic proxy (service-token auth, key injection,
  streaming).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Implement Jupyter Notebook Chat Agent with .claude Skills Support for July Hackathon

3 participants