Skip to content

Commit 194407a

Browse files
[One Workflow] Register server-side agent builder tools for workflow authoring (elastic#255180)
Close elastic/security-team#15740 ## Summary Registers workflow-related tools and a skill with the Agent Builder, enabling AI-powered workflow authoring from within the Kibana AI Assistant. ### Tools added (namespace `platform.workflows.*`) | Tool | Screenshot | |---|---| | **get_step_definitions**<br/>`platform.workflows.get_step_definitions`<br/>Discover available workflow step types, their input/config params, categories, and usage examples. Supports filtering by type, keyword search, or category. Optionally returns full JSON Schema. | <img width="1600" height="1400" alt="02-get_step_definitions" src="https://github.com/user-attachments/assets/f8ecb7a5-2767-4d57-91d0-84d3bf1345be" /> | | **get_trigger_definitions**<br/>`platform.workflows.get_trigger_definitions`<br/>Look up available trigger types (manual, scheduled, alert) with schemas and YAML examples. | <img width="1600" height="1400" alt="03-get_trigger_definitions" src="https://github.com/user-attachments/assets/8358fa2a-e281-4485-a044-e11439150dff" /> | | **get_examples**<br/>`platform.workflows.get_examples`<br/>Search and retrieve example workflow YAML files from the bundled library. Supports category and keyword filtering. | <img width="1600" height="1400" alt="04-get_examples" src="https://github.com/user-attachments/assets/2d4ba4d4-7ce8-4f68-a8f9-bb36f9701718" /> | | **get_connectors**<br/>`platform.workflows.get_connectors`<br/>List connector instances configured in the user's environment (Slack, Jira, etc.) to get connector IDs for workflow steps. | <img width="1600" height="1400" alt="05-get_connectors" src="https://github.com/user-attachments/assets/26160604-7f57-47bc-92ba-936c4e6dfe8e" /> | | **validate_workflow**<br/>`platform.workflows.validate_workflow`<br/>Validate a workflow YAML string against all rules (syntax, schema, step name uniqueness, Liquid templates). | <img width="801" height="401" alt="Screenshot 2026-03-05 at 20 50 46" src="https://github.com/user-attachments/assets/d25c63da-541c-4e57-87a6-b33d898e9437" /> <img width="813" height="1021" alt="Screenshot 2026-03-05 at 20 50 37" src="https://github.com/user-attachments/assets/e2a780ec-b421-4189-b2fd-cefe52b136cc" /> | | **list_workflows**<br/>`platform.workflows.list_workflows`<br/>List workflows in the user's environment with search, tag, and status filtering. | <img width="1600" height="1400" alt="07-list_workflows" src="https://github.com/user-attachments/assets/0b3ae20e-32a6-4031-9f9f-bdabff54a464" /> | | **get_workflow**<br/>`platform.workflows.get_workflow`<br/>Retrieve a specific workflow by ID, including its full YAML definition. | <img width="1600" height="1400" alt="08-get_workflow" src="https://github.com/user-attachments/assets/11709457-46e5-48a5-a2fb-2d443533e377" /> | ### Skill added - **`workflow-authoring`** — bundles all seven tools above with a detailed system prompt covering YAML structure, step types, Liquid templating, connector usage, and self-validation workflow. ### Other changes - Added `platform.workflows` tool namespace to the Agent Builder allow-list - Exported helper utilities from `@kbn/workflows` for building agent-friendly step/trigger schemas - Added unit tests for tools --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 225a87d commit 194407a

45 files changed

Lines changed: 3259 additions & 156 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/platform/packages/shared/kbn-workflows/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,31 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
export * from './spec/lib/build_step_schema_for_agent';
1011
export * from './spec/lib/generate_yaml_schema_from_connectors';
1112
export * from './spec/lib/get_workflow_json_schema';
1213
export { getElasticsearchConnectors } from './spec/elasticsearch';
1314
export { getKibanaConnectors } from './spec/kibana';
1415
export * from './spec/schema';
1516
export { builtInStepDefinitions, getBuiltInStepDefinition } from './spec/builtin_step_definitions';
1617
export type { BuiltInStepDefinition } from './spec/builtin_step_definitions';
17-
export { StepCategory } from './spec/step_definition_types';
18+
export {
19+
builtInTriggerDefinitions,
20+
getBuiltInTriggerDefinition,
21+
} from './spec/builtin_trigger_definitions';
22+
export type {
23+
BaseTriggerDefinition,
24+
TriggerDocumentation,
25+
} from './spec/builtin_trigger_definitions';
26+
export {
27+
WORKFLOW_EXAMPLES,
28+
WORKFLOW_EXAMPLE_IDS,
29+
getWorkflowExamples,
30+
getWorkflowExample,
31+
getWorkflowExamplesDir,
32+
} from './spec/examples';
33+
export type { WorkflowExampleEntry } from './spec/examples';
34+
export { StepCategory, StepCategories } from './spec/step_definition_types';
1835
export type { BaseStepDefinition, StepDocumentation } from './spec/step_definition_types';
1936
export * from './types/latest';
2037
export * from './types/utils';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
/* eslint-disable import/no-nodejs-modules -- we need this on server to read yaml files */
11+
import { readFile } from 'fs/promises';
12+
import path from 'path';
13+
14+
import { getWorkflowExamplesDir, WORKFLOW_EXAMPLE_IDS } from '../spec/examples';
15+
16+
/**
17+
* Load the YAML content of a bundled workflow example by its catalog ID.
18+
* Returns `undefined` if the ID is not in the allowlist or the file cannot be read.
19+
*/
20+
export async function loadWorkflowExampleContent(entry: {
21+
id: string;
22+
filename: string;
23+
}): Promise<string | undefined> {
24+
if (!WORKFLOW_EXAMPLE_IDS.has(entry.id)) {
25+
return undefined;
26+
}
27+
try {
28+
return await readFile(path.join(getWorkflowExamplesDir(), entry.filename), 'utf-8');
29+
} catch {
30+
return undefined;
31+
}
32+
}

src/platform/packages/shared/kbn-workflows/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
export { ExecutionError } from './errors/execution_error';
11+
export { loadWorkflowExampleContent } from './examples';
1112
export {
1213
getStepExecutionsByIds,
1314
getStepExecutionsByWorkflowExecution,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import {
11+
builtInTriggerDefinitions,
12+
getBuiltInTriggerDefinition,
13+
} from './builtin_trigger_definitions';
14+
15+
const EXPECTED_TRIGGER_IDS = ['manual', 'scheduled', 'alert'];
16+
17+
describe('builtInTriggerDefinitions', () => {
18+
it('covers all expected trigger types', () => {
19+
const ids = builtInTriggerDefinitions.map((d) => d.id);
20+
expect(ids.sort()).toEqual([...EXPECTED_TRIGGER_IDS].sort());
21+
});
22+
23+
it.each(EXPECTED_TRIGGER_IDS)('"%s" has a non-empty description', (id) => {
24+
const def = builtInTriggerDefinitions.find((d) => d.id === id);
25+
expect(def).toBeDefined();
26+
expect(def!.description.length).toBeGreaterThan(0);
27+
});
28+
29+
it.each(EXPECTED_TRIGGER_IDS)('"%s" has a non-empty label', (id) => {
30+
const def = builtInTriggerDefinitions.find((d) => d.id === id);
31+
expect(def).toBeDefined();
32+
expect(def!.label.length).toBeGreaterThan(0);
33+
});
34+
35+
it.each(EXPECTED_TRIGGER_IDS)('"%s" has a schema with parse()', (id) => {
36+
const def = builtInTriggerDefinitions.find((d) => d.id === id);
37+
expect(def).toBeDefined();
38+
expect(typeof def!.schema.parse).toBe('function');
39+
});
40+
41+
it.each(EXPECTED_TRIGGER_IDS)('"%s" has non-empty documentation examples', (id) => {
42+
const def = builtInTriggerDefinitions.find((d) => d.id === id);
43+
expect(def).toBeDefined();
44+
expect(def!.documentation.examples.length).toBeGreaterThan(0);
45+
expect(def!.documentation.examples[0].length).toBeGreaterThan(0);
46+
});
47+
});
48+
49+
describe('getBuiltInTriggerDefinition', () => {
50+
it('returns the definition for a known id', () => {
51+
const def = getBuiltInTriggerDefinition('scheduled');
52+
expect(def).toBeDefined();
53+
expect(def!.id).toBe('scheduled');
54+
});
55+
56+
it('returns the correct definition for alert', () => {
57+
const def = getBuiltInTriggerDefinition('alert');
58+
expect(def).toBeDefined();
59+
expect(def!.id).toBe('alert');
60+
});
61+
62+
it('returns undefined for an unknown id', () => {
63+
expect(getBuiltInTriggerDefinition('nonexistent')).toBeUndefined();
64+
});
65+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import type { z } from '@kbn/zod/v4';
11+
import { AlertRuleTriggerSchema, ManualTriggerSchema, ScheduledTriggerSchema } from './schema';
12+
13+
export interface TriggerDocumentation {
14+
details?: string;
15+
examples: string[];
16+
}
17+
18+
export interface BaseTriggerDefinition {
19+
id: string;
20+
label: string;
21+
description: string;
22+
schema: z.ZodType;
23+
documentation: TriggerDocumentation;
24+
}
25+
26+
export const builtInTriggerDefinitions: BaseTriggerDefinition[] = [
27+
{
28+
id: 'manual',
29+
label: 'Manual',
30+
description: 'Trigger a workflow manually via the UI or API',
31+
schema: ManualTriggerSchema,
32+
documentation: {
33+
examples: [
34+
`triggers:
35+
- type: manual`,
36+
],
37+
},
38+
},
39+
{
40+
id: 'scheduled',
41+
label: 'Scheduled',
42+
description:
43+
'Run a workflow on a recurring schedule using a simple interval (e.g. every 5m) or an rrule for complex patterns (specific days, hours)',
44+
schema: ScheduledTriggerSchema,
45+
documentation: {
46+
details:
47+
'Supports two formats: simple interval (`every: "5m"`) for fixed-rate schedules, or `rrule` for calendar-based patterns with day-of-week, hour, and timezone control.',
48+
examples: [
49+
`triggers:
50+
- type: scheduled
51+
with:
52+
every: "5m"`,
53+
`triggers:
54+
- type: scheduled
55+
with:
56+
rrule:
57+
freq: WEEKLY
58+
interval: 1
59+
byweekday: [MO, WE, FR]
60+
byhour: [9]
61+
byminute: [0]
62+
tzid: America/New_York`,
63+
],
64+
},
65+
},
66+
{
67+
id: 'alert',
68+
label: 'Alert',
69+
description:
70+
'Trigger a workflow when an alerting rule fires. Optionally filter by rule_id or rule_name',
71+
schema: AlertRuleTriggerSchema,
72+
documentation: {
73+
examples: [
74+
`triggers:
75+
- type: alert`,
76+
`triggers:
77+
- type: alert
78+
with:
79+
rule_name: "High CPU Usage"`,
80+
],
81+
},
82+
},
83+
];
84+
85+
const builtInTriggerDefinitionsMap = new Map(builtInTriggerDefinitions.map((t) => [t.id, t]));
86+
87+
export function getBuiltInTriggerDefinition(id: string): BaseTriggerDefinition | undefined {
88+
return builtInTriggerDefinitionsMap.get(id);
89+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: ES|QL Query Output to New Index
2+
description: Runs an ES|QL query to detect suspicious transactions over $10k, then indexes each result as a SAR report document.
3+
enabled: true
4+
5+
triggers:
6+
- type: manual
7+
8+
steps:
9+
- name: detect-amount-over-10k
10+
type: elasticsearch.esql.query
11+
with:
12+
query: >
13+
FROM fraud-workshop*
14+
| WHERE event.amount >= 10000 AND (wire.inbound.bank_name IS NOT NULL OR wire.outbound.bank_name IS NOT NULL)
15+
| GROK account.address "%{GREEDYDATA:account.address}, %{WORD:account.city}, %{WORD:account.state} %{NUMBER:account.zip}$"
16+
| DISSECT account.name "%{account.first_name} %{account.last_name}"
17+
| RENAME wire.inbound.bank_name AS bank_name
18+
| RENAME wire.outbound.bank_name AS bank_name
19+
| RENAME wire.inbound.routing_number AS routing_number
20+
| RENAME wire.outbound.routing_number AS routing_number
21+
| RENAME wire.direction AS direction
22+
| STATS SARs = COUNT(*) BY event.amount, account.event, account.address, account.city, account.first_name, account.last_name, account.state, account.zip, account.type, account.telephone_number, bank_name, routing_number, direction
23+
| SORT event.amount DESC
24+
25+
- name: loop_over_columns
26+
type: foreach
27+
foreach: '{{steps.detect-amount-over-10k.output.values}}'
28+
steps:
29+
# id uses execution.id + foreach.index to guarantee uniqueness across runs
30+
- name: log_to_elasticsearch
31+
type: elasticsearch.index
32+
with:
33+
index: 'sar-reports'
34+
id: '{{ execution.id }}-{{ foreach.index }}'
35+
document:
36+
timestamp: '{{ execution.startedAt }}'
37+
total_dollar_amount: '${{ foreach.item[1] }}'
38+
activity_description: 'Account had {{ foreach.item[2] }} amount exceeding SAR threshold.'
39+
activity_type: '{{ foreach.item[13] }} {{ foreach.item[2] }} in excess of $10,000'
40+
financial_institution_name: '${{ foreach.item[11] }}'
41+
financial_institution_ein: '${{ foreach.item[12] }}'
42+
report_date: '{{ execution.startedAt }}'
43+
suspect_first_name: '${{ foreach.item[5] }}'
44+
suspect_last_name: '${{ foreach.item[6] }}'
45+
suspect_address: '${{ foreach.item[3] }}'
46+
suspect_city: '${{ foreach.item[4] }}'
47+
suspect_state: '${{ foreach.item[7] }}'
48+
suspect_zip: '${{ foreach.item[8] }}'
49+
suspect_phone: '${{ foreach.item[10] }}'

src/platform/packages/shared/kbn-workflows/spec/examples/example_merge.yml

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/platform/packages/shared/kbn-workflows/spec/examples/example_pishing_from_wireframes.yml

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)