Skip to content

feat(server,sdk): Control Templates#158

Open
lan17 wants to merge 16 commits intomainfrom
lev/controltemplates
Open

feat(server,sdk): Control Templates#158
lan17 wants to merge 16 commits intomainfrom
lev/controltemplates

Conversation

@lan17
Copy link
Copy Markdown
Contributor

@lan17 lan17 commented Apr 1, 2026

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 pattern parameter and a step_name parameter, provide values for those parameters, and get a fully rendered, validated control back.

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 + a definition_template with {"$param": "..."} placeholders) and template_values. The server substitutes the values, validates the result as an ordinary ControlDefinition, and stores both the rendered control and the template metadata in the same controls.data JSONB column. No schema migration needed.

                         ┌─────────────────────┐
  TemplateDefinition ──▶ │  Server-side render  │ ──▶ ControlDefinition
  + parameter values     │  (validate, coerce,  │     (concrete, stored
                         │   substitute $param) │      in controls.data)
                         └─────────────────────┘
                                                        │
                                                        ▼
                                                 ┌──────────────┐
                                                 │   Evaluation  │
                                                 │    engine     │
                                                 │ (reads only   │
                                                 │  concrete     │
                                                 │  fields)      │
                                                 └──────────────┘

The evaluation engine never sees template metadata. It reads controls through a slim ControlDefinitionRuntime model with extra="ignore", so template fields in the JSONB are skipped at zero cost. From the engine's perspective, a template-backed control is just a control.

Key design decisions:

  • No new CRUD endpoints for controls. Existing create (PUT /controls) and update (PUT /controls/{id}/data) endpoints detect template payloads via a ControlDefinition | TemplateControlInput union and render transparently. One new endpoint (POST /control-templates/render) provides stateless previews.
  • enabled and name stay outside the template. Templates cannot set or bind these fields. enabled is managed via PATCH and preserved across template updates. This prevents a template update from silently re-enabling a disabled control.
  • Template-backed controls cannot be converted back to raw controls in v1. PUT /data with a raw ControlDefinition on a template-backed control returns 409 to prevent accidental template stripping by older clients.
  • Validation errors map back to template parameters. When a rendered control fails validation, the server traces the error back through a reverse path map to the originating $param binding, so the caller sees "field": "template_values.pattern" instead of "field": "condition.evaluator.config.pattern".

Reviewer guide

Start here — these three tests show the full lifecycle:

  1. test_render_control_template_preview_returns_rendered_control — preview a template without persisting
  2. test_create_template_backed_control_persists_template_metadata — create and verify stored state
  3. test_template_backed_control_evaluates_after_policy_attachment — attach to agent and verify evaluation behavior

Then follow by layer:

Layer Key files What to look for
Shared models models/.../controls.py Template types, _ConditionBackedControlMixin, ControlDefinition extension (template/template_values + invariant validator), ControlDefinitionRuntime
Payload discrimination models/.../server.py _parse_control_input — discriminates raw vs template payloads, rejects mixed payloads with a clear error
Rendering service server/.../services/control_templates.py Parameter resolution, $param substitution, reverse path map, error remapping, agent-scoped evaluator rejection
Endpoints server/.../endpoints/controls.py Modified create/update/validate handlers, new render preview endpoint, template_backed list filter
Runtime split server/.../services/controls.py, engine/.../core.py ControlDefinitionRuntime wired into evaluation hot path, ControlDefinitionLike protocol
Python SDK sdks/python/.../controls.py to_template_control_input() reshape helper, template-aware create/update/validate/render

V1 limitations

  • No agent-scoped evaluators in templates — the renderer explicitly rejects evaluator names containing : before the existing validator can attempt to load an agent
  • No in-place template-to-raw conversion — delete and recreate to switch a control from template-backed to raw
  • No $param escaping — the $param key is reserved in all template JSON values
  • No string interpolation$param replaces the entire JSON value, not a substring
  • No template catalogs — callers supply the template definition on each request
  • Last-write-wins concurrency — no optimistic locking in v1

Validation

  • make check
  • make sdk-ts-generate
  • make sdk-ts-name-check
  • make sdk-ts-typecheck
  • make sdk-ts-build

@lan17 lan17 changed the title feat: add control template support feat(server): Control Temlates Apr 1, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

@lan17 lan17 changed the title feat(server): Control Temlates feat: add control template support Apr 1, 2026
@lan17 lan17 changed the title feat: add control template support feat(server,sdk): add control template support Apr 1, 2026
@lan17 lan17 marked this pull request as ready for review April 1, 2026 07:08
@lan17 lan17 changed the title feat(server,sdk): add control template support feat(server,sdk): Control Templates Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant