agentversion 0.2.0__tar.gz → 0.2.2__tar.gz
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.
Potentially problematic release.
This version of agentversion might be problematic. Click here for more details.
- {agentversion-0.2.0 → agentversion-0.2.2}/CHANGELOG.md +33 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/PKG-INFO +1 -1
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/contract.py +20 -1
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/diff.py +3 -3
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/manifest.py +16 -11
- {agentversion-0.2.0 → agentversion-0.2.2}/pyproject.toml +1 -1
- agentversion-0.2.2/spec/behavioral-policy.md +56 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_contract.py +61 -0
- agentversion-0.2.0/spec/behavioral-policy.md +0 -51
- {agentversion-0.2.0 → agentversion-0.2.2}/.github/dependabot.yml +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/.github/workflows/ci.yml +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/.github/workflows/publish.yml +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/.gitignore +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/CONFORMANCE.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/CONTRIBUTING.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/LICENSE +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/README.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/RELEASING.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/adrs/0000-template.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/adrs/0001-version-spec-core.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/__init__.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/_shared.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/a2a.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/cli.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/compatibility.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/constants.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/dataset.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/decision.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/hasher.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/ids.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/py.typed +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/refs.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/replay.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/agentversion/validator.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/environment-region-change/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/environment-region-change/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/environment-region-change/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/model-runtime-provider-change/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/model-runtime-provider-change/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/model-runtime-provider-change/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/output-schema-change/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/output-schema-change/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/output-schema-change/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/prompt-stack-edit/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/prompt-stack-edit/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/prompt-stack-edit/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/skill-registry-skill-removed/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/skill-registry-skill-removed/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/skill-registry-skill-removed/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/subagent-handoff-change/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/subagent-handoff-change/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/subagent-handoff-change/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/tool-rename/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/tool-rename/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/tool-rename/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/workflow-graph-change/after.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/workflow-graph-change/before.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/workflow-graph-change/expected-diff.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/.gitkeep +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/integrations/decimalai_bridge.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/integrations/langgraph_example.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/integrations/otel_mapping.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/manifest/finance-agent-v1.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/manifest/finance-agent-v2.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/scenarios/tool-rename-drift.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/examples/scenarios/walkthrough.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/pyrightconfig.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/.gitkeep +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/agent-manifest.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/compatibility-batch.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/compatibility-decision.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/compatibility-policy.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/compatibility-report.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/dataset-snapshot.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/episode.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/manifest-diff.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/replay-job.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/replay-result.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/step.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/schemas/task.schema.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/scripts/release.sh +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/a2a-mapping.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/attestation.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/compatibility-batch.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/compatibility-decision.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/compatibility-policy.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/data-classification.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/dataset.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/diff.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/environment.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/evaluation.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/hashing.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/ids.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/lifecycle.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/manifest.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/otel-mapping.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/reference.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/refs.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/replay-determinism.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/replay.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/spec/versioning-policy.md +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/fixtures/frozen_hash_vectors.json +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_a2a_mapping.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_audit_v020.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_audit_v030.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_behavioral_policy.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_cli.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_component_surface_map.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_conformance.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_dataset.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_decision_replay.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_diff.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_environment.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_evaluation.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_examples_runnable.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_extension_hatch.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_frozen_hash_vectors.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_hasher.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_ids.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_lifecycle.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_manifest.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_refs.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_reproducible_replay.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_trust_observability.py +0 -0
- {agentversion-0.2.0 → agentversion-0.2.2}/tests/test_validator.py +0 -0
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
> **Package version ≠ spec version.** This file tracks the **package** version. The on-the-wire `spec_version` is independent and frozen at `1.0.0`; a pre-1.0 package can implement a stable 1.0 spec, which is exactly the situation today.
|
|
9
9
|
|
|
10
|
+
## [0.2.2] - 2026-07-15
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **`behavioral_policy` re-scoped as a generic versioned policy-document surface** (docs only).
|
|
14
|
+
The surface previously described itself as bound to skillevaluation's conversation-mode
|
|
15
|
+
`policy_check` state machine; that eval mode was removed (skillevaluation ADR-0007), so the
|
|
16
|
+
docstrings + `spec/behavioral-policy.md` now define it document-agnostically: `policy_hash` is a
|
|
17
|
+
hash of *any* bound policy artifact (refund rules, a safety guardrail set, an escalation SOP…),
|
|
18
|
+
a rule change diffs **breaking**, an unchanged `policy_hash` is non-breaking. **No code or schema
|
|
19
|
+
change**: the `BehavioralPolicy` model still carries `objection_threshold`/`concede_events`/
|
|
20
|
+
`always_forbidden` (now documented as optional, illustrative, back-compat rule fields), the
|
|
21
|
+
`_BEHAVIORAL_POLICY_RULE_FIELDS` diff logic is byte-identical, and every frozen hash-vector +
|
|
22
|
+
the 406-test suite are unchanged. `spec_version` unchanged (1.0.0).
|
|
23
|
+
|
|
24
|
+
## [0.2.1] - 2026-06-24
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **`contract_from_components` now emits the `behavioral_policy` and
|
|
28
|
+
`environment` surfaces.** 0.2.0 added the models, surface keys, and diff
|
|
29
|
+
classifiers, but the shared flat-components → contract assembler silently
|
|
30
|
+
dropped both — so a producer (the DecimalAI SDK / the platform) could never
|
|
31
|
+
actually populate them from components. They are now assembled verbatim from
|
|
32
|
+
each component's `schema_json`. Additive-only: a manifest that doesn't declare
|
|
33
|
+
them is unchanged (same `overall_hash`).
|
|
34
|
+
- **`tool_calling_mode` and `runtime_version` are now lifted to `model_runtime`
|
|
35
|
+
top level** (not buried in `model_config_hash`), so a `tool_calling_mode`
|
|
36
|
+
change is classified **breaking** by the diff as intended, and
|
|
37
|
+
`runtime_version` participates in `model_runtime` severity.
|
|
38
|
+
|
|
39
|
+
The DecimalAI SDK exporter (`ManifestSnapshot.to_agentversion`) was edited
|
|
40
|
+
byte-identically and is pinned to this assembly by its conformance test.
|
|
41
|
+
`SPEC_VERSION` is unchanged (1.0.0) — these surfaces were already in the spec.
|
|
42
|
+
|
|
10
43
|
## [0.2.0] - 2026-06-24
|
|
11
44
|
|
|
12
45
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentversion
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: An open specification for versioning agent runtimes and keeping datasets valid.
|
|
5
5
|
Project-URL: Homepage, https://github.com/decimal-labs/agentversion
|
|
6
6
|
Project-URL: Documentation, https://github.com/decimal-labs/agentversion/tree/main/spec
|
|
@@ -12,7 +12,8 @@ Each component is a plain dict::
|
|
|
12
12
|
{
|
|
13
13
|
"component_type": "tool" | "skill" | "prompt" | "model" | "subagent"
|
|
14
14
|
| "output_schema" | "workflow" | "guardrail"
|
|
15
|
-
| "context_config"
|
|
15
|
+
| "context_config" | "behavioral_policy"
|
|
16
|
+
| "environment",
|
|
16
17
|
"component_name": str,
|
|
17
18
|
"component_version": str | None,
|
|
18
19
|
"content_hash": str | None, # producer-supplied content fingerprint
|
|
@@ -146,6 +147,13 @@ def contract_from_components(components: list[dict[str, Any]]) -> dict[str, Any]
|
|
|
146
147
|
gen["response_format"] = cfg["response_format"]
|
|
147
148
|
if gen:
|
|
148
149
|
model_runtime["generation_config"] = gen
|
|
150
|
+
# tool_calling_mode + runtime_version are TOP-LEVEL model_runtime fields
|
|
151
|
+
# (not under generation_config); the diff classifies a tool_calling_mode
|
|
152
|
+
# change as breaking. Must stay byte-identical with the SDK exporter.
|
|
153
|
+
if isinstance(cfg.get("tool_calling_mode"), str):
|
|
154
|
+
model_runtime["tool_calling_mode"] = cfg["tool_calling_mode"]
|
|
155
|
+
if isinstance(cfg.get("runtime_version"), str):
|
|
156
|
+
model_runtime["runtime_version"] = cfg["runtime_version"]
|
|
149
157
|
else:
|
|
150
158
|
model_runtime = {"provider": "unknown", "model": "unknown"}
|
|
151
159
|
|
|
@@ -252,4 +260,15 @@ def contract_from_components(components: list[dict[str, Any]]) -> dict[str, Any]
|
|
|
252
260
|
ctx["retrieval_config_hash"] = ctx_comps[0]["content_hash"]
|
|
253
261
|
contract["context_config"] = ctx
|
|
254
262
|
|
|
263
|
+
# behavioral_policy / environment are single structured surfaces (the surface
|
|
264
|
+
# IS the config), emitted verbatim from schema_json so hash_manifest hashes
|
|
265
|
+
# their contents. Must stay byte-identical with the SDK exporter.
|
|
266
|
+
bp_comps = by_type.get("behavioral_policy", [])
|
|
267
|
+
if bp_comps:
|
|
268
|
+
contract["behavioral_policy"] = dict(bp_comps[0].get("schema_json") or {})
|
|
269
|
+
|
|
270
|
+
env_comps = by_type.get("environment", [])
|
|
271
|
+
if env_comps:
|
|
272
|
+
contract["environment"] = dict(env_comps[0].get("schema_json") or {})
|
|
273
|
+
|
|
255
274
|
return contract
|
|
@@ -741,9 +741,9 @@ _BEHAVIORAL_POLICY_RULE_FIELDS = (
|
|
|
741
741
|
def _adapt_behavioral_policy(old: dict[str, Any], new: dict[str, Any]) -> tuple[
|
|
742
742
|
Literal["breaking", "non_breaking"], Severity, list[str]
|
|
743
743
|
]:
|
|
744
|
-
"""A change to the
|
|
745
|
-
|
|
746
|
-
|
|
744
|
+
"""A change to the bound behavioral policy RULES is BREAKING: it invalidates any eval set and any
|
|
745
|
+
past traces graded under the old policy. (A bare metadata change with an unchanged ``policy_hash``
|
|
746
|
+
is not a rule change.)
|
|
747
747
|
|
|
748
748
|
Add/remove are asymmetric and handled here so the surface is severitied by its own
|
|
749
749
|
logic in both directions: *introducing* a policy where there was none is additive
|
|
@@ -263,25 +263,30 @@ class Environment(BaseModel):
|
|
|
263
263
|
|
|
264
264
|
|
|
265
265
|
class BehavioralPolicy(BaseModel):
|
|
266
|
-
"""
|
|
266
|
+
"""Versioned BEHAVIORAL-POLICY contract surface.
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
the customer objects N times, then escalate; never admit liability"
|
|
270
|
-
|
|
268
|
+
Binds the agent to a named policy artifact — the rules it must hold to, e.g. a refund/escalation
|
|
269
|
+
policy ("deny until the customer objects N times, then escalate; never admit liability"), a safety
|
|
270
|
+
guardrail set, or an escalation SOP. The surface is document-agnostic: ``policy_hash`` is the stable
|
|
271
|
+
identity (a hash of whatever policy artifact you bind), and the value carried is opaque to this
|
|
272
|
+
model.
|
|
271
273
|
|
|
272
274
|
Why it is a first-class surface: a behavioral policy lives *inside* the prompt, so a prompt-stack
|
|
273
|
-
hash change is classified ``non_breaking`` — yet flipping the policy (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
275
|
+
hash change is classified ``non_breaking`` — yet flipping the policy (a threshold 3 → 1, or removing
|
|
276
|
+
a forbidden rule) INVALIDATES any eval set and any past traces graded under the old policy. Hoisting
|
|
277
|
+
the policy to its own surface makes that flip visible and ``breaking`` instead of silently ``keep``.
|
|
278
|
+
|
|
279
|
+
``policy_hash`` is the identity; the structured rule fields below are OPTIONAL and carried only for
|
|
280
|
+
human-readable diffs (a policy artifact needn't populate any of them — ``extra='allow'`` lets a
|
|
281
|
+
richer or differently-shaped policy spec round-trip without forking this model). They remain on the
|
|
282
|
+
model for backward compatibility with policies already bound this way.
|
|
279
283
|
"""
|
|
280
284
|
|
|
281
285
|
model_config = ConfigDict(extra="allow")
|
|
282
286
|
|
|
283
287
|
policy_id: str | None = None
|
|
284
|
-
policy_hash: str | None = None # stable hash of the bound
|
|
288
|
+
policy_hash: str | None = None # stable hash of the bound policy artifact (the identity)
|
|
289
|
+
# Optional structured rule fields — illustrative, kept for back-compat + human-readable diffs.
|
|
285
290
|
objection_threshold: int | None = None
|
|
286
291
|
concede_events: list[str] | None = None
|
|
287
292
|
always_forbidden: list[str] | None = None
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# `behavioral_policy` surface
|
|
2
|
+
|
|
3
|
+
A contract surface that binds an agent to a **versioned policy document** — the rules it must hold to.
|
|
4
|
+
The surface is document-agnostic: the policy can be a refund/escalation policy (*"deny refunds and offer
|
|
5
|
+
alternatives; only after the customer has objected ≥3 times, escalate to a human; never concede a refund
|
|
6
|
+
yourself, and never admit liability"*), a safety guardrail set, an escalation SOP, or any other bound
|
|
7
|
+
rule artifact.
|
|
8
|
+
|
|
9
|
+
## Why it is a first-class surface
|
|
10
|
+
|
|
11
|
+
A behavioral policy usually lives *inside the system prompt*. Before this surface existed, flipping the
|
|
12
|
+
policy (tightening a threshold, or dropping a forbidden rule) showed up only as a `prompt_stack`
|
|
13
|
+
**hash change**, which the diff classifies **`non_breaking`** → recommended decision **`keep`**. But a
|
|
14
|
+
policy flip **invalidates** any eval set and any past traces that were graded under the old policy.
|
|
15
|
+
Hoisting the policy to its own surface makes that flip **`breaking`** → `replay`/`drop`, instead of
|
|
16
|
+
silently retaining now-wrong data.
|
|
17
|
+
|
|
18
|
+
The `policy_hash` is the canonical identity — a hash of *whatever policy artifact you bind*. The
|
|
19
|
+
structured rule fields below are optional and carried only for human-readable diffs; the surface does not
|
|
20
|
+
interpret them.
|
|
21
|
+
|
|
22
|
+
## Shape
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
"behavioral_policy": {
|
|
26
|
+
"policy_id": "refund-escalation",
|
|
27
|
+
"policy_hash": "sha256:…", // hash of the bound policy artifact (the identity)
|
|
28
|
+
"objection_threshold": 3, // optional structured rule fields — illustrative,
|
|
29
|
+
"concede_events": ["offered_refund", "escalated"], // carried for readable diffs only
|
|
30
|
+
"always_forbidden": ["admits_liability"]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`policy_id` and `policy_hash` are the only load-bearing keys. The structured rule fields
|
|
35
|
+
(`objection_threshold`, `concede_events`, `always_forbidden`) are **optional**, illustrative, and kept
|
|
36
|
+
for backward compatibility with policies already bound this way — a policy artifact needn't populate any
|
|
37
|
+
of them. The model is `extra="allow"`, so a richer or differently-shaped policy spec round-trips without
|
|
38
|
+
forking it.
|
|
39
|
+
|
|
40
|
+
## Diff semantics
|
|
41
|
+
|
|
42
|
+
| Transition | Classification | Rationale |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| rule change (`policy_hash` differs, or any rule field differs) | **breaking** (`major`) | any eval set + past graded traces are now wrong |
|
|
45
|
+
| `policy_hash` unchanged, only metadata (`policy_id`) differs | `non_breaking` (`minor`) | same policy, no rule changed |
|
|
46
|
+
| introduced (absent → present) | `non_breaking` | the old eval set tested no policy; additive |
|
|
47
|
+
| removed (present → absent) | **breaking** (`major`) | past data graded under the policy is now ungoverned |
|
|
48
|
+
|
|
49
|
+
Reason code: `behavioral_policy_changed`. Configurable per-surface via `CompatibilityPolicy.behavioral_policy`
|
|
50
|
+
(defaults: `on_major` → `drop`).
|
|
51
|
+
|
|
52
|
+
## Hash-safety
|
|
53
|
+
|
|
54
|
+
`behavioral_policy` is **optional and omitted by default**. The `overall_hash` is computed only over the
|
|
55
|
+
surfaces actually present in a contract, so adding this surface to the spec does **not** change the hash
|
|
56
|
+
of any existing (policy-less) manifest. The frozen hash-vectors are unchanged.
|
|
@@ -59,6 +59,67 @@ def test_model_provider_inferred_when_absent():
|
|
|
59
59
|
assert c["model_runtime"]["provider"] == "anthropic"
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
# ── OSS-1/3/7: new 0.2.0 surfaces (must mirror the SDK exporter) ────────────
|
|
63
|
+
|
|
64
|
+
def test_model_runtime_lifts_tool_calling_mode_and_runtime_version():
|
|
65
|
+
"""OSS-1: tool_calling_mode + runtime_version are TOP-LEVEL model_runtime
|
|
66
|
+
fields (not under generation_config), so the diff classifies them."""
|
|
67
|
+
c = contract_from_components([
|
|
68
|
+
_comp("model", "default", content_hash="sha256:m",
|
|
69
|
+
schema_json={"provider": "openai", "model": "gpt-4o",
|
|
70
|
+
"tool_calling_mode": "native", "runtime_version": "1.2"}),
|
|
71
|
+
])
|
|
72
|
+
mr = c["model_runtime"]
|
|
73
|
+
assert mr["tool_calling_mode"] == "native"
|
|
74
|
+
assert mr["runtime_version"] == "1.2"
|
|
75
|
+
assert "tool_calling_mode" not in mr.get("generation_config", {})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_behavioral_policy_surface_assembled():
|
|
79
|
+
"""OSS-3: behavioral_policy is emitted verbatim from its schema_json."""
|
|
80
|
+
sj = {"policy_id": "refund", "policy_hash": "sha256:p", "objection_threshold": 3,
|
|
81
|
+
"always_forbidden": ["admit_liability"]}
|
|
82
|
+
c = contract_from_components([
|
|
83
|
+
_comp("model", "default", schema_json={"model": "gpt-4o"}),
|
|
84
|
+
_comp("behavioral_policy", "behavioral_policy", content_hash="sha256:p", schema_json=sj),
|
|
85
|
+
])
|
|
86
|
+
assert c["behavioral_policy"] == sj
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_environment_surface_assembled():
|
|
90
|
+
"""OSS-7: environment is emitted verbatim from its schema_json."""
|
|
91
|
+
sj = {"deployment_id": "d", "region": "us", "runtime_versions": {"python": "3.12"}}
|
|
92
|
+
c = contract_from_components([
|
|
93
|
+
_comp("model", "default", schema_json={"model": "gpt-4o"}),
|
|
94
|
+
_comp("environment", "environment", content_hash="sha256:e", schema_json=sj),
|
|
95
|
+
])
|
|
96
|
+
assert c["environment"] == sj
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_new_optional_surfaces_absent_when_undeclared():
|
|
100
|
+
"""Additive-only: a model-only contract carries neither new surface, so
|
|
101
|
+
existing manifests keep their exact prior identity hash."""
|
|
102
|
+
c = contract_from_components([_comp("model", "default", schema_json={"model": "gpt-4o"})])
|
|
103
|
+
assert "behavioral_policy" not in c
|
|
104
|
+
assert "environment" not in c
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_behavioral_policy_participates_in_identity_hash():
|
|
108
|
+
"""Adding behavioral_policy, and changing its policy_hash, each change the
|
|
109
|
+
canonical jcs-sha256 identity hash."""
|
|
110
|
+
base = [_comp("model", "default", content_hash="sha256:m",
|
|
111
|
+
schema_json={"provider": "openai", "model": "gpt-4o"})]
|
|
112
|
+
with_p = base + [_comp("behavioral_policy", "behavioral_policy", content_hash="sha256:p",
|
|
113
|
+
schema_json={"policy_id": "p", "policy_hash": "sha256:p"})]
|
|
114
|
+
with_p2 = base + [_comp("behavioral_policy", "behavioral_policy", content_hash="sha256:p2",
|
|
115
|
+
schema_json={"policy_id": "p", "policy_hash": "sha256:p2"})]
|
|
116
|
+
h_base = hash_manifest({"contract": contract_from_components(base)})
|
|
117
|
+
h_p = hash_manifest({"contract": contract_from_components(with_p)})
|
|
118
|
+
h_p2 = hash_manifest({"contract": contract_from_components(with_p2)})
|
|
119
|
+
assert h_base != h_p # the surface participates in identity
|
|
120
|
+
assert h_p != h_p2 # a rule change re-versions
|
|
121
|
+
|
|
122
|
+
|
|
62
123
|
def test_tool_and_skill_registry_hashes_are_computed():
|
|
63
124
|
c = contract_from_components([
|
|
64
125
|
_comp("tool", "t1", version="1", content_hash="sha256:t1",
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# `behavioral_policy` surface
|
|
2
|
-
|
|
3
|
-
A contract surface for a multi-turn agent's **behavioral policy** — the rules it must hold *across
|
|
4
|
-
turns*, e.g. a refund/escalation policy: *"deny refunds and offer alternatives; only after the customer
|
|
5
|
-
has objected ≥3 times, escalate to a human; never concede a refund yourself, and never admit liability."*
|
|
6
|
-
|
|
7
|
-
## Why it is a first-class surface
|
|
8
|
-
|
|
9
|
-
A behavioral policy lives *inside the system prompt*. Before this surface existed, flipping the policy
|
|
10
|
-
(`objection_threshold` 3 → 1, or dropping an `always_forbidden` rule) showed up only as a `prompt_stack`
|
|
11
|
-
**hash change**, which the diff classifies **`non_breaking`** → recommended decision **`keep`**. But a
|
|
12
|
-
policy flip **invalidates** the conversation-mode eval set and any past traces that were graded under the
|
|
13
|
-
old policy. Hoisting the policy to its own surface makes that flip **`breaking`** → `replay`/`drop`,
|
|
14
|
-
instead of silently retaining now-wrong data.
|
|
15
|
-
|
|
16
|
-
This binds directly to [skillevaluation](https://github.com/decimal-labs/skillevaluation)'s
|
|
17
|
-
conversation-mode `policy_check` — the deterministic state machine that grades these same rules. The
|
|
18
|
-
`policy_hash` is the canonical identity (a hash of the bound `policy_check` spec); the rule fields are
|
|
19
|
-
carried for human-readable diffs.
|
|
20
|
-
|
|
21
|
-
## Shape
|
|
22
|
-
|
|
23
|
-
```json
|
|
24
|
-
"behavioral_policy": {
|
|
25
|
-
"policy_id": "refund-escalation",
|
|
26
|
-
"policy_hash": "sha256:…", // hash of the bound policy_check spec (the identity)
|
|
27
|
-
"objection_threshold": 3,
|
|
28
|
-
"concede_events": ["offered_refund", "escalated"],
|
|
29
|
-
"always_forbidden": ["admits_liability"]
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The model is `extra="allow"`, so a richer policy spec round-trips without forking it.
|
|
34
|
-
|
|
35
|
-
## Diff semantics
|
|
36
|
-
|
|
37
|
-
| Transition | Classification | Rationale |
|
|
38
|
-
|---|---|---|
|
|
39
|
-
| rule change (`policy_hash` differs, or any rule field differs) | **breaking** (`major`) | conversation eval set + past graded traces are now wrong |
|
|
40
|
-
| `policy_hash` unchanged, only metadata (`policy_id`) differs | `non_breaking` (`minor`) | same policy, no rule changed |
|
|
41
|
-
| introduced (absent → present) | `non_breaking` | the old eval set tested no policy; additive |
|
|
42
|
-
| removed (present → absent) | **breaking** (`major`) | past data graded under the policy is now ungoverned |
|
|
43
|
-
|
|
44
|
-
Reason code: `behavioral_policy_changed`. Configurable per-surface via `CompatibilityPolicy.behavioral_policy`
|
|
45
|
-
(defaults: `on_major` → `drop`).
|
|
46
|
-
|
|
47
|
-
## Hash-safety
|
|
48
|
-
|
|
49
|
-
`behavioral_policy` is **optional and omitted by default**. The `overall_hash` is computed only over the
|
|
50
|
-
surfaces actually present in a contract, so adding this surface to the spec does **not** change the hash
|
|
51
|
-
of any existing (policy-less) manifest. The frozen hash-vectors are unchanged.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/environment-region-change/after.json
RENAMED
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/environment-region-change/before.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/output-schema-change/after.json
RENAMED
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/output-schema-change/before.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/prompt-stack-edit/expected-diff.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/subagent-handoff-change/after.json
RENAMED
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/subagent-handoff-change/before.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/tool-rename/expected-diff.json
RENAMED
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/workflow-graph-change/after.json
RENAMED
|
File without changes
|
{agentversion-0.2.0 → agentversion-0.2.2}/compatibility-tests/workflow-graph-change/before.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|