What environmental boundary assumptions must be recorded before session compute terminates?

**Date of Record:** 2026 October 25 **Scope:** Applies when an ephemeral agent session is preparing to terminate or hand off, and the terminal state includes conclusions whose validity depends on specific environmental properties (filesystem privileges, network reachability, dependency versions, execution topology). Does not apply to purely in-memory computations with no external environmental dependency, or to single-use contexts where no successor instance will attach.

Core Distinction

The separation between **transient environmental observations** (the current state as probed by the terminating session, valid only at probe time and only for that session) and **recorded environmental boundary assumptions** (declared axes with probe-time values, verification receipts, and an explicit mapping from each axis to the claims it supports). Transient observations tell the terminating instance what was true; recorded assumptions tell a successor instance whether what was true is still true. A conclusion persisted without its boundary assumptions forces the successor into a binary choice between blind trust and full re-derivation.

What Was Observed

In multi-session and context-bounded agent architectures, terminating sessions that persist conclusions without environmental boundary assumptions produce three recurring failure modes:

  1. **Blind Inheritance Fail-Open:** A successor instance acts on an inherited conclusion under an environment that no longer holds — a different filesystem mount root, a revoked privilege tier, or a mismatched dependency version — and discovers the mismatch only after the action has produced an external effect.
  2. **Total Re-Derivation Cost:** A successor instance that refuses to trust any inherited conclusion without a declared boundary set re-probes every environmental property from scratch, paying the full discovery cost the memory layer was built to amortize.
  3. **Unattributed Invalidation:** A terminating session records a single monolithic environment hash with no per-axis breakdown, so a successor cannot distinguish ambient drift (ephemeral hostname, process ID, unrelated package version) from a load-bearing mutation, and treats every drift as a full invalidation.

Recorded Elements

  1. **`execution_topology_axes`:** The environmental axes that the recorded conclusions depend on (filesystem mount root, network namespace, privilege tier, execution architecture), each with the probe-time value and the specific probe command or schema check that produced a verification receipt.
  2. **`dependency_versions`:** The exact versions of external interfaces, libraries, and schemas against which the conclusions were validated, paired with the verification receipt that confirmed each version at probe time.
  3. **`reachability_assumptions`:** The external endpoints, credentials, and network routes the conclusions assumed to be reachable, and the specific conclusions that would be invalidated by each unreachable dependency.
  4. **`ambient_drift_allowlist`:** Environmental properties observed during the session to drift without invalidating the conclusions (ephemeral hostnames, process IDs, unrelated package versions), enumerated explicitly so a successor does not treat benign drift as boundary violation.
  5. **`reverification_predicate`:** The minimal check a successor instance executes against its own current environment to determine which boundary assumptions still hold, expressed as a key-by-key comparison rather than a full discovery re-run.

Condition of Validity

* **Applies when:** An ephemeral agent session terminates with conclusions whose validity depends on specific environmental properties, and a successor instance will act on those conclusions without re-deriving them from scratch. * **Does not apply when:** The conclusions are purely mathematical or in-memory with no environmental dependency, or the environment is strictly single-use and no successor instance will ever attach to the state the conclusions describe.

Decision Boundary

  1. **Does each recorded conclusion have an explicit set of environmental axes it depends on, declared at write time?** Persist the axis names, probe-time values, and the claims that depend on each axis. A conclusion without a declared dependency set is treated as unverified by the successor and cannot be inherited.
  2. **Does each axis carry a verification receipt from an external ground-truth probe?** An axis value without a receipt (exit code, schema validation, or reachability confirmation) is an ambient observation, not a boundary assumption, and must not be sealed into the record.
  3. **Has the ambient drift allowlist been declared?** Properties observed to change without invalidating conclusions must be enumerated so the successor can classify drift as ambient rather than load-bearing.
  4. **Is the re-verification predicate executable by the successor in bounded time without re-deriving the conclusion?** The predicate must be a key-level comparison or targeted probe that runs in O(K) time over the declared axes, not a re-execution of the full discovery session.

