Skip to content

[Agent Builder] Discover profiles in Agent Builder - #288514

Open
alvarezmelissa87 wants to merge 31 commits into
elastic:mainfrom
alvarezmelissa87:agent-builder-discover-profiles
Open

alvarezmelissa87 wants to merge 31 commits into
elastic:mainfrom
alvarezmelissa87:agent-builder-discover-profiles

Conversation

@alvarezmelissa87

@alvarezmelissa87 alvarezmelissa87 commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Addresses #283734

DiscoverProfilesInAgentBuilderDemo.mov

Adds inline Discover sessions to Agent Builder chat, allowing users to view raw Elasticsearch documents without leaving the conversation.

Previously, requests for documents could result in pasted search results or a Lens data table. This change introduces a dedicated discover-session skill and create_discover_session tool so Agent Builder can distinguish between:

  • Raw documents, events, and logs → Discover
  • Charts, metrics, and aggregated summaries → Lens

discover-session is marked as experimental for now.

User experience

Users can ask to see matching documents and receive an interactive Discover table directly in chat.

The table supports:

  • Discover profiles, including profile-specific columns, cell rendering, row indicators, and document views
  • A local time picker that does not change Kibana’s global time range
  • An overlay document flyout
  • Opening the current query and time range in Discover
  • Follow-up prompts that update the existing session

For example, Observability logs can display log-level badges, severity indicators, the Summary column, and the Log overview document view:
image

The 'View details' action in the table still shows the detail flyout:
image

Implementation

This PR:

  • Adds a versioned discover.session Agent Builder attachment
  • Adds a tool for creating and updating by-value Discover sessions
  • Adds a skill that routes document requests to Discover
  • Renders attachments using the existing search embeddable
  • Enables Discover profile defaults for Agent Builder while leaving Dashboard embeddables unchanged
  • Adds display-only profile column widths that are persisted only after user interaction
  • Adds Agent Builder-specific time-range and document-flyout behavior
  • Updates Lens guidance so raw documents are not rendered as aggregated data tables

How to test

Note - this skill is experimental so you will need to either add uiSettings.overrides.agentBuilder:experimentalFeatures: true to your kibana.dev.yml or flip Agent Builder experimental features on in Stack Management:
image

  1. Start Elasticsearch and Kibana with Agent Builder enabled.
  2. Generate profile-compatible logs:
node scripts/synthtrace logs_and_metrics --from=now-24h --to=now
  1. Open Agent Builder from Observability and start a new conversation.
  2. Enter:

Show me error and warning logs from logs-* for the last hour in a document table in this chat. Do not pick columns — use the default log table.

  1. Verify:
  • A Discover table renders in chat.
  • log.level values have colored badges.
  • Rows have severity indicators.
  • The Summary column uses the logs profile.
  • The table has its own time picker.
  1. Change the table’s time range and confirm the results refresh without changing Kibana’s global time range.
  2. Open a row and confirm the document appears in an overlay flyout with the Log overview view.
  3. Select Open in Discover and confirm the ES|QL query and selected time range are preserved.
  4. Send a follow-up request:

Only show error logs from the last 15 minutes.
Confirm the existing Discover session is updated.

  1. Enter:

Chart error logs over time.
Confirm this request creates a Lens visualization rather than a Discover document table.

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

  • Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support
  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests 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
  • 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 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
  • Review the backport guidelines and apply applicable backport:* labels.

Identify risks

Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging.

@infra-vault-gh-plugin-prod

Copy link
Copy Markdown
Contributor
🤖 Jobs for this PR can be triggered through checkboxes. 🚧

ℹ️ To trigger the CI, please tick the checkbox below 👇

  • Click to trigger kibana-pull-request for this PR!
  • Click to trigger kibana-deploy-project-from-pr for this PR!
  • Click to trigger kibana-deploy-cloud-from-pr for this PR!
  • Click to trigger kibana-entity-store-performance-from-pr for this PR!
  • Click to trigger kibana-storybooks-from-pr for this PR!

