Skip to content

feat(sdl): make episode structure a first-class member of participant behavior - #1465

Draft
doublewhy wants to merge 6 commits into
devfrom
309-participant-episode-structure
Draft

doublewhy wants to merge 6 commits into
devfrom
309-participant-episode-structure

Conversation

@doublewhy

@doublewhy doublewhy commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ACT-623 asks the ecosystem to support participant episode structure (initialization, turn or interaction ordering, resettable starts, and end conditions) as a first-class participant concern. PR #1463 (issue #307, DSL-120) adds the authored behavior_specifications.*.episode_policy record. This PR builds on it and makes that record a first-class member of the participant behavior aggregate. The scope comes from the #122 design (specs/formal/participant-episode-model/README.md): the ACT-623 commitments and the matrix row "First-class attachment across kinds (ACT-623/624)". That row names the behavior specification aggregate, SemanticValidator behavior-spec verification as the enforcement point, and a policy attached to a non-autonomous participant as the positive fixture. Its negative fixture, "budget available only on the autonomous profile", is worded for ACT-624; the ACT-623 negative fixture mirrors it by analogy, with an episode policy nested in the autonomous execution profile.

What changes:

  • A behavior specification may aggregate an episode policy as its only behavior surface. Until now such a specification failed with "behavior specifications must aggregate at least one behavior surface reference".
  • The compiled aggregate names its member: ParticipantBehaviorSpecificationRuntime.episode_policy_address is participant.episode-policy.<name>, or empty when there is no policy.
  • The policy validates and compiles for all seven governed behavior_mode terms and for a specification without one. The closed autonomous execution profile rejects a nested policy, so episode structure is never an autonomous-profile option.
  • participant_episode_structure_conformance_diagnostics() in raes_conformance compares recorded ADR-013 episode history with the compiled structure. For a governed participant, a terminal reason other than interrupted must come from an authored condition. A recorded reset or restart must also be one the reset policy admits, and a policy without a reset policy admits any reset or restart. It reads history only. It never evaluates a condition or claims that a backend enforces the policy, and no executable or runtime adoption is claimed. It is a library check that callers invoke with the compiled policies (RuntimeModel.participant_episode_policies) and snapshot history (RuntimeSnapshot.participant_episode_history), and no conformance runner invokes it yet (tracked in Run the participant conformance probes from run_target_conformance #1523).

No schema changes. The aggregate rule is a model validator, and episode_policy_address is processor IR that is never planned. The rule that one episode policy governs each participant, which this member relies on, is enforced by SemanticValidator in #1463.

This PR is stacked on #1463 and targets dev, because CI runs only for PRs against dev or main. It sits on #1463's current head, a6218096. Until #1463 merges, the diff also shows #307's three commits. The #309 change is the last three commits: a30f0873 (code), a0bcf20e (documentation, tests, and traceability), and 85d7370f (evidence).

Because this changes source bound by the research evidence, both captures are republished one release above #1463's current captures (specification coverage 70.0.0, formal validation 71.0.0 with baseline 70.0.0); retained outcomes, classifications and claim limits are unchanged, and the formal retest records no deviations.

Requirement UIDs

Related Issues

Closes #309

ADR Impact

Changes

  • raes/participant_behavior_specification.py: episode_policy counts as a behavior surface in the aggregate check (one line).
  • raes_processor/models/behavior_resources.py and raes_processor/compiler/participant_behaviors.py: episode_policy_address on the compiled aggregate.
  • raes_conformance/conformance/participant_episode_structure.py (new): the conformance diagnostics, with code conformance.participant-episode-structure-invalid in diagnostics.py. History events are read through ParticipantEpisodeHistoryEvent.from_payload. Malformed events are skipped, because iter_participant_episode_snapshot_violations already reports them. A participant listed by two compiled policies is reported instead of checked. The function is exported from raes_conformance.conformance, and tests/test_conformance_facade_parity.py pins the new export.
  • contracts/fixtures/sdl/participant-episode-policy-v1/: valid/standalone-episode-structure.yaml (a participant whose behavior specification aggregates only an episode policy, with an episode_local_reset scope whose reset authority is the declared participant agents.replayer, because feat(sdl): add authored participant episode policies to behavior specifications #1463 now requires the authority to resolve to one declared element) and invalid/autonomous-profile-episode-structure.yaml (the same policy nested in the autonomous execution profile), with the README updated.
  • implementations/python/tests/test_issue_309_act_623_participant_episode_structure.py (18 cases): every behavior mode and the mode-free fixture compile with the aggregate naming its member. The standalone aggregate validates, a policy-free aggregate names no member, and the autonomous profile rejects a nested policy. Six conformance cases drive the stub backend's RuntimeControlPlane through initialize, reset, restart, and terminate and read the real snapshot history: a conforming history, whose full recorded event sequence the test asserts; a contradicting one; a policy without a reset policy, which admits a restart that the standalone policy rejects; and three shapes that snapshot integrity reports (a non-list history, non-mapping events, malformed events), which yield no conformance diagnostic. One conformance test covers two compiled policies for one participant.
  • specs/sdl/sections.md: two paragraphs in "Participant episode structure" covering the first-class member and conformance, including that a policy without a reset policy admits any recorded reset or restart. docs/explain/sdl/sections.md: matching guide paragraphs. docs/explain/sdl/lineage.md: an ACT-623 bullet; the lineage ledger is unchanged.
  • specs/formal/assurance-fulfillment.yaml: records Participant Episode Structure (ACT-623) #309's delivered unit tests and removes Participant Episode Structure (ACT-623) #309 from the remaining waiver.
  • docs/requirements/ACT-623/requirement.md: 15 traceability lines, including TESTS lines for the three evidence modules. status (DRAFT) and updated_at are unchanged.
  • Specification-coverage release 70.0.0 (execution-snapshot-v70.json, analysis-v70.json, issue-309 bundle) replays the retained matrix against this branch's source.
  • Formal-validation release 71.0.0 (execution-snapshot-v71.json, analysis-v71.json, retest-v71.json) replays the retained formal cases with baseline 70.0.0 and records no deviations.
  • Revision pins advanced in tools/check_specification_coverage.py, tools/formal_semantic_validation/, and the three evidence test modules. Both research indexes record the new releases.

Test Plan

  • Unit tests pass
  • Integration tests pass if applicable
  • Full completion suite required in CI before merge
  • No coverage regression

uv run --project implementations/python --frozen --all-extras python -m pytest implementations/python/tests/test_issue_309_act_623_participant_episode_structure.py -q -p no:cacheprovider passed (18 passed). The same command with -m integration selected nothing (18 deselected), because the module has no integration-marked tests. With --cov=raes_conformance.conformance.participant_episode_structure, the module covers every statement and branch of the conformance module (44 statements, 20 branches, no missing lines).

The tests fail without the change. With implementations/python/packages checked out from #1463's head a6218096, 17 of the 18 cases fail once the conformance import is deferred. Without the deferral, collection fails, because #1463's raes_conformance.conformance has no such export. The case that passes pins behavior #1463 already provides and the matrix requires: the autonomous profile rejects a nested policy.

The tests are not vacuous. I applied eleven temporary source mutations, one at a time, ran the whole module against each, and restored the source before the next run:

  • Dropping episode_policy from the aggregate check failed 8 cases, every case that loads the standalone fixture.
  • Leaving episode_policy_address empty failed the 8 cross-kind cases.
  • Naming a member for a policy-free aggregate failed the policy-free case.
  • Accepting any terminal reason in conformance failed the contradiction case.
  • Ignoring the reset policy failed the contradiction case.
  • Removing the interrupted exemption failed the conforming-history case.
  • Skipping the duplicate-governance check failed its case.
  • Checking resets when the policy has no reset policy failed the no-reset-policy case.
  • Reading a non-list history failed the non-list case.
  • Refusing a non-mapping event instead of skipping it failed the non-mapping case.
  • Letting a malformed event's parse error propagate failed the malformed-events case.

Every case except the autonomous-profile rejection fails under at least one of these mutations. That case pins #1463's strict decoding, which no #309 source path implements.

With the change, 18 related modules passed (701 passed, 1 deselected): the #307 and #309 modules, conformance facade parity, RUN-310 supervisory lifecycle, SEM-220 decision surface, SEM-208, runtime models, runtime conformance, runtime control plane, RUN-311 episode lifecycle, SEM-222 termination semantics and termination oracle, DSL-142 delivery, ACT-617 mixed control, catalog parity, classification migration, assurance policy, and #1069 participant control orchestration. With -m integration they passed too (1 passed: test_live_sdl_catalogs_match_authorities).

tools/check_specification_coverage.py and tools/check_formal_semantic_validation.py pass (integrity and replay). The three evidence modules pass with default markers (209 passed, 19 deselected) and with -m integration (19 passed). nox -s contracts -- --base-rev origin/dev passed (14 steps, including generated-schema drift, catalog parity and lineage). nox -s verify-fast-feedback -- --base-rev origin/dev, nox -s lint, make policy, and nox -s docs-local (Vale and the Sphinx build) passed. The policy runner skips requirement governance on its own, because this UID-free branch has no requirement-scope file. The forced runs are described under Requirement UIDs.

No coverage regression: at head 85d7370f, the SonarCloud PR gate is OK (97.5% new-code coverage, 0 new violations, 0.0% duplication), and canonical / coverage-reduce (job 114249077710) passed in CI run 38063369365.

Ground Control Checks

  • Repository policy command passes
  • Pre-push code review and test-quality review completed, or not run for this lane

make policy passes (Test Plan). Three independent code and test-quality reviews ran on this branch, and this revision applies the third review's findings: ACT-623 traceability for the evidence modules, the code commit message, the no-reset-policy rule in the specification and guide, history-shape tests, and line wrapping. Each new assertion group except the autonomous-profile rejection fails under a targeted source mutation (mutation runs above).

Traceability

  • IMPLEMENTS: ACT-623 ← issue Participant Episode Structure (ACT-623) #309, implementations/python/packages/raes/participant_behavior_specification.py, implementations/python/packages/raes/participant_episode_policy.py (Participant Episode Structure And Termination Surface (DSL-120) #307's authored policy, which the member aggregates), implementations/python/packages/raes_processor/models/behavior_resources.py, implementations/python/packages/raes_processor/compiler/participant_behaviors.py, implementations/python/packages/raes_conformance/conformance/participant_episode_structure.py, implementations/python/packages/raes_conformance/conformance/diagnostics.py, implementations/python/packages/raes_conformance/conformance/__init__.py, specs/sdl/sections.md
  • TESTS: ACT-623 ← implementations/python/tests/test_issue_309_act_623_participant_episode_structure.py, implementations/python/tests/test_conformance_facade_parity.py, implementations/python/tests/test_specification_coverage.py, implementations/python/tests/test_formal_semantic_validation.py, implementations/python/tests/test_issue_989_versioned_evidence.py
  • DOCUMENTS: ACT-623 ← specs/formal/participant-episode-model/README.md, docs/explain/sdl/sections.md

Checklist

  • Code follows the project coding standards (ruff format and ruff check clean; every touched package file stays under the 500-line cap, the largest being behavior_resources.py at 487)
  • FM3: the conformance relation reads recorded ADR-013 lifecycle transitions (T2 to T6 of the Participant episode/reset & budget/quota: semantics + language surfaces + model (SEM-222, SEM-223, DSL-120, DSL-121, ACT-623, ACT-624) #122 abstract state machine in specs/formal/participant-episode-model/README.md, unchanged), backed by typed IR (episode_policy_address) and unit tests. Invariants: a governed participant's recorded terminal reason is interrupted or one an authored condition produces. A recorded reset or restart is admitted by the declared reset policy, and nothing is checked without one. The policy attaches to every behavior mode and never to the autonomous profile. No executable or runtime adoption is claimed: conformance reads history, and the runtime neither evaluates conditions nor enforces the policy.
  • Published contract schemas regenerated if models changed: not applicable, no schema changed (tools/check_generated_schemas.py passes)
  • PR title is a Conventional Commit; feat cuts a minor release for the first-class participant member
  • Architectural docs updated if applicable: normative SDL section, explain guide, and lineage notes

Documentation

Updated: specs/sdl/sections.md, docs/explain/sdl/sections.md, docs/explain/sdl/lineage.md, contracts/fixtures/sdl/participant-episode-policy-v1/README.md. Verified unchanged: specs/formal/participant-episode-model/README.md (design artifact) and the ADR-013 text.

…ifications

Add the optional behavior_specifications.*.episode_policy record (DSL-120):
initialization precondition assertions, decision-epoch turn structure,
completion and timeout terminal conditions, truncation conditions, and a
reset policy with admitted control actions and a participant-memory scope.
Every condition maps to exactly one ADR-013 terminal reason and names the
evidence requirements that back the transition; interruption is never
authored. The closed record rejects realized episode coordinates by name.

Validate references, assertion roles, owner-bound timeouts and evidence
fail closed, rewrite references under module composition, and compile each
policy to participant.episode-policy.<name> keyed to the selected
participants' participant.behavior.<agent> addresses. Regenerate the five
scenario schemas with their publication entries, add the reference-catalog
rows, valid and invalid fixtures, normative and explain documentation,
lineage notes and DSL-120 traceability.
…nct from completion

Resolve reset_policy.memory_reset_authority_ref to exactly one declared
targetable SDL element during semantic validation. Dangling and ambiguous
refs fail closed, and ${var} refs wait for the post-instantiation pass.
Module composition now namespaces the authority. Compilation records the
element's canonical sdl.* address as memory_reset_authority_address, adds
it to the policy's refresh dependencies, and re-asserts that only an
episode_local_reset scope names one. Add the matching reference-catalog
row.

Reject a truncation condition that reuses the exact assertion set of a
completion condition, so one evidenced fact cannot end an episode as both
completed and truncated.

Reword the reset-policy comment, the normative section and the explain
guide: the ADR-095 assurance and information-state contracts require the
same pairing, but the policy does not supply their values. State that a
condition holds only when every assertion it names holds and that
validation checks only that its evidence requirements are declared, and
point the public participant-control guide at the episode policy fixture.
… behavior

Treat the DSL-120 episode policy as a first-class member of the behavior
specification aggregate (ACT-623). A specification may aggregate an episode
policy as its only behavior surface, and the compiled aggregate names its
member through episode_policy_address. The member relies on the semantic
validation rule, added with the DSL-120 policy, that one episode policy
governs each participant. The policy attaches to participants of every
behavior mode, while the closed autonomous execution profile cannot carry
one.

Add participant_episode_structure_conformance_diagnostics() to
raes_conformance: for a governed participant, recorded ADR-013 history may end
an episode only with a reason an authored condition produces or with an
interruption, and may reset or restart only as the reset policy admits.
Record standalone and autonomous-profile fixtures, specification and guide
paragraphs, lineage notes, ACT-623 traceability and assurance fulfillment.
…ence tests

State in the normative section and the explain guide that conformance
admits any recorded reset or restart for a policy without a reset_policy,
and re-wrap the ACT-623 paragraphs in the specification and lineage notes.

Trace the three research-evidence modules this change republishes to
ACT-623, and describe the module's uniqueness case as the conformance
refusal it is.

Assert the full recorded event sequence in the conforming-history test, and
cover a policy without a reset policy and history that snapshot integrity
reports: a non-list history, non-mapping events and malformed events.

This branch has not been deployed

No deployments
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