#782·parlant

[Enhancement] Extend the DI container

Author: StaszekMCreated Apr 21, 2026Updated May 13, 2026
Labelsenhancement

Motivation

Some classes are not managed by the Lagom DI container and are instead instantiated via hardcoded imports. This makes them difficult to customize without modifying or extending upstream code.

Problem Example

Consider a class A (managed by DI) that directly constructs an instance of class B (not managed by DI). Because the construction is hardcoded, replacing B with a custom implementation (e.g. B') requires subclassing A and overriding the method responsible for creating B.

Example from GenericGuidelineMatchingStrategy:

python
def _create_batch_observational_guideline(
    self,
    guidelines: Sequence[Guideline],
    journeys: Sequence[Journey],
    context: GuidelineMatchingContext,
) -> GenericObservationalGuidelineMatchingBatch:
    return GenericObservationalGuidelineMatchingBatch(
        logger=self._logger,
        meter=self._meter,
        optimization_policy=self._optimization_policy,
        schematic_generator=self._observational_guideline_schematic_generator,
        guidelines=guidelines,
        journeys=journeys,
        context=context,
    )

Here, GenericObservationalGuidelineMatchingBatch is instantiated directly. To replace it with a custom implementation (e.g. MyGenericObservationalGuidelineMatchingBatch), we must override the entire GenericGuidelineMatchingStrategy.

Classes That Currently Require Workarounds

The following classes require similar overrides or monkey patching:

  1. Guideline matching batches (Actionable, Observational, PreviouslyAppliedActionableCustomerDependent) We replace these to customize prompt construction (e.g. injecting shots with proper ASCII escaping for language diacritics). This requires overriding GenericGuidelineMatchingStrategy in DI.

    While Parlant supports prompt customization via named sections, some section names are reused across the codebase (e.g. BuiltInSection.GUIDELINES), which creates ambiguity.

  2. GenericResponseAnalysisBatch Same motivation as above — deeper prompt customization than section replacement allows.

  3. PluginClient Monkey patched to propagate the current request’s trace ID across HTTP boundaries (engine → tool). Without this, spans created inside tools are attached to disconnected (rootless) traces.

  4. PromptBuilder Monkey patched to override _INTERACTION_BODY and _EMPTY_HISTORY.

Proposed Solution

Allow these classes to be managed and overridden via the DI container.

Discussion

Most of these issues (except PluginClient) stem from the need to modify every prompt sent to the model.

We tailor the engine for a very small LLM with limited instruction-following ability, trading capability for lower latency and energy consumption. This requires fine-grained control over prompt structure.

In many cases, replacing prompt sections by name is sufficient. However, some changes require deeper modifications, such as:

  • adjusting response format instructions (e.g. _format_of_guideline_check_json_description in GenericResponseAnalysisBatch)
  • modifying how data is passed into templates

Additionally, some section names are reused in different contexts with different meanings (e.g. guideline-action-proposer-output-format). This makes section-level overrides unreliable and forces us to modify internal class logic instead of working at the abstraction level described in the docs.