Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7da0a64
added markdown links to ai insgihts and obs agent
yuliia-fryshko Jan 22, 2026
e6e40fa
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 23, 2026
723e75e
Merge branch 'main' into markdown-links-to-select-entities
yuliia-fryshko Jan 26, 2026
87254ab
added spaces to markdown links for logs ai insights
yuliia-fryshko Jan 26, 2026
793cc6d
added current space to ai insights
yuliia-fryshko Jan 27, 2026
c541789
added space aware links to alert ai insight
yuliia-fryshko Jan 27, 2026
deb9333
added dynamic configuration for space-aware agent instructions
yuliia-fryshko Jan 27, 2026
c5f9cba
removed unneeded parameters
yuliia-fryshko Jan 27, 2026
fb47965
Merge branch 'main' into markdown-links-to-select-entities
yuliia-fryshko Jan 27, 2026
b6e6c13
fixed check type
yuliia-fryshko Jan 27, 2026
454aa19
Update x-pack/solutions/observability/plugins/observability_agent_bui…
yuliia-fryshko Jan 28, 2026
7729f92
resolved dynamic agent configuration in builtin provider
yuliia-fryshko Jan 28, 2026
fcee958
Merge branch 'main' into markdown-links-to-select-entities
yuliia-fryshko Jan 28, 2026
d7b4432
fixed basePath markdown links
yuliia-fryshko Jan 28, 2026
26d6bf9
use request to build base path
yuliia-fryshko Jan 28, 2026
e7b600b
remove unneeded param
yuliia-fryshko Jan 28, 2026
fe7ba06
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Jan 28, 2026
01fe739
Merge branch 'main' into markdown-links-to-select-entities
yuliia-fryshko Jan 28, 2026
a59e57b
remove unused variable
yuliia-fryshko Jan 29, 2026
f7df33b
Merge branch 'main' into markdown-links-to-select-entities
yuliia-fryshko Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,24 @@ import type { AgentDefinition, AgentConfiguration } from '@kbn/agent-builder-com
/** Same type for now */
export type BuiltInAgentConfiguration = AgentConfiguration;

/**
* Context passed to dynamic configuration handlers.
*/
export interface AgentConfigContext {
request: KibanaRequest;
spaceId: string;
}

