Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guides you step-by-step through defining a business metric (aggregation) on a Honeydew entity. Covers SQL expression building and pushes to Honeydew via the MCP tools.
.claude/skills/hashgraph-online-metric-creation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | 191% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 103% | 0% |
| case-07 | ✓→✗ | ▼ Worse | 64% | 0% |
| case-24 | ✓→✗ | ▼ Worse | 86% | 0% |
Before creating metrics, ensure you are on the correct workspace and branch. Use get_session_workspace_and_branch to check the current session context. For development work, create a branch with create_workspace_branch (the session switches automatically). See the model-exploration skill for the full workspace/branch tool reference.
A Honeydew metric is a named, reusable aggregation anchored to an entity. Unlike a calculated attribute (which is per-row), a metric collapses multiple rows into a single value. Metrics are context-sensitive: they automatically respond to whatever filters and groupings the consuming BI tool or analyst applies.
Your job is to build an aggregation function that users can later group by.
If a user asks for a group by ("sum sales by category"), ignore the group. You're building the aggregation to sum sales by ANYTHING. The user will later use it in a query with their chosen dimensions.
Use a metric when:
Do not use a metric when the value is per-row (use a calculated attribute instead).
entity.count) if available,or COUNT(entity.key_field) on a specific key column.
reference it by name (e.g., entity.attribute_name) rather than repeating its SQL logic in the new metric expression. This keeps definitions DRY and ensures changes propagate.
entity.attribute, not just attributeSee reference.md for: aggregation functions, filtered aggregations, date handling, text summarization, data types, metric types, and format strings.
Always use create_object with full YAML to ensure proper datatype and all properties are set.
Call create_object with yaml_text:
yamltype: metric entity: <entity_name> name: <snake_case_name> display_name: <Human Readable Name> description: |- <business description> owner: <owner_email_or_team> datatype: float|number|string|date|timestamp sql: |- <aggregation SQL expression>
Required fields:
type: metricentity — the entity this metric belongs toname — snake_case identifierowner — CRITICAL: always set to current username (from workspace context)datatype — CRITICAL: always set explicitly (default to float for most metrics, number for counts)sql — the aggregation expressionOptional fields:
display_name — human readable namedescription — business contextformat_string — display format (e.g., $#,##0.00)labels — categorization tagsfolder — organizational pathTo modify an existing metric:
get_entity with the entity name to find the metric and its details.search_model (with search_mode: EXACT) to find the metric's object_key.update_object with the full updated YAML (yaml_text) and the object_key.> Minimal diff rule: When updating, preserve the existing field order and formatting from the current YAML. Only change the fields you need to modify. Objects are versioned in git, so unnecessary reordering or reformatting creates noisy diffs.
After a successful create_object or update_object call, the response includes a ui_url field. Always display this URL to the user so they can quickly open the object in the Honeydew application.
search_model (with search_mode: EXACT) to find the metric's object_key.delete_object with the object_key.See examples.md for full worked examples covering: basic, derived, filtered, ratio, count, distinct count, fixed grouping, nested aggregation, text summary, update, and delete.
Use these MCP tools to explore existing metrics:
get_entity — Get entity details including all its metrics, attributes, datasets, and relationsget_field — Get detailed info about a specific metric by entity and field namesearch_model — Search for metrics across the model by name (use search_mode: EXACT for known names, OR for broad discovery)list_entities — List entities to identify where to anchor new metricsMany metric requests are ambiguous. ALWAYS clarify before implementing:
| Ambiguous Term | Possible Interpretations | Ask User | | -------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------- | | "per day/week/month" | A) Breakdown by period (multiple rows) | "Do you want revenue _for each day_ (fixed grouping) or _average daily revenue_ (single KPI)?" | | | B) Average per period (single value) | | | "rate" | A) Ratio (X / Y) | "Is this a ratio (e.g., conversion rate = orders/visits) or velocity (e.g., orders per hour)?" | | | B) Velocity (X per time unit) | | | "growth" | A) Absolute difference | "Do you want absolute growth ($100 → $150 = $50) or percentage growth (50%)?" | | | B) Percentage change | | | "average" | A) Simple mean | "Simple average or weighted average? If weighted, by what?" | | | B) Weighted mean | |
After user clarifies intent, use the correct SQL pattern:
| User Choice | SQL Pattern | Example | | ----------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------- | | Breakdown by period | AGG(field) GROUP BY (time_field) | SUM(order_header.order_total) GROUP BY (order_header.order_date) | | Average per period | AGG(metric GROUP BY (*, time_field)) or SUM/COUNT DISTINCT | AVG(order_header.total_revenue GROUP BY (*, order_header.order_date)) | | Ratio | metric_a / NULLIF(metric_b, 0) | order_count / NULLIF(customer_count, 0) | | Velocity (per time) | AGG(field) / COUNT(DISTINCT time_field) | SUM(order_total) / NULLIF(COUNT(DISTINCT order_date), 0) | | Absolute growth | current - previous | Requires time comparison logic | | Percentage growth | (current - previous) / NULLIF(previous, 0) * 100 | Requires time comparison logic |
CRITICAL: "Breakdown by X" or "for each X" = Fixed GROUP BY
SUM(order_header.order_total) GROUP BY (order_header.order_date)SUM(order_header.order_total) ← requires manual grouping at query timeWhen the user's request contains phrases suggesting a specific granularity, ask before creating:
| Phrase | Likely Intent | Clarifying Question | | -------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------- | | "in an order", "per order", "by order" | Fixed grouping by order_id | "Should this always be calculated per order (fixed grouping), or flexible to group by any dimension?" | | "in a day", "per day", "daily" | Fixed grouping by date | "Should this always be at daily granularity, or flexible?" | | "per customer", "by customer" | Fixed grouping by customer_id | "Should this always be per customer, or flexible?" |
Rule: If the request mentions "per X" or "in an X", clarify whether they want:
SUM(entity.field) GROUP BY (entity.x_id)SUM(entity.field) that users can group by anything laterUse the honeydew-docs MCP tools to search the Honeydew documentation when:
reference.md coversSearch for topics like: "metrics", "aggregation", "derived metrics", "fixed grouping", "time intelligence", "period over period", "YTD", "trailing window".
FILTER (WHERE ...) for filtered aggregations — NOT CASE WHEN.The FILTER syntax is cleaner, more readable, and the native Honeydew pattern.
SUM(orders.amount) FILTER (WHERE orders.is_promotional)COUNT(truck.truck_id) FILTER (WHERE truck.is_electric)SUM(CASE WHEN orders.is_promotional THEN orders.amount ELSE 0 END)COUNT(CASE WHEN truck.is_electric THEN truck.truck_id END)If you created a calculated attribute (e.g., orders.net_price), reference it in your metric (SUM(orders.net_price)) rather than inlining the attribute's SQL expression. Similarly, if a metric already exists, reference it in derived metrics by name (entity.existing_metric). This keeps definitions DRY and ensures changes propagate automatically.
entity.count) when available.Otherwise, use COUNT(entity.key_field) on the entity's key column.
gross_margin is better than revenue_minus_cogs_divided_by_revenue.orders.amount, not just amount.After creating ANY metric, you MUST invoke the validation skill to test and validate results.
See validation skill for:
get_data_from_fieldsQuick validation:
Call get_data_from_fields with:
metrics: ["<entity>.<metric_name>"]COUNT(*) — use the entity's built-in count metric if available, or COUNT(entity.key_field) on a specific key.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 9,931 | 9,248 | -7% | 1 | 1 | 0% | 386 | 3,110 | +706% | 0 | 0 | — |
case-02 | fail→fail | 9,838 | 9,835 | -0% | 1 | 1 | 0% | 339 | 3,270 | +865% | 0 | 0 | — |
case-03 | fail→fail | 8,060 | 9,570 | +19% | 1 | 1 | 0% | 728 | 3,353 | +361% | 0 | 0 | — |
case-04 | fail→fail | 7,466 | 8,611 | +15% | 1 | 1 | 0% | 1,028 | 3,213 | +213% | 0 | 0 | — |
case-05 | fail→fail | 9,340 | 13,030 | +40% | 1 | 1 | 0% | 1,382 | 3,105 | +125% | 0 | 0 | — |
case-06 | fail→fail | 9,440 | 9,102 | -4% | 1 | 1 | 0% | 1,412 | 3,319 | +135% | 0 | 0 | — |
case-07 | pass→fail | 12,720 | 11,418 | -10% | 1 | 1 | 0% | 2,158 | 3,529 | +64% | 0 | 0 | — |
case-08 | fail→fail | 10,881 | 5,029 | -54% | 1 | 1 | 0% | 1,466 | 3,317 | +126% | 0 | 0 | — |
case-09 | pass→pass | 13,958 | 11,471 | -18% | 1 | 1 | 0% | 2,163 | 4,617 | +113% | 0 | 0 | — |
case-10 | fail→fail | 11,192 | 9,950 | -11% | 1 | 1 | 0% | 1,644 | 3,338 | +103% | 0 | 0 | — |
case-11 | fail→fail | 15,454 | 9,354 | -39% | 1 | 1 | 0% | 1,754 | 3,217 | +83% | 0 | 0 | — |
case-12 | fail→fail | 9,920 | 13,966 | +41% | 1 | 1 | 0% | 1,774 | 3,191 | +80% | 0 | 0 | — |
case-13 | fail→pass | 9,339 | 9,485 | +2% | 1 | 1 | 0% | 1,627 | 4,741 | +191% | 0 | 0 | — |
case-14 | fail→fail | 11,780 | 8,364 | -29% | 1 | 1 | 0% | 1,258 | 3,454 | +175% | 0 | 0 | — |
case-15 | fail→fail | 9,435 | 6,662 | -29% | 1 | 1 | 0% | 658 | 3,203 | +387% | 0 | 0 | — |
case-16 | fail→fail | 8,292 | 9,931 | +20% | 1 | 1 | 0% | 1,036 | 3,584 | +246% | 0 | 0 | — |
case-17 | fail→fail | 6,348 | 7,818 | +23% | 1 | 1 | 0% | 882 | 3,181 | +261% | 0 | 0 | — |
case-18 | fail→pass | 17,980 | 13,846 | -23% | 1 | 1 | 0% | 2,518 | 4,238 | +68% | 0 | 0 | — |
case-19 | pass→pass | 12,709 | 8,157 | -36% | 1 | 1 | 0% | 1,102 | 3,314 | +201% | 0 | 0 | — |
case-24 | pass→fail | 18,080 | 10,013 | -45% | 1 | 1 | 0% | 1,783 | 3,325 | +86% | 0 | 0 | — |
case-20 | pass→pass | 21,972 | 9,367 | -57% | 1 | 1 | 0% | 1,690 | 3,413 | +102% | 0 | 0 | — |
case-21 | fail→pass | 16,337 | 4,528 | -72% | 1 | 1 | 0% | 1,746 | 3,552 | +103% | 0 | 0 | — |
case-22 | pass→pass | 27,220 | 7,376 | -73% | 1 | 1 | 0% | 4,956 | 3,165 | -36% | 0 | 0 | — |
case-23 | fail→fail | 10,497 | 14,339 | +37% | 1 | 1 | 0% | 1,528 | 3,327 | +118% | 0 | 0 | — |
case-25 | fail→fail | 14,906 | 11,347 | -24% | 1 | 1 | 0% | 1,820 | 3,503 | +92% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted, and 10 counted toward the lift figure. The other 15 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +4 percentage points is the difference between those two pass rates over the 10 comparable cases. 6 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.