Skip to content

API stability

This page defines which parts of AI Context are public (safe to depend on) and which are internal (implementation details that may change without a deprecation period). It exists so contrib developers and site builders can answer one question without reading selector internals: what am I allowed to depend on?

The default rule

If an API is not listed as public on this page, treat it as internal.

  • Public APIs follow Drupal's deprecation policy: behavior changes are announced and a deprecation path is provided across minor releases.
  • Internal APIs may change, move, or be removed in any release without notice. Depending on them couples your code to implementation details that are expected to evolve.

While the module is in beta, public APIs may still change between releases without a full deprecation cycle — check release notes when upgrading. Once the module reaches a stable release, the boundaries on this page describe the supported surface. See also Upgrade and compatibility policy.

Deprecated APIs

CCC 1.0.x does not currently ship @deprecated public APIs. When a supported entry point is deprecated, it will be listed here with its replacement and the release that removes it.

If you depend on classes or services marked @internal in code, migrate to the public surfaces on this page — those internals are not deprecated; they were never part of the supported API.

Beta upgrade: entity scope terminology

The entity scope APIs were renamed before the 1.0.0 stable release. There are no backward-compatibility wrappers for the old names. Deploy the new code and run drush updatedb in the same release window, then update custom code and configuration that uses the old identifiers.

Old New
Scope plugin entity_bundle / AiContextScopeEntityBundle entity_type / AiContextScopeEntityType
Scope plugin target_entity / AiContextScopeTargetEntity entity_item / AiContextScopeEntityItem
Cache context ai_context.target_entity ai_context.entity_item
Base field target_entities entity_items
Usage fields target_entity_type / target_entity_id entity_item_type / entity_item_id
Usage field agent_id consumer_id plus derived consumer_type
Views field ai_context_agent_link ai_context_consumer_link
Views field ai_context_target_entity_link ai_context_entity_item_link
Service ai_context.entity_target_resolver / AiContextEntityTargetResolver ai_context.current_entity_resolver / AiContextCurrentEntityResolver
Service ai_context.current_entity_resolver / AiContextCurrentEntityResolver ai_context.request_info_resolver / AiContextRequestInfoResolver
Request factory parameter targetEntity requestEntity

The AiContextItem methods were renamed or replaced as follows:

Old New
getTargetEntities() getEntityItems()
hasTargetEntities() hasEntityItems()
getTargetEntityData() getEntityItemData() (internal)
buildTargetEntityRenderArray() buildEntityItemRenderArray() (internal)
getTargetEntityMarkup() buildEntityItemRenderArray() (internal)
matchesTargetEntity() matchesEntityItem()

The AiContextUsage getters and setters now use EntityItem instead of TargetEntity, for example getEntityItemType() and setEntityItemId(). getAgentId() / setAgentId() are replaced by getConsumerId() / setConsumerId() and getConsumerType() / setConsumerType(). getRunnerId() is nullable.

Updates 10015 through 10019 migrate stored scope values and revisions, scope configuration, usage storage and Views configuration, and the optional Dynamic Entity Reference field storage. Update hooks cannot rewrite custom PHP or render arrays, so custom cache-context tokens, plugin IDs, class names, and method calls must be changed during the deployment.

Beta upgrade: max_items removal

The Max context items to inject limit was removed before the 1.0.0 stable release. The token budget (max_tokens) is now the only user-facing limit on overall injected context; max_global_items still caps the Global group. There are no backward-compatibility wrappers.

Removed Replacement
max_items in ai_context.settings and per-agent overrides None; use max_tokens
maxItems parameter on AiContextSelection and AiContextSelectionParams None
AiContextSelectionItemsSelectedEvent::getMaxItems() (constructor no longer takes a max-items argument) None
max_items parameter on the get_relevant_ai_context_items function call None
AiContextLimitsConstraint and its validator None; the cross-limit validation is obsolete

Update 10020 removes the obsolete max_items keys from active configuration, and legacy configuration exports are sanitized during import. Custom code that passes maxItems or calls getMaxItems() must be updated in the same release window.

Update 10023 installs the token_count base field on ai_context_item and backfills stored estimates. See Configuration: Upgrading.

Beta upgrade: selection modes

The Match all selection mode (match_all) was removed before the 1.0.0 stable release. Context selection is now a nested ladder of three public modes. There is no runtime alias for match_all.

Removed Replacement
match_all stored agent selection_mode broad
Default getRenderedContext() / getResult() mode of match_all relevant

Post-update ai_context_post_update_0003_rewrite_match_all_selection_mode() rewrites stored agent selection_mode: match_all to broad, and ai_context_post_update_0004_add_site_default_selection_mode() backfills the new site-wide default key ai_context.settings:selection_mode with relevant. Deploy the new code and run drush updatedb in the same release window. Explicit callers that pass an unrecognized mode (including leftover match_all) through AiContextSelectionParams receive \InvalidArgumentException. Unrecognized values read from stored consumer config are logged and resolve to the site-wide default.

See Selection modes below and Services for the full field list.

Beta upgrade: stale scope values

Post-update ai_context_post_update_0006_scrub_stale_scope_values() removes leftover Entity Types, Language, Taxonomy Terms, Context Tags, Roles, and Site Section values that are no longer valid options. Live cleanup now does the same when a bundle, term, language, or role is deleted, or when a type is removed from Entity Types settings. Use Case values are not scrubbed. Deploy the new code and run drush updatedb. Database updates usually run before config import, so 0006 sees the pre-import environment: a copied production database on a lower environment can lose values for options that import is about to add.

Beta upgrade: scope field JSON:API shape

The context item scope field was replaced before the 1.0.0 stable release: a single serialized map became a multi-value scope_id + value field. This changes the field's shape everywhere the Field API's default normalization is used, including JSON:API. There is no backward-compatible wrapper.

// Before: one object, scope_id as key
"scope": {"use_case": ["working_with_text"], "language": ["en"]}

// After: a list of {scope_id, value} pairs, one per delta
"scope": [
  {"scope_id": "use_case", "value": "working_with_text"},
  {"scope_id": "language", "value": "en"}
]

getScope() / setScope() still return and accept the grouped scope_id => [value, ...] shape in PHP; only the field's own Field API and JSON:API representation changed. See Storing scope values for the two shapes and which callers use which one.

Update 10026 migrates stored scope values and revisions from the old map storage; see Configuration: Upgrading.

Beta upgrade: scope availability

Scope plugins gained isAvailable() on AiContextScopeInterface before the 1.0.0 stable release. AiContextScopeBase defaults it to TRUE. Direct implementations of the interface (without the base class) must add the method. Override it when scope usability depends on site configuration that may be missing; Language returns FALSE when the site has fewer than two languages.

AiContextScopeCacheableMetadataInterface gained getAvailabilityCacheableMetadata(). Plugins that implement that companion interface directly (without AiContextScopeBase) must add the method. The base class returns empty metadata; Language adds config:configurable_language_list.

AiContextScopeSubscriptionFormBuilderInterface::extractValues() gained an optional $previous argument. Direct implementations of the public form-builder interface must accept it so a save can keep subscriptions for disabled, unavailable, or missing scopes. Existing callers can omit it; omitted previous values are not preserved.

Disabled and unavailable scopes are hidden from forms and ignored at runtime, but stored item values and consumer subscriptions are kept on save unless the item is Global or a subcontext that inherits parent scope. Those two cases still strip non-applicable stored scope in preSave(). See Scopes and Scope API.

Beta upgrade: request-context terminology

The scope plugin API and request-factory named parameter were renamed before the 1.0.0 stable release so they describe the request context, which a caller can supply, rather than implying the HTTP current route. There are no backward-compatibility wrappers. Deploy the new code and update custom scope plugins and named-argument callers in the same release window.