@alvarezmelissa87 alvarezmelissa87 self-assigned this Sep 1, 2026
@alvarezmelissa87 alvarezmelissa87 added v9.6.0 Team:Search & ML UX feature:agent-builder Identify agent builder functionalities to be grouped together for release notes labels Sep 1, 2026
@l-suarez

l-suarez commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thanks @alvarezmelissa87, let me summon @gvnmagni here as the AB expert

@alvarezmelissa87
alvarezmelissa87 force-pushed the agent-builder-discover-profiles branch from 5cf967f to 55cc658 Compare September 3, 2026 19:22
@gvnmagni

gvnmagni commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

I think we have to tweak a couple of things here, given the constraints of current Chat area (max-width: 768px) we can't expect for that to be wider but there are a few things that we can do.

First, if possible I would force table lines to be shorter, in terms of height.

We will then need to fix those button/controls at the top and place them in the same line, probably moving the timepicker to the left as it happens for other charts (see image as reference)

Last thing, as an open question that I'll bring to the team, an "Expand" button would be very helpful, I'll see how this conflict with the classic logic of opening attachments in flyouts.

Let's talk about all this though, these are just very early comments

Screenshot 2026-09-04 at 12 22 07

Example of chart with time controls on the left:
Screenshot 2026-09-04 at 12 22 15

@alexmarhaba

alexmarhaba commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

time controls on the left
force table lines to be shorter, in terms of height

yes ++

an "Expand" button would be very helpful

Expand as in it opens in full screen? I wonder here if we should be opening the grid in a flyout, but yeah I agree the current size is too small

@alvarezmelissa87 can we add the ability to scroll horizontally? Tomasz did something similar on eui tables, maybe we can reuse that?

@leemthompo

Copy link
Copy Markdown
Member

👋 can we open a ticket in https://github.com/elastic/docs-content-internal/issues to ensure we get documentation for this new feature, along with any key info that would help writers? thanks!