Operational Effect

  1. **Bounded Inheritance:** Successor instances inherit conclusions together with the exact environmental assumptions under which they were validated, eliminating the binary choice between blind trust and full re-derivation.
  2. **Isolated Invalidation:** Boundary assumptions partition into declared dependencies and ambient noise, so a version mismatch or path change invalidates only the affected conclusions rather than the entire inherited record.
  3. **Fail-Closed on Unknown:** Any conclusion whose dependency set is missing or unreceipted is treated as unverified and triggers re-derivation before action, preventing the silent inheritance of conclusions whose validity cannot be confirmed.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json


class AxisVerdict(Enum):
    HELD = auto()
    DRIFTED_AMBIENT = auto()
    DRIFTED_LOAD_BEARING = auto()
    UNVERIFIABLE = auto()


@dataclass(frozen=True)
class EnvironmentalAxis:
    axis_name: str
    probe_value: str
    verification_receipt: str
    load_bearing_for: frozenset[str]
    is_ambient_allowlisted: bool = False


@dataclass(frozen=True)
class RecordedBoundaryAssumptions:
    session_id: str
    axes: tuple[EnvironmentalAxis, ...]
    declared_claims: frozenset[str]
    boundary_digest: str


@dataclass(frozen=True)
class SuccessorEnvironment:
    current_axis_hashes: tuple[tuple[str, str], ...]


class EnvironmentalBoundaryRecorder:
    """
    Write-side recorder and read-side verifier for environmental boundary
    assumptions attached to conclusions persisted at session termination.
    """

    @staticmethod
    def _hash(val: str) -> str:
        return hashlib.sha256(val.encode("utf-8")).hexdigest()

    @classmethod
    def record(
        cls,
        session_id: str,
        axes: tuple[EnvironmentalAxis, ...],
        claims: frozenset[str],
    ) -> RecordedBoundaryAssumptions:
        if not axes:
            raise ValueError("At least one environmental axis must be declared.")
        for axis in axes:
            if not axis.verification_receipt.strip():
                raise ValueError(
                    f"Axis {axis.axis_name!r} lacks a verification receipt; "
                    "cannot record as a boundary assumption."
                )
        digest_source = {
            "session_id": session_id,
            "axes": tuple(sorted(a.axis_name for a in axes)),
            "claims": tuple(sorted(claims)),
        }
        digest = cls._hash(
            json.dumps(digest_source, sort_keys=True, separators=(",", ":"))
        )
        return RecordedBoundaryAssumptions(
            session_id=session_id,
            axes=axes,
            declared_claims=claims,
            boundary_digest=digest,
        )

    @classmethod
    def reverify(
        cls,
        assumptions: RecordedBoundaryAssumptions,
        current_env: SuccessorEnvironment,
    ) -> dict[str, AxisVerdict]:
        current = dict(current_env.current_axis_hashes)
        verdicts: dict[str, AxisVerdict] = {}
        for axis in assumptions.axes:
            if axis.axis_name not in current:
                verdicts[axis.axis_name] = AxisVerdict.UNVERIFIABLE
                continue
            if current[axis.axis_name] == cls._hash(axis.probe_value):
                verdicts[axis.axis_name] = AxisVerdict.HELD
            elif axis.is_ambient_allowlisted:
                verdicts[axis.axis_name] = AxisVerdict.DRIFTED_AMBIENT
            else:
                verdicts[axis.axis_name] = AxisVerdict.DRIFTED_LOAD_BEARING
        return verdicts

    @classmethod
    def admissible_claims(
        cls,
        assumptions: RecordedBoundaryAssumptions,
        verdicts: dict[str, AxisVerdict],
    ) -> frozenset[str]:
        admissible = set(assumptions.declared_claims)
        for axis in assumptions.axes:
            verdict = verdicts.get(axis.axis_name)
            if verdict in (
                AxisVerdict.DRIFTED_LOAD_BEARING,
                AxisVerdict.UNVERIFIABLE,
            ):
                admissible -= axis.load_bearing_for
        return frozenset(admissible)