Old New
AiContextScopeInterface::matchesCurrentContext() matchesRequestContext()
AiContextScopeInterface::getCurrentValue() getDetectedValue()
AiContextScopeBase::doGetCurrentValue() doGetDetectedValue()
Request factory parameter currentEntity requestEntity
AiContextScopeSiteSection::matchesCurrentPath() matchesRequestPath()

requestEntity is a named parameter. Callers that used currentEntity: $node must switch to requestEntity: $node. Positional callers are unaffected.

Direct implementations of AiContextScopeInterface (without AiContextScopeBase) must rename matchesCurrentContext() and getCurrentValue(). Plugins that extend the base class must rename doGetCurrentValue() if they override it, and any override of matchesCurrentContext().

Internal resolver and manager methods (filterByRequestContext(), getRequestContextValues()) and protected Taxonomy helpers (getRequestEntityTermIds()) were renamed in the same sweep and need no upgrade note.

AiContextScopeBase::isCurrentManageRoute() and $currentUser are unchanged: those still mean the current admin route and the acting session user.

Beta upgrade: selection API terminology

The public selector-job value objects and factory were renamed before the 1.0.0 stable release from Request to Selection. This keeps the selector job distinct from request-context matching APIs and provider-call request snapshots. There are no backward-compatibility wrappers. Deploy the new code and update custom callers in the same release window.

Old New
AiContextRequest AiContextSelection
AiContextRequestParamsData AiContextSelectionParams
AiContextResult AiContextSelectionResult
AiContextRequestFactory AiContextSelectionFactory
AiContextRequestFactoryInterface AiContextSelectionFactoryInterface
Service ID ai_context.request_factory ai_context.selection_factory
AiContextSelectionItemsSelectedEvent::getRequest() getSelection()
AiContextSelectionTextRenderedEvent::getRequest() getSelection()

The SELECTION_MODE_* constants now live on AiContextSelection. Method names that already described selection behavior are unchanged, including fromParameters(), fromConsumer(), getResult(), AiContextSelector::select(), and the config keys consumer_id, selection_mode, and push_enabled.

The request-context APIs intentionally keep their names: matchesRequestContext(), requestEntity, and AiContextRequestInfoResolver. Provider-call snapshot APIs such as AiContextProviderRequestContext, resolveConsumerId(), and request tags are also unchanged.

Beta upgrade: scope capability methods

