src/templates/claude/skills/bonnard-design-guide/SKILL.md
Design principles for building semantic layers that work well for AI agents and business users. Use when building views, writing descriptions, or improving agent accuracy.
npx skillsauth add meal-inc/bonnard-cli bonnard-design-guideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This guide covers design decisions that determine whether your semantic layer
works for end users and AI agents. It complements the setup guides
(/bonnard-get-started, /bonnard-metabase-migrate) which cover mechanics.
Read this before building views, or revisit it when agents return wrong answers or users can't find the right metrics.
The natural instinct is: look at tables, build cubes, expose everything. This produces a semantic layer that mirrors your warehouse schema — technically correct but useless to anyone who doesn't already know the schema.
Instead, start from what people ask:
If you have a BI tool (Metabase, Looker, Tableau), your top dashboards by view count are the best source of real questions. If not, ask each team: "What 3 numbers do you check every week?"
Why this matters: A semantic layer built from questions produces focused, audience-scoped views. One built from tables produces generic views that agents struggle to choose between. Governance policies control which views each user or role can access, so build as many views as your audiences need — but make each one purposeful with clear descriptions.
A common mistake is creating one view per cube (table). This produces views
like orders_view, users_view, invoices_view — which is just the
warehouse schema with extra steps.
Views should represent how a team thinks about data:
BAD (model-centric): GOOD (audience-centric):
views/ views/
orders_view.yaml (1 cube) management.yaml (revenue + users)
users_view.yaml (1 cube) sales.yaml (opportunities + invoices)
invoices_view.yaml (1 cube) product.yaml (users + funnel + contracts)
opportunities_view.yaml (1 cube) partners.yaml (partners + billing)
The same cube often appears in multiple views. An opportunities cube might
contribute to sales_opportunities (filtered to active offers), a management
KPI view, and a partner performance view. Each view exposes different
measures and dimensions because different audiences need different slices.
Name views by what they answer, not what table they wrap:
sales_pipeline not opportunities_overview.
This is the single most impactful thing you can do. Look at any real
dashboard: it almost never shows COUNT(*) from a raw table. It shows
"active offers" (type=Angebot AND status!=Cancelled), "unpaid invoices"
(status NOT IN terminal states).
Without filtered measures, agents return unfiltered totals that don't match what users see in their dashboards. Users lose trust immediately.
For every important dashboard card, check the WHERE clause:
# Dashboard shows "Active Offers: 7,500"
# But raw COUNT(*) on opportunities returns 29,000
# The card SQL has: WHERE type = 'Angebot' AND status != 'Abgesagt'
measures:
- name: count
type: count
description: Total opportunities (all types and statuses)
- name: active_offer_count
type: count
description: Non-cancelled offers only (type=Angebot, status!=Abgesagt)
filters:
- sql: "{CUBE}.type = 'Angebot'"
- sql: "{CUBE}.status != 'Abgesagt'"
Common patterns that need filtered measures:
A good rule of thumb: if a BI dashboard card has a WHERE clause beyond just a date range, that filter should probably be a filtered measure.
For AI agents, descriptions are not documentation — they are the primary
mechanism for choosing which view and measure to use. When an agent calls
explore_schema, it sees view names and descriptions. That's all it has
to decide where to query.
# BAD: Generic, doesn't help agent choose
description: User metrics and dimensions
# GOOD: Scoped, navigational, includes data values
description: >-
Sales pipeline — active and historical opportunities with contract values
and assignee details. Default view for pipeline questions, deal counts,
and contract value analysis. Use the type dimension (values: Angebot,
Auftrag) to filter by opportunity type. For invoice-level detail, use
sales_invoices instead.
Lead with scope, not mechanics. "All users across both products" not "Wraps the all_users cube." Agents match question keywords against description keywords.
Include actual dimension values. If a dimension has known categorical
values, list them: (values: erben, vererben), (values: B2B, Organic).
This helps agents map user language to filter values.
Use the same vocabulary as your users. If your team says "testaments" not "will_and_testament", the description should say "testaments."
Cross-reference related views. When two views could plausibly answer the same question, both descriptions should point to each other: "For company-wide totals, use company_users instead." This is the most effective way to prevent agents from picking the wrong view.
# BAD
description: Count of orders
# GOOD
description: >-
Orders with status 'completed' or 'shipped' only.
Excludes cancelled and pending. For all orders, use total_order_count.
Sometimes the most common question doesn't map to any single table. "How many total signups do we have?" might require combining users from two separate product tables.
Options:
join_path.Don't force everything into one view. It's better to have an agent make two clear queries than one confused query against an over-joined view.
Verifying that bon query '{"measures": ["orders.count"]}' returns the
right number is necessary but not sufficient. The actual failure mode is:
User asks "how many active offers do we have?" Agent queries
orders.countinstead ofsales_pipeline.active_offer_countReturns 29,000 instead of 7,500
To test properly, give real questions to an AI agent via MCP and check:
Steps 1-3 are where most failures happen, caused by description and view structure problems — not wrong data.
Build a small eval set:
The semantic layer is not a one-time build. The effective workflow is:
Build views -> Deploy -> Test with questions -> Find agent mistakes
^ |
+---- Improve descriptions/measures <----------+
Expect 2-4 iterations before agents reliably answer the top 10 questions. Each iteration typically involves:
Don't try to get it perfect before deploying. Deploy early with your best guess, test with real questions, and fix what breaks.
Before deploying, review each view against this checklist:
documentation
Guide migration from an existing Metabase instance to a Bonnard semantic layer. Use when user says "migrate from metabase", "import metabase", "metabase to semantic layer", or has Metabase data they want to model.
documentation
Guide a user through setting up their first semantic layer after bon init. Use when user says "get started", "what next", "help me set up", or has just run bon init.
development
Guide a user through building and deploying a markdown dashboard. Use when user says "build a dashboard", "create a chart", "visualize data", or wants to create a dashboard.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.