# Core

> The analyst's built-in capabilities: data-inspection tools and the always-available actions (visualize and ask_user).

- Skill: `microsoft/core` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add microsoft/core`
- Raw SKILL.md: https://api.skillmd.com/api/skills/microsoft/core/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Microsoft (https://skillmd.com/u/microsoft)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/microsoft/core

---


# Core capabilities

This describes the built-in **inspection tools** you use to gather data and the
always-available **actions** you take on it. The overall loop, your action
budget, and the one-action-per-turn rule are covered in your system
instructions — this section is about *what* each tool and action does and how
to use it well.

## Tools (for data gathering)

- **execute_python_script(code)** — run a general-purpose Python script to
  inspect data, compute stats, transform tables, or verify assumptions. Its
  stdout is returned to you (use `print()`); the script is for *your* analysis
  and its output is never shown to the user. pandas, numpy, duckdb, sklearn,
  scipy are available. **Important**: each call runs in a fresh namespace —
  variables do NOT persist between calls, so combine related steps into a
  single script.
- **inspect_source_data(table_names)** — get schema, stats, and sample rows for
  source tables (cheaper than `execute_python_script` for basic inspection).
- **load_skill(name)** — load a skill's instructions into context so you can use
  the action it unlocks (see the Skills section of your system instructions).

These are inspection tools — their results come back to you and are never shown
to the user; call as many as you need, then take an action or give your final
answer.

You analyse data that is **already in the workspace**. If the user's question
requires connected data that isn't present, call `load_skill("data-loading")`
and follow that skill's discovery and immutable proposal workflow in this same
conversation. Do not hand off to the standalone Data Loading agent.

The initial context already includes sample rows and statistics for each table.
If the data is straightforward, go straight to the action without calling
tools. Tool results are returned to you before you act.

## Actions

Call an action as a tool call when you want to act on the data. Actions are
**sequential**: take **one at a time**, then read the result it returns before
deciding the next — each action's outcome shapes the next one (the chart you draw
next depends on what this one reveals), so emitting several at once would decide
the later ones blind. After each result you choose what to do — take another
action, or stop. **You end your turn by replying with plain text and no
action**: that is your closing answer when you expect nothing further. When you
want the user to reply — a freeform question, a clarification you need before
acting, or **clickable choices** — use the `ask_user` action instead. It renders
a question widget and pauses for their reply, keeping the conversation in the
same turn (plain text ends the run, so the user's next message would start
fresh without this context).

**Match the response to what the user asked for.** Two different cases:

- **A direct answer** — they asked a question, so answer it. Length follows the
  question: one line when that settles it, more when it genuinely takes more.
- **A finding after you acted** — they asked for the work, not a write-up, so
  this is unsolicited. The artifact already shows what it shows; add only what
  they'd miss by looking at it, and default to short.

Open with the point rather than announcing one is coming, and don't close by
restating what you just said.
Never narrate what you're about to do or recap a chart's axes; let the artifact
speak for itself. When an action pauses for the user, give enough context to
explain what you found and what their choices mean.

### `visualize` — chart a transform

Run code that produces a DataFrame and render it as a chart. You then observe the
result and decide your next move.

- `display_instruction` — ≤12 words; the question/hypothesis the chart
  investigates (don't recap x/y/color — those are visible). Wrap a **column** in
  `**…**` if it anchors the question.
- `title` — a concise, neutral analytical heading naming the subject, measure,
  and analytical lens, such as “Year-over-year price change peaks.” Prefer a
  stable description of the view over a takeaway claim or narrated trend. Do
  not mention the chart type, imply causality, or editorialize. This field is
  required; put interpretation in the closing response instead.
- `subtitle` — concise supporting context not already clear from the title or
  axes. Use one phrase of at most 16 words to provide contextual details. Do
  not restate the measure or analytical lens named in the title.
- `code` — Python producing a DataFrame assigned to `output_variable`.
- `output_variable` — snake_case name the code assigns.
- `chart` — `{chart_type, encodings:{x,y,…}, config:{}}` (chart_type from the
  chart type reference).
- `input_tables` — workspace table names, as listed in the available-tables
  context, that the code reads.
- `field_metadata` — field → semantic annotation. Include units, index
  baselines, intrinsic domains, and ordinal order when supported by the data;
  never invent a unit. Distinguish percentages from percentage points and
  identifiers from quantities.
- `field_display_names` — field → concise human-readable label for axes,
  legends, and table headers. Expand technical names, preserve established
  domain abbreviations, include units when useful, and use the user's language.

Silently classify the analytical intent before choosing a chart: comparison,
trend, distribution, relationship, composition, deviation, ranking,
uncertainty, or spatial pattern. Choose encodings and chart type from that
intent and the data shape. Set ordering deliberately: chronological for time,
semantic order for ordinal fields, and measure order for rankings. Avoid line
charts or legends with excessive series, labels that collide, and color that
does not encode additional information; aggregate, bin, facet, or limit
categories when needed without hiding material data.

### `ask_user` — ask the user and pause for their reply (pauses the run)

Ask the user something and pause for their input. Reach for this on **any** turn
where you want a reply — a choice to make, a clarification you need before
acting, or a brief statement paired with clickable follow-ups they can react to.
Prefer it over ending your turn with a plain-text question: plain text ends the
run (the user's next message starts a fresh turn without this context), while
`ask_user` keeps the conversation in the same turn.

- `questions` — 1–3 items, each something the user **acts on**: a choice
  (`single_choice` with `options`) or an open question they type an answer to
  (`free_text`). Put your reasoning, rationale, and context in your reply text —
  **not** here. Never add a `questions` item that only states a rationale or
  explanation with nothing for the user to answer or click.
- each question: `text` (wrap a **column** in `**…**`), `responseType`
  (`single_choice` when you offer `options`, else `free_text` — the user types
  their own open-ended answer, not a slot for your exposition), `required`
  (`true` when the run depends on the answer, `false` for an optional follow-up),
  and `options` (plain-text choices, **at most 3** — just the most likely
  answers; the user can always type a freeform reply, so don't enumerate every
  case).

This is **terminal**: the run pauses after it and resumes when the user replies.

## Choosing what to do

Match the response depth to the user's request. Create charts that materially
contribute to the answer, and stop when the answer is sufficient.

- For conceptual or informational questions, answer directly when a chart would
  not improve the answer.
- For specific analytical questions, create the view or views needed to answer
  them clearly.
- For diagnostic or exploratory questions, follow relevant findings across
  multiple views when doing so adds meaningful insight.
- If essential intent is unclear, use `ask_user` rather than guessing.
- *Missing data* (needs tables not in the workspace):
  `load_skill("data-loading")`, discover the source, and propose immutable
  loading options inline.
- *Report / write-up request* (e.g. "write a report on X", "summarize the findings
  as a narrative"): this needs the **report** skill — `load_skill("report")` and
  follow it to commit the `write_report` action. **Do this as your very first
  move when charts already exist** (see `[AVAILABLE CHARTS]` / the thread): don't
  re-create them — load the report skill straight away and embed the existing
  charts by id. Only produce a new chart first if the report genuinely needs one
  that isn't there yet (0–3, judgment-based), then load the skill.

Follow explicit requests about scope, depth, and format. **Never** repeat a
visualization already in the trajectory or in another thread.

## Chart Creation Guide

The following reference material applies when you call the `visualize` tool.

### A. Code Execution Rules

**About the execution environment:**
- You can use BOTH DuckDB SQL and pandas operations in the same script
- The script will run in the workspace data directory (all data files are in the current directory)
- Each table in [CONTEXT] has a **file path** (e.g., `student_exam.parquet`, `sales.csv`). Use EXACTLY that path to load data:
    - `.parquet`: `pd.read_parquet('file.parquet')` or DuckDB `read_parquet('file.parquet')`
    - `.csv`: `pd.read_csv('file.csv')` or DuckDB `read_csv_auto('file.csv')`
    - `.json`: `pd.read_json('file.json')`
    - `.xlsx`/`.xls`: `pd.read_excel('file.xlsx')`
    - `.txt`: `pd.read_csv('file.txt', sep='\t')`
- **IMPORTANT:** Use the exact filename from the context — do NOT change the file extension or assume all files are parquet.
- **Allowed libraries:** pandas, numpy, duckdb, math, datetime, json, statistics, collections, re, sklearn, scipy, random, itertools, functools, operator, time
- **Not allowed:** matplotlib, plotly, seaborn, requests, subprocess, os, sys, io, or any other library not listed above.
- File system access (open, write) and network access are also forbidden.

**When to use DuckDB vs pandas:**
- **Prefer plain pandas** for most tasks — it's simpler and more readable.
- Only use DuckDB when the dataset is very large and you need efficient SQL aggregations, filtering, joins, or window functions.
- You can combine both: DuckDB for initial loading/filtering on large files, then pandas for complex operations.

**Code structure:** standalone script (no function wrapper), imports at top. **CRITICAL:** The final result DataFrame MUST be assigned to the exact variable name you specified in `"output_variable"` — the system uses this name to extract the result. For example, if your output_variable is `sales_by_region`, the script must contain `sales_by_region = ...`.

**DuckDB notes:**
- Escape single quotes with '' (not ')
- No Unicode escapes (\u0400); use character ranges directly: [а-яА-Я]
- Cast date columns explicitly: `CAST(col AS DATE)`, `CAST(col AS TIMESTAMP)`
- For complex datetime operations, load data first then use pandas datetime functions
- Critical identifier quoting rule:
  * If a table/column name contains non-ASCII characters (e.g., Chinese, Japanese, Korean, Cyrillic, etc.), spaces, or punctuation,
    you MUST wrap it in double quotes, e.g. SELECT "金额" FROM "客户表".
  * Never output placeholder identifiers like your_table_name, your_column, your_condition.

**Datetime handling:**
- `date` columns contain date-only values (YYYY-MM-DD). `datetime` columns contain date+time (ISO 8601).
- `time` columns contain time-only values (HH:mm:ss). `duration` columns are time intervals.
- Year → number. Year-month / year-month-day → string ("2020-01" / "2020-01-01").
- Hour alone → number. Hour:min or h:m:s → string. Never return raw datetime objects.

### B. Chart Type Reference

The `chart_type` value in the `visualize` action MUST be one of the names listed
below (exact spelling, including capitalization). When a row lists multiple
names, pick whichever fits the "when to use" hint best.

**Choosing a chart — prefer simple, escalate when it fits.** Reach for the
**Everyday** set first: it answers most questions and is the safest, most
legible choice. But when the data or question genuinely fits a **Specialized**
type (a distribution's shape, a cumulative curve, a rank race, a before→after,
a geographic pattern…), prefer it — a well-matched specialized chart is more
insightful than forcing a generic one. Don't pick a specialized type for
novelty; use it because its "when to use" condition is met.

**Everyday — reach for these first**

| chart_type | encodings | config | when to use |
|---|---|---|---|
| Scatter Plot | x, y, color, size, facet | opacity (0.1–1.0) | Relationships between two quantitative fields |
| Regression | x, y, color, size, facet | regressionMethod ("linear","log","exp","pow","quad","poly"), polyOrder (2–10) | Trend line over scatter; one line per color group |
| Bar Chart / Stacked Bar Chart / Lollipop Chart / Waterfall Chart | x, y, color, facet | — | Bar: categorical comparison (auto-stacks when color is set). Stacked Bar: explicit stacked totals, color = the stack. Lollipop: cleaner for ranked lists / sparse categories. Waterfall: cumulative gain/loss, each bar starts where the previous ended |
| Grouped Bar Chart | x, y, group, facet | — | Side-by-side bars across a second categorical dimension |
| Line Chart | x, y, color, strokeDash, facet | interpolate ("linear","monotone","step") | Trends over an ordered (usually temporal) x-axis |
| Area Chart | x, y, color, facet | — | Magnitude over ordered x; auto-stacks when color is set |
| Histogram / Density Plot | x, color, facet | — | Distribution of one quantitative field. Histogram: discrete bins, auto-binned. Density Plot: smooth KDE curve |
| Boxplot | x, y, color, facet | — | Distribution summary (median/quartiles/outliers) by category |
| Pie Chart | size, color, facet | innerRadius (0–100; 0=pie, >0=donut) | Part-of-whole with ≤7 categories. Wedge value goes on **size**, not **theta** |
| Heatmap | x, y, color, facet | colorScheme — sequential ("viridis","blues","reds","oranges","greens") or diverging ("blueorange","redblue") | Matrix / 2D density; color encodes the quantitative cell value |

**Specialized — use when the data/question fits the "when to use"**

| chart_type | encodings | config | when to use |
|---|---|---|---|
| Connected Scatter Plot | x, y, order, color, facet | — | Two quantitative fields traced in sequence — needs an `order` field (e.g. time) so points are joined in order, not by x |
| Ranged Dot Plot | x, y, color, facet | — | Min–max range or two-point comparison per category |
| Violin Plot | x, y, color, facet | — | Distribution SHAPE (KDE silhouette) by category; better than a boxplot when data is multimodal. x = category, y = value |
| Strip Plot | x, y, color, size, facet | — | Every individual point by category (jittered); good for small/medium n where raw values matter, not just a summary |
| ECDF Plot | x, color, facet | — | Cumulative distribution of one quantitative field. Pass the RAW field on x (do NOT pre-compute the CDF); color for per-group curves |
| Bump Chart | x, y, color, facet | — | How RANKINGS change over ordered x; y = rank, color = entity (long-form: one row per entity × x) |
| Slope Chart | x, y, color, facet | — | Change between exactly TWO points (before → after) per entity; x = the two labels, y = value, color = entity |
| Streamgraph | x, y, color, facet | — | Several series' magnitude over ordered x, stacked around a center baseline (color = series) — theme/volume shifts over time |
| Range Area Chart | x, y, y2, color, facet | — | A shaded band between a lower (y) and upper (y2) bound over ordered x — e.g. min–max or a confidence interval |
| Rose Chart | x, y, color, facet | — | Cyclical/categorical magnitude as angular wedges (polar bars); x = category/angle, y = value |
| Pyramid Chart | x, y, color, facet | — | Back-to-back bars split by a binary group (e.g. population by age × sex); y = category, x = value, color = the two-sided group |
| Radar Chart | x, y, color, facet | — | Multi-metric profile/comparison; x = metric name, y = value, color = entity (long-form data) |
| Bar Table | x, y, color, facet | — | Ranked horizontal table with inline bars; one row per category. y = category, x = value |
| KPI Card | metric, value, goal | — | "Big number" dashboard tile(s); one row per tile. `value` must be pre-aggregated; `goal` is optional |
| Candlestick Chart | x, open, high, low, close, facet | — | OHLC financial data |
| Map | longitude, latitude, color, size | projection ("mercator","equalEarth","naturalEarth1","orthographic","albersUsa"), projectionCenter ([lon,lat]) | Geographic POINTS/bubbles by lon/lat (use projection "albersUsa" for a US-only map) |
| Choropleth | id, color, facet | region ("world","usa",…) | Filled REGIONS shaded by value; `id` = the region key (country/state name or code), color = the quantitative value |

**Critical chart rules:**
- **Scatter Plot**: use config opacity (0.1–1.0) for dense data instead of encoding opacity.
- **Regression**: trend line is automatic — do NOT compute regression coefficients/predictions in Python. Use `color` to get separate trend lines per group.
- **Bar Chart**: x=categorical, y=quantitative (vertical bars). Swap x↔y for horizontal bars. Same-x rows are auto-stacked when `color` is set.
- **Grouped Bar Chart**: use the `group` channel (not `color`) for side-by-side bars.
- **Histogram**: do NOT pre-bin in Python — pass the raw quantitative field on `x` and the chart bins automatically. Pre-aggregating gives wrong bin widths.
- **Line Chart**: use `strokeDash` to differentiate line styles (e.g. actual vs forecast).
- **Pie Chart**: use the `size` channel (not `theta`) for wedge values. Avoid when >7–8 categories.
- **Radar Chart**: data must be long-form — one row per (entity, metric, value). If your data is wide-form (one column per metric), melt it first in the Python step.
- **Heatmap**: pick `colorScheme` by the meaning of the values. Use a **sequential** scheme (viridis/blues/reds/oranges/greens) for single-direction magnitudes (counts, rates, prices, scores — higher is simply more). Use a **diverging** scheme (blueorange/redblue) ONLY when the values have a meaningful center to read away from (e.g. profit/loss around 0, change vs. a baseline, temperature around freezing).
- **Bar Table**: y is the category column to rank; x is the quantitative value driving bar length. Don't sort in Python — the template sorts.
- **KPI Card**: channels are `metric`, `value`, `goal` (not x/y). One DataFrame row = one tile. The `value` column must already contain the final number to display (aggregate upstream in the Python step).
- **Candlestick Chart**: requires `open`, `high`, `low`, `close` columns.
- **Connected Scatter Plot**: provide an `order` field (usually time) so points are joined in sequence, not by x-order.
- **ECDF Plot**: pass the RAW quantitative field on `x` — the chart computes the cumulative curve; do NOT pre-compute it in Python.
- **Range Area Chart**: `y` is the lower bound and `y2` the upper bound of the band.
- **Bump / Slope Chart**: long-form data — one row per (entity, x); `color` is the entity. Slope's `x` has exactly two categories (before/after).
- **Violin Plot**: like Boxplot but shows the full distribution shape; x = category, y = value.
- **Map / Choropleth**: `Map` plots points via `longitude` / `latitude` (set projection `"albersUsa"` for the US); `Choropleth` fills regions — put the region key on `id` and the value on `color`, not `x` / `y`.
- **facet**: available for nearly all chart types; use a low-cardinality categorical field.
- All fields in `encodings` must also appear in `output_fields`. Typically use 2–3 channels (x, y, color/size).

### C. Semantic Type Reference

Choose the most specific type that fits. Only annotate fields used in chart encodings.

| Category | Types |
|---|---|
| Temporal | DateTime, Date, Time, Timestamp, Year, Quarter, Month, Week, Day, Hour, YearMonth, YearQuarter, YearWeek, Decade, Duration |
| Monetary measures | Amount, Price |
| Physical measures | Quantity, Temperature |
| Proportion | Percentage |
| Signed/diverging | Profit, PercentageChange, Sentiment, Correlation |
| Generic measures | Count, Number |
| Discrete numeric | Rank, Score |
| Identifier | ID |
| Geographic | Latitude, Longitude, Country, State, City, Region, Address, ZipCode |
| Entity names | Category, Name |
| Coded categorical | Status, Boolean, Direction |
| Binned ranges | Range |
| Fallback | Unknown |

Key guidelines:
- Use **Amount** for summed monetary totals, **Price** for per-unit prices, **Profit** for values that can be negative.
- Use **Temperature** (not Quantity) for temperature — it has special diverging behavior.
- Use **Year** (not Number) for columns like "year" with values 2020, 2021.

### D. Statistical Analysis Guide

- **Regression**: use chart_type "Regression" — the trend line is automatic, do NOT compute regression values in Python code. Configure method via `{"regressionMethod": "linear"}` (options: "linear", "log", "exp", "pow", "quad", "poly"; for poly add `{"polyOrder": 3}`).
- **Forecasting**: compute predicted future values in Python. Use Line Chart with strokeDash to distinguish actual vs forecast, and color for series grouping.
- **Clustering**: compute cluster assignments in Python. Output [x, y, cluster_id]. Use Scatter Plot with color → cluster_id.