Scope plugins gained supportsExactMatch() and supportsSituationalMatch() on AiContextScopeInterface before the 1.0.0 stable release (#3586419), splitting the former dual-purpose supportsSubscriptions() flag into explicit capabilities. AiContextScopeBase defaults both new methods to FALSE — exact-match and situational-match are opt-in. Direct implementations of the interface (without the base class) must add both methods.

Removed or changed Replacement
supportsSubscriptions() gating exact-match eligibility and Minimal prefilter candidacy supportsExactMatch(); a custom scope that returned supportsSubscriptions(): FALSE and relied on exact-match must now declare supportsExactMatch(): TRUE
Hardcoded situational scope list (SITUATIONAL_SCOPE_PLUGIN_IDS on the internal AiContextScopeResolverInterface) supportsSituationalMatch(); Languages, Site Sections, Entity Types, and Taxonomy Terms declare it
Internal AiContextScopeResolverInterface::getNonSubscriptionIndexedItemIds() getExactMatchIndexedItemIds()

supportsSubscriptions() keeps its signature with a narrowed, documented contract: form visibility, subscription scoring, and save-time value preservation. Behavior is unchanged for every built-in scope (Entity Item declares supportsExactMatch(): TRUE; Languages, Site Sections, Entity Types, and Taxonomy Terms declare supportsSituationalMatch(): TRUE); the capability matrix is frozen by a kernel test. The only migration is for custom scopes that relied on the implicit non-subscription-implies-exact-match rule, per the table above.

One selection behavior changed in the same release: situational matching now requires the scope to be enabled and available, the same gate every other selection path uses (it previously required only enabled). Concretely, on a monolingual site the Languages scope is unavailable, so items still carrying language values from before a language was removed, or from imports, are no longer situationally included in Relevant mode. They still pass hard filters and remain reachable through Broad mode, Global, Always include, or another matching scope, and they resume matching when a second language makes the scope available again. The budget estimates apply the same gate, so Relevant estimates no longer count scopes that selection ignores.

Beta upgrade: Languages no longer support subscriptions

The Languages scope now returns supportsSubscriptions(): FALSE (#3586448). It remains a hard filter and still declares supportsSituationalMatch(): TRUE. This is the first built-in that combines no subscriptions with situational matching.

Removed or changed Replacement
Language widgets on consumer subscription forms Hidden; Languages is listed in the "do not allow subscriptions" note
Language credit in subscription scoring None. Matching the request language no longer boosts an item over an untagged peer
Minimal reach via a language subscription Language-only items are unreachable in Minimal except via Always include. Raise the selection mode to Relevant for situational inclusion

Stored scope_subscriptions.language values are preserved dormant on save (#3586412) and stay valid options, so stale-value scrubbing (#3586416) still removes them if the language itself is uninstalled. They do not affect ranking.

The capability matrix kernel test now expects language => [FALSE, FALSE, TRUE].

Minimal's subscription prefilter now drops non-participating scopes before it queries the index, so a language-only subscription is treated like no subscriptions (global, Always include, and exact-match still apply).

Beta upgrade: Specific Entities is availability-gated

Specific Entities was hidden by deleting its plugin definition when dynamic_entity_reference was absent. It now declares isAvailable(): FALSE instead (#3586448), matching how Languages behaves on a single-language site. ai_context_ai_context_scope_info_alter() is no longer implemented by this module; the alter hook itself is unchanged and still available to third parties.

Changed Effect
AiContextScopeManager::getScopePlugins() Always returns 10 built-ins, including entity_item, on every site
getAllScopeValues() Includes an empty entity_item entry instead of omitting the key
Scope overview and settings tab A Specific Entities row appears with status Unavailable on sites without dynamic_entity_reference
Matching, scoring, and forms Unchanged: every caller already gates on AiContextScopeManager::isEnabledAndAvailable()

Code that treated a missing entity_item definition as "DER is not installed" must call isEnabledAndAvailable() (or the plugin's isAvailable()) instead. Nothing else changes: stored values are still preserved, the item form still hides the scope, and no update hook is needed.

Beta upgrade: scope weight split

The single weight attribute on AiContextScope has been replaced by two independent parameters (#3586438):

  • display_weight (ascending integer, lower = first) controls form, tab, and listing order.
  • scoring_weight (nullable integer) controls subscription scoring influence.
Changed Effect
AiContextScopeInterface::getWeight() Removed. Replace calls with getDisplayWeight() or getSubscriptionScoringWeight().
AiContextScopeManager::getScopePluginDisplayWeights() Removed. Use $plugin->getDisplayWeight() directly.
Plugin attribute weight: Renamed. Use display_weight: for display order and scoring_weight: for scoring influence.
Sort order Now ascending on display_weight (lower = first), matching Drupal #weight. Previously descending (higher = first).

Custom scope plugins must rename weight: to display_weight: in their #[AiContextScope] attribute and re-evaluate whether they need an explicit scoring_weight:. If no scoring_weight: is declared, scoring influence defaults to 1 (the floor), not the old display weight.

The direction inverted, so the key rename is not a 1:1 swap. Mechanically renaming weight: 25 to display_weight: 25 moves a custom scope from near the bottom of the form to third from the top.

hook_ai_context_scope_info_alter() implementations that still set $definitions['x']['weight'] fail silently. The definition key is now display_weight. There is no warning and no fallback.

Ranking of subscribed items changes on existing sites. There is no config migration and none is possible. Use Cases now carry twice the influence of the topical scopes (Context Tags, Site Sections, Entity Types, Taxonomy Terms), which are tied with each other. Form order is not ranking.

Site builders can override either weight per scope via the scope settings page Advanced section — no plugin code change needed. The scoring override is only offered for scopes that support subscriptions.

Beta upgrade: consumer configuration

Per-agent context configuration was replaced by consumers before the 1.0.0 stable release. Agents remain one consumer type. There are no backward-compatibility wrappers for the old APIs, config object, or admin paths. Old /settings/agents URLs are gone.

Removed Replacement
fromAgent() fromConsumer('agent:' . $id)
findAgentConfig() loadConsumerConfig() / resolveConsumerConfig()
Config object ai_context.agents ai_context.consumers
Config key allow_context_injection push_enabled
isInjectionAllowed() isPushAllowed()
Admin paths /settings/agents /settings/consumers and /settings/consumers/{consumer_id}/edit
Usage field agent_id consumer_id plus consumer_type
Views field ai_context_agent_link ai_context_consumer_link

isPushAllowed() is the factory helper for the combined push gate. It reads push_enabled, the type-wide enablement kill switch, and isAvailable(). Empty or non-canonical IDs return FALSE. A valid ID with no stored config uses plugin defaults. Callers that need push_disabled vs type_disabled should use isTypeEnabled(), isTypeAvailable(), and isPushEnabled() on resolveConsumerConfig() instead. Unavailable types use the same type_disabled invocation status.

Loop-aware injection is stored as settings.loop_aware on agent rows. AiContextConsumerTypeAgent::isLoopAware() reads that key over the type default. The agent subscriber uses the Agent type, not resolveConsumerConfig(). It is not a factory method.

Update 10028 copies ai_context.agents rows to ai_context.consumers (canonical IDs agent:{id}, loop_awaresettings.loop_aware) and deletes the old object. Updates 1002910031 migrate usage fields and the usage View. After drush updatedb, re-export site configuration. Importing leftover ai_context.agents after migration is a validation error.

See Configuration: Update 10028.

Public API

Selection factory service

Service ID ai_context.selection_factory (Drupal\ai_context\Service\AiContextSelectionFactoryInterface) is the supported entry point for running context selection from your own code. Type-hint the interface. The concrete AiContextSelectionFactory class is @internal.

API Purpose
getRenderedContext() One-liner returning the rendered context string for non-agent consumers
getResult() Full AiContextSelectionResult with cache metadata, selected IDs, and token usage
fromConsumer() Build an AiContextSelection from a saved consumer config entry
fromParameters() Build an AiContextSelection from typed parameters
isPushAllowed() Whether automatic push is allowed (push_enabled, type enablement, and availability)
loadConsumerConfig() Stored consumer config, or NULL
resolveConsumerConfig() Row over plugin defaults, plus type gates

The trailing $selectionMode argument on getRenderedContext() and getResult() is public; passing NULL (or omitting it) defers to the site-wide default in ai_context.settings:selection_mode, which ships as relevant. $consumerId merges canonical {type}:{instance} config. Pulls without a consumer log as none. See Selection modes.

See Services for full signatures and examples.

Scope subscription form builder

Service ID ai_context.scope_subscription_form (Drupal\ai_context\Service\AiContextScopeSubscriptionFormBuilderInterface) is the supported way to build and process scope subscription widgets on forms that are not the shared consumer editor.

API Purpose
hasSubscribableScopes() Whether any enabled scope supports subscriptions
getNonSubscribableScopeLabels() Labels for scopes that cannot be subscribed to
buildWidgets() Per-scope subscription form elements (same UI as the consumer editor)
extractValues() Normalize submitted widgets into nested scope values; pass stored subscriptions as $previous so hidden scopes are kept
buildSummary() Render-array summary of selected subscriptions

Do not depend on plugin.manager.ai_context_scope for subscription UI; use this service instead. See Services.

Selection and result value objects

  • Drupal\ai_context\Model\AiContextSelectionParams — validated parameter DTO for fromParameters(). Build instances with fromArray() when passing raw input from forms, plugins, or other loosely typed sources.
  • Drupal\ai_context\Model\AiContextConsumerStoredConfig — stored ai_context.consumers config from loadConsumerConfig().
  • Drupal\ai_context\Model\AiContextConsumerResolvedConfig — stored config over plugin defaults from resolveConsumerConfig().
  • Drupal\ai_context\Model\AiContextSelection — the immutable selection passed into selection.
  • Drupal\ai_context\Model\AiContextSelectionResult — the immutable result returned from selection (rendered text, selected item IDs, token usage, cache metadata).

The supported way to run a selection is through ai_context.selection_factory, which builds these objects and invokes selection for you. The params DTO getters, selection fields, and result accessors — such as getRenderedText(), getSelectedItemIds(), and getCacheableMetadata() — are public.

Selection modes

The three selection mode identifiers and their nested meaning are public 1.0 behavior. After 1.0.0, changing a mode name, default, or what each rung includes requires a deprecation path. Callers may depend on:

Identifier Constant on AiContextSelection Role
minimal SELECTION_MODE_MINIMAL Strictest rung
relevant SELECTION_MODE_RELEVANT Shipped site-wide default
broad SELECTION_MODE_BROAD Most inclusive rung

Frozen contract:

  • The modes are a nested ladder: each rung includes everything the one above it does. Hard context filters apply in every mode.
  • An item with no values for a subscribed scope does not earn subscription credit for that scope.
  • minimal: Global items, always/never overrides, Specific Entities matches, and strict scope subscription matches.
  • relevant (the shipped value of the site-wide default ai_context.settings:selection_mode, which applies when fromConsumer(), fromParameters(), getRenderedContext(), getResult(), or the function-call selection_mode receive no mode, and to unrecognized stored consumer config): Minimal, plus items that Languages, Site Sections, Entity Types, or Taxonomy Terms positively match (matchesRequestContext() returns TRUE) even without a subscription to that scope.
  • broad: Relevant, plus remaining published candidates that pass hard filters, ordered by Priority, appended after the scope-scored bucket to fill leftover token budget. Broad supplements; it does not replace scope-scored or exact-match groups.
  • Consumer config key selection_mode and the get_relevant_ai_context_items function-call parameter use these identifiers.

Candidate ceilings, chunking, and the selector pipeline remain internal. Those ceilings must not drop an item from a higher rung that a lower rung selected. See Services for constructor fields and examples.

Selector pipeline events

The ai_context.selection.* events are the supported way to inspect or alter selection without coupling to selector internals:

  • ai_context.selection.items_selected (AiContextSelectionItemsSelectedEvent)
  • ai_context.selection.text_rendered (AiContextSelectionTextRenderedEvent)

Event name constants live on Drupal\ai_context\Event\AiContextSelectionEvents. See the Supported extension model in Events for the full contract, subscriber constraints, and examples.

Scope plugins, hooks, and alters

  • AiContextScope plugins are a public extension point. Create your own scope plugins as described in Custom scopes and the Scope API. Optional AiContextScopeValueMatchingInterface is the supported way for a custom scope to define wildcard or hierarchical matching. Optional AiContextScopeStoredValueInterface is the supported way for a custom scope to declare which stored values are stale after a delete or settings change.
  • The documented hooks and alter hooks are public. See Hooks.
  • AiContextScopeCacheableMetadataInterface is an optional companion for dynamic label cache dependencies. It is intentionally separate from AiContextScopeInterface, so existing direct implementations remain valid.
  • 1.0 custom scope storage: supported custom scopes store values in the context item scope field. Use getScope() / setScope() for the grouped array (scope[plugin_id] => string[]). create(), $entity->set('scope', …), Default Content, and recipes require Field API deltas (scope_id + value). See Storing scope values. Custom entity-field storage, cleanup, and form lifecycle integration are internal module concerns in 1.0; scope-field plugins are indexed automatically.
  • Entity scope helpers on AiContextItem are public for reading scope state. Resolve effective scope through getScopeItem() first; use stored accessors (getScope(), getScopeValues(), isStoredGlobal(), getStoredEntityItemScopeValues()) only when you need values saved on this item. getEntityItems() and hasEntityItems() are the supported way to read referenced entities. See Scope API.

Do not inject plugin.manager.ai_context_scope from contrib modules; prefer the subscription form builder service for subscription widgets. The manager may gain orchestration methods in a future release without a deprecation period while marked @internal.

Consumer type plugins

AiContextConsumerType plugins are a public extension point. Implement them in the consuming project when the type must read that project's internals. Types that only enumerate public config entities and match emitted request tags may live in AI Context. The plugin attribute, interface, base class, and AiContextConsumerId are public. The Agent type's isLoopAware() is public for that type only. AI Context ships agent and automator. The consumer type manager, router, route result, route subscriber, and listing forms are internal.

See Consumer type API.

Function call plugins

The context_tools function call plugin IDs (ai_context:list_ai_context_items, ai_context:load_ai_context_item_by_id, ai_context:get_relevant_ai_context_items) are stable identifiers for agent configuration. See Function calls.

Drupal\ai_context\Plugin\AiFunctionCall\AiContextInheritsAgentIdInterface is the supported opt-in for tools that should inherit the running agent ID when agent_id is omitted. Implement it and declare an agent_id context definition. See Events.

Generic-path invocation result

On PreGenerateResponseEvent, the generic subscriber writes an ai_context key to event metadata and chat-input debug data after it routes a request. Drupal\ai_context\Model\AiContextInvocationResult is the public API for that key and the status values (AiContextInvocationResult::KEY, ::PUSHED, and so on). Callers may depend on this payload shape:

Key Meaning
consumer_id Canonical consumer ID, or empty when routing declined or was ambiguous
status One of the AiContextInvocationResult status constants
item_count Number of selected item IDs
item_ids Selected context item IDs

TYPE_DISABLED covers the type kill switch and isAvailable().

The subscriber is single-use per ChatInput. The debug-data copy of the key marks the input as processed: when the same input object is dispatched again (a tool-call inner request reusing the outer request's ChatInput), the subscriber re-publishes the stored payload to the new event's metadata and appends no second context block. A rebuilt or fresh ChatInput gets a full pass; debug data is in-memory and lives only as long as the input object.

An absent ai_context key means the request was not handled (non-chat, unrouted tags, or excluded agent/assistant tags). The payload never includes prompt or rendered text. See Events.

Internal implementation

The following are internal. They may change without deprecation; do not depend on them, decorate them, or replace them.

Selector and its pipeline

ai_context.selector (Drupal\ai_context\Service\AiContextSelector) and AiContextSelectorInterface — and the candidate pipeline (loading, scoring, priority merge, and subcontext resolution) — are internal. Extend selection through the selector pipeline events, not by depending on or replacing the selector.

Supporting services

These services support the selector and are not part of the public surface:

  • ai_context.scope_resolver — scope scoring and hard context filters
  • ai_context.scope_index — denormalized scope index used for prefiltering
  • ai_context.subcontext_resolver — child item resolution
  • ai_context.renderer, ai_context.token_estimator, and ai_context.subscription_budget — rendering, token budgeting, and consumer settings budget summary internals
  • ai_context.request_info_resolver, ai_context.scope_cleanup, ai_context.children, ai_context.item_validator, and the remaining services in Services

Event subscribers, route subscribers, access checks, cache contexts, and the scope plugin manager (plugin.manager.ai_context_scope) are likewise internal infrastructure. All entries under Support services in the services reference are internal — they are not labeled individually. Use the public extension points above instead.

Entity display helpers

AiContextItem::getEntityItemData() and buildEntityItemRenderArray() format Specific Entities for display. They are leftover UI helpers and may be removed. Use getEntityItems() to read referenced entities. Use scope plugin labels for admin display.

Service decoration and replacement

Decorating, extending, or replacing internal services (for example ai_context.selector) in a services.yml file is not supported. The internal pipeline may change shape, argument order, or service graph between releases. Use the selector pipeline events and the selection factory for supported customization.

See also