Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Notebook

ASecurity

Load before you edit or run notebook cells in chat. Covers the notebook tools, the sp SDK, governed queries, files, and charts.

485 stars
0 votes
0 copies
1 views
Added 9/24/2026
datapythongobashsqlexpressdatabase

Works with

mcp

Security Analysis

A100/100

Scanned 10/1/2026

$npx -y skills add SignalPilot-Labs/SignalPilot --skill notebook --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Notebook?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Notebook
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/signalpilot-labs-notebook/badge)](https://www.skillsdirectory.com/skills/signalpilot-labs-notebook)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: notebook
description: "Load before you edit or run notebook cells in chat. Covers the notebook tools, the sp SDK, governed queries, files, and charts."
type: skill
---

# Notebook

The chat gives you one analysis notebook and optional named notebooks. Use them
to run queries, compute results, and save files. Evidence for an answer comes
from the analysis notebook.

## Start a notebook

1. Call `start_analysis_notebook` only when you will run cells. A notebook
   that you start and do not use rejects the whole run.
2. Pass `notebook: "scratch"` (short, lowercase) to start a separate notebook
   for drafting. Each name has its own kernel and `session_id`.
3. Use the `session_id` from the tool result with every notebook tool.
4. Make the first visible cell a title: `sp.md("# <title of the analysis>")`.
   Write a short title from the user's request, not the prompt text. Update
   it when the question changes. A markdown cell does not need `sp.init()`
   and must not import `signalpilot` again.

## The seeded cells

The notebook starts with two hidden cells and one empty visible cell. Never
edit or remove the hidden cells. Put the title in the empty cell.

- Cell 1 DEFINES `os`, `Path`, `runtime_context` and `sp`, sets
  `SP_CHAT_SCRATCH_DIRECTORY` and `SP_CHAT_ARTIFACTS_DIRECTORY`, and EXPORTS
  `Path`, `runtime_context`, `sp`.
- Cell 2 defines and exports `db`, `np`, `pd`.

Redefining any of those seven names raises `MultipleDefinitionError`. That
includes `import os`, even though `os` is not exported to your cells: it is
defined, so you cannot define it again, and it is not exported, so you cannot
use it. Import nothing that is already defined.

**Nothing else is defined. `plt` is not.** `sp.init()` has already applied the
house theme to matplotlib's `rcParams`, including `savefig.dpi` and
`savefig.bbox`, but the module itself is not imported. Put
`import matplotlib.pyplot as plt` in one cell of your own before the first
chart cell.

`sp.init()` returns None. There is no `signalpilot.db` export. Use `db` and
`sp` from these cells. Do not import `signalpilot` again.

## Before you write the first cell

Write every cell of the analysis before running any of them. Fixing cells until
they stop erroring produces code that runs and answers the wrong question.

Settle these four facts first, from the dbt project and `sp.describe_table`,
not by trial:

1. The connection's SQL dialect. SQL Server takes `YEAR(x)`, `select top 10`,
   and three-part `Database.schema.table` names, which the governed parser
   requires.
2. The definition of every column you will name, read from its model SQL. A
   column that sums cleanly can still mean something other than its name.
3. Which names must be public because a later cell reads them, and which are
   `_`-prefixed scratch private to one cell.
4. The grain of each mart, so you know whether your `GROUP BY` fans out.

## Cell rules (marimo, not Jupyter)

- Put one text cell before every code cell. The text cell gives a one line
  summary of what the code cell below it does. Give every code cell its own
  summary, including the first one.
  - In marimo a text cell is a separate cell. It holds one `sp.md("...")` call.
    Each step is therefore two cells: the summary cell, then the code cell.
  - Add both cells in the same `edit_notebook` batch. Add the summary cell
    first, so the notebook reads in the correct order.
  - Write what the step does, in one line. Write
    `sp.md("Join invoice lines to the account dimension for one row per account
    per month")`. Do not write `sp.md("Run a query")`.
  - Use one line. Do not use a heading mark.
  - A text cell does not need `sp.init()`. Do not import `signalpilot` in it. A
    text cell defines no name, so it cannot cause `MultipleDefinitionError`.
- One live cell defines each top-level name. A second definition raises
  `MultipleDefinitionError`. Fix all conflicting cells in one edit batch.
- An underscore name is local to the cell that writes it, and the editor
  rejects the WHOLE batch if another cell reads it. Decide before you write: a
  value the next cell needs gets a public name; a value used only inside this
  cell gets one underscore. `_fig`, `_ax` and loop variables are always
  underscore. After a `PrivateVariableCrossCellReference`, promote the name in
  the defining cell and resend both cells in one batch.
- Get every DataFrame with `db.query_df(sql)`. Show a small preview only.
- One query per cell, and no more than a few derived columns after it.
  Aggregate in SQL; use Python for ratios and formatting only. Keep a cell
  under 20 lines, define at most one public name in it, and end it in one bare
  expression or one `print`. A cell that runs three queries and twenty pandas
  statements fails as a unit, and its error output is truncated.
- Batching many small cells into one `edit_notebook` call is correct. Putting
  many steps into one cell is not.
- `pd` and `np` are already imported in the setup cell. Do not import them
  again.
- Use only the methods this skill names. The SDK has no other functions.

## Tools

| Tool | Use |
|---|---|
| `edit_notebook` | `edits`: `add_cell` (code), `update_cell` (cell_id, code), `delete_cell` (cell_id). One batch per change set. |
| `run_cells` | Runs the given `cell_ids` (all cells when omitted) and blocks. Returns outputs, console text, and errors per cell. Set `timeout` for long queries. |
| `save_data_snapshot` | Saves a compact aggregate for an external deliverable. Do not dump raw tables. |
| `start_notebook_session` | Opens a `.py` notebook file you wrote with `Write` and returns its `session_id`. |

Read every `run_cells` result. A cell with `status: "failed"` has an
`error.message`. Fix the cell and run it again.

## The sp SDK

| Call | Result |
|---|---|
| `sp.connections()` | Names of the connections you may query. |
| `db.query_df(sql, row_limit=1000)` | **The default.** A typed DataFrame: numeric and date columns are already converted. |
| `db.query(sql, row_limit=1000)` | Rows as a list of dicts. Call `.df()` on it to get a typed DataFrame. |
| `db.query_result(sql, row_limit=100_000)` | Dict with `rows`, `columns`, and `completeness`. `truncated` means the limit cut the result. Aggregate or add a limit. |
| `db.query_dataset(sql)` | A private Parquet dataset for large results. Open it with `sp.open_dataset(ref)`. |
| `sp.artifact_path("name.png")` | The path for one artifact file. |
| `sp.dashboard_dataset(name, connection=..., sql=...)` | Runs the SQL and writes a dashboard snapshot. See the `signalpilot-dbt:dashboard` skill. |
| `sp.publish_result(dataframe, name=..., ...)` | Publishes a compact result without exposing rows to the agent. |

Rules:

- Never write your own query helper. `db.query_df(sql)` already returns a typed
  frame; `db.query(sql)` returns a list of row dicts, and only
  `db.query_result(sql)` returns a dict with a `rows` key. They are three
  different shapes, so do not index one like another. Never hand-write a
  `pd.to_numeric` or `.astype(float)` pass over a query result: if you are
  writing one, you called the wrong method.
- Every query goes through `db` or `sp.connect(name)`. Never open a warehouse
  connection from `Bash`, a driver, or a script. The data plane is read-only.
- Write SQL in the dialect of the connection. SQL Server connections take
  T-SQL, for example `select top 10`. Postgres connections take Postgres SQL.
- Never copy MCP row previews into a DataFrame. Query the data in the notebook.
- `Gateway error (HTTP 400): Query blocked` means governance refused the SQL.
  Read the reason. Fix the SQL or the table access. Do not work around it.
- `aggregate_required` means: rewrite the work as a bounded warehouse
  aggregate. `refuse` means: stop and tell the user.

## Files

- `sp.artifact_path("name.png")` returns the path for a deliverable and
  creates its directory. Pass it straight to `savefig`, `to_csv` or `open`.
  Never read `SP_CHAT_ARTIFACTS_DIRECTORY` yourself, never `mkdir`, never copy
  a file into it, and there is no publish call. Save a file again after you
  change it.
- Saving a file makes it exist; only a reference makes it visible. Reference it
  once, on its own line, under the finding it supports: an image as
  `![alt](artifacts/name.png)`, anything else as
  `[Download name.ext](artifacts/name.ext)`.
- Working notes (`analytics-steps.md`, `prebuild-state.md`) go to
  `$SP_CHAT_SCRATCH_DIRECTORY`, not to `artifacts/`.
- Use short lowercase file names with underscores.
- The chat ignores dot-files, `*.log`, `__pycache__`, dbt target dirs, and
  top-level `*.py` and `*.duckdb` files.

## Charts

1. Make charts in the analysis notebook with matplotlib. `plt` is NOT
   pre-imported: add `import matplotlib.pyplot as plt` once, in one cell, before
   the first chart. The house theme is already applied to `rcParams` by
   `sp.init()`. Do not set colors, fonts, or a style.
2. One chart per cell, one finding per chart, and one figure per chart. Never
   call `plt.subplots(rows, cols)` with more than one axis to build a
   multi-panel dashboard: the chat shows each saved PNG at one size, and a 3x2
   grid is unreadable. Six findings are six cells and six files. Add a title,
   axis labels with units, and a legend for more than one series.
3. Save with `fig.savefig(sp.artifact_path("revenue_by_month.png"))`. Do not
   pass `dpi`, `facecolor`, or `bbox_inches`. The SDK sets them.
4. You cannot see the image. Check the data first: x values sorted, 8 series
   or fewer, 24 categories or fewer, no null values. The saved file is the only
   proof that the chart rendered.
5. Never draw charts with block characters, ASCII, or emoji.

## Tables and reports

- A table the user will reuse: `dataframe.to_csv(sp.artifact_path("name.csv"), index=False)`.
  Keep the column names, precision, and date format rules from the skills.
- A long analysis: also save `artifacts/report.md` or `artifacts/report.html`.
  The chat answer is still the full answer.

## Troubleshooting

| Symptom | Cause | Action |
|---|---|---|
| `MultipleDefinitionError` | Two cells define one name. | Rename with an underscore, or update the old cell in the same batch. |
| `NameError` for `db` or `sp` | The seeded cells did not run. | Run all cells once with `run_cells` and no `cell_ids`. |
| `Query blocked: SQL parse error` | The SQL is not valid in the connection dialect. | Fix the SQL for that dialect. |
| `completeness: "truncated"` | The row limit cut the result. | Aggregate in SQL or raise `row_limit`. |
| `run_cells` timed out | A slow query. | Add a `timeout`, or aggregate in SQL. |
| `NameError: name 'plt' is not defined` | `plt` is not seeded. | Add one cell with `import matplotlib.pyplot as plt`, then run the chart cells again. |
| `PrivateVariableCrossCellReference` | A cell reads an `_`-prefixed name from another cell. | Promote the name in the defining cell and resend both cells in one batch. |
| `CellNotFound` | The kernel restarted, so cell ids from before the restart are gone. | Call `get_lightweight_cell_map` for current ids. Re-add the cell and run all cells once. Do not add your own `import signalpilot` or `sp.init()` to recover. |

Attribution

SignalPilot-LabsSignalPilot-Labs
View sourceSee grades on GitHubMore from SignalPilot-Labs →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Rank Tracker

This skill helps you track, analyze, and report on keyword ranking positions over time. It monitors both traditional SERP rankings and AI/GEO visibility to provide comprehensive search performance insights.

1821 votes

Youtube Competitor Analyzer

Find and analyze YouTube competitor channels using YouTube Data API v3. Discover competitors through keyword search, category matching, content similarity, and related channel discovery. Compare metrics, content strategies, and market positioning. Use when users want to (1) Find competitors for their YouTube channel, (2) Analyze competitor performance metrics, (3) Compare their channel against competitors, (4) Identify content gaps and opportunities, (5) Benchmark against similar creators, (6...

31 votes

Xlsx

Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .xltx, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the...

1798860 votes

Weather Fetcher

Instructions for fetching current weather temperature data for Karachi, Pakistan from wttr.in API

672240 votes

Weather

Get current weather and forecasts (no API key required).

486960 votes
View all in data →