Skip to content

Commit 6889d71

Browse files
miguelmartin-elasticclaudekibanamachinedominiqueclarke
authored
[alerting_v2] Register rules as SML type and attachment in Agent Builder (elastic#265363)
## Summary Resolves elastic#265349 Registers Alerting V2 rules as an SML type and attachment in the Agent Builder platform, enabling an end-to-end flow where the agent can propose, create, and manage rules through conversation. https://github.com/user-attachments/assets/aaa8f900-825f-4820-bc6e-a0604572bdee ### Flow 1. **Discovery** — Rules are registered as an SML type. The agent can search for existing rules and attach them to a conversation. 2. **Proposal** — The agent uses the `manage_rule` tool to propose a rule definition. This creates an inline attachment visible in the conversation. 3. **Preview** — The user clicks "Preview" to open the canvas flyout, which renders the full `RuleSidebar` (Conditions + Runbook tabs) and `RuleHeaderDescription` (description + tags) from the rule details page — the same components used in the standalone rules app. 4. **Save / Update** — The canvas header exposes: - **Save as Rule** (unsaved): persists the agent's proposal to the server. - **Update Rule** (saved): pushes agent-revised data back to the server. - **View in Rules** (overflow): navigates to the rule details page. ### Implementation - **Server**: SML rule type for discovery, rule attachment type with create/update/delete/enable/disable operations, `manage_rule` tool, and a `rule-management` skill. - **UI**: Inline attachment card (name, status badge, kind badge, schedule, description, tags) and canvas flyout embedding `RuleSidebar` + `RuleHeaderDescription` inside the alerting_v2 DI container context. - **DI fix**: The canvas flyout renders in the Agent Builder React tree (no alerting_v2 DI container). `RuleCanvasContent` wraps its subtree with `<Context.Provider value={container}>` so `useService` calls inside `RuleSidebar` resolve correctly. ### Screenshot <img width="1928" height="910" alt="image" src="https://github.com/user-attachments/assets/de56dfab-19be-460d-a43f-d8ce08b5c888" /> ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The \`release_note:breaking\` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct \`release_note:*\` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable \`backport:*\` labels. ### Identify risks - The `data as unknown as RuleApiResponse` cast in `RuleCanvasContent` bridges the gap between optional attachment data fields and required API response fields. If the agent omits a required field, `RuleSidebar` may render incomplete data — acceptable for a preview-only PR. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dominique Belcher <dominique.clarke@elastic.co> Co-authored-by: Dominique Clarke <doclarke71@gmail.com>
1 parent d22f5eb commit 6889d71

35 files changed

Lines changed: 3038 additions & 51 deletions

packages/kbn-optimizer/limits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pageLoadAssetSize:
77
aiAssistantManagementSelection: 11569
88
aiops: 15227
99
alerting: 22371
10-
alertingVTwo: 47881
10+
alertingVTwo: 76778
1111
apm: 27979
1212
apmSourcesAccess: 2278
1313
automaticImport: 18629

x-pack/platform/packages/shared/agent-builder/agent-builder-common/base/namespaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212
export const internalNamespaces = {
1313
platformCore: 'platform.core',
14+
platformAlerting: 'platform.alerting',
1415
platformDashboard: 'platform.dashboard',
1516
platformStreams: 'platform.streams',
1617
filestore: 'filestore',
@@ -27,6 +28,7 @@ export const internalNamespaces = {
2728
*/
2829
export const protectedNamespaces: string[] = [
2930
internalNamespaces.platformCore,
31+
internalNamespaces.platformAlerting,
3032
internalNamespaces.attachments,
3133
internalNamespaces.filestore,
3234
internalNamespaces.observability,

x-pack/platform/packages/shared/agent-builder/agent-builder-server/allow_lists.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export const AGENT_BUILDER_BUILTIN_TOOLS = [
1818
// Streams / Significant Events
1919
...Object.values(platformStreamsSigEventsTools),
2020

21+
// Alerting
22+
`${internalNamespaces.platformAlerting}.manage_rule`,
23+
2124
// Observability
2225
`${internalNamespaces.observability}.get_anomaly_detection_jobs`,
2326
`${internalNamespaces.observability}.run_log_rate_analysis`,
@@ -102,6 +105,9 @@ export const AGENT_BUILDER_BUILTIN_SKILLS = [
102105
'visualization-creation',
103106
'graph-creation',
104107

108+
// Platform – Alerting
109+
'rule-management',
110+
105111
// Platform – Dashboard
106112
'dashboard-management',
107113

x-pack/platform/packages/shared/agent-builder/agent-builder-server/skills/type_definition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
export type SkillsDirectoryStructure = Directory<{
2727
skills: Directory<{
2828
platform: FileDirectory<{
29+
alerting: FileDirectory;
2930
dashboard: FileDirectory;
3031
discover: FileDirectory;
3132
streams: FileDirectory;

x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
*/
77

88
export * from './rule_data_schema';
9+
export * from './rule_attachment_schema';
910
export * from './constants';
11+
export { durationSchema } from './common';
1012
export {
1113
validateDuration,
1214
validateMaxDuration,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { z } from '@kbn/zod/v4';
9+
import { ruleResponseSchema } from './rule_data_schema';
10+
11+
export const RULE_ATTACHMENT_TYPE = 'rule' as const;
12+
export const RULE_SML_TYPE = 'alerting_v2_rule' as const;
13+
14+
/**
15+
* Data stored inside a rule attachment.
16+
*
17+
* Server-generated fields (id, enabled, createdBy, createdAt, updatedBy, updatedAt)
18+
* are optional so that the same schema covers both:
19+
* - proposed rules (by-value, not yet saved — no id or audit fields)
20+
* - saved rules (by-reference, linked via attachment.origin = rule saved object id)
21+
*/
22+
export const ruleAttachmentDataSchema = ruleResponseSchema.extend({
23+
id: z.string().optional(),
24+
enabled: z.boolean().optional(),
25+
createdBy: z.string().nullable().optional(),
26+
createdAt: z.string().optional(),
27+
updatedBy: z.string().nullable().optional(),
28+
updatedAt: z.string().optional(),
29+
});
30+
31+
export type RuleAttachmentData = z.infer<typeof ruleAttachmentDataSchema>;

x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/rule_data_schema.ts

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { MAX_CONSECUTIVE_BREACHES, MIN_SCHEDULE_INTERVAL } from './constants';
1818

1919
/** Primitives */
2020

21-
const esqlQuerySchema = z
21+
export const esqlQuerySchema = z
2222
.string()
2323
.min(1)
2424
.max(10000)
@@ -31,54 +31,61 @@ const esqlQuerySchema = z
3131

3232
/** Kind */
3333

34-
export const ruleKindSchema = z.enum(['alert', 'signal']).describe('The kind of rule.');
34+
export const ruleKindSchema = z
35+
.enum(['alert', 'signal'])
36+
.describe(
37+
'Rule kind: "alert" for stateful alerting with transitions, "signal" for stateless detection.'
38+
);
3539

3640
export type RuleKind = z.infer<typeof ruleKindSchema>;
3741

3842
/** Metadata (required) */
3943

40-
const metadataSchema = z
44+
export const metadataSchema = z
4145
.object({
42-
name: z.string().min(1).max(256).describe('Unique rule name/identifier.'),
46+
name: z.string().min(1).max(256).describe('Rule name (must be unique within the space).'),
4347
description: z
4448
.string()
4549
.max(1024)
4650
.optional()
47-
.describe('Optional human-readable description of the rule.'),
51+
.describe('Human-readable description of the rule.'),
4852
owner: z.string().max(256).optional().describe('Owner of the rule.'),
4953
tags: z
5054
.array(z.string().max(MAX_TAG_LENGTH))
5155
.max(100)
5256
.optional()
53-
.describe('Tags for categorization.'),
57+
.describe('Tags for categorization, e.g. ["production", "infra"].'),
5458
})
5559
.strict()
5660
.describe('Rule metadata.');
5761

5862
/** Schedule (required) */
5963

60-
const scheduleEverySchema = durationSchema.superRefine((value, ctx) => {
64+
/** Duration with an additional minimum-interval guard for schedule frequency. */
65+
export const scheduleEverySchema = durationSchema.superRefine((value, ctx) => {
6166
const error = validateMinDuration(value, MIN_SCHEDULE_INTERVAL);
6267
if (error) {
6368
ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
6469
}
6570
});
6671

67-
const scheduleSchema = z
72+
export const scheduleSchema = z
6873
.object({
69-
every: scheduleEverySchema.describe('Execution interval, e.g. 1m, 5m.'),
74+
every: scheduleEverySchema.describe('Execution interval, e.g. 1m, 5m, 1h.'),
7075
lookback: durationSchema
7176
.optional()
72-
.describe('Lookback window for the query (can also be expressed in ES|QL).'),
77+
.describe('Lookback window for the query, e.g. 5m, 1h. Can also be expressed in ES|QL.'),
7378
})
7479
.strict()
7580
.describe('Execution schedule configuration.');
7681

7782
/** Evaluation (required) */
7883

79-
const evaluationQuerySchema = z
84+
export const evaluationQuerySchema = z
8085
.object({
81-
base: esqlQuerySchema.describe('Base ES|QL query.'),
86+
base: esqlQuerySchema.describe(
87+
'Base ES|QL query. Time filters are applied automatically via the lookback window.'
88+
),
8289
})
8390
.strict();
8491

@@ -95,25 +102,27 @@ export const recoveryPolicyTypeSchema = z.enum(['query', 'no_breach']);
95102
export const recoveryPolicyType = recoveryPolicyTypeSchema.enum;
96103
export type RecoveryPolicyType = z.infer<typeof recoveryPolicyTypeSchema>;
97104

98-
const recoveryPolicySchema = z
105+
export const recoveryPolicySchema = z
99106
.object({
100-
type: recoveryPolicyTypeSchema.describe('Recovery detection type.'),
107+
type: recoveryPolicyTypeSchema.describe('Recovery detection type: "query" or "no_breach".'),
101108
query: z
102109
.object({
103-
base: esqlQuerySchema.optional().describe('Base ES|QL query for recovery.'),
110+
base: esqlQuerySchema
111+
.optional()
112+
.describe('Recovery ES|QL query. Required when type is "query".'),
104113
})
105114
.strict()
106115
.optional()
107-
.describe('Recovery query when type is query.'),
116+
.describe('Recovery query configuration; required when type is "query".'),
108117
})
109118
.strict()
110119
.describe('Recovery detection configuration.');
111120

112121
/** State transition (optional, alert-only) */
113122

114-
const stateTransitionOperatorSchema = z.enum(['AND', 'OR']);
123+
export const stateTransitionOperatorSchema = z.enum(['AND', 'OR']);
115124

116-
const stateTransitionSchema = z
125+
export const stateTransitionSchema = z
117126
.object({
118127
pending_operator: stateTransitionOperatorSchema
119128
.optional()
@@ -124,8 +133,10 @@ const stateTransitionSchema = z
124133
.min(0)
125134
.max(MAX_CONSECUTIVE_BREACHES)
126135
.optional()
127-
.describe('Consecutive breaches before active.'),
128-
pending_timeframe: durationSchema.optional().describe('Time window for pending evaluation.'),
136+
.describe('Consecutive breaches before transitioning to active.'),
137+
pending_timeframe: durationSchema
138+
.optional()
139+
.describe('Time window for pending evaluation, e.g. 5m, 15m.'),
129140
recovering_operator: stateTransitionOperatorSchema
130141
.optional()
131142
.describe('How to combine count and timeframe for recovering.'),
@@ -135,10 +146,10 @@ const stateTransitionSchema = z
135146
.min(0)
136147
.max(MAX_CONSECUTIVE_BREACHES)
137148
.optional()
138-
.describe('Consecutive recoveries before inactive.'),
149+
.describe('Consecutive recoveries before transitioning to inactive.'),
139150
recovering_timeframe: durationSchema
140151
.optional()
141-
.describe('Time window for recovering evaluation.'),
152+
.describe('Time window for recovering evaluation, e.g. 5m, 15m.'),
142153
})
143154
.strict()
144155
.describe('Episode state transition thresholds (alert-only).')
@@ -147,12 +158,14 @@ const stateTransitionSchema = z
147158

148159
/** Grouping (optional) */
149160

150-
const groupingSchema = z
161+
export const groupingSchema = z
151162
.object({
152163
fields: z
153164
.array(z.string().max(256))
154165
.max(16)
155-
.describe('Fields to group by (convention: use ES|QL GROUP BY fields).'),
166+
.describe(
167+
'Fields to group alerts by, e.g. ["host.name", "service.name"]. Should match ES|QL GROUP BY fields.'
168+
),
156169
})
157170
.strict()
158171
.describe('Grouping configuration.');
@@ -165,7 +178,9 @@ const noDataSchema = z
165178
.enum(['no_data', 'last_status', 'recover'])
166179
.optional()
167180
.describe('Behavior when no data is detected.'),
168-
timeframe: durationSchema.optional().describe('Time window after which no data is detected.'),
181+
timeframe: durationSchema
182+
.optional()
183+
.describe('Time window after which no data is detected, e.g. 10m, 1h.'),
169184
})
170185
.strict()
171186
.describe('No data handling configuration.');
@@ -217,28 +232,28 @@ const createRuleDataBaseSchema = z
217232
})
218233
.strip();
219234

220-
/**
221-
* The `.refine` method adds a custom validation to the schema.
222-
* In this case, it enforces that the `state_transition` property is only allowed when `kind` is "alert".
223-
* The predicate `data.kind === 'alert' || data.state_transition == null` means:
224-
* - If the rule kind is "alert", `state_transition` may be present (or absent).
225-
* - For any other `kind`, `state_transition` must be `null` or `undefined`.
226-
* If validation fails, the specified error message will be associated with the `state_transition` field.
227-
*/
235+
/** Cross-field validation predicates — shared between the CRUD API and the manage_rule tool. */
236+
237+
export const isStateTransitionAllowed = (data: {
238+
kind?: string;
239+
state_transition?: unknown;
240+
}): boolean => data.kind === 'alert' || data.state_transition == null;
241+
242+
export const isRecoveryPolicyQueryProvided = (data: {
243+
recovery_policy?: { type?: string; query?: { base?: string } };
244+
}): boolean =>
245+
data.recovery_policy?.type !== 'query' ||
246+
(data.recovery_policy.query?.base != null && data.recovery_policy.query.base.length > 0);
247+
228248
export const createRuleDataSchema = createRuleDataBaseSchema
229-
.refine((data) => data.kind === 'alert' || data.state_transition == null, {
249+
.refine(isStateTransitionAllowed, {
230250
message: 'state_transition is only allowed when kind is "alert".',
231251
path: ['state_transition'],
232252
})
233-
.refine(
234-
(data) =>
235-
data.recovery_policy?.type !== 'query' ||
236-
(data.recovery_policy.query?.base != null && data.recovery_policy.query.base.length > 0),
237-
{
238-
message: 'recovery_policy.query.base is required when recovery_policy.type is "query".',
239-
path: ['recovery_policy', 'query', 'base'],
240-
}
241-
);
253+
.refine(isRecoveryPolicyQueryProvided, {
254+
message: 'recovery_policy.query.base is required when recovery_policy.type is "query".',
255+
path: ['recovery_policy', 'query', 'base'],
256+
});
242257

243258
export type CreateRuleData = z.infer<typeof createRuleDataSchema>;
244259

x-pack/platform/plugins/shared/alerting_v2/kibana.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"eventLog",
3232
"unifiedDocViewer"
3333
],
34-
"optionalPlugins": ["usageCollection"],
34+
"optionalPlugins": ["usageCollection", "agentBuilder", "agentContextLayer"],
3535
"requiredBundles": ["alerting", "kibanaReact"],
3636
"extraPublicDirs": []
3737
}

x-pack/platform/plugins/shared/alerting_v2/moon.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ dependsOn:
103103
- '@kbn/unified-doc-viewer-plugin'
104104
- '@kbn/core-user-profile-browser'
105105
- '@kbn/user-profile-components'
106+
- '@kbn/agent-builder-plugin'
107+
- '@kbn/agent-builder-server'
108+
- '@kbn/agent-builder-common'
109+
- '@kbn/agent-builder-browser'
110+
- '@kbn/agent-context-layer-plugin'
111+
- '@kbn/core-saved-objects-api-server'
112+
- '@kbn/core-elasticsearch-server'
106113
tags:
107114
- plugin
108115
- prod

0 commit comments

Comments
 (0)