--- AI TRACKER METADATA ---
Update Summary: Simple approach to bringing advanced metadata into Canvas AI
Check-in Date: MM/DD/YYYY (US format) [When we should see progress/get an update]
Due Date: MM/DD/YYYY (US format) [When the issue should be fully completed]
Blocked by: [#XXXXXX] (New issues on new lines)
Additional Collaborators: @username1, @username2
AI Tracker found here: https://www.drupalstarforge.ai/
--- END METADATA ---

Overview

Currently we only have a one shot process of the building agent and the template agent of picking the right components and how they are being used by the agents. This one looks at all available components, their title, description and the props and slot descriptions to figure out what it should pick.

For the Driesnote we did use this together with the Mercury theme and it worked mostly due to a limit of components and the possibility to remove components that we knew never would be used (see #3549432: Make it possible to disable component for Canvas AI selection). The context window was still around 13k tokens just for this information.

Before Mercury theme we were working with Civic Theme, that is very much aligning to atomic design, meaning loads of atoms that goes into slots at different places. The actual context window, if the above would have been used would have been more or less impossible to use, without disabling components.

The problem with this was twofold:

  1. It is too much context window to do one shoting, the props and the slots information might not be needed.
  2. It is at the same time to little context to decide if a component is actually good to use and how to use it and how it relates to other components.

We need a process that is similar to how we as humans actually would do the same process, by:

  1. Scanning on labels and descriptions for possible candidates what we are trying to solve.
  2. Using the potential candidates to figure out why and how they should be used.
  3. Possibly redo #1 if you see a slot that should be filled out or if at #2 you realize that this was not the correct component.

Proposed resolution

  • Add to the CanvasAiComponentDescriptionSettingsForm the possibility to add a textarea with markdown metadata, which can include information on how, why and where to use that component. Save it on submit.
  • Add a function call that only provides the id, labels and descriptions of the components.
  • Add a second function call that takes a list of ids, and provides the ids, label, description, props, slots and the above metadata as part of the response.
  • Change the agents that use these, to have the initial tool in memory (default information tools) and instruct it to get more information about possible candidates of components it could pick and execute the function call.
  • Allow it to use the function call as many loops as it needs.

Improvements

Discuss if there should be some way a component creator can add this data in the components files.

Issue fork canvas-3545816

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

catia_penas created an issue. See original summary.

marcus_johansson’s picture

Issue summary: View changes
marcus_johansson’s picture

I do not have access to set contributors on this issue - but the idea should be credited to ahmedjabar, not me or Catia.

tedbow’s picture

tedbow’s picture

I don't understand

Add to the CanvasAiSettingsForm the possibility to add a textarea with markdown metadata, which can include information on how, why and where to use that component. Save it on submit.

CanvasAiSettingsForm is an existingglobal settings form for the module not dealing with individual compoments but " that component" seems to imply it is setting info for a specific component.

we have admin/config/system/canvas-ai-component-description-settings that uses CanvasAiComponentDescriptionSettingsForm to make "description" text area for each available component. How is what is being proposed different from this?

tedbow’s picture

Status: Active » Postponed (maintainer needs more info)
marcus_johansson’s picture

Issue summary: View changes
Status: Postponed (maintainer needs more info) » Active

CanvasAiSettingsForm is an existingglobal settings form for the module not dealing with individual compoments but " that component" seems to imply it is setting info for a specific component.

Sorry about that, I mean the CanvasAiComponentDescriptionSettingsForm and having a setting for each. I'll edit the issue.

we have admin/config/system/canvas-ai-component-description-settings that uses CanvasAiComponentDescriptionSettingsForm to make "description" text area for each available component. How is what is being proposed different from this?

So the issue is the following - say that you have 100 components. Some of the components are atoms that should be in specific molecules, most of the components have props and/or slots with further descriptions. Some of the components should just be used in certain use cases and not in others.

If you look at the extra schema Salsa Digital did specifically for AI where we did a lot of testing, you can see that one component has a lot of metadata for the AI to get it right. For instance: https://github.com/salsadigitalauorg/xb_metadata/blob/develop/web/themes...

The problem is that if you have 100s of components with such extensive metadata, you have all of a sudden 100k input token and the AI will hallucinate and create bad results due to context drifting, even on the first loop.

So the idea is rather that we split this up in two tools (or one tool with a setting). One tool just exposes the label and description. The other tool exposes label, description, props, slots and this extra metadata field for specific components.

That way the agent can use the first tool on all the 100 components and get a low input token count (<1k tokens) and decide candidates that it should use to solve the users request.

Then in a second loop, it will request those candidates and get more information on when and how to use them. This will just fill with input tokens for those components information.

On a third loop it might get atoms that it saw should be used in slots for the initial component information and possible other component information, because the initial candidates where wrong for whatever reason.

The theory is that this should make it possible to have a larger component library and still be able to pick the correct components. We did test that in the above repo and it seemed to work there at least, where the one shot layouts had more problems.

You could also argue that it will save you money/remove unneccasary compute.

Hope that clarifies things?

akhil babu’s picture

Assigned: Unassigned » akhil babu
yautja_cetanu’s picture

- It would be good to have a hook as well so that third party modules could override the advanced field.
- It would be good to have the AI Agents to make use of it.

akhil babu’s picture

see #16

marcus_johansson’s picture

For the get_metadata_of_components I think we also will need an (optional) other textarea for a longer description, so if you take the example space-accordion, a complex metadata system would add metadata like:

when_to_use:
  - To organize and condense related content into collapsible sections on a page, reducing clutter.
  - On FAQ pages where each question/answer can be individually expanded for clarity.
  - In layouts with limited vertical space, especially on mobile or in sidebars, to minimize scrolling.
  - When presenting a multi-step process or workflow, where steps should be revealed one at a time.
  - To provide users with a quick overview, allowing them to expand only sections of interest.
  - For grouping detailed options or advanced settings beneath concise headings in forms.
when_not_to_use:
  - When all content must be visible during user workflows, or mandated for compliance/accessibility reasons.
  - On pages with minimal content—an accordion here adds unnecessary complexity.
  - For content that naturally flows as a continuous narrative (e.g., articles, stories).
  - When content hierarchy requires multiple sublevels; consider tree or nested navigation instead.
  - When printed output of all content is frequently required, unless an “Expand All” is provided.
  - If section headings are ambiguous or content cannot be meaningfully divided.
best_practices:
  - Use clear, descriptive titles for each accordion item so users understand what’s inside before clicking.
  - Ensure that the entire header is clickable, not just the icon, for easier interaction.
  - Use familiar, accessible icons (“plus/minus”, “chevron”) to represent open/closed states, and maintain consistent icon alignment.
  - Add smooth but responsive animations for opening/closing to provide clear interaction feedback.
  - Support keyboard navigation and include appropriate ARIA attributes for accessibility.
  - Only hide content that isn’t critical for users to see immediately—show vital info by default if needed.
  - Avoid nesting accordions within accordions as it adds complexity and potential confusion.
  - Be consistent with spacing, typography, and border use to maintain visual harmony.
  - Consider an option to expand/collapse all sections if the accordion contains many items.
  - If only one section should be open at a time (as this implementation forces), communicate this clearly to users.
variations:
  - 'icon_type': Choose between "PlusMinus", "chevronDown", or "noIcon" for expand/collapse indicators.
  - 'alignment': Set icon on left or right of header.
  - 'border': Toggle border above accordion sections.
  - 'container_width': Use default, half-width, or three-quarters-width layouts for overall accordion sizing.
  - 'Initial Expansion': Can be implemented to start with some or all sections expanded (though not built-in here).
description: |
  The Space Accordion component is a vertically stacked content organizer that reveals or hides sections on demand via header toggles. It offers flexible iconography (plus/minus, chevron, or no icon), icon alignment, optional border styling, and width controls. Animated transitions and strong visual cues ensure intuitive expand/collapse interaction. This implementation only allows one section open at a time, making it best for focused workflows or independent information sections.
slots_usage:
  items:
    - Use multiple instances of the 'space_ds:space-accordion-item' component to fill each accordion section.
    - Each item should include a clear label/title and any content to display when expanded.

So its not just about token saving/context drift management, but also about making sure that you can express how, why, when not, when to use a component. Since its unclear exactly what the best practices for this information is, it should be a textarea for now, so we can run tests what works and what doesn't.

akhil babu’s picture

from #3558241: Canvas AI: CanvasAiComponentDescriptionSettingsForm redirects to 'canvas.api.config.list' after submission

We also need additional tests to verify:

  • When a component/prop/slot description is overridden using the form, the tool should return the overridden description.
  • When components from a particular source (e.g. Blocks) are disabled using the form, the get_component_context tool should return only the components from the enabled sources.
akhil babu’s picture

Assigned: Unassigned » akhil babu
Status: Needs review » Needs work

Resuming the work

akhil babu’s picture

Here are the changes pushed in the last commit.

  • Added a Detailed description field to all components in the form.
  • The get_component_context tool returns the ID, name, and description of all components.
  • The get_metadata_of_components tool returns the ID, name, detailed description, props, and slots of the components.

The get_component_context tool will output something like this

Blocks:
  block.system_menu_block.footer:
    name: Footer
    description: Footer
  block.system_menu_block.main:
    name: 'Main navigation'
    description: 'Main navigation'
  block.system_messages_block:
    name: Messages
    description: Messages
'Single-Directory Components':
  sdc.space_ds.space-accordion:
    name: 'Space Accordion'
    description: 'Space Accordion'
  sdc.space_ds.space-alert:
    name: 'Space Alert'
    description: 'A customizable alert component for displaying different types of status messages.'
  sdc.space_ds.space-button:
    name: 'Space Button'
    description: 'A customizable button with options for icon, text, size, style, and state.'
'Code Components':
  js.hero_banner:
    name: 'Hero Banner'
    description: 'Hero Banner'

If the user has not used the CanvasAiComponentDescriptionSettingsForm to override the component, prop, or slot descriptions, then:

  • For SDCs, the description defined in each component’s YAML file will be used. If no description is provided, the description will default to the component name.
  • For Code Components and Blocks, the description will default to the component name.

If the CanvasAiComponentDescriptionSettingsForm has been used to override the descriptions, then those overridden descriptions will be loaded for all components.

A new tool, get_metadata_of_components, has been created. It accepts an array of component IDs and returns a single YAML response containing detailed metadata for each component.

  • For SDCs and Code Components, this detailed metadata includes detailed description(Note: the key name would be still 'description',but the value will be the content added in 'detailed description') , props and slots.
  • For Blocks, the output will contain block name, id and detailed description

For example, if the input is
[block.system_menu_block.footer, sdc.space_ds.space-button, js.hero_banner], then the output of get_metadata_of_components tool would be

block.system_menu_block.footer:
  id: block.system_menu_block.footer
  name: Footer
  description: Footer
sdc.space_ds.space-button:
  id: sdc.space_ds.space-button
  name: 'Space Button'
  description: 'A customizable button with options for icon, text, size, style, and state to fit different interactions.'
  group: Atoms
  props:
    icon:
      name: Icon
      description: 'The image used as an icon within the button.'
      type: object
      default: { src: /themes/contrib/space_ds/components/01-atoms/space-button/images/time.png, alt: 'Boring placeholder', width: 50, height: 50 }
    text:
      name: 'Button text'
      description: 'The text displayed on the button.'
      type: string
      default: 'Contact Us'
      required: true
    url:
      name: URL
      description: 'The action URL that the button links to when clicked.'
      type: string
      default: 'https://www.qed42.com/contact'
      required: true
    variant:
      name: Variants
      description: 'The visual style of the button (e.g., primary, secondary).'
      type: string
      default: primary
      enum: [primary, secondary, tertiary, ghost, link]
    size:
      name: Size
      description: 'The size of the button (e.g., small, medium, large).'
      type: string
      default: small
      required: true
      enum: [small, medium, large, xlarge, xxlarge, xxxlarge]
    button_corners:
      name: Corners
      description: 'The style of the button corners (e.g., rounded or square).'
      type: string
      default: rounded_half_circle
      enum: [rectangle_corner, rounded_half_circle]
    button_type:
      name: Type
      description: 'The type of button, determining whether it shows only an icon, only text, or both.'
      type: string
      default: only_text
      required: true
      enum: [only_icon, text_with_icon, only_text]
    icon_alignment:
      name: 'Icon alignment'
      description: 'The alignment of the icon relative to the button text (left or right).'
      type: string
      default: right
      enum: [only-icon, left, right]
    expanded:
      name: Expanded
      description: 'Whether the button should be full width or auto width.'
      type: string
      default: 'no'
      enum: ['yes', 'no']
    button_state:
      name: State
      description: 'The state of the button (e.g., focused or disabled).'
      type: string
      default: focused
      enum: [focused, disabled]
    classes:
      name: 'Class names'
      description: 'Custom CSS class names (separate by spaces), applied when custom style is enabled.'
      type: string
      default: null
  slots: 'No slots'
js.hero_banner:
  id: js.hero_banner
  name: 'Hero Banner'
  description: 'Hero Banner'
  props:
    headline:
      name: headline
      description: headline
      type: string
      default: 'Welcome to Our Site'
      format: ''
      enum: ''
    subtext:
      name: subtext
      description: subtext
      type: string
      default: 'Discover amazing content and connect with our community.'
      format: ''
      enum: ''
    ctaText:
      name: ctaText
      description: ctaText
      type: string
      default: 'Get Started'
      format: ''
      enum: ''
    ctaLink:
      name: ctaLink
      description: ctaLink
      type: string
      default: /
      format: uri-reference
      enum: ''
    intent:
      name: intent
      description: intent
      type: string
      default: primary
      format: ''
      enum: [primary, secondary]
  slots: {  }
akhil babu’s picture

Status: Needs work » Needs review
marcus_johansson’s picture

Issue tags: +AI Initiative Sprint
marcus_johansson’s picture

I've added some comments regarding the code.

When testing it, I get a unknown form error, when I try to save it without Blocks enabled, see

In this case, I just ran it on a vanilla site, not Drupal CMS, so the descriptions there seems autogenerated. But it doesn't really give me an error message at all, also nothing in the log. The error after debugging seems to be "At least one source must be enabled.", however its connected to "component_context" element, so the message seems to not show up.

rakhimandhania’s picture

Issue tags: +AI Innovation
akhil babu’s picture

Thanks for reviewing. I'm waiting for #3533079: Introduce AI Agents and tools to create entire page templates using available component entities to merge, since it may cause conflicts once it's merged.

rakhimandhania’s picture

Issue tags: +AI Page Generation
akhil babu’s picture

rakhimandhania’s picture

Issue tags: -AI Initiative Sprint
alex ua’s picture

Following up on the metadata optimization discussion here. We’ve built a complementary approach: horizontal scoping that reduces which components the agent sees during edit operations, rather than reducing metadata per component.

The problem

When editing a single heading, the page builder agent receives the full page layout — every region, every section, every nested component with all props and slots. On a 15-component demo page, the full layout JSON is ~11.5K bytes (~2,900 tokens). The agent only needs the section containing the selected component.

Approach

A BuildSystemPromptEvent subscriber (priority -10, after ai_context at 0) that runs when active_component_uuid is set:

  1. Identifies which region contains the selected component
  2. Identifies which top-level section (within that region) contains it
  3. Replaces the full layout with a scoped version:
    • Active section: full detail (all props, slots, nested components)
    • Sibling sections in same region: name + UUID only
    • Other regions: component count only
    • Region index: lightweight map of all regions (~200 bytes) for cross-region awareness

Fail-open design: if the selected component can’t be located in the layout, the subscriber falls through to the full layout. Never degrades the editing experience.

Known limitation

The subscriber replaces layout JSON in the system prompt via string matching. If the serialization format between the tempstore and the prompt differs (whitespace, key ordering), the match fails silently (falls through to full layout). This works but is fragile.

Would a structured layout accessor on the event be a cleaner path? Something like a layout_data token via the existing setToken()/getTokens() API, so subscribers can work with parsed data rather than doing string surgery on the prompt? We prototyped this using the token bag and it works without requiring changes to BuildSystemPromptEvent itself.

Measured results (N=1 heading edit, 15-component demo page)

Layout is approximately 10% of total per-loop cost — system prompt instructions and ai_context items dominate the other 90%. Layout scoping yields a modest reduction on its own but compounds with other optimizations:

Layer What it addresses Measured savings
Loop-aware context injection (#3582288: SystemPromptSubscriber re-injects full context on every agent loop iteration in ai_context) ai_context re-injected every loop 52% total
Region scoping (this) Layout sent for irrelevant components ~10% of per-loop cost

How this complements this issue

  • This issue reduces tokens per component description (vertical optimization)
  • Region scoping reduces which components are sent (horizontal optimization)
  • Applied together: only the relevant components in the relevant section, with compressed metadata for each

Cross-region edits

Scoped layout preserves cross-region awareness via the region index but limits cross-region component detail. Operations requiring full cross-section context ("match the style of the hero section") would need the agent to request the full layout via existing tools, or would fall through to an unscoped prompt.

Prototype

Working LayoutScopingSubscriber in a custom module. Uses CanvasAiTempStore to read the current layout and BuildSystemPromptEvent to replace layout JSON in the system prompt. Unit tests covering region index generation, section scoping, nested components, and edge cases.

Happy to share the code or contribute a patch if this direction is useful.

alex ua’s picture

Status: Needs work » Needs review

I had been carrying a local rebase of MR !719 for a demo site. Before posting it, I verified against current Canvas 1.x HEAD and found that the functionality from this issue has been implemented upstream — likely through other MRs, since the API details differ from !719's approach. MR !719 appears to no longer be needed.

Specifically, all of these are now in Canvas 1.2.0:

- CanvasAiComponentContextHelper (method renamed to getLessDetailedComponentContext)
- GetMetadataOfComponents plugin (present, slightly different error handling)
- CanvasAiPageBuilderHelper subrequest removal + array return type
- CanvasAiComponentDescriptionSettingsForm detailed_description field
- GetComponentContext switched to CanvasAiComponentContextHelper
- canvas_ai.services.yml context_helper registration
- Agent config with get_metadata_of_components tool

One minor difference: upstream getDetailedMetadataOfComponents() silently skips unrecognized component IDs, where !719 threw \InvalidArgumentException.

If the maintainers agree this is resolved, marking Fixed would unblock #3549232: Canvas AI: Updating page contents with agents and downstream #3583357: Deterministic edit controller: resolve simple property edits without LLM.