Skip to content

Commit 2ff4c12

Browse files
authored
feat(server)!: implement recursive control condition trees (#115)
corresponding docs update: agentcontrol/docs-agent-control#28 ## Summary - replace flat control predicates with recursive `condition` trees across the shared models, engine, server, SDKs, and UI contracts - add pre-upgrade control migration tooling plus safe `condition_trace` metadata for composite evaluation - bump package majors and regenerate the TypeScript SDK/API types for the new contract ## Scope - User-facing/API changes: - `ControlDefinition` now uses a top-level `condition` tree instead of top-level `selector` / `evaluator` - controls now support nested `and` / `or` / `not` composition with a hard API depth limit of `6` - `/api/v1` stays the same, but this is a major-version wire break for the canonical contract and old SDK majors are unsupported against the new server - added `make server-migrate-control-conditions MIGRATE_ARGS=--dry-run` and `make server-migrate-control-conditions MIGRATE_ARGS=--apply` - Internal changes: - recursive engine evaluation with short-circuiting and `metadata.condition_trace` - recursive server-side validation with nested field paths - Python SDK recursive leaf checks plus non-blocking server-version mismatch warnings - UI keeps the existing single-condition editor behavior for leaf conditions; composite trees are shown but preserved on save rather than fully edited in this PR - Rollout compatibility notes: - server/models still canonicalize legacy flat control payloads (`selector` + `evaluator`) into `condition` during rollout - strict runtime rejection of legacy flat payloads is deferred until after the migration window closes - Out of scope: - full condition-tree editing in the current UI modal - runtime strict rejection of legacy flat control payloads - drag-and-drop tree editing ## Risk and Rollout - Risk level: high - Rollback plan: - take a database backup before running the migration apply step - if rollout fails before migration, revert to the previous server/SDK major - if rollout fails after migration, restore the pre-migration database snapshot before rolling back the server major ## Testing - [x] Added or updated automated tests - [x] Ran `make check` - [ ] Manually verified behavior `make check` passed, which covered: - `make server-test` - `make engine-test` - `make sdk-test` - `make evaluators-test` - repo linting - repo typechecking Notes: - the run emitted a few existing third-party warnings (`sqlglot[rs]` deprecation, Strands resource/deprecation warnings), but the full target passed cleanly ## Checklist - [x] Linked issue/spec: sc-57582, sc-58429 - [x] Updated docs/examples for user-facing changes - [x] Included any required follow-up tasks
1 parent 2c55d27 commit 2ff4c12

82 files changed

Lines changed: 5009 additions & 1296 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
- name: Upload coverage to Codecov
6363
uses: codecov/codecov-action@v4
6464
with:
65-
files: coverage-engine.xml,coverage-server.xml,coverage-sdk.xml
65+
files: coverage-models.xml,coverage-engine.xml,coverage-server.xml,coverage-sdk.xml
6666
fail_ci_if_error: false
6767
token: ${{ secrets.CODECOV_TOKEN }}
6868

‎Makefile‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help sync openapi-spec openapi-spec-check test test-extras test-all test-models test-sdk lint lint-fix typecheck check build build-models build-server build-sdk publish publish-models publish-server publish-sdk hooks-install hooks-uninstall prepush evaluators-test evaluators-lint evaluators-lint-fix evaluators-typecheck evaluators-build galileo-test galileo-lint galileo-lint-fix galileo-typecheck galileo-build sdk-ts-generate sdk-ts-overlay-test sdk-ts-name-check sdk-ts-generate-check sdk-ts-build sdk-ts-test sdk-ts-lint sdk-ts-typecheck sdk-ts-release-check sdk-ts-publish-dry-run sdk-ts-publish
1+
.PHONY: help sync openapi-spec openapi-spec-check test test-extras test-all models-test test-models test-sdk lint lint-fix typecheck check build build-models build-server build-sdk publish publish-models publish-server publish-sdk hooks-install hooks-uninstall prepush evaluators-test evaluators-lint evaluators-lint-fix evaluators-typecheck evaluators-build galileo-test galileo-lint galileo-lint-fix galileo-typecheck galileo-build sdk-ts-generate sdk-ts-overlay-test sdk-ts-name-check sdk-ts-generate-check sdk-ts-build sdk-ts-test sdk-ts-lint sdk-ts-typecheck sdk-ts-release-check sdk-ts-publish-dry-run sdk-ts-publish
22

33
# Workspace package names
44
PACK_MODELS := agent-control-models
@@ -31,7 +31,8 @@ help:
3131
@echo " make openapi-spec-check - verify OpenAPI generation succeeds"
3232
@echo ""
3333
@echo "Test:"
34-
@echo " make test - run tests for core packages (server, engine, sdk, evaluators)"
34+
@echo " make test - run tests for core packages (models, server, engine, sdk, evaluators)"
35+
@echo " make models-test - run shared model tests with coverage"
3536
@echo " make test-extras - run tests for contrib evaluators (galileo, etc.)"
3637
@echo " make test-all - run all tests (core + extras)"
3738
@echo " make sdk-ts-test - run TypeScript SDK tests"
@@ -81,7 +82,12 @@ openapi-spec-check: openapi-spec
8182
# Test
8283
# ---------------------------
8384

84-
test: server-test engine-test sdk-test evaluators-test
85+
test: models-test server-test engine-test sdk-test evaluators-test
86+
87+
models-test:
88+
cd $(MODELS_DIR) && uv run pytest --cov=src --cov-report=xml:../coverage-models.xml -q
89+
90+
test-models: models-test
8591

8692
# Run tests for contrib evaluators (not included in default test target)
8793
test-extras: galileo-test

‎README.md‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,12 @@ async def setup():
160160
"enabled": True,
161161
"execution": "server",
162162
"scope": {"stages": ["post"]},
163-
"selector": {"path": "output"},
164-
"evaluator": {
165-
"name": "regex",
166-
"config": {"pattern": r"\b\d{3}-\d{2}-\d{4}\b"},
163+
"condition": {
164+
"selector": {"path": "output"},
165+
"evaluator": {
166+
"name": "regex",
167+
"config": {"pattern": r"\b\d{3}-\d{2}-\d{4}\b"},
168+
},
167169
},
168170
"action": {"decision": "deny"},
169171
},
@@ -182,6 +184,8 @@ async def setup():
182184
asyncio.run(setup())
183185
```
184186

187+
Controls now store leaf `selector` and `evaluator` definitions under `condition`, which also enables composite `and`, `or`, and `not` trees.
188+
185189
**Tip**: If you prefer a visual flow, use the UI instead - see the [UI Quickstart](https://docs.agentcontrol.dev/core/ui-quickstart).
186190

187191
Run both scripts in order:

0 commit comments

Comments
 (0)