agent-contracts 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,10 +228,39 @@ Workflow steps support additional properties:
228
228
  * `group` — consecutive steps with the same group are rendered as `par` (parallel) blocks in sequence diagrams
229
229
  * `retry` (handoff steps only) — defines a conditional retry loop with `condition`, `fix_task`, and optional `revalidate_task`
230
230
 
231
- ### Handoff
231
+ ### Guardrail
232
232
 
233
- A **Handoff** is a runtime delegation instance.
234
- The YAML defines the allowed handoff types and constraints; concrete handoffs are created at runtime.
233
+ A **Guardrail** declares a cross-cutting constraint:
234
+
235
+ * description — what is protected
236
+ * scope — which DSL entities it applies to (agents, tasks, tools, artifacts, workflows)
237
+ * rationale — why the constraint exists
238
+ * tags — classification for filtering
239
+ * exemptions — glob patterns or entity IDs exempt from the guardrail
240
+
241
+ ### Guardrail Policy
242
+
243
+ A **Guardrail Policy** defines enforcement strategy for guardrails:
244
+
245
+ * rules — array of enforcement rules mapping guardrails to actions
246
+ * Each rule specifies: severity (`critical`/`mandatory`/`warning`/`info`), action (`block`/`warn`/`shadow`/`info`), override permissions
247
+
248
+ ### Handoff Type
249
+
250
+ A **Handoff Type** defines the schema for inter-agent messages:
251
+
252
+ * `schema` — a JSON Schema object describing the full message structure
253
+ * `description`
254
+ * `example`
255
+ * `version`
256
+
257
+ Schemas can use `allOf` with `$ref: "#/components/schemas/..."` to compose shared fields (e.g., common envelope) with type-specific properties.
258
+
259
+ ### Components
260
+
261
+ **Components** provide reusable definitions, following the OpenAPI pattern:
262
+
263
+ * `components.schemas` — named JSON Schema fragments that can be referenced from anywhere via `$ref: "#/components/schemas/<name>"`
235
264
 
236
265
  ---
237
266
 
@@ -265,12 +294,17 @@ Design regressions become testable.
265
294
  * **Static schema validation**
266
295
  * **Reference integrity checks**
267
296
  * **Semantic linting**
268
- * **Structured handoff definitions**
297
+ * **Structured handoff definitions** with formal JSON Schema and `allOf` composition
298
+ * **Reusable schema components** via `components.schemas` and JSON Pointer `$ref`
269
299
  * **Artifact ownership and lifecycle modeling**
270
300
  * **Config-driven prompt rendering**
271
301
  * **Variable substitution** via `${vars.xxx}` in DSL values
272
302
  * **Inheritance with merge operators via `extends`**
273
- * **Flexible file splitting** via `$ref` (replacement) and `$refs` (import + deep-merge)
303
+ * **Guardrail definitions** for cross-cutting process constraints
304
+ * **Guardrail policies** with configurable enforcement (block/warn/shadow/info)
305
+ * **Software bindings** (DI) for tool-specific guardrail implementation (Cursor, Git, GitHub)
306
+ * **Guardrail generation** from DSL + policy + bindings via `generate guardrails`
307
+ * **Flexible file splitting** via `$ref` (replacement), `$refs` (import + deep-merge), and JSON Pointer `$ref` (in-document)
274
308
  * **JSON Schema for editor support and external tooling**
275
309
  * **CI-friendly workflow checks**
276
310
 
@@ -304,6 +338,10 @@ validations: {}
304
338
  handoff_types: {}
305
339
  workflow: {}
306
340
  policies: {}
341
+ guardrails: {}
342
+ guardrail_policies: {}
343
+ components:
344
+ schemas: {}
307
345
  ````
308
346
 
309
347
  This makes definitions easy to merge, extend, and reference by stable identifiers.
