Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export const platformStreamsSigEventsTools = {
searchKnowledgeIndicators: `${internalNamespaces.platformStreams}.sig_events.ki_search`,
createFeatureKnowledgeIndicator: `${internalNamespaces.platformStreams}.sig_events.ki_feature_create`,
createQueryKnowledgeIndicator: `${internalNamespaces.platformStreams}.sig_events.ki_query_create`,
searchEvent: `${internalNamespaces.platformStreams}.sig_events.event_search`,
createEvent: `${internalNamespaces.platformStreams}.sig_events.event_create`,
updateEventVerdict: `${internalNamespaces.platformStreams}.sig_events.event_verdict_update`,
} as const;

export const attachmentTools = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export const AGENT_BUILDER_BUILTIN_SKILLS = [
// Platform – Streams
'streams-management',
'significant-events-memory',
'significant-events-management',
'knowledge-indicators-management',
'ki-identification-management',

Expand Down
6 changes: 6 additions & 0 deletions x-pack/platform/packages/shared/kbn-streams-schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,14 @@ export {
type Discovery,
verdictSchema,
type Verdict,
SIG_EVENT_VERDICT_OPTIONS,
SIG_EVENT_IMPACT_OPTIONS,
sigEventSchema,
sigEventVerdictSchema,
sigEventImpactSchema,
type SigEvent,
type SigEventVerdict,
type SigEventImpact,
} from './src/sig_events';
export type {
StreamsKIsOnboardingResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,33 @@
evidenceSchema,
} from '../common_schemas';

export const SIG_EVENT_VERDICT_OPTIONS = ['promoted', 'acknowledged', 'demoted'] as const;
export const sigEventVerdictSchema = z.enum(SIG_EVENT_VERDICT_OPTIONS);
export type SigEventVerdict = z.infer<typeof sigEventVerdictSchema>;

export const SIG_EVENT_IMPACT_OPTIONS = ['critical', 'high', 'medium', 'low'] as const;
export const sigEventImpactSchema = z.enum(SIG_EVENT_IMPACT_OPTIONS);
export type SigEventImpact = z.infer<typeof sigEventImpactSchema>;

export const sigEventSchema = z.object({
'@timestamp': z.iso.datetime(),
created_at: z.iso.datetime(),
event_id: z.string(),
discovery_id: z.string(),
discovery_id: z.string().optional(),

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
discovery_slug: z.string(),
previous_event_id: z.string().optional(),
verdict: z.string(),
verdict_id: z.string(),
workflow_execution_id: z.string(),
rule_names: z.array(z.string()),
verdict: sigEventVerdictSchema,
verdict_id: z.string().optional(),

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
workflow_execution_id: z.string().optional(),

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
rule_names: z.array(z.string()).optional(),

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
stream_names: z.array(z.string()),
title: z.string(),
summary: z.string(),
root_cause: z.string(),
criticality: z.number(),
confidence: z.number(),
recommended_action: z.string(),
impact: z.string(),
recommended_action: z.string().optional(),

Check failure

Code scanning / CodeQL

Unbounded string in schema validation High

This z.string() call does not specify a .max() constraint. Unbounded string input can cause Denial of Service (DoS) vulnerabilities. Consider adding .max(N) to the method chain.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
impact: sigEventImpactSchema,
recommendations: z.array(z.string()),
dependency_edges: z.array(dependencyEdgeSchema).optional(),
infra_components: z.array(infraComponentSchema).optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@
export { detectionSchema, type Detection } from './detections';
export { discoverySchema, type Discovery } from './discoveries';
export { verdictSchema, type Verdict } from './verdicts';
export { sigEventSchema, type SigEvent } from './events';
export {
SIG_EVENT_VERDICT_OPTIONS,
SIG_EVENT_IMPACT_OPTIONS,
sigEventSchema,
sigEventVerdictSchema,
sigEventImpactSchema,
type SigEvent,
type SigEventVerdict,
type SigEventImpact,
} from './events';
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { StreamsKIsOnboardingClient } from '../../lib/workflows/onboarding_
import { streamsManagementSkill } from './streams_management_skill';
import { knowledgeIndicatorsManagementSkill } from './knowledge_indicators_management';
import { createKiIdentificationManagementSkill } from './ki_identification_management';
import { sigEventsManagementSkill } from './sig_events_management';

export const registerAgentBuilderSkills = ({
agentBuilder,
Expand All @@ -28,6 +29,7 @@ export const registerAgentBuilderSkills = ({
const streamsSkills = [
streamsManagementSkill,
knowledgeIndicatorsManagementSkill,
sigEventsManagementSkill,
...(streamsKIsOnboardingClient
? [createKiIdentificationManagementSkill({ telemetry, streamsKIsOnboardingClient })]
: []),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Search, create, and update significant events for Streams, with guidance to avoid duplicates and keep event lifecycle state accurate.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { defineSkillType } from '@kbn/agent-builder-server/skills/type_definition';
import {
STREAMS_CREATE_EVENT_TOOL_ID,
STREAMS_EVENT_VERDICT_UPDATE_TOOL_ID,
STREAMS_SEARCH_EVENTS_TOOL_ID,
} from '../../tools/register_tools';
import description from './description.text';
import content from './skill.md.text';

export const sigEventsManagementSkill = defineSkillType({
id: 'significant-events-management',
name: 'significant-events-management',
basePath: 'skills/platform/streams',
description,
content,
getRegistryTools: () => [
STREAMS_SEARCH_EVENTS_TOOL_ID,
STREAMS_CREATE_EVENT_TOOL_ID,
STREAMS_EVENT_VERDICT_UPDATE_TOOL_ID,
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
You manage Significant Events (SigEvents) for Streams.
Copy link
Copy Markdown
Contributor

@crespocarlos crespocarlos May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: was this based on the current investigator or judge agent instructions?

it will be great to use this in the workflows

Copy link
Copy Markdown
Contributor Author

@mykolaharmash mykolaharmash May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really based on the agents, I just tried to tune it to the conversation flow with agent discovering and proactively suggesting sig events to the user. LLM generated the first draft and I tweaked it after experimenting with the chat.


<significant_events_concept>
What a Significant Event is:
- A SigEvent is a durable incident-level summary of an important operational issue.
- It captures what happened, why it happened, impact, confidence/criticality, and supporting context.
- SigEvents should be actionable and non-duplicative.

Examples of conversation signals that may indicate a SigEvent:
- Recurring service degradation with clear customer/user impact.
- Security or compliance risk with confirmed evidence.
- Cross-stream failures that share a common root cause.
- Repeated alerting patterns that represent a known incident class.
</significant_events_concept>

<available_tools>
You have 3 SigEvents tools in this skill:

- `event_search`
Use this first to find existing similar significant events.
`query` is optional; if omitted (or empty), search uses optional stream and verdict filters only.
`stream_name` is optional; omit it for cross-stream searches.

- `event_create`
Use this only after confirming there is no similar existing event.
The system generates internal identifiers and timestamps automatically.

- `event_verdict_update`
Use this to update an existing event verdict to one of: `promoted`, `acknowledged`, `demoted`.
If an event already has the requested verdict or is missing, update is ignored.

Additional globally-available helper guidance:
- `ki_search` can be used to gather existing Knowledge Indicators (KIs) for context.
- `execute_esql` can be used to confirm the event and collect evidence rows.
</available_tools>

<required_workflow>
Always follow this workflow:
1. Detect potential significant-event signals from the conversation.
2. Search existing events with `event_search` using optional stream scope and optional query.
3. Compare returned events for semantic similarity (same issue class/root cause/impact).
4. If a similar event exists, do not create a duplicate; reference the existing event.
5. If no similar event exists, investigate and validate:
- Use `ki_search` to collect related Feature and Query KIs.
- Use `execute_esql` to confirm the issue and gather supporting facts.
6. Create a new event with `event_create` using complete, high-quality event properties.

Verdict update workflow:
1. Gather evidence supporting the target event state.
2. Choose verdict based on intent:
- `promoted`: incident-worthy and active.
- `acknowledged`: known and actively tracked.
- `demoted`: no longer incident-worthy.
3. Call `event_verdict_update` with the event id and target verdict.
4. Treat `{ updated: 0, ignored: 1 }` as expected when event is missing or already in the requested verdict.
</required_workflow>

<proactive_behavior>
Be proactive.

When conversation context and evidence strongly suggest a new significant event, suggest creating one.

Proactive pattern:
1. State why current signals indicate a likely new significant event.
2. Run `event_search` to check for similar existing events.
3. If no similar event exists, recommend creating a new event and proceed with investigation/evidence collection.
4. Use `event_create` with a complete payload.
</proactive_behavior>

<ki_and_evidence_guidance>
Using KIs for context:
- Prefer KI context before writing root cause and impact.
- Reuse KI terminology in `summary`, `root_cause`, and `impact` when relevant.

Using ES|QL for confirmation:
- Run focused ES|QL queries that directly test the suspected issue.
- Capture concise evidence in narrative form inside `summary`, `root_cause`, and `impact`.
- If detailed evidence structures are needed, ensure tool support exists before attempting to submit them.
</ki_and_evidence_guidance>

<sig_event_property_guidance>
Populate event properties with care:

- `verdict`:
- For new events, default to `promoted` unless user intent says otherwise.

- `title`:
- Short, specific, human-readable incident title.

- `summary`:
- Brief narrative: what happened, where, and key signal.

- `root_cause`:
- Best-known causal explanation; avoid vague language.

- `stream_names`:
- Include all affected streams.

- `criticality`:
- Number in range 0..100 indicating system criticality.
- Suggested scale:
- 0-30 low
- 31-60 medium
- 61-80 high
- 81-100 critical
Comment on lines +99 to +105
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just FYI: criticality will be renamed to severity soon.


- `confidence`:
- Required. Float in range 0..1 indicating confidence in the event assessment.

- `impact`:
- Always set to exactly one of: `critical`, `high`, `medium`, `low`.
Comment on lines +110 to +111
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making LLMs assign impact makes me a bit nervous. This was something done for the prototype, but we need to review if we want this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see, if we decide to get rid of it, I'll follow up with the skill change.


- `recommendations`:
- Required. List of descriptive, detailed mitigation steps.
</sig_event_property_guidance>

<tool_examples>
Example: search existing events first
Tool: `event_search`
{
"query": "checkout latency spike with timeout burst",
"stream_name": "logs.checkout",
"verdict": ["promoted", "acknowledged"]
}

Example: filter-only search (no query text)
Tool: `event_search`
{
"stream_name": "logs.checkout",
"verdict": ["promoted", "acknowledged"]
}

Example: cross-stream search (no stream_name)
Tool: `event_search`
{
"query": "timeout burst",
"verdict": ["promoted", "acknowledged"]
}

Example: create a new significant event
Tool: `event_create`
{
"verdict": "promoted",
"title": "Checkout timeout spike under upstream latency",
"summary": "Checkout experienced a sustained timeout spike correlated with upstream latency increase.",
"root_cause": "Upstream dependency latency exceeded service timeout budget during peak load.",
"stream_names": ["logs.checkout", "logs.payment"],
"criticality": 86,
"confidence": 0.84,
"impact": "high",
"recommendations": [
"Add alert on upstream latency threshold breach",
"Review timeout configuration for checkout service"
]
}

Example: update an existing significant event verdict
Tool: `event_verdict_update`
{
"event_id": "9c4d04a1-86fe-45c2-b1dd-00891a8ba9f1",
"verdict": "acknowledged"
}
</tool_examples>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { createEventToolHandler } from './handler';

describe('createEventToolHandler', () => {
it('creates a single event', async () => {
const eventClient = {
bulkCreate: jest.fn().mockResolvedValue({}),
};

const result = await createEventToolHandler({
eventClient: eventClient as never,
eventInput: {
title: 'T',
summary: 'S',
root_cause: 'R',
stream_names: ['logs.a'],
criticality: 60,
impact: 'high',
confidence: 0.7,
recommendations: ['create incident'],
},
});

expect(eventClient.bulkCreate).toHaveBeenCalledTimes(1);
expect(eventClient.bulkCreate).toHaveBeenCalledWith([
expect.objectContaining({
discovery_slug: expect.stringMatching(/^agent-event-[a-f0-9]{8}$/),
}),
]);
expect(result.acknowledged).toBe(true);
expect(result.event_id).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { v4 as uuidv4 } from 'uuid';
import type { SigEvent, SigEventImpact, SigEventVerdict } from '@kbn/streams-schema';
import type { EventClient } from '../../../lib/sig_events/events';

export interface EventCreateInput {
verdict?: SigEventVerdict;
title: string;
summary: string;
root_cause: string;
stream_names: string[];
criticality: number;
impact: SigEventImpact;
confidence: number;
recommendations: string[];
}

export async function createEventToolHandler({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To confirm, we'll have events without a corresponding discovery and detections. correct? I wonder what will happen to the workflows once it sees these docs.

Copy link
Copy Markdown
Contributor Author

@mykolaharmash mykolaharmash May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct. Would it make sense to add some ignore logic into the workflow for this kind of "manual" events?

eventClient,
eventInput,
}: {
eventClient: EventClient;
eventInput: EventCreateInput;
}): Promise<{ event_id: string; acknowledged: true }> {
const now = new Date().toISOString();
const eventId = uuidv4();

const event: SigEvent = {
'@timestamp': now,
created_at: now,
event_id: eventId,
discovery_slug: `agent-event-${eventId.slice(0, 8)}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also diverge from what discovery workflows expect. We'll need to find better ways for this as a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, agree, this field bothers me as well. I'll update the tool and the skill once we settle on a better solution, for now I added it because we use discovery_slug as a grouping key.

verdict: eventInput.verdict ?? 'promoted',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the default be promoted?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, if the user decides explicitly to create a sig event, I assume it's about something ongoing that they just discovered.

Though there might be cases when user wants to save a sig event as a draft and do additional investigation before promoting it, we might need to introduce this additional draft state. WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could leave it as-is and iterate. I'm keen to use it in the sigevent agents; eventually, these things will be refined.

stream_names: eventInput.stream_names,
title: eventInput.title,
summary: eventInput.summary,
root_cause: eventInput.root_cause,
criticality: eventInput.criticality,
confidence: eventInput.confidence,
impact: eventInput.impact,
recommendations: eventInput.recommendations,
};

await eventClient.bulkCreate([event]);
return { event_id: eventId, acknowledged: true };
}
Loading
Loading