You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Deduplicate shared runtime fields and behavior between ControlDefinition and ControlDefinitionRuntime by introducing a shared Pydantic base model.
This supports adding future root-level control fields, such as on_evaluation_error, without repeating schema fields and validators across both models.
Motivation
ControlDefinition and ControlDefinitionRuntime currently duplicate the same runtime-relevant fields: description, enabled, execution, scope, condition, action, and tags.
They already share behavior through _ConditionBackedControlMixin, but that mixin only provides methods; it does not share Pydantic fields, defaults, validators, or schema metadata.
Compare ControlDefinition and ControlDefinitionRuntime.
Observe that the runtime fields are repeated across both models.
Proposed solution (optional)
Introduce a shared Pydantic base, for example ControlDefinitionBase, containing the runtime-relevant fields and common validators.
Keep template-only fields and template validation only on ControlDefinition.
Keep runtime parsing behavior, including extra="ignore", on ControlDefinitionRuntime.
Add tests that compare existing accepted/rejected payloads before and after the refactor.
A concrete target shape could look like this:
classControlDefinitionBase(_ConditionBackedControlMixin, BaseModel):
"""Runtime-relevant fields shared by authored and runtime controls."""description: str|None=Field(None, description="Detailed description of the control")
enabled: bool=Field(True, description="Whether this control is active")
execution: Literal["server", "sdk"] =Field(
..., description="Where this control executes"
)
scope: ControlScope=Field(
default_factory=ControlScope,
description="Which steps and stages this control applies to",
)
condition: ConditionNode=Field(
...,
description=(
"Recursive boolean condition tree. Leaf nodes contain selector + evaluator; ""composite nodes contain and/or/not."
),
)
action: ControlAction=Field(..., description="What action to take when control matches")
on_evaluation_error: Literal["fail_open", "fail_closed"] =Field(
"fail_closed",
description="How the control behaves when condition evaluation fails.",
)
tags: list[str] =Field(default_factory=list, description="Tags for categorization")
@model_validator(mode="before")@classmethoddefcanonicalize_legacy_condition_shape(cls, data: Any) ->Any:
"""Accept legacy flat selector/evaluator payloads."""returncanonicalize_control_payload(data)
@model_validator(mode="after")defvalidate_condition_constraints(self) ->Self:
"""Validate runtime-relevant control constraints."""_validate_common_control_constraints(self.condition, self.action)
returnselfclassControlDefinition(ControlDefinitionBase):
"""Authored control definition, including template metadata."""template: TemplateDefinition|None=Field(
None,
description="Template metadata for template-backed controls",
)
template_values: dict[str, TemplateValue] |None=Field(
None,
description="Resolved parameter values for template-backed controls",
)
@model_validator(mode="after")defvalidate_template_pairing(self) ->Self:
"""Validate authoring-only template constraints."""has_template=self.templateisnotNonehas_template_values=self.template_valuesisnotNoneifhas_template!=has_template_values:
raiseValueError(
"template and template_values must both be present or both absent"
)
returnselfclassControlDefinitionRuntime(ControlDefinitionBase):
"""Runtime control model that ignores authoring-only metadata."""model_config=ConfigDict(extra="ignore")
canonicalize_control_payload(...) can be a small module-level helper extracted from today's ControlDefinition.canonicalize_payload(...), so both authored and runtime models share legacy-shape handling without depending on a subclass.
The template validator should stay only on ControlDefinition, because runtime controls ignore template metadata.
Verify generated JSON schemas do not regress unexpectedly for API consumers.
Summary
ControlDefinitionandControlDefinitionRuntimeby introducing a shared Pydantic base model.on_evaluation_error, without repeating schema fields and validators across both models.Motivation
ControlDefinitionandControlDefinitionRuntimecurrently duplicate the same runtime-relevant fields:description,enabled,execution,scope,condition,action, andtags._ConditionBackedControlMixin, but that mixin only provides methods; it does not share Pydantic fields, defaults, validators, or schema metadata.Current behavior
ControlDefinitionandControlDefinitionRuntimeare separate Pydantic models with repeated runtime fields.ControlDefinitionadds template authoring fields (template,template_values) and template-specific validation.ControlDefinitionRuntimeignores extra fields and is used by engine/server/SDK runtime evaluation paths._ConditionBackedControlMixin, but shared model fields do not.Expected behavior
ControlDefinitionshould extend that base with authoring/template fields and template-specific validation.ControlDefinitionRuntimeshould extend that base with runtime-specific config such asextra="ignore".Reproduction (if bug)
models/src/agent_control_models/controls.py.ControlDefinitionandControlDefinitionRuntime.Proposed solution (optional)
ControlDefinitionBase, containing the runtime-relevant fields and common validators.ControlDefinition.extra="ignore", onControlDefinitionRuntime.A concrete target shape could look like this:
Notes for implementation:
on_evaluation_errorfield is shown here because issue Define control-level semantics for evaluator failures and timeouts #199 is expected to add it; if this refactor lands first, leave that field out and add it later on the shared base.canonicalize_control_payload(...)can be a small module-level helper extracted from today'sControlDefinition.canonicalize_payload(...), so both authored and runtime models share legacy-shape handling without depending on a subclass.ControlDefinition, because runtime controls ignore template metadata.Additional context
models/src/agent_control_models/controls.py