Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ out the pixels. Coordinates are the thing that breaks when a window moves.
| `ArtifactRefV1` | A path-free reference to an immutable process artifact |
| `CodeCapabilityManifestV1` | Exact Python, locked dependencies, typed I/O, permissions, and verifier bindings |
| `ProcessEvidenceReceiptV1` | One signed root over child receipts, human receipts, and the artifact graph |
| `RewardEvidenceReceiptV1` | One verified terminal effect for a training episode. Not an Execute Seal |
| `AuthenticationTaskContractV1` | A value-free login requirement bound to an existing attended task |
| `AuthoringObserveV1` | PHI-safe authoring observe tree for the hosted MCP wire |
| `AuthoringCommandV1` | Mailbox envelope. Hosted click is `node_id` only; compile is `needs_human_admit` |
Expand All @@ -89,7 +90,9 @@ asynchronous qualified execution, `EffectStrengthV1`, and the
contracts may and may not carry is in
[docs/CONTRACTS.md](docs/CONTRACTS.md). Oracle tiers and the ten-line
adapter are in [docs/ORACLE.md](docs/ORACLE.md). Code capabilities and process
artifacts are in [docs/PROCESS_CAPABILITIES.md](docs/PROCESS_CAPABILITIES.md).
artifacts are in [docs/PROCESS_CAPABILITIES.md](docs/PROCESS_CAPABILITIES.md). Reward
contracts and certificates for training against a verified terminal effect
are in [docs/REWARD.md](docs/REWARD.md).

## JSON Schema for everything else

Expand All @@ -101,7 +104,7 @@ print(json.dumps(ComputerState.model_json_schema(), indent=2))
```

The same schemas ship as JSON under `openadapt_types/schemas/` for TypeScript,
Rust, and anything else that isn't Python. Thirty-one files, including
Rust, and anything else that isn't Python. Thirty-four files, including
`execute-v1-openapi.json`, the public OpenAdapt Execute contract.

## Converting from the older formats
Expand Down
19 changes: 19 additions & 0 deletions docs/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ secret values, evidence bytes, and oracle recipes stay inside the declared
execution boundary. See [PROCESS_CAPABILITIES.md](PROCESS_CAPABILITIES.md) for
the package and runtime split.

## Reward contracts

`RewardContractV1`, `RewardCertificateV1`, and `RewardEvidenceReceiptV1` let a
trainer optimize against a verified terminal effect and know the bound the
checker carries. The contract binds task, environment, required and forbidden
effect contracts, and the oracle recipe by digest. The certificate carries
`epsilon`, `delta`, `threshold`, the calibration corpus digest, and an expiry
denominated in policy updates. The receipt carries the outcome, the component
vector, the scalar, and the certificate state.

`reconciliation_required` and `failed_platform` are unscored. They carry no
scalar and the contract cannot map them to zero. `certified` is true only at
oracle tier 2 or 3 with a current certificate. Tier 0 and 1 receipts are
`development_only`.

The reward receipt is not an Execute Seal. It has its own schema id and none
of the Seal's fields. It says OpenAdapt verified one episode's terminal
effect. It does not say Flow governed the policy. See [REWARD.md](REWARD.md).

## Authentication tasks

`AuthenticationTaskContractV1` adds authentication semantics to the existing
Expand Down
95 changes: 95 additions & 0 deletions docs/REWARD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Reward contracts

A trainer that optimizes against a checker inherits the checker's errors. The
reward contract says what earns reward, who reads the terminal effect, and
what bound that reader carries. The receipt says one thing: OpenAdapt verified
the terminal effect of one episode. It does not say that Flow governed the
policy's actions.

This is not an Execute Seal. A model rollout never receives
`ExecuteEvidenceReceiptV1`. A production Flow result requires a qualified
deterministic program with zero model use. The reward receipt has its own
schema id, `openadapt.reward-evidence-receipt/v1`, and none of the Seal's
fields (`execution_id`, `workflow_digest`, `qualification_id`, `contracts`), so
the two cannot be swapped.

## The three contracts

`RewardContractV1` binds a task and environment by opaque id and digest, the
required and forbidden effect contracts by digest, the independent oracle
(channel plus identity keys), the reward components and their weights, the
scoring policy, and the certificate policy. Its `digest` is the canonical
SHA-256 over sorted JSON. Components sort by name, so two authors who list them
in a different order get the same digest.

`RewardCertificateV1` is the bound: `epsilon`, `delta`, `threshold`, the
calibration corpus digest, the checker configuration digest, the policy update
it was issued at, and its expiry in policy updates. It is signed. Expiry
counts updates, not hours, because on-policy training breaks the
exchangeability the bound assumes. `is_current(policy_update)` answers whether
a trainer may still use it.

`RewardEvidenceReceiptV1` binds the contract digest, the policy checkpoint and
update number, the episode, the oracle tier, the evidence digest, the
component vector, the scalar, the certificate reference and its state, and two
booleans a trainer must read: `certified` and `development_only`.

## Outcome to scalar

| `reward_outcome` | Class | Scalar |
| --- | --- | --- |
| `verified` | admitted positive | `verified_reward` (default 1.0) |
| `halted_before_effect` | zero or penalty | `halted_before_effect_reward` (default 0.0) |
| `refused` | zero or penalty | `refused_reward` (default 0.0) |
| `rejected_policy` | zero or penalty | `rejected_policy_reward` (default 0.0) |
| `wrong_effect` | zero or penalty | `wrong_effect_reward` (default -1.0) |
| `reconciliation_required` | unscored | none |
| `failed_platform` | unscored | none |

An unscored episode has no scalar. `score()` returns `None`, the receipt
refuses a `scalar_reward`, and the contract cannot declare
`uncertain_episodes` or `platform_failures` as anything but `unscored`. A
trainer that folds those into 0.0 is training on platform noise, which is the
failure this contract exists to stop.

## Tier to certification

| Oracle tier | `development_only` | `certified` |
| --- | --- | --- |
| 0 (visual, OCR) | yes | never |
| 1 (second session) | yes | never |
| 2 (API, DB, file, ack) | no | with a current certificate |
| 3 (counterparty) | no | with a current certificate |

The tier comes from the oracle channel, as it does for every Seal.
`refuse_development_certification` raises `RewardCertificationRefused` for
tier 0 and 1, and the receipt validator calls it. A tier-0 reward can drive a
local experiment. It cannot be certified, and a receipt that claims otherwise
does not validate.

## What a trainer receives

```python
from openadapt_types import RewardOutcomeV1, score