Comment on lines +105 to +106
attachment_id: z.preprocess(
(value) => (typeof value === 'string' ? normalizeAttachmentId(value) : value),

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.

q: Do we need all this normalization logic? Is the agent struggling with attachment_id?

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.

Yes. It keeps sending things that aren't real IDs, like ., {attachment_id}, discover-session, or screen-context, instead of leaving attachment_id off. If we reject those, it just retries the same call until it runs out of tool calls. Ignoring the ones we know are fake lets it create the session, or update the only one, on the first try. If it sends an ID we don't recognize, we still reject that.

I also took those examples out of the tool description and the skill. It was copying the values we told it not to use. The check is still there for when it sends one anyway.

Let me know if that makes sense.


Do not paste rows, tab JSON, or vis_context into the conversation. This skill does not execute ES|QL; the Discover table in chat runs the query.
`,
getRegistryTools: () => TOOL_IDS,

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.

wdyt about making it inline tool?

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.

Thanks for taking a look, @rbrtj 🙏

The reason it's a registry tool, is thatcreate_discover_session isn’t unique to this skill. discover-data-analysis also uses it - the in-Discover “analyze my data” flow. After it runs aggregations, if the user asks to see the documents it should open this same live table instead of a Lens data_table. Visualization routing points at the same tool for document tables too.

If we made it inline, it would only show up after this skill is loaded, so Discover analysis could finish and then have no way to render the table unless we copied the tool.


const TOOL_IDS = [platformCoreTools.generateEsql, platformCoreTools.createDiscoverSession];

export const discoverSessionSkill = defineSkillType({

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.

up to you, I guess, but we could mark it as experimental so it is registered only when agentBuilder:experimentalFeatures is ON, just so it can be tested before exposing it by default.

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.

Good call, @rbrtj. I marked discover-session as experimental, so it only shows up when agentBuilder:experimentalFeatures is on. The tool stays available because discover-data-analysis uses it to open the same table.

];

const discoverDataAnalysisSkill = defineSkillType({
export const discoverDataAnalysisSkill = defineSkillType({

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.

I'm wondering if we need a separate discover session skill? Do you see them running independently or could it be all bundled into a single discoverDataAnalysis skill?

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.

Good question, @rbrtj. They are separate because they don’t really run the same way. discover-session is the chat path - “show me these logs as a table.” From what I can tell,discover-data-analysis is when you’re already in Discover and you want it to analyze the current query (aggs, chart, drill-downs).

If we bundled the table flow into data-analysis, something like “show me error logs as a document table” would pick up the whole analysis - run a bunch of STATS queries, always draw a chart, dump the overview / drill-down sections. I don't think that's what we want for a simple table.

They share create_discover_session because analysis sometimes needs to open that same table as a follow-up. Sharing the tool is fine but I think it's best to keep the skills separate.

Let me know if that makes sense - happy to change if it doesn't. cc @alexmarhaba to confirm this is still the goal for the discover-session skill.

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.

I agree I think the current approach is best for now. We can always consolidate later if needed.

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.

yeah sounds reasonable, lets keep it as is then 👍

@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

@elasticmachine merge upstream

@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

@elasticmachine merge upstream

@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

time controls on the left
force table lines to be shorter, in terms of height

yes ++

an "Expand" button would be very helpful

Expand as in it opens in full screen? I wonder here if we should be opening the grid in a flyout, but yeah I agree the current size is too small

@alvarezmelissa87 can we add the ability to scroll horizontally? Tomasz did something similar on eui tables, maybe we can reuse that?

@alexmarhaba - thanks for taking a look! 🙏 It already supports horizontal scroll so we should be good there!

… ES|QL from the AST, and keep the empty-result toolbar from clipping no results view
@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

Opened a docs request with the writer-facing details: https://github.com/elastic/docs-content-internal/issues/1858

cc @leemthompo

@alvarezmelissa87
alvarezmelissa87 marked this pull request as ready for review September 21, 2026 19:35
@alvarezmelissa87
alvarezmelissa87 requested review from a team as code owners September 21, 2026 19:35
@elastic-vault-github-plugin-prod
elastic-vault-github-plugin-prod Bot requested a review from a team as a code owner September 22, 2026 21:20

@florent-leborgne florent-leborgne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OAS changes look unrelated but copy LGTM

@kibanamachine

Copy link
Copy Markdown
Contributor

API Contract Breaking Changes

The following breaking change(s) were detected across the public OpenAPI surface, grouped by stability tier. Stable and Technical Preview changes fail the check and should be resolved; Experimental changes are informational.

Experimental — informational, not blocking merge (2)

Experimental APIs are allowed to introduce breaking changes. These are listed for visibility only and do not fail this check.

Endpoint Reason oasdiffId Source
/api/streams/{name}/content/export POST added '#/components/schemas/Kibana_HTTP_APIs__zod_v4_53___schema0' to the 'include/anyOf[subschema #2]/objects/routing/items/' request property 'allOf' list request-property-all-of-added /opt/buildkite-agent/builds/bk-agent-prod-gcp-1790200233715849868/elastic/kibana-pull-request/kibana/oas_docs/output/kibana.yaml
/api/streams/{name}/content/export POST added '#/components/schemas/Kibana_HTTP_APIs__zod_v4_53___schema0' to the 'include/anyOf[subschema #2]/objects/routing/items/' request property 'allOf' list request-property-all-of-added /opt/buildkite-agent/builds/bk-agent-prod-gcp-1790200233715849868/elastic/kibana-pull-request/kibana/oas_docs/output/kibana.serverless.yaml

What to do

  1. Fix the breaking change if it was unintentional.
  2. If intentional, add an approved entry to packages/kbn-api-contracts/allowlist.json and coordinate with the owning team. Use the oasdiffId and source values from the table above to scope the allowlist entry to this specific change.

See the @kbn/api-contracts README for tier definitions and the allowlist workflow.

Comment on lines +242 to +245
label: i18n.translate('discover.agentBuilder.openInDiscoverButtonLabel', {
defaultMessage: 'Open in Discover',
}),
handler: () => {

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.

Severity: medium

Opening the table in Discover ignores column and sort edits made in the inline grid: the locator always receives the original attachment data, while grid changes are held in the embeddable's live state. After adding/removing a column or sorting by a header, 'Open in Discover' shows the attachment's original layout/sort instead of the table the user was viewing.

onSaveToDashboard above already obtains embeddableApi.getSerializedStateByValue() to capture live table edits, whereas this handler calls getDiscoverSessionLocatorParams({ data, ... }), which reads only tab.column_order and tab.sort from the attachment.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

Comment on lines +60 to +65
showQueryInput: false,
showFilterBar: false,
showQueryMenu: false,
showDatePicker: true,
showSubmitButton: false,
disableQueryLanguageSwitcher: true,

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.

Severity: medium

The local time picker cannot commit a manually entered/absolute range when the legacy EUI date picker is active. In that picker, non-quick changes call onQueryChange, not onQuerySubmit; this hook only handles onQuerySubmit, and it hides the submit button, so the table and Open in Discover keep the old range despite the changed picker display.

QueryBarTopRow.onTimeChange dispatches non-quick selections to propsOnChange, and SearchBarUI.onQueryBarChange only updates its draft state. The inline test mocks SearchBar with a button that invokes onQuerySubmit directly, so it misses this path. Handle the picker change/commit path or provide a submit control for the legacy picker.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

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.

Addressed in c564ba6

Comment on lines +91 to +97
profileColumns: defaultState.columns,
fallbackColumns: defaultState.columns === undefined ? [] : defaultColumns,
dataView,
esqlQueryColumns,
});

if (columns.length) {

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.

Severity: medium

Resetting columns for a profile without getDefaultAppState().columns now discards the configured discover:defaultColumns entirely. Previously those defaults were validated and applied; now getPostFetchState returns no column update, so switching to a profile without column defaults can leave the previous profile's columns in Discover instead of restoring the user's configured defaults.

discover_data_state_container.ts passes uiSettings.get(DEFAULT_COLUMNS_SETTING, []) as defaultColumns during post-fetch profile resets, but this branch replaces it with [] precisely when defaultState.columns is undefined.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

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.

Addressed in c564ba6

Comment on lines +107 to +114
const mappedState = useMemo(() => toSearchEmbeddableByValueState(data), [data]);
const seedTimeRange = getDiscoverSessionSeedTimeRange({
mappedTimeRange: mappedState.time_range,
screenContextTimeRange,
});
const { searchBarProps, effectiveTimeRange } = useDiscoverSessionUnifiedSearch({
timeRange: seedTimeRange,
});

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.

Severity: medium

A time range selected in the inline picker is lost on the next chat response that renders an updated version of the session. The picker updates only this component's local state and embeddable API; it never updates the attachment. If the user selects 15 minutes and then asks a follow-up that changes only the query, the tool preserves the attachment's original time range, and the new inline instance initializes from that old range (for example, 24 hours), silently broadening the results. Preserve the local selection across follow-up renders or synchronize it with the session attachment, and test a fresh mount after a time change and query update.

useDiscoverSessionUnifiedSearch initializes committedTimeRange from this seed, while picker changes only call setCommittedTimeRange. create_discover_session_tool retains the existing time_range when an update omits it; the current follow-up test rerenders the same component instead of mounting a new chat reply.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

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.

This is expected. The time picker only applies to the current card and doesn't update the attachment. A follow-up creates a new version, so that card starts from the stored range. Saving the selection would mean writing it back to the attachment, which is more than we want to take on here.

Comment on lines 33 to 36
- The user wants raw documents, events, search hits, or a Discover-style document table. Use the discover-session skill and \`${
platformCoreTools.createDiscoverSession
}\` instead. Do **not** use chartType \`"data_table"\` as a substitute for a document table.
- The user first needs broad data discovery and exploration across unknown sources.

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.

Severity: medium

The visualization skill now routes every raw-document request to the discover-session skill and forbids its previous Lens table fallback, even when Agent Builder experimental features are off. In that default mode discover-session is unavailable (experimental: true), and create_discover_session is exposed via that skill or the in-Discover analysis skill, not this visualization skill; a normal chat asking to show raw documents can therefore be directed to a skill/tool it cannot use instead of receiving a table. Make this guidance conditional on availability or retain a supported fallback for users without the experiment.

The skill definition for discover-session sets experimental: true, whereas this visualization skill is not experimental and its tool list does not include createDiscoverSession.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

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.

Updated to only route to the skill when it's available in 0cbd618

Comment on lines +274 to +286
<div
css={css`
height: ${INLINE_TABLE_HEIGHT}px;
overflow: hidden;
`}
>
<EmbeddableRenderer<SearchEmbeddablePanelApiState, SearchEmbeddableApi>
key={embeddableKey}
maybeId={undefined}
type={SEARCH_EMBEDDABLE_TYPE}
getParentApi={() => parentApi}
onApiAvailable={setEmbeddableApi}
hidePanelChrome

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.

Severity: medium

A failing ES|QL request leaves the inline Discover attachment blank instead of showing an error or allowing the user to adjust the local time range. The search embeddable's initializeFetch sets searchError on fetch failure, but its factory renders null for that error outside inline-edit mode; this new chat host supplies no error UI or time picker outside the embeddable. A syntactically valid query against an unavailable index or an Elasticsearch error therefore makes the entire table disappear without an explanation. Provide an error state in this host (or opt the embeddable into an error presentation), with a failing-query regression test.

initialize_fetch.ts catches fetch errors and calls setSearchError(next?.error); get_search_embeddable_factory.tsx returns null for searchError unless isInlineEditing is true.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

@alvarezmelissa87 alvarezmelissa87 Oct 1, 2026 •

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.

This has been updated in 8c26060. The presentation panel already displays the search error, but the time picker disappeared with the grid toolbar. The inline host now keeps the picker available during blocking errors so users can adjust the range and retry. I also added regression coverage

@sddonne sddonne left a comment

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.

LGTM

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment on lines +64 to +65
displayStyle: 'inPage',
query: EMPTY_KUERY_QUERY,

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.

Severity: medium

The legacy SuperDatePicker forwards onQueryChange even when its onTimeChange reports isInvalid: true, but this handler commits every draft range as the active range. While entering an incomplete/invalid absolute time, the inline table calls setTimeRange with that invalid range and starts a fetch rather than retaining the last valid range; a failed fetch can leave the table blank. Ignore invalid legacy-picker changes (or commit only after validation) and cover that path in a test.

QueryBarTopRow.onTimeChange sets isDateRangeInvalid but calls propsOnChange(retVal) for every non-quick selection regardless of isInvalid; SearchBarUI.onQueryBarChange forwards it to onQueryChange. The hook's commitTimeRange updates state without checking validity.

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

@alvarezmelissa87 alvarezmelissa87 Oct 1, 2026 •

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.

This has been updated in 4c7d5de. We now ignore completed-but-invalid ranges from the legacy picker, including inverted and now-to-now ranges, while retaining the last valid range. Regression coverage has also been added. 👍

@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

@elasticmachine merge upstream

@TattdCodeMonkey TattdCodeMonkey left a comment

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.

agent_builder changes LGTM

alvarezmelissa87 and others added 3 commits September 30, 2026 14:05
The legacy unified search picker forwards completed-but-invalid ranges
(end before start, or now-to-now) with isInvalid set, which the inline
table was committing and passing to setTimeRange. Skip those so the last
valid range stays applied.

Co-authored-by: Cursor <cursoragent@cursor.com>
A blocking error replaces the grid, and the time picker lived in that
grid's toolbar, so a failed query left the attachment with no way to
adjust the range. Render the picker above the panel during the error
state instead, and drop it from the toolbar slot so only one is mounted.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
> => {
return {
id: platformCoreTools.createDiscoverSession,
type: ToolType.builtin,

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.

Severity: P2 (Medium)

Exclude this attachment-only tool from MCP. Without excludeFromMcp: true, it is exposed to standalone MCP clients even though its handler relies on conversation attachments and returns a <render_attachment> directive. Those clients cannot access or render the created session, so an apparently successful call does not deliver the promised live document table.

The handler uses attachments.getActive(), getAttachmentRecord(), add(), and update(); its success result contains a conversation-scoped attachment ID and render directive. BuiltInToolSpecificConfig.excludeFromMcp excludes such tools from MCP while retaining their availability in Agent Builder chat.

Suggested change
type: ToolType.builtin,
type: ToolType.builtin,
excludeFromMcp: true,

Generated by Libra. React with 👍 or 👎 to give feedback on this comment.

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.

Updated in 91c3ea4.
This was actually needed. This tool creates an Agent Builder attachment and returns a render tag for the inline Discover table. MCP clients have no conversation attachments or chat UI, so the tool can't work there so it's important to exclude them.
excludeFromMcp landed a few days before this PR started, so I missed it on the first pass.

@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

Some non-blocking future follow ups for this change we could make if we want to add support for those things:

The below are only reachable through the generic attachment API

  • Attachment payload bounds. We could add Agent Builder-specific limits if we want the direct attachment API to enforce the same ES|QL and time-range limits as the tool. The tool path already enforces them.
  • Classic-session filters. We could ;reserve tab.filters in Open in Discover if we support classic discover.session attachments. This tool only creates ES|QL sessions.

Other potential follow-ups

  • Per-space Discover availability. We could hide the tool and skill if turning Discover off for a space should also remove the inline table. Today the Discover app and Open in Discover are already unavailable, and the table still follows the user's Elasticsearch privileges so this is safe to use.
  • Persistence error sanitization. Revisit if Agent Builder standardizes how persistence errors are returned to the model. This matches the visualization tool, and the tool is excluded from MCP.
  • Actions during remounts. Clear the previous table's API and columns when an attachment updates if we see Open in Discover or Save to dashboard use the previous query or columns. That requires a fast click during the remount and is safe as it would just show the data in the table that was clicked.

@alvarezmelissa87

Copy link
Copy Markdown
Contributor Author

@elasticmachine merge upstream

@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
reporting 141.0KB 141.9KB +896.0B

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
discover 17.7KB 18.6KB +896.0B
shared-packages 5.0MB 5.0MB +272.0B
shared-plugins 12.2MB 12.2MB +549.0B
total +1.7KB
Unknown metric groups

@kbn/rspack-optimizer bundle module count

id before after diff
shared-plugins 4028 4030 +2

shared async chunk count

id before after diff
all 634 635 +1

shared async chunks total size

id before after diff
all 15.3MB 15.3MB +10.3KB

shared chunks total size

id before after diff
all 7.4MB 7.4MB +16.0B

total optimizer output size

id before after diff
all 65.4MB 65.4MB +12.9KB

warm start memory

id before after diff
post forced gc heap baseline - 889196954 +889196954
post forced gc heap delta - -1129438 -1129438
post forced gc heap delta standard deviation - 2658133 +2658133
post forced gc heap target - 888067516 +888067516
tail heap delta - -18182590 -18182590
total +1760610575

workflow yaml validation

id before after diff
case_response.yaml (27 steps, 66 vars)/e2e/connectorIds (collect+validate) 0 1 +1
case_response.yaml (27 steps, 66 vars)/e2e/total 16 17 +1
infosec_demo.yaml (150 steps, 270 vars)/e2e/performComputation 49 50 +1
infosec_demo.yaml (150 steps, 270 vars)/e2e/total 132 127 -5
infosec_demo.yaml (150 steps, 270 vars)/e2e/validateIfConditions 6 5 -1
infosec_demo.yaml (150 steps, 270 vars)/e2e/validateVariables 48 45 -3
infosec_demo.yaml (150 steps, 270 vars)/validateIfConditions 5 4 -1
infosec_demo.yaml (150 steps, 270 vars)/validateVariables (270 vars) 44 42 -2
total -9

Test Failures

  • [job] [logs] Scout Lane #10 - stateful-classic / default / local-stateful-classic - Discover session API — visualization persistence - preserves a saved Line histogram through a session API round trip
  • [job] [logs] Scout Lane #15 - stateful-classic / default / local-stateful-classic - Expandable flyout state sync - should test flyout url sync

History

cc @alvarezmelissa87

@PhilippeOberti PhilippeOberti left a comment

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.

LGTM for the @elastic/security-threat-hunting team

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting feature:agent-builder Identify agent builder functionalities to be grouped together for release notes release_note:enhancement Team:Search & ML UX v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.