Merged
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Template controls can now be created without providing parameter values. The server validates the template structure but skips rendering, storing the template metadata with enabled=false. Unrendered controls are visible in listings, attachable to agents, but excluded from evaluation. - Add UnrenderedTemplateControl model for GET response union - Create endpoint branches: complete values render, incomplete store unrendered - Update endpoint supports unrendered→rendered transition when values provided - GET endpoints discriminate via condition key presence in stored JSONB - PATCH rejects enabling unrendered templates with 422 - ControlSummary gains template_rendered field - Runtime evaluation query skips unrendered templates - Agent policy validation skips unrendered templates - 10 new server tests covering the full unrendered lifecycle
- Prevent rendered→unrendered downgrade: updating a rendered template control with incomplete values now forces a full render attempt, returning a clear error about missing parameters instead of silently stripping rendered fields - Deepen unrendered structural validation: validate_template_structure now walks definition_template to check $param bindings, reject undefined parameter references, detect unused parameters, and reject hardcoded agent-scoped evaluator names - Fix PATCH enabled=false on unrendered templates: detect unrendered state before attempting ControlDefinition.model_validate, treating disable as a no-op instead of raising CORRUPTED_DATA - Add 4 behavioral tests: rendered rejects incomplete update, PATCH disable no-op, unrendered rejects undefined $param / unused param / agent-scoped evaluator
- Reject optional params without defaults in unrendered structural validation (catches templates that can never render at creation time) - Fix PATCH rename-only on unrendered templates: detect unrendered state before ControlDefinition.model_validate to avoid false CORRUPTED_DATA - Export UnrenderedTemplateControl from Python SDK - Strengthen rename test to verify enabled=false in response - Add test for optional-param-without-default rejection on unrendered create
- Reject unknown keys and wrong-typed values in partial template_values on unrendered create (fail fast instead of persisting garbage) - Deduplicate structural validation: render_template_control_input now calls validate_template_structure instead of inlining the same checks - Fix description fallback in list endpoint: unrendered templates show template.description when top-level description is absent - Fix _reject_hardcoded_agent_scoped_evaluators to report actual condition path instead of hardcoded "condition.evaluator.name" - Fix PATCH error message indentation - Move UnrenderedTemplateControl import to module level in controls service - Add tests: unknown value key rejection, wrong-type value rejection, description fallback in list
- Wrap UnrenderedTemplateControl.model_validate in _parse_stored_control_data with proper error handling (returns 422 CORRUPTED_DATA instead of 500) - Wrap unrendered parse in list_controls_for_agent with try/except to skip corrupted rows instead of crashing the entire listing - Remove redundant unused-parameter check in render_template_control_input (already caught by validate_template_structure called at the top)
…elper - Skip unrendered template controls in check_evaluation_with_local so they don't trigger the server-call fallback (prevents hot-path latency regression for agents with attached unrendered templates) - Accept UnrenderedTemplateControl in to_template_control_input so callers can round-trip unrendered template data from GET endpoints - Add test: unrendered template does not trigger server fallback - Add test: to_template_control_input accepts unrendered template data
- Make /controls/validate mirror create: incomplete template values validate structure only (returns 200) instead of forcing a full render that rejects missing params. Use the render preview endpoint to check renderability. - Exclude unrendered templates from list filters that reference rendered-only fields (execution, step_type, stage, tag). Unrendered templates still appear in unfiltered listings and template_backed filter. - Update validate test to expect 200 for incomplete values - Add test: validate rejects structurally invalid unrendered templates - Add test: unrendered templates excluded from rendered-field filters but included in unfiltered listings
namrataghadi-galileo
approved these changes
Apr 3, 2026
Contributor
namrataghadi-galileo
left a comment
There was a problem hiding this comment.
Let wait for a final Go from @yashsheth46
# Conflicts: # sdks/python/src/agent_control/evaluation.py # server/src/agent_control_server/endpoints/evaluation.py
## What this does Adds UI support for creating and editing template-backed controls. This PR targets the `lev/controltemplates` branch which implements the server-side template rendering and API changes. video: https://github.com/user-attachments/assets/f4d70e1c-9961-4140-ac56-3da4448853ac ## How it works **Creating template-backed controls:** Users paste a `TemplateControlInput` JSON via the existing "From JSON" flow. The server discriminates template payloads from raw payloads and handles rendering transparently — no separate UI flow needed. **Editing template-backed controls:** When a user opens a template-backed control, the edit modal detects template metadata and shows a dedicated editor with: - **Parameters mode** (default): auto-generated form inputs driven by the template's parameter definitions, with a collapsible rendered preview panel - **Full JSON mode**: editable textarea with the full `TemplateControlInput` JSON for power users - A read-only left panel showing the rendered control's description, action, execution, and template info **Controls list:** Template-backed controls show a "Template" badge. The enable/disable toggle uses PATCH (not PUT /data) to avoid the 409 that the server returns when sending a raw `ControlDefinition` to a template-backed control. ## New components | Component | What it does | |---|---| | `TemplateParamForm` | Auto-generates form inputs from template parameter definitions (string→TextInput, string_list→TagsInput, enum→Select, boolean→Switch, regex_re2→monospace TextInput) | | `TemplatePreview` | Debounced render preview that calls `POST /control-templates/render` and strips `template`/`template_values` from the output so users see what the engine sees | | `TemplateEditContent` | Template-backed edit modal with Parameters/Full JSON toggle, read-only summary panel, and save via `TemplateControlInput` payload | | `useRenderTemplate` | Mutation hook wrapping the render preview API call | ## Test plan 13 Playwright tests covering: - Template badge display and absence for raw controls - Parameter form rendering with fields from template definition - Parameters ↔ Full JSON mode switching - Preview panel strips template metadata from rendered output - Read-only summary shows rendered control info - Control name pre-fill and editing - Raw control editing regression (standard form, no template UI) - From JSON creation flow still works All 123 tests pass (110 existing + 13 new), 0 regressions. ## Notes - Template types are defined manually in `types.ts` until `npm run fetch-api-types` is run against the updated server. They can be replaced with generated types after. - The `CreateFromTemplate` modal component is included but not wired up (we decided to use "From JSON" instead of a separate creation flow). It can be removed or repurposed later.
# Conflicts: # ui/src/core/page-components/agent-detail/agent-detail.tsx # ui/src/core/page-components/agent-detail/modals/edit-control/edit-control-content.tsx
…/controltemplates
…/controltemplates
Replace the plain <Textarea> in template-edit-content with the Monaco-based
JsonEditorMonaco component, adding a new 'template' editor mode that
understands the template JSON envelope structure.
Smart features now available when editing templates in Full JSON mode:
- Schema-driven property completions inside definition_template
- Evaluator name and selector path autocomplete
- Auto-edit: evaluator config fill on name change, steering_context toggle
- $param reference completions at value positions in definition_template
- Parameter name suggestions inside {"$param": ""} and template_values keys
- Inline validation via the template render endpoint
- Hover info on properties and $param references
- Code actions (wrap in AND/OR/NOT) on conditions in definition_template
- Empty value hints for evaluator names, selector paths, and $param values
The implementation uses a path-prefix approach: a definitionPrefix field
(['template', 'definition_template']) on the autocomplete context allows
existing location-check functions to work on relative paths without
per-function template awareness.
Fix Prettier formatting issues in the template editor language support and editor view files. Regenerate the TypeScript SDK client to reflect updated list-controls endpoint defaults (all states by default).
Use inline enum definitions in the operations file instead of extracted model files, matching what CI's Speakeasy generation produces. Updates default filter values from 'rendered'/'enabled' to 'all'/'all'.
The template editor now uses JsonEditorMonaco instead of a plain Textarea, so the test needs to read the value via the Monaco test bridge (getJsonEditorValue) instead of inputValue().
galileo-automation
pushed a commit
that referenced
this pull request
Apr 7, 2026
## [2.2.0](ts-sdk-v2.1.0...ts-sdk-v2.2.0) (2026-04-07) ### Features * **evaluators:** add starts_with/ends_with mode to list evaluator ([#154](#154)) ([bf1f7d7](bf1f7d7)) * **sdk:** [Enterprise Integration]: Add provider agnostic traceing ([#145](#145)) ([f1ca27c](f1ca27c)) * **sdk:** Add telemetry package to support sinks ([#164](#164)) ([2186ba1](2186ba1)) * **sdk:** default merge events in SDK ([#155](#155)) ([5984a60](5984a60)) * **server,sdk, ui:** Control Templates ([#158](#158)) ([78bb538](78bb538)) * **server:** Override PG password in dockerfile ([#148](#148)) ([5d70c7d](5d70c7d)) * **server:** Remove container name for dev postgres ([92b2d13](92b2d13)) * **server:** Start local dev pg under docker compose project endign with dev ([88bee63](88bee63)) * **ui, server:** Intuitive JSON editing for Controls ([#151](#151)) ([8c23cef](8c23cef)) * **ui:** add full control JSON editing and create-from-JSON ([#147](#147)) ([e685ed0](e685ed0)) ### Bug Fixes * **docs:** add explicit shutdown to quickstart example ([#149](#149)) ([b76014f](b76014f)) * **sdk:** use sync shutdown flush fallback ([#150](#150)) ([90265ba](90265ba)) * **server:** remove unused evaluator config store ([#152](#152)) ([dea2873](dea2873)) * **server:** Omit null fields in control JSON editor ([#157](#157)) ([0aa2f3c](0aa2f3c)) * **server:** Update docker-compose.dev.yml to use different container name ([14d4c87](14d4c87)) * **ui:** improve edit control ux, no layout shift, consistent spacing ([#122](#122)) ([76d67b9](76d67b9))
Collaborator
|
🎉 This PR is included in version 2.2.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Today, creating or updating a control requires hand-editing the full control JSON — condition trees, evaluator configs, selector paths, and all. Templates let callers define a reusable control shape with named, typed parameters, and then create or update controls by filling in just the parameter values.
For example, instead of constructing an entire regex-denial control from scratch, a caller can submit a template with a
patternparameter and astep_nameparameter, provide values for those parameters, and get a fully rendered, validated control back.Template controls can also be created without parameter values (unrendered templates). This supports a "set up now, configure later" workflow — the template is attached to an agent but excluded from evaluation until values are provided.
RFC and implementation plan: https://gist.github.com/lan17/ea9aaca990c9bcbfda6595469f3e76c5
How it works
Templates use a render-before-save design. The caller sends a
TemplateDefinition(parameter schema + adefinition_templatewith{"$param": "..."}placeholders) andtemplate_values. The server substitutes the values, validates the result as an ordinaryControlDefinition, and stores both the rendered control and the template metadata in the samecontrols.dataJSONB column. No schema migration needed.A template control exists in one of two states:
ControlDefinition, and the control is ready for evaluation (once enabled).enabled: falseand excluded from evaluation.The evaluation engine never sees template metadata or unrendered templates. Rendered template controls use
ControlDefinitionRuntimewithextra="ignore"to skip template fields. Unrendered templates are filtered from runtime queries viadata ? 'condition'.Key design decisions:
PUT /controls) and update (PUT /controls/{id}/data) endpoints detect template payloads via aControlDefinition | TemplateControlInputunion and render transparently. One new endpoint (POST /control-templates/render) provides stateless previews.template_valuesstores an unrendered template (enabled: false). The server validates template structure (parameter references, forbidden fields, agent-scoped evaluators) but skips rendering. Partial values are type-checked on create.enabledandnamestay outside the template. Templates cannot set or bind these fields.enabledis managed viaPATCHand preserved across template updates. Enabling an unrendered template is rejected with 422.PUT /datawith a rawControlDefinitionon a template-backed control returns 409.$parambinding.GET /controls/{id}/datareturnsControlDefinitionfor rendered controls orUnrenderedTemplateControlfor unrendered templates.template_backedfilter.check_evaluation_with_localto avoid triggering server-call fallbacks.Reviewer guide
Start here — these tests show the full lifecycle:
test_render_control_template_preview_returns_rendered_control— preview a template without persistingtest_create_template_backed_control_persists_template_metadata— create rendered and verify stored statetest_create_unrendered_template_control_without_values— create without values and verify unrendered statetest_update_unrendered_template_with_complete_values_renders— provide values to render an unrendered templatetest_template_backed_control_evaluates_after_policy_attachment— attach to agent and verify evaluationtest_unrendered_template_excluded_from_evaluation— verify unrendered templates don't affect evaluationThen follow by layer:
models/.../controls.pyUnrenderedTemplateControl,_ConditionBackedControlMixin,ControlDefinitionextension,ControlDefinitionRuntimemodels/.../server.py_parse_control_input— discriminates raw vs template payloads, rejects mixed payloads. Response unions forGetControlResponse,GetControlDataResponseserver/.../services/control_templates.pycan_render_template,validate_template_structure,validate_partial_template_values,render_template_control_input, reverse path map, error remappingserver/.../endpoints/controls.py_materialize_control_input(rendered vs unrendered branching), PATCH handler (enable guard), list filters,_parse_stored_control_dataunionserver/.../services/controls.py,engine/.../core.pyControlDefinitionRuntimewired into evaluation, unrendered templates skipped in runtime and agent-controls queriessdks/python/.../controls.py,.../evaluation.pyto_template_control_input()handles both rendered and unrendered shapes.check_evaluation_with_localskips unrendered templatesV1 limitations
$paramescaping — the$paramkey is reserved in all template JSON values$paramreplaces the entire JSON value, not a substringTemplateControlInputonly (no rendered fields). Useto_template_control_input()SDK helper to reshape.Validation
make check(lint + typecheck + all tests)make sdk-ts-generate+make sdk-ts-name-check+make sdk-ts-typecheck+make sdk-ts-build