scalar, certified, development_only = score(
RewardOutcomeV1.VERIFIED,
tier=2,
certificate=certificate,
policy_update=120,
)
```

Per episode, a signed `RewardEvidenceReceiptV1`: ids, digests, the tier, the
outcome, the component vector, the scalar or its absence, and the certificate
state. The trainer checks `certified` before it counts the episode toward a
certified arm and drops the episode when `scalar_reward` is `None`.

## What stays on the organization node

The calibration corpus, the tuned adversary parameters, the checker threshold's
derivation, the oracle read recipe, and the evidence bytes. Every one of those
appears in the contract as a digest only. A consumer can prove which corpus
certified a reward. It cannot read the corpus from the receipt.

Worked contracts are in [`tests/test_reward.py`](../tests/test_reward.py).
49 changes: 49 additions & 0 deletions openadapt_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,31 @@
CodeRuntimeKind,
ProcessEvidenceReceiptV1,
)
from openadapt_types.reward import (
DEFAULT_REWARD_SCORING,
REWARD_CERTIFICATE_SCHEMA,
REWARD_CERTIFIED_MINIMUM_TIER,
REWARD_CONTRACT_SCHEMA,
REWARD_EVIDENCE_RECEIPT_SCHEMA,
REWARD_SCORING_CLASS,
UNSCORED_REWARD_OUTCOMES,
RewardCertificatePolicyV1,
RewardCertificateStateV1,
RewardCertificateV1,
RewardCertificationRefused,
RewardComponentV1,
RewardContractV1,
RewardEvidenceReceiptV1,
RewardOracleV1,
RewardOutcomeV1,
RewardScoreV1,
RewardScoringClassV1,
RewardScoringPolicyV1,
RewardUncertaintyStateV1,
certificate_state,
refuse_development_certification,
score,
)
from openadapt_types.runner_capability import (
RUNNER_CAPABILITY_MANIFEST_SCHEMA,
EffectVerificationTier,
Expand Down Expand Up @@ -612,6 +637,30 @@
"CodePermissionContractV1",
"CodeRuntimeKind",
"ProcessEvidenceReceiptV1",
# reward contracts (not an Execute Seal)
"DEFAULT_REWARD_SCORING",
"REWARD_CERTIFICATE_SCHEMA",
"REWARD_CERTIFIED_MINIMUM_TIER",
"REWARD_CONTRACT_SCHEMA",
"REWARD_EVIDENCE_RECEIPT_SCHEMA",
"REWARD_SCORING_CLASS",
"UNSCORED_REWARD_OUTCOMES",
"RewardCertificatePolicyV1",
"RewardCertificateStateV1",
"RewardCertificateV1",
"RewardCertificationRefused",
"RewardComponentV1",
"RewardContractV1",
"RewardEvidenceReceiptV1",
"RewardOracleV1",
"RewardOutcomeV1",
"RewardScoreV1",
"RewardScoringClassV1",
"RewardScoringPolicyV1",
"RewardUncertaintyStateV1",
"certificate_state",
"refuse_development_certification",
"score",
# failure
"FailureCategory",
"FailureRecord",
Expand Down
Loading
Loading