{"slug":"azure-kusto-graph","title":"azure-kusto-graph","summary":"Build and query Kusto graphs from natural language. Covers transient graphs (make-graph), persistent graph models/snapshots, pattern matching (graph-match), shortest paths, connected components, and graph-to-table export. Generates the edges-first thinking: define edges, define n","platform":"GitHub Copilot","tags":[],"authorName":"Ciza","authorSlug":"ciza","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-12T21:05:26.670269Z","repo":{"url":"https://github.com/microsoft/skills","stars":3075,"forks":352,"license":"MIT","updatedAt":"2026-10-02T16:39:30Z"},"bodyHtml":"<hr>\n<h2>name: azure-kusto-graph\ndescription: \"Build and query Kusto graphs from natural language. Covers transient graphs (make-graph), persistent graph models/snapshots, pattern matching (graph-match), shortest paths, connected components, and graph-to-table export. Generates the edges-first thinking: define edges, define node lookups, union, make-graph. WHEN: make-graph, graph-match, graph-shortest-paths, graph-to-table, graph-mark-components, persistent graph, graph model, graph snapshot, build a graph from data, find paths between nodes, pattern matching in graph, connected components, transient graph, Kusto graph, KQL graph.\"\nlicense: MIT\nmetadata:\nauthor: Microsoft\nversion: \"1.2.1\"</h2>\n<h1>Kusto Graph Semantics</h1>\n<p>Build transient and persistent graphs from tabular data using KQL graph operators. This skill translates natural language into the edges-first graph construction pattern and graph query operators.</p>\n<h2>Activation Triggers</h2>\n<p>Use this skill when the user:</p>\n<ul>\n<li>Wants to build a graph from tabular data (<code>make-graph</code>)</li>\n<li>Asks to find patterns, paths, or relationships in data</li>\n<li>Mentions <code>graph-match</code>, <code>graph-shortest-paths</code>, <code>graph-to-table</code>, <code>graph-mark-components</code></li>\n<li>Wants to create a persistent graph model or snapshot</li>\n<li>Says \"build a graph\", \"find the shortest path\", \"find connected components\", \"show relationships\"</li>\n<li>Asks about transient vs persistent graphs</li>\n</ul>\n<p><strong>Not a natural-language-to-KQL converter.</strong> The input should generally be a working KQL query whose results the user wants converted to a graph, plus a natural-language description of the desired graph structure. Basic NL source requests are supported only when they map directly to a known table with obvious columns. For general NL-to-KQL conversion, use a dedicated query-generation skill (available separately).</p>\n<p><strong>Complementary skills:</strong></p>\n<ul>\n<li><code>azure-kusto-irql</code> -- composable security query primitives that produce the tabular inputs for graphs</li>\n<li><code>azure-kusto-irql-graph</code> -- IRQL's <code>Lift_To_Graph</code> JSON mapping system for richly-typed, icon-decorated graphs in Kusto Explorer</li>\n</ul>\n<h2>The Edges-First Approach</h2>\n<p>The fundamental pattern for building graphs in Kusto:</p>\n<pre><code>1. Define your EDGES       -&gt; src --&gt; dest, with relationship type/properties\n2. Define your NODE LOOKUPS -&gt; display names, types, properties for each node ID\n3. Union edge types         -&gt; if you have multiple relationship types\n4. Union node lookups       -&gt; if you have multiple node types\n5. Call make-graph          -&gt; edges | make-graph Source --&gt; Target with nodes on nodeId\n</code></pre>\n<p>This is how to think in <code>make-graph</code>. Edges are the relationships you care about. Nodes are lookup tables that give those IDs a face -- display names, types, properties.</p>\n<h2>Graph Operators Reference</h2>\n<h3><code>make-graph</code> -- Build a graph from tables</h3>\n<pre><code>Edges | make-graph SourceId --&gt; TargetId with Nodes on NodeId\n</code></pre>\n<ul>\n<li><code>Edges</code>: tabular source where each row is an edge</li>\n<li><code>SourceId --&gt; TargetId</code>: columns containing source and target node IDs</li>\n<li><code>with Nodes on NodeId</code>: optional node property table joined by ID</li>\n<li>Supports multiple node tables: <code>with Nodes1 on Id1, Nodes2 on Id2</code></li>\n<li>Nodes appearing in edges but missing from the node table get empty properties</li>\n</ul>\n<h3><code>graph-match</code> -- Find patterns</h3>\n<pre><code>G | graph-match (a)-[e]-&gt;(b) where &lt;constraints&gt; project &lt;output&gt;\n</code></pre>\n<p>Pattern notation:</p>\n<table>\n<thead>\n<tr>\n<th>Element</th>\n<th>Named</th>\n<th>Anonymous</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Node</td>\n<td><code>(n)</code></td>\n<td><code>()</code></td>\n</tr>\n<tr>\n<td>Edge left-&gt;right</td>\n<td><code>-[e]-&gt;</code></td>\n<td><code>--&gt;</code></td>\n</tr>\n<tr>\n<td>Edge right-&gt;left</td>\n<td><code>&lt;-[e]-</code></td>\n<td><code>&lt;--</code></td>\n</tr>\n<tr>\n<td>Any direction</td>\n<td><code>-[e]-</code></td>\n<td><code>--</code></td>\n</tr>\n<tr>\n<td>Variable length</td>\n<td><code>-[e*1..5]-&gt;</code></td>\n<td><code>-[*1..5]-&gt;</code></td>\n</tr>\n</tbody>\n</table>\n<p>Multi-hop patterns: <code>(a)-[e1]-&gt;(b)-[e2]-&gt;(c)</code>\nStar patterns: <code>(a)--(center)--(b), (c)--(center)--(d)</code>\nCycles control: <code>cycles = all | none | unique_edges</code> (default: <code>unique_edges</code>)</p>\n<h3><code>graph-shortest-paths</code> -- Find shortest paths</h3>\n<pre><code>G | graph-shortest-paths (start)-[e*1..20]-&gt;(end)\n      where start.name == \"Alice\" and end.name == \"Server01\"\n      project Path = e, Length = array_length(e)\n</code></pre>\n<ul>\n<li>Requires at least one variable-length edge</li>\n<li><code>output = any</code> (default, one path per pair) or <code>output = all</code> (all equal-length shortest paths)</li>\n<li>Variable-length edge properties returned as dynamic arrays</li>\n</ul>\n<h3><code>graph-to-table</code> -- Export graph to tables</h3>\n<pre><code>G | graph-to-table nodes                                     // export nodes\nG | graph-to-table edges                                     // export edges\nG | graph-to-table nodes as N, edges as E                    // export both\nG | graph-to-table nodes with_node_id=Id                     // include node hash ID\nG | graph-to-table edges with_source_id=Src with_target_id=Tgt  // include edge endpoint IDs\n</code></pre>\n<h3><code>graph-mark-components</code> -- Find connected components</h3>\n<pre><code>G | graph-mark-components with_component_id=ComponentId\n  | graph-to-table nodes\n  | summarize Members = make_list(name) by ComponentId\n</code></pre>\n<p>Assigns a <code>ComponentId</code> to each node. Nodes in the same connected component share the same ID.</p>\n<h3><code>graph()</code> function -- Query persistent graphs</h3>\n<pre><code>graph(\"MyGraphModel\")                              // latest snapshot\ngraph(\"MyGraphModel\", \"Snapshot_2025_01\")           // specific snapshot\ngraph(\"MyGraphModel\", true)                         // transient from model definition\n</code></pre>\n<h2>Transient Graphs</h2>\n<p>Created dynamically during query execution. No setup required. Ideal for ad-hoc analysis, exploration, and prototyping.</p>\n<h3>Template: Basic two-entity graph</h3>\n<pre><code>// 1. Define edges\nlet edges = &lt;SourceTable&gt;\n    | summarize &lt;aggregations&gt; by SourceCol, TargetCol;\n// 2. Define node lookups\nlet source_nodes = edges\n    | distinct SourceCol\n    | project nodeId = SourceCol, label = SourceCol, nodeType = \"&lt;SourceType&gt;\";\nlet target_nodes = edges\n    | distinct TargetCol\n    | project nodeId = TargetCol, label = TargetCol, nodeType = \"&lt;TargetType&gt;\";\nlet all_nodes = union source_nodes, target_nodes;\n// 3. Build and query the graph\nedges\n| make-graph SourceCol --&gt; TargetCol with all_nodes on nodeId\n| graph-match (s)-[e]-&gt;(t)\n    where &lt;constraints&gt;\n    project Source = s.label, Target = t.label, &lt;edge properties&gt;\n</code></pre>\n<h3>Template: Multi-relationship graph</h3>\n<pre><code>// Multiple edge types -&gt; union them with a common schema\nlet auth_edges = AuthEvents\n    | project Source = username, Target = hostname, edgeType = \"authenticates\", ts = timestamp;\nlet net_edges = NetworkEvents\n    | project Source = src_ip, Target = url, edgeType = \"connects\", ts = timestamp;\nlet all_edges = union auth_edges, net_edges;\n// Node lookups from all sources\nlet user_nodes = Employees | project nodeId = username, label = name, nodeType = \"User\";\nlet host_nodes = AuthEvents | distinct hostname | project nodeId = hostname, label = hostname, nodeType = \"Host\";\nlet all_nodes = union user_nodes, host_nodes;\nall_edges\n| make-graph Source --&gt; Target with all_nodes on nodeId\n</code></pre>\n<h2>Persistent Graphs</h2>\n<p>For large-scale, reusable graphs. Stored in database metadata. Support snapshots for historical comparison.</p>\n<blockquote>\n<p><strong>Safety:</strong> Creating or altering graph models and snapshots modifies the database. Always show the exact command and confirm with the user before executing <code>.create-or-alter graph_model</code> or <code>.make graph_snapshot</code>.</p>\n</blockquote>\n<h3>Step 1: Create a graph model</h3>\n<pre><code>.create-or-alter graph_model SecurityGraph\n{\n  \"Schema\": {\n    \"Nodes\": {\n      \"User\": {\"name\": \"string\", \"role\": \"string\"},\n      \"Host\": {\"hostname\": \"string\"},\n      \"IP\":   {\"ip\": \"string\"}\n    },\n    \"Edges\": {\n      \"AuthenticatesTo\": {\"timestamp\": \"datetime\", \"result\": \"string\"},\n      \"ConnectsFrom\":    {\"timestamp\": \"datetime\"}\n    }\n  },\n  \"Definition\": {\n    \"Steps\": [\n      {\n        \"Kind\": \"AddNodes\",\n        \"Query\": \"Employees | project name, role\",\n        \"NodeIdColumn\": \"name\",\n        \"Labels\": [\"User\"]\n      },\n      {\n        \"Kind\": \"AddNodes\",\n        \"Query\": \"AuthenticationEvents | distinct hostname | project hostname\",\n        \"NodeIdColumn\": \"hostname\",\n        \"Labels\": [\"Host\"]\n      },\n      {\n        \"Kind\": \"AddEdges\",\n        \"Query\": \"AuthenticationEvents | project username, hostname, timestamp, result\",\n        \"SourceColumn\": \"username\",\n        \"TargetColumn\": \"hostname\",\n        \"Labels\": [\"AuthenticatesTo\"]\n      }\n    ]\n  }\n}\n</code></pre>\n<h3>Step 2: Create a snapshot</h3>\n<pre><code>.make graph_snapshot SecurityGraph Snapshot_2025_07\n</code></pre>\n<h3>Step 3: Query the snapshot</h3>\n<pre><code>graph(\"SecurityGraph\")\n| graph-match (user)-[auth]-&gt;(host)\n    where user.role == \"Admin\" and auth.result == \"Failed Login\"\n    project User = user.name, Host = host.hostname, Time = auth.timestamp\n</code></pre>\n<h3>Management commands</h3>\n<blockquote>\n<p><strong>Safety:</strong> All control commands below modify or delete database objects. Never execute <code>.drop</code>, <code>.create-or-alter graph_model</code>, or <code>.make graph_snapshot</code> automatically. Always show the exact command, cluster, database, and affected object, then require explicit user confirmation before execution.</p>\n</blockquote>\n<pre><code>.show graph_models                        // list all models\n.show graph_model SecurityGraph           // show model details\n.show graph_snapshots SecurityGraph       // list snapshots\n.drop graph_snapshot SecurityGraph Snapshot_2025_07  // delete a snapshot (CONFIRM FIRST)\n.drop graph_model SecurityGraph           // delete model and all snapshots (CONFIRM FIRST)\n</code></pre>\n<h2>Transient vs Persistent: When to Use Which</h2>\n<table>\n<thead>\n<tr>\n<th>Factor</th>\n<th>Transient (<code>make-graph</code>)</th>\n<th>Persistent (<code>graph()</code>)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Setup</td>\n<td>None -- inline in query</td>\n<td>Create model + snapshot</td>\n</tr>\n<tr>\n<td>Lifetime</td>\n<td>Query execution only</td>\n<td>Stored in database metadata</td>\n</tr>\n<tr>\n<td>Data freshness</td>\n<td>Always current</td>\n<td>Snapshot at creation time</td>\n</tr>\n<tr>\n<td>Scale</td>\n<td>Limited by query memory</td>\n<td>Enterprise-scale</td>\n</tr>\n<tr>\n<td>Reuse</td>\n<td>Rebuilt every query</td>\n<td>Shared across users/queries</td>\n</tr>\n<tr>\n<td>Best for</td>\n<td>Ad-hoc hunts, prototyping</td>\n<td>Production workflows, dashboards</td>\n</tr>\n</tbody>\n</table>\n<h2>Security &amp; Threat Hunting Examples</h2>\n<h3>Authentication graph: who logged into what from where</h3>\n<pre><code>let auth_edges = AuthenticationEvents\n    | summarize\n        logins = count(),\n        fails = countif(result == \"Failed Login\")\n      by src_ip, username, hostname;\nlet ip_nodes = auth_edges | distinct src_ip\n    | project nodeId = src_ip, label = src_ip, nodeType = \"IP\";\nlet user_nodes = auth_edges | distinct username\n    | project nodeId = username, label = username, nodeType = \"User\";\nlet host_nodes = auth_edges | distinct hostname\n    | project nodeId = hostname, label = hostname, nodeType = \"Host\";\nlet all_nodes = union ip_nodes, user_nodes, host_nodes;\n// IP -&gt; User edges\nlet ip_user = auth_edges\n    | project Source = src_ip, Target = username, logins, fails;\n// User -&gt; Host edges\nlet user_host = auth_edges\n    | project Source = username, Target = hostname, logins, fails;\nunion ip_user, user_host\n| make-graph Source --&gt; Target with all_nodes on nodeId\n| graph-match (ip)-[e1]-&gt;(user)-[e2]-&gt;(host)\n    where e2.fails &gt; 20\n    project\n        IP = ip.label,\n        User = user.label,\n        Host = host.label,\n        Failures = e2.fails\n| order by Failures desc\n</code></pre>\n<h3>Lateral movement detection: users sharing compromised hosts</h3>\n<pre><code>// Pattern: (user1)-[auth1]-&gt;(host)&lt;-[auth2]-(user2)\n// Two users both failing on the same host = possible credential spray\nlet edges = AuthenticationEvents\n    | summarize fails = countif(result == \"Failed Login\"), logins = count()\n      by username, hostname;\nlet nodes = union\n    (edges | distinct username | project nodeId = username, nodeType = \"User\"),\n    (edges | distinct hostname | project nodeId = hostname, nodeType = \"Host\");\nedges\n| make-graph username --&gt; hostname with nodes on nodeId\n| graph-match (u1)-[e1]-&gt;(h)&lt;-[e2]-(u2)\n    where u1.nodeId != u2.nodeId and e1.fails &gt; 10 and e2.fails &gt; 10\n    project\n        User1 = u1.nodeId, User2 = u2.nodeId,\n        SharedHost = h.nodeId,\n        User1Fails = e1.fails, User2Fails = e2.fails\n| distinct User1, SharedHost, User2, User1Fails, User2Fails\n| order by User1Fails + User2Fails desc\n</code></pre>\n<h3>Shortest attack path</h3>\n<pre><code>let edges = SecurityEvents\n    | project Source = source_entity, Target = target_entity, action, timestamp;\nlet nodes = union\n    (edges | distinct Source | project nodeId = Source),\n    (edges | distinct Target | project nodeId = Target);\nedges\n| make-graph Source --&gt; Target with nodes on nodeId\n| graph-shortest-paths (start)-[e*1..10]-&gt;(end)\n    where start.nodeId == \"ExternalIP_1.2.3.4\" and end.nodeId == \"DatabaseServer\"\n    project\n        PathLength = array_length(e),\n        Actions = e.action,\n        Hops = e.Target\n</code></pre>\n<h3>Connected components: find isolated clusters</h3>\n<pre><code>let edges = NetworkFlows\n    | project Source = src_ip, Target = dst_ip;\nlet nodes = union\n    (edges | distinct Source | project nodeId = Source),\n    (edges | distinct Target | project nodeId = Target);\nedges\n| make-graph Source --&gt; Target with nodes on nodeId\n| graph-mark-components with_component_id = ComponentId\n| graph-to-table nodes\n| summarize Members = make_list(nodeId), Size = count() by ComponentId\n| order by Size desc\n</code></pre>\n<h3>Visualize in Kusto Explorer</h3>\n<p>End a query at <code>make-graph</code> (without piping to <code>graph-match</code>) to trigger Kusto Explorer's interactive graph visualization window:</p>\n<pre><code>edges\n| make-graph Source --&gt; Target with all_nodes on nodeId\n// &lt;- stop here. Kusto Explorer renders the graph visually.\n</code></pre>\n<p>To flatten back to a table for dashboards or export, pipe through <code>graph-match | project</code> or <code>graph-to-table</code>.</p>\n<h2>Using with IRQL</h2>\n<p>When working with security data, consider using IRQL selectors (<code>Get_*</code>) from the <code>azure-kusto-irql</code> skill as the data source. IRQL gives you a unified schema without memorizing raw table names or column mappings. For rich visualization with icons and node folding, the <code>azure-kusto-irql-graph</code> skill's <code>Lift_To_Graph</code> is the faster path.</p>\n<table>\n<thead>\n<tr>\n<th>Approach</th>\n<th>Best For</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Raw <code>make-graph</code> (this skill)</td>\n<td>Full control, persistent models, shortest paths, connected components, custom schemas</td>\n</tr>\n<tr>\n<td><code>Lift_To_Graph</code> (<code>azure-kusto-irql-graph</code>)</td>\n<td>Quick icon-decorated visualization in Kusto Explorer, node folding</td>\n</tr>\n<tr>\n<td>IRQL <code>Get_*</code> -&gt; <code>make-graph</code></td>\n<td>IRQL's unified schema as input, then raw graph operators for analysis</td>\n</tr>\n<tr>\n<td>IRQL <code>Get_*</code> -&gt; <code>Lift_To_Graph</code> -&gt; <code>Graph_Render_View</code></td>\n<td>Fastest path from question to visual graph</td>\n</tr>\n</tbody>\n</table>\n<blockquote>\n<p><strong>Note:</strong> <code>Lift_To_Graph</code>, <code>Graph_Render_View</code>, and <code>Graph_Fold_By_Property</code> are stored functions, not built-in operators. They are pre-deployed on the kc7001 example cluster but may need deployment on other clusters. See <code>azure-kusto-irql-graph/references/DEPLOY_IRQL_FUNCTIONS.md</code> for function definitions and deployment instructions.</p>\n</blockquote>\n<h3>Example: IRQL selectors -&gt; make-graph -&gt; shortest path</h3>\n<p>IRQL handles the data retrieval; <code>make-graph</code> handles the graph analysis. This finds the shortest path from an external IP to a mail server through auth events:</p>\n<pre><code>// IRQL provides unified columns (ClientIp, Hostname, Username, Result)\nlet auth = Get_Event_Authentication_All\n    | where Result == \"Failed Login\";\nlet edges = auth\n    | summarize Failures = count() by ClientIp, Hostname;\nlet nodes = union\n    (edges | distinct ClientIp | project nodeId = ClientIp, nodeType = \"IP\"),\n    (edges | distinct Hostname | project nodeId = Hostname, nodeType = \"Host\");\nedges\n| make-graph ClientIp --&gt; Hostname with nodes on nodeId\n| graph-shortest-paths (src)-[e*1..5]-&gt;(dest)\n    where src.nodeType == \"IP\" and dest.nodeId == \"MAIL-SERVER01\"\n    project\n        SourceIP = src.nodeId,\n        PathLength = array_length(e),\n        Hops = e.Hostname\n</code></pre>\n<h3>Example: IRQL selectors -&gt; make-graph -&gt; connected components</h3>\n<p>Find clusters of IPs and domains that are interconnected -- potential C2 infrastructure:</p>\n<pre><code>let dns = Get_Dns_All;\nlet edges = dns | project Source = ClientIp, Target = Domain;\nlet nodes = union\n    (edges | distinct Source | project nodeId = Source, nodeType = \"IP\"),\n    (edges | distinct Target | project nodeId = Target, nodeType = \"Domain\");\nedges\n| make-graph Source --&gt; Target with nodes on nodeId\n| graph-mark-components with_component_id = ComponentId\n| graph-to-table nodes\n| summarize\n    IPs = make_set_if(nodeId, nodeType == \"IP\"),\n    Domains = make_set_if(nodeId, nodeType == \"Domain\"),\n    Size = count()\n  by ComponentId\n| where Size &gt; 3\n| order by Size desc\n</code></pre>\n<h3>Example: IRQL + make-graph integration</h3>\n<p>See <a href=\"references/EXAMPLES.md\">references/EXAMPLES.md</a> for multi-source investigation graphs combining IRQL selectors with <code>make-graph</code>, and <code>Lift_To_Graph</code> visual graph examples.</p>\n<h2>Practical Usage Scenarios</h2>\n<p>See <a href=\"references/SCENARIOS.md\">references/SCENARIOS.md</a> for full worked examples including:</p>\n<ul>\n<li>Reachability analysis (shortest paths to critical assets)</li>\n<li>Network segmentation validation (connected components)</li>\n<li>Blast radius of compromised accounts (variable-length path matching)</li>\n<li>Persistent graph models for SOC teams (graph_model + snapshots)</li>\n</ul>\n<h2>MCP Tools Used</h2>\n<table>\n<thead>\n<tr>\n<th>Tool</th>\n<th>Purpose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>kusto_query</code></td>\n<td>Execute KQL queries including <code>make-graph</code>, <code>graph-match</code>, and management commands</td>\n</tr>\n<tr>\n<td><code>kusto_table_schema_get</code></td>\n<td>Discover table columns before building edge/node projections</td>\n</tr>\n<tr>\n<td><code>kusto_cluster_list</code></td>\n<td>List available ADX clusters</td>\n</tr>\n<tr>\n<td><code>kusto_database_list</code></td>\n<td>List databases in a cluster</td>\n</tr>\n</tbody>\n</table>\n<h2>Opening Queries in Kusto Explorer (Windows Only)</h2>\n<blockquote>\n<p><strong>Optional convenience feature.</strong> The default workflow is to output the KQL in chat and let the user copy it into Kusto Explorer or the VS Code Kusto extension manually. Auto-launch is opt-in only.</p>\n</blockquote>\n<h3>Default: Output KQL in Chat</h3>\n<p>Always output the complete KQL with Step 1 (connect) and Step 2 (query) clearly labeled:</p>\n<pre><code>// Step 1: Connect to your cluster (skip if already connected)\n// Example: uncomment to connect to the KC7 training cluster\n// #connect cluster('kc7001.eastus.kusto.windows.net').database('ValdyTimes')\n// Or replace with your own cluster:\n// #connect cluster('&lt;YOUR_CLUSTER&gt;').database('&lt;YOUR_DATABASE&gt;')\n\n// Step 2: Run the query below\n&lt;KQL_QUERY ending at make-graph&gt;\n</code></pre>\n<p>Then immediately below, output an <strong>ADX Web Explorer version</strong> that appends <code>| graph-to-table nodes as N, edges as E</code> since ADX Web Explorer cannot render <code>make-graph</code> directly:</p>\n<pre><code>// ADX Web Explorer version (tabular output):\n&lt;SAME_QUERY&gt;\n| graph-to-table nodes as N, edges as E\n</code></pre>\n<p>This ensures the output works in both Kusto Explorer (graph visualization) and ADX Web Explorer (tabular results) without the user having to modify anything.</p>\n<h3>Optional: Save and Launch</h3>\n<p>If the user asks to save or open the query in Kusto Explorer, follow the procedure in <a href=\"references/KUSTO_EXPLORER_LAUNCH.md\">references/KUSTO_EXPLORER_LAUNCH.md</a>. Key rules:</p>\n<ul>\n<li><strong>Always</strong> use <code>ask_user</code> to confirm before writing files or launching executables</li>\n<li><strong>Always</strong> display the file contents in chat so the user can review before opening</li>\n<li><strong>Never</strong> use shell interpolation or here-strings — write files via <code>Set-Content</code>/<code>Add-Content</code></li>\n<li><strong>Never</strong> encode queries into browser URLs</li>\n<li>On macOS/Linux, save the <code>.kql</code> file and suggest the VS Code Kusto extension or ADX Web Explorer</li>\n</ul>\n<p>For <code>make-graph</code> visualization (the graph window), the query must <strong>end at <code>make-graph</code></strong> — do not pipe to <code>graph-match</code>. Kusto Explorer only opens the graph visualization window when the output is a graph object, not a table.</p>\n","files":[{"path":"references/EXAMPLES.md","sizeBytes":2975,"isText":true},{"path":"references/KUSTO_EXPLORER_LAUNCH.md","sizeBytes":2659,"isText":true},{"path":"references/SCENARIOS.md","sizeBytes":3853,"isText":true},{"path":"SKILL.md","sizeBytes":19229,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-12T21:53:01.73659Z","sha256":"039D6C271F43A32DC13ED711E287B53EC09F6C65259B8B7BB1E40F20FAADB699","sizeBytes":10852},"review":null,"source":{"repositoryUrl":"https://github.com/microsoft/skills","path":".github/plugins/azure-kusto-graph-skills/skills/azure-kusto-graph","license":"MIT","commit":"ce7edea90860e0c69fa36db164584c87908e09f5","subtreeSha":"0942C057FD0F1CE1A8C1D20035ED4EC73162E51D8AC6DBF60BDD5BD151F30472","lastSyncedAt":"2026-10-03T15:23:32.814566Z"},"reviewedAt":"2026-08-12T22:00:21.742245Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-kusto-graph-skills/skills/azure-kusto-graph"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart"},{"target":"git","command":"git clone https://github.com/microsoft/skills.git"}]}