@@ -421,8 +459,44 @@ Overlapping map keys are deep-merged recursively. Conflicting leaf values (scala
421
459
  | Directive | Type | Behavior |
422
460
  | --------- | ------ | -------------------------------------------------------- |
423
461
  | `$ref` | string | Replace the object at that position with file contents |
462
+ | `$ref` (`#/...`) | string | Replace with the value at the given JSON Pointer path within the document |
424
463
  | `$refs` | array | Import files and deep-merge into the containing map |
425
464
 
465
+ ### JSON Pointer `$ref`
466
+
467
+ `$ref` also supports **in-document references** using JSON Pointer syntax (RFC 6901).
468
+ When the value starts with `#/`, it resolves against the root document instead of the file system.
469
+
470
+ ````yaml
471
+ components:
472
+ schemas:
473
+ handoff-common:
474
+ type: object
475
+ required: [from_agent, to_agent]
476
+ properties:
477
+ from_agent: { type: string }
478
+ to_agent: { type: string }
479
+
480
+ handoff_types:
481
+ task-delegation:
482
+ version: 1
483
+ schema:
484
+ allOf:
485
+ - $ref: "#/components/schemas/handoff-common"
486
+ - type: object
487
+ required: [payload]
488
+ properties:
489
+ payload:
490
+ type: object
491
+ required: [objective]
492
+ properties:
493
+ objective: { type: string }
494
+ ````
495
+
496
+ This is particularly useful for sharing common schema fragments across multiple `handoff_types` entries via `components.schemas`.
497
+
498
+ JSON Pointer references are resolved in the same processing phase as file `$ref` — before Zod validation. They can be used anywhere in the document, not just within `handoff_types`.
499
+
426
500
  ---
427
501
 
428
502
  ## Example: Agent definition
@@ -545,6 +619,80 @@ workflow:
545
619
 
546
620
  ---
547
621
 
622
+ ## Example: Handoff type definition
623
+
624
+ Handoff types define the schema for inter-agent messages using JSON Schema.
625
+
626
+ ````yaml
627
+ handoff_types:
628
+ task-delegation:
629
+ version: 1
630
+ description: "Delegate a task to a sub-agent"
631
+ schema:
632
+ type: object
633
+ required: [task, objective]
634
+ properties:
635
+ task: { type: string }
636
+ objective: { type: string }
637
+ constraints:
638
+ type: array
639
+ items: { type: string }
640
+ ````
641
+
642
+ ### Using `components.schemas` with `allOf`
643
+
644
+ Common fields (e.g., `from_agent`, `to_agent`, `run_id`) can be shared across handoff types by placing them in `components.schemas` and composing via `allOf`:
645
+
646
+ ````yaml
647
+ components:
648
+ schemas:
649
+ handoff-common:
650
+ type: object
651
+ required: [from_agent, to_agent]
652
+ properties:
653
+ from_agent: { type: string }
654
+ to_agent: { type: string }
655
+ run_id: { type: string }
656
+
657
+ handoff_types:
658
+ task-delegation:
659
+ version: 1
660
+ description: "Delegate a task"
661
+ schema:
662
+ allOf:
663
+ - $ref: "#/components/schemas/handoff-common"
664
+ - type: object
665
+ required: [payload]
666
+ properties:
667
+ payload:
668
+ type: object
669
+ required: [objective]
670
+ properties:
671
+ objective: { type: string }
672
+
673
+ implementation-result:
674
+ version: 1
675
+ description: "Return implementation results"
676
+ schema:
677
+ allOf:
678
+ - $ref: "#/components/schemas/handoff-common"
679
+ - type: object
680
+ required: [payload]
681
+ properties:
682
+ payload:
683
+ type: object
684
+ required: [result]
685
+ properties:
686
+ result: { type: string }
687
+ evidence:
688
+ type: array
689
+ items: { type: string }
690
+ ````
691
+
692
+ The `$ref: "#/..."` references are resolved during loading, before validation. The resulting merged schema is then meta-validated as valid JSON Schema.
693
+
694
+ ---
695
+
548
696
  ## Inheritance and merge operators
549
697
 
550
698
  `agent-contracts` supports shared base definitions with project-level overrides through `extends`.
@@ -665,6 +813,7 @@ npx agent-contracts
665
813
  | `agent-contracts validate [path]` | Validate schema and references |
666
814
  | `agent-contracts lint [path]` | Run semantic lint |
667
815
  | `agent-contracts render` | Render outputs from config |
816
+ | `agent-contracts generate guardrails` | Generate guardrail artifacts from bindings |
668
817
  | `agent-contracts check` | Run resolve → validate → lint → render --check |
669
818
 
670
819
  The `[path]` argument defaults to `agent-contracts.yaml` in the current directory.
@@ -731,6 +880,8 @@ Each `context` type provides a different rendering scope:
731
880
  | `handoff_type` | Per handoff type | `{handoff_type.id}` in output path | `handoff_type`, `relatedTasks`, `dsl` |
732
881
  | `workflow` | Per workflow phase | `{workflow.id}` in output path | `workflow`, `relatedAgents`, `relatedTasks`, `relatedTools`, `relatedArtifacts`, `relatedValidations`, `dsl` |
733
882
  | `policy` | Per policy | `{policy.id}` in output path | `policy`, `dsl` |
883
+ | `guardrail` | Per guardrail | `{guardrail.id}` in output path | `guardrail`, `dsl` |
884
+ | `guardrail_policy` | Per guardrail policy | `{guardrail_policy.id}` in output path | `guardrail_policy`, `dsl` |
734
885
 
735
886
  #### Enriched context details
736
887
 
@@ -764,7 +915,7 @@ Templates can use these built-in helpers:
764
915
  | `notEmpty` | `{{#if (notEmpty obj)}}` | True when object has at least one key |
765
916
  | `inc` | `{{inc @index}}` | Increment number by 1 (for 1-based indexing) |
766
917
  | `yamlBlock` | `{{{yamlBlock obj}}}` | Render value as YAML-formatted text |
767
- | `lookupPayloadFields` | `{{#each (lookupPayloadFields payload)}}` | Extract payload field info (name, type, required, enum) |
918
+ | `lookupPayloadFields` | `{{#each (lookupPayloadFields schema)}}` | Extract schema field info (name, type, required, enum); resolves `allOf` internally |
768
919
  | `join` | `{{join arr ", "}}` | Join array elements with separator |
769
920
  | `contains` | `{{#if (contains arr "x")}}` | True when array includes value |
770
921
  | `groupBy` | `{{#with (groupBy arr "key")}}` | Group array elements by field value |
@@ -781,6 +932,102 @@ Templates can use these built-in helpers:
781
932
 
782
933
  ---
783
934
 
935
+ ## Guardrail DI system
936
+
937
+ `agent-contracts` includes a dependency injection system for guardrails that separates **what to protect** from **how to enforce** and **where to output**.
938
+
939
+ ### Architecture
940
+
941
+ ```text
942
+ agent-contracts.yaml (DSL) agent-contracts.config.yaml
943
+ ├─ guardrails: (what + why) ├─ bindings: [cursor.yaml, git.yaml, ...]
944
+ ├─ guardrail_policies: (how) ├─ active_guardrail_policy: default
945
+ └─ agents, tasks, ... ├─ paths: {cursor_root: .cursor, ...}
946
+ └─ vars, renders (existing)
947
+ ```
948
+
949
+ ### Guardrail definition
950
+
951
+ Guardrails declare constraints in the DSL without any implementation details:
952
+
953
+ ````yaml
954
+ guardrails:
955
+ no-force-push:
956
+ description: "Force push to protected branches is forbidden"
957
+ scope:
958
+ tools: [git]
959
+ rationale: "Force push destroys commit history"
960
+ tags: [branch-protection, safety]
961
+ ````
962
+
963
+ ### Guardrail policy
964
+
965
+ Policies define enforcement strategies:
966
+
967
+ ````yaml
968
+ guardrail_policies:
969
+ default-enforcement:
970
+ rules:
971
+ - guardrail: no-force-push
972
+ severity: critical
973
+ action: block
974
+ - guardrail: english-only-code
975
+ severity: warning
976
+ action: warn
977
+ allow_override: true
978
+ ````
979
+
980
+ ### Software bindings
981
+
982
+ Bindings define software-specific check implementations:
983
+
984
+ ````yaml
985
+ # bindings/cursor.yaml
986
+ software: cursor
987
+ version: 1
988
+
989
+ guardrail_impl:
990
+ no-force-push:
991
+ checks:
992
+ - hook_event: beforeShellExecution
993
+ matcher:
994
+ type: command_regex
995
+ pattern: "git\\s+push\\s+.*--force"
996
+ message: "Force push is forbidden"
997
+
998
+ outputs:
999
+ hook-script:
1000
+ target: "{cursor_root}/hooks/evaluate-hook.sh"
1001
+ mode: write
1002
+ executable: true
1003
+ template: ./templates/cursor-hook-wrapper.sh.hbs
1004
+ ````
1005
+
1006
+ ### Config
1007
+
1008
+ ````yaml
1009
+ # agent-contracts.config.yaml
1010
+ bindings:
1011
+ - ./bindings/cursor.yaml
1012
+ - ./bindings/git.yaml
1013
+
1014
+ active_guardrail_policy: default-enforcement
1015
+
1016
+ paths:
1017
+ cursor_root: .cursor
1018
+ git_hooks_root: scripts/git-hooks
1019
+ ````
1020
+
1021
+ ### Generate command
1022
+
1023
+ ````bash
1024
+ agent-contracts generate guardrails -c agent-contracts.config.yaml
1025
+ agent-contracts generate guardrails -c agent-contracts.config.yaml --binding cursor
1026
+ agent-contracts generate guardrails -c agent-contracts.config.yaml --dry-run
1027
+ ````
1028
+
1029
+ ---
1030
+
784
1031
  ## Validation model
785
1032
 
786
1033
  `agent-contracts` validates your system in multiple layers.
@@ -792,7 +1039,8 @@ Checks:
792
1039
  * required fields
793
1040
  * types
794
1041
  * enums
795
- * handoff payload shape
1042
+ * handoff schema shape (meta-validated as valid JSON Schema via ajv)
1043
+ * `allOf` composition in handoff schemas
796
1044
  * invalid custom properties without `x-` prefix (checked at all nesting levels)
797
1045
 
798
1046
  Custom properties with `x-` prefix are allowed on any object in the DSL — top-level entities (agents, tasks, artifacts, …), nested objects (rules, execution steps, workflow steps, …), and the root DSL itself.
@@ -803,7 +1051,7 @@ Checks:
803
1051
 
804
1052
  * cross-entity references
805
1053
  * owner / producer / editor / consumer validity
806
- * handoff and payload consistency
1054
+ * handoff schema consistency (`required` vs. `properties` alignment)
807
1055
  * permission alignment between agents and artifacts
808
1056
 
809
1057
  ### Semantic lint
@@ -839,16 +1087,16 @@ That separation keeps runtime concerns and architecture concerns from being mixe
839
1087
 
840
1088
  ## Tech stack
841
1089
 
842
- | Category | Choice |
843
- | ------------ | ---------------------------------- |
844
- | Language | TypeScript (ESM, strict mode) |
845
- | Schema | Zod |
846
- | YAML parsing | yaml |
847
- | Lint | TypeScript custom rules + Spectral |
848
- | Templates | Handlebars |
849
- | CLI | commander |
850
- | Testing | Vitest |
851
- | Build | tsup |
1090
+ | Category | Choice |
1091
+ | -------------- | ---------------------------------- |
1092
+ | Language | TypeScript (ESM, strict mode) |
1093
+ | Schema | Zod + ajv (JSON Schema meta-validation) |
1094
+ | YAML parsing | yaml |
1095
+ | Lint | TypeScript custom rules + Spectral |
1096
+ | Templates | Handlebars |
1097
+ | CLI | commander |
1098
+ | Testing | Vitest |
1099
+ | Build | tsup |
852
1100
 
853
1101
  ---
854
1102