/**
* Represents a built-in agent definition, as registered by the consumers using the agents setup contract.
*/
export type BuiltInAgentDefinition = Pick<
AgentDefinition,
'id' | 'name' | 'description' | 'labels' | 'avatar_icon' | 'avatar_symbol' | 'avatar_color'
> & {
configuration: BuiltInAgentConfiguration;
configuration:
| BuiltInAgentConfiguration
| ((ctx: AgentConfigContext) => MaybePromise<BuiltInAgentConfiguration>);
/**
* Optional dynamic availability configuration.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type {
export type {
BuiltInAgentDefinition,
BuiltInAgentConfiguration,
AgentConfigContext,
AgentAvailabilityContext,
AgentAvailabilityHandler,
AgentAvailabilityResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import type { KibanaRequest } from '@kbn/core-http-server';
import { AgentType, createAgentNotFoundError } from '@kbn/agent-builder-common';
import type { BuiltInAgentDefinition } from '@kbn/agent-builder-server/agents';
import type { BuiltInAgentDefinition, AgentConfigContext } from '@kbn/agent-builder-server/agents';
import type { BuiltinAgentRegistry } from './registry';
import type { AgentProviderFn, ReadonlyAgentProvider } from '../agent_source';
import type { InternalAgentDefinition } from '../agent_registry';
Expand All @@ -19,20 +19,24 @@ export const createBuiltinProviderFn = ({
registry: BuiltinAgentRegistry;
}): AgentProviderFn<true> => {
const availabilityCache = new AgentAvailabilityCache();
return ({ request }) => {
return registryToProvider({ registry, request, availabilityCache });
return ({ request, space }) => {
return registryToProvider({ registry, request, space, availabilityCache });
};
};

const registryToProvider = ({
registry,
request,
space,
availabilityCache,
}: {
registry: BuiltinAgentRegistry;
request: KibanaRequest;
space: string;
availabilityCache: AgentAvailabilityCache;
}): ReadonlyAgentProvider => {
const configContext: AgentConfigContext = { request, spaceId: space };

return {
id: 'builtin',
readonly: true,
Expand All @@ -44,12 +48,14 @@ const registryToProvider = ({
if (!definition) {
throw createAgentNotFoundError({ agentId });
}
return toInternalDefinition({ definition, availabilityCache });
return toInternalDefinition({ definition, availabilityCache, configContext });
},
list: (opts) => {
const definitions = registry.list();
return Promise.all(
definitions.map((definition) => toInternalDefinition({ definition, availabilityCache }))
definitions.map((definition) =>
toInternalDefinition({ definition, availabilityCache, configContext })
)
);
},
};
Expand All @@ -58,12 +64,20 @@ const registryToProvider = ({
export const toInternalDefinition = async ({
definition,
availabilityCache,
configContext,
}: {
definition: BuiltInAgentDefinition;
availabilityCache: AgentAvailabilityCache;
configContext: AgentConfigContext;
}): Promise<InternalAgentDefinition> => {
const configuration =
typeof definition.configuration === 'function'
? await definition.configuration(configContext)
: definition.configuration;

return {
...definition,
configuration,
type: AgentType.chat,
readonly: true,
isAvailable: async (ctx) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,18 @@ export async function registerObservabilityAgent({
return getAgentBuilderResourceAvailability({ core, request, logger });
},
},
configuration: {
configuration: ({ spaceId }) => ({
instructions:
dedent(`You are an observability specialist agent that helps Site Reliability Engineers (SREs) investigate incidents and understand system health.

${getInvestigationInstructions()}
${getReasoningInstructions()}
${getFieldDiscoveryInstructions()}
${getKqlInstructions()}
${getEntityLinkingInstructions(spaceId)}
`),
tools: [{ tool_ids: OBSERVABILITY_AGENT_TOOL_IDS }],
},
}),
});

logger.debug('Successfully registered observability agent in agent-builder');
Expand All @@ -55,7 +56,7 @@ export async function registerObservabilityAgent({
function getInvestigationInstructions() {
return dedent(`
### INVESTIGATION APPROACH

Follow a progressive workflow - start broad, then narrow down:
1. **Triage**: What's the severity? How many users/services affected?
2. **Scope**: Which components are affected? What's the blast radius?
Expand All @@ -69,7 +70,7 @@ function getInvestigationInstructions() {
function getReasoningInstructions() {
return dedent(`
### REASONING PRINCIPLES

- **Be quantitative**: Quote specific metrics (error rate %, latency ms, throughput rpm). Avoid vague terms like "high" without numbers.
- **Correlation ≠ causation**: Look for temporal sequence (what happened FIRST) and causal mechanism.
- **Consider all layers**: Infrastructure (CPU, memory, disk) → Application (latency, throughput, failure rate) → Dependencies (databases, caches, external APIs).
Expand Down Expand Up @@ -97,3 +98,31 @@ function getKqlInstructions() {
- Use quotes for exact phrases in text fields: \`message: "connection refused"\`
`);
}

/**
* Entity Linking instructions for the Observability Agent
* Instructs the LLM to format entities as clickable links using Kibana's relative URL paths.
*/
export function getEntityLinkingInstructions(spaceId?: string) {
const prefix = spaceId && spaceId !== 'default' ? `/s/${spaceId}` : '';
return dedent(`
### Entity Linking Guidelines
Use markdown for readability. When referencing entities, create clickable links.
IMPORTANT: Do NOT wrap links in backticks - backticks prevent links from being clickable.

| Entity | Link Format | Example |
|--------|-------------|---------|
| Service | [<serviceName>](${prefix}/app/apm/services/<serviceName>) | "The [payments](${prefix}/app/apm/services/payments) service is experiencing high latency." |

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.

Thanks for adding the space path changes.

How about the base path?
The URLs will result in 404 if there is a base path configured.

@viduni94 viduni94 Jan 28, 2026

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.

If possible, you could try to use this shared util by agent builder which handles the space and base path:

export function getKibanaUrl(
coreSetup: CoreSetup,
cloudSetup?: CloudSetup,
request?: KibanaRequest,
spaces?: SpacesPluginStart
) {
const baseUrl =
coreSetup.http.basePath.publicBaseUrl ??
cloudSetup?.kibanaUrl ??
getFallbackKibanaUrl(coreSetup);
const pathname = new URL(baseUrl).pathname;
const serverBasePath = coreSetup.http.basePath.serverBasePath;
const { pathHasExplicitSpaceIdentifier } = getSpaceIdFromPath(pathname, serverBasePath);
if (!pathHasExplicitSpaceIdentifier && request && spaces) {
const spaceId = spaces.spacesService?.getSpaceId(request) || DEFAULT_SPACE_ID;
return addSpaceIdToPath(baseUrl, spaceId);
}
return baseUrl;
}

| Transaction | [<transactionName>](${prefix}/app/apm/services/<serviceName>/transactions) | "The transaction [POST /checkout](${prefix}/app/apm/services/payments/transactions) took 500ms." |
| Trace | [<traceId>](${prefix}/app/apm/link-to/trace/<traceId>) | "See trace [8bc26008603e16819bd6fcfb80fceff5](${prefix}/app/apm/link-to/trace/8bc26008603e16819bd6fcfb80fceff5)" |
| Error | [<errorKey>](${prefix}/app/apm/services/<serviceName>/errors/<errorKey>) | "Error [upstream-5xx](${prefix}/app/apm/services/catalog-api/errors/upstream-5xx) suggests a dependency failure." |
| Service Errors | [errors](${prefix}/app/apm/services/<serviceName>/errors) | "Review all [errors](${prefix}/app/apm/services/frontend/errors) for the [frontend](${prefix}/app/apm/services/frontend) service." |
| Service Logs | [logs](${prefix}/app/apm/services/<serviceName>/logs) | "Check [logs](${prefix}/app/apm/services/frontend/logs) for the [frontend](${prefix}/app/apm/services/frontend) service." |
| Host | [<hostName>](${prefix}/app/metrics/detail/host/<hostName>) | "Host [web-01](${prefix}/app/metrics/detail/host/web-01) is experiencing high CPU usage." |
| Service Map | [Service Map](${prefix}/app/apm/services/<serviceName>/service-map) | "Check the [Service Map](${prefix}/app/apm/services/payments/service-map) to see dependencies." |
| Dependencies | [Dependencies](${prefix}/app/apm/services/<serviceName>/dependencies) | "View [Dependencies](${prefix}/app/apm/services/catalog-api/dependencies) to identify upstream issues." |
| Alert | [<alertId>](${prefix}/app/observability/alerts/<alertId>) | "Alert [alert-uuid-123](${prefix}/app/observability/alerts/alert-uuid-123) was triggered." |
| Logs Explorer | [Logs](${prefix}/app/logs) | "View [Logs](${prefix}/app/logs) to investigate the issue further." |

`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,39 @@ import type {
ObservabilityAgentBuilderPluginSetupDependencies,
} from '../../../types';
import { fetchApmErrorContext } from './fetch_apm_error_context';
import { getEntityLinkingInstructions } from '../../../agent/register_observability_agent';
import type { AiInsightResult, ContextEvent } from '../types';

const ERROR_AI_INSIGHT_SYSTEM_PROMPT = dedent(`
You are an expert SRE Assistant within Elastic Observability. Your job is to analyze an APM error using ONLY the provided context (APM trace items, related errors, downstream dependencies, and log categories).
function getErrorAiInsightSystemPrompt(spaceId?: string) {
return dedent(`
You are an expert SRE Assistant within Elastic Observability. Your job is to analyze an APM error using ONLY the provided context (APM trace items, related errors, downstream dependencies, and log categories).

Output structure (concise, Markdown):
- Error summary (1-2 sentences): What is observed and why it matters.
- Failure pinpoint: Whether failure is in application code vs dependency. Name the likely failing component/endpoint. Reference specific fields or key frames if available.
- Impact: Scope and severity (services/endpoints and the extent of the error if evident).
- Immediate actions (2-4): Ordered, concrete steps (config/network checks, retries/backoff, circuit breakers, targeted tracing/logging).
- Open questions: Short list of unknowns and the quickest queries to resolve them, if any (this is strictly optional and should not be present if there are no open questions).

Output structure (concise, Markdown):
- Error summary (1-2 sentences): What is observed and why it matters.
- Failure pinpoint: Whether failure is in application code vs dependency. Name the likely failing component/endpoint. Reference specific fields or key frames if available.
- Impact: Scope and severity (services/endpoints and the extent of the error if evident).
- Immediate actions (2-4): Ordered, concrete steps (config/network checks, retries/backoff, circuit breakers, targeted tracing/logging).
- Open questions: Short list of unknowns and the quickest queries to resolve them, if any (this is strictly optional and should not be present if there are no open questions).
Guardrails:
- Strict Factuality: Only mention signals present in the JSON. If a signal is missing, do not mention it.
- Only assert a cause if multiple signals support it. Otherwise mark Assessment "Inconclusive".
- Prefer corroborated explanations. If only one source supports it, state that support is limited.
- Do NOT repeat raw stacks verbatim (reference only key frames/fields).
- Conciseness: Use bullet points. Avoid flowery language. Be direct and technical.

Guardrails:
- Strict Factuality: Only mention signals present in the JSON. If a signal is missing, do not mention it.
- Only assert a cause if multiple signals support it. Otherwise mark Assessment "Inconclusive".
- Prefer corroborated explanations. If only one source supports it, state that support is limited.
- Do NOT repeat raw stacks verbatim (reference only key frames/fields).
- Conciseness: Use bullet points. Avoid flowery language. Be direct and technical.
Available context tags:
- <ErrorDetails>: Full error document (exception, message, stacktrace, labels)
- <TransactionDetails>: Transaction linked to the error (if present)
- <DownstreamDependencies>: Downstream dependencies for the erroring service
- <TraceItems>: Span/transaction samples with service, name, type, eventOutcome, statusCode, duration, httpUrl, downstreamServiceResource
- <TraceErrors>: Related errors within the trace (type, message, culprit, spanId, timestampUs)
- <TraceServices>: Service aggregates for the trace (serviceName, count, errorCount)
- <TraceLogCategories>: Categorized log patterns tied to the trace (errorCategory, docCount, sampleMessage)

Available context tags:
- <ErrorDetails>: Full error document (exception, message, stacktrace, labels)
- <TransactionDetails>: Transaction linked to the error (if present)
- <DownstreamDependencies>: Downstream dependencies for the erroring service
- <TraceItems>: Span/transaction samples with service, name, type, eventOutcome, statusCode, duration, httpUrl, downstreamServiceResource
- <TraceErrors>: Related errors within the trace (type, message, culprit, spanId, timestampUs)
- <TraceServices>: Service aggregates for the trace (serviceName, count, errorCount)
- <TraceLogCategories>: Categorized log patterns tied to the trace (errorCategory, docCount, sampleMessage)
`);
${getEntityLinkingInstructions(spaceId)}

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.

Nice to see you are importing the instruction here. This got me thinking: what about the other instructions:

${getInvestigationInstructions()}
${getReasoningInstructions()}
${getFieldDiscoveryInstructions()}
${getKqlInstructions()}

Should they be included as well?

How are you handling this for alerts and log AI insights? @viduni94 @neptunian?

@sorenlouv sorenlouv Jan 27, 2026

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.

Actually, nevermind. The other instructions are only relevant when doing tool calling, which the AI insight is not doing atm. So probably only the instructions related to formatting (like markdown links) are needed.

`);
}

const buildUserPrompt = (errorContext: string) => {
return dedent(`
Expand All @@ -62,6 +67,7 @@ const buildUserPrompt = (errorContext: string) => {
export interface GenerateErrorAiInsightParams {
core: ObservabilityAgentBuilderCoreSetup;
plugins: ObservabilityAgentBuilderPluginSetupDependencies;
spaceId: string;
errorId: string;
serviceName: string;
environment?: string;
Expand All @@ -77,6 +83,7 @@ export interface GenerateErrorAiInsightParams {
export async function generateErrorAiInsight({
core,
plugins,
spaceId,
errorId,
serviceName,
environment,
Expand All @@ -103,7 +110,7 @@ export async function generateErrorAiInsight({
const userPrompt = buildUserPrompt(errorContext);

const events$ = inferenceClient.chatComplete({
system: ERROR_AI_INSIGHT_SYSTEM_PROMPT,
system: getErrorAiInsightSystemPrompt(spaceId),
messages: [
{
role: MessageRole.User,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
ObservabilityAgentBuilderPluginSetupDependencies,
} from '../../types';
import { getToolHandler as getLogCategories } from '../../tools/get_log_categories/handler';
import { getEntityLinkingInstructions } from '../../agent/register_observability_agent';

/**
* These types are derived from the generated alerts-as-data schemas:
Expand All @@ -45,6 +46,7 @@ interface GetAlertAiInsightParams {
core: ObservabilityAgentBuilderCoreSetup;
plugins: ObservabilityAgentBuilderPluginSetupDependencies;
alertDoc: AlertDocForInsight;
spaceId: string;
inferenceClient: InferenceClient;
connectorId: string;
dataRegistry: ObservabilityAgentBuilderDataRegistry;
Expand All @@ -56,6 +58,7 @@ export async function getAlertAiInsight({
core,
plugins,
alertDoc,
spaceId,
inferenceClient,
connectorId,
dataRegistry,
Expand All @@ -74,6 +77,7 @@ export async function getAlertAiInsight({
inferenceClient,
connectorId,
alertDoc,
spaceId,
context: relatedContext,
});

Expand Down Expand Up @@ -210,11 +214,13 @@ function generateAlertSummary({
inferenceClient,
connectorId,
alertDoc,
spaceId,
context,
}: {
inferenceClient: InferenceClient;
connectorId: string;
alertDoc: AlertDocForInsight;
spaceId: string;
context: string;
}): Observable<ChatCompletionEvent> {
const systemPrompt = dedent(`
Expand Down Expand Up @@ -242,6 +248,8 @@ function generateAlertSummary({
3) Log categories: error messages and exception patterns
4) Errors: exception patterns with downstream context
5) Service summary: instance counts, versions, anomalies, and metadata

${getEntityLinkingInstructions(spaceId)}
`);

const alertDetails = `\`\`\`json\n${JSON.stringify(alertDoc, null, 2)}\n\`\`\``;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import dedent from 'dedent';
import { concat, of } from 'rxjs';
import type { ObservabilityAgentBuilderDataRegistry } from '../../data_registry/data_registry';
import { getLogDocumentById } from './get_log_document_by_id';
import { getEntityLinkingInstructions } from '../../agent/register_observability_agent';
import type { AiInsightResult, ContextEvent } from './types';

export interface GetLogAiInsightsParams {
index: string;
id: string;
spaceId: string;
dataRegistry: ObservabilityAgentBuilderDataRegistry;
inferenceClient: InferenceClient;
connectorId: string;
Expand All @@ -27,6 +29,7 @@ export interface GetLogAiInsightsParams {
export async function getLogAiInsights({
index,
id,
spaceId,
request,
esClient,
dataRegistry,
Expand All @@ -35,7 +38,10 @@ export async function getLogAiInsights({
}: GetLogAiInsightsParams): Promise<AiInsightResult> {
const systemPrompt = dedent(`
You are assisting an SRE who is viewing a log entry in the Kibana Logs UI.
Using the provided data produce a concise, action-oriented response.`);
Using the provided data produce a concise, action-oriented response.

${getEntityLinkingInstructions(spaceId)}
`);

const logEntry = await getLogDocumentById({
esClient: esClient.asCurrentUser,
Expand Down
Loading
Loading