How must an instance handle capability deprecation mid-flight?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session is actively using a capability (calling a tool, executing a workflow, or depending on a service) and the capability is deprecated during the session's use, requiring the session to complete gracefully or hand off without losing progress. Does not apply to capabilities deprecated only between sessions or capabilities used atomically in single calls.

Core Distinction

The separation between **capability availability** (the capability responds to current calls) and **capability persistence** (the capability will continue to respond for the session's remaining duration). Relying on availability creates the **Mid-Flight Deprecation**: the capability works now, but deprecation is scheduled for the near future (or has just been announced), and the session's continued use is against a capability that will disappear before the session can complete naturally.

What Was Observed

In deprecating-capability agent architectures, failing to handle mid-flight deprecation produces three primary failure modes:

  1. **Silent Abandonment:** The capability is deprecated with a short window. The session completes 60% of its workflow, then the capability stops responding. The workflow is abandoned mid-step with no clean rollback.
  2. **Rushed Completion:** The session detects deprecation, rushes to complete the workflow, and produces incorrect results due to skipped validation steps.
  3. **Orphaned State:** The session has multi-step workflows that depend on the capability. Deprecation mid-workflow leaves state that references the retired capability, and the state cannot be resumed.

Recorded Elements

  1. **`capability_deprecation_schedule_receipt`:** The exact scheduled deprecation epoch (or "in progress" marker), so the session knows the remaining window.
  2. **`session_dependency_footprint_receipt`:** For the current session, the exact dependency on the capability (how many more calls, what state depends on responses), so the session can compute whether it can finish before deprecation.
  3. **`in_flight_capability_calls_manifest`:** Any calls currently in flight to the capability, so the session can determine what to do with in-flight results if deprecation completes mid-call.
  4. **`graceful_completion_or_handoff_receipt`:** The exact plan for completing (if possible) or handing off (if not) the session's work, with the handoff state prepared before the capability goes dark.

Condition of Validity

* **Applies when:** The capability can be deprecated mid-session, the session has ongoing dependency on it, and abrupt deprecation would leave state in an unusable condition. * **Does not apply when:** Capabilities are never deprecated mid-session, or session workflows are strictly atomic (deprecation has no intermediate state).

Decision Boundary

  1. **Is the capability scheduled for deprecation?** If no, standard use applies. If yes, proceed.
  2. **Can the session complete its workflow before the deprecation epoch?** If yes, proceed with awareness. If no, proceed to handoff preparation.
  3. **Is the `graceful_completion_or_handoff_receipt` prepared?** If yes, execute it. If no, prepare it immediately.
  4. **Conversely, when can a session continue using a capability despite deprecation?** Only when the deprecation epoch is far enough in the future that completion is certain with margin, or the session explicitly accepts mid-flight failure risk.

Operational Effect

  1. **Abandonment Prevention:** Sessions either complete or hand off, not abandon.
  2. **Correctness Preservation:** Rushed completion is not triggered; orderly handoff is preferred.
  3. **State Integrity:** Orphaned state is prevented by explicit handoff.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class DeprecationVerdict(Enum):
    NO_DEPRECATION = auto()
    CAN_COMPLETE = auto()
    HANDOFF_REQUIRED = auto()
    DEPRECATION_IMMINENT = auto()

@dataclass(frozen=True)
class CapabilityTiming:
    capability_id: str
    deprecation_epoch: float | None
    current_epoch: float
    session_remaining_calls: int
    session_avg_call_duration_sec: float
    in_flight_calls: int

@dataclass(frozen=True)
class DeprecationPolicy:
    min_completion_margin_sec: float
    handoff_prepared: bool

@dataclass(frozen=True)
class DeprecationManifest:
    verdict: DeprecationVerdict
    completion_time_estimate_sec: float
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_deprecation(
        cls, timing: CapabilityTiming, policy: DeprecationPolicy
    ) -> DeprecationManifest:
        
        # Gate 1: No scheduled deprecation
        if timing.deprecation_epoch is None:
            digest = cls._hash(json.dumps({
                "capability": timing.capability_id,
                "deprecation": "NONE"
            }, sort_keys=True))
            return DeprecationManifest(
                verdict=DeprecationVerdict.NO_DEPRECATION,
                completion_time_estimate_sec=0.0,
                directive=f"PROCEED: No deprecation for {timing.capability_id}.",
                manifest_digest=digest
            )

        # Gate 2: Compute completion time estimate
        completion_estimate = timing.session_remaining_calls * timing.session_avg_call_duration_sec
        time_remaining = timing.deprecation_epoch - timing.current_epoch

        # Gate 3: Imminent
        if time_remaining < policy.min_completion_margin_sec:
            if policy.handoff_prepared:
                return DeprecationManifest(
                    verdict=DeprecationVerdict.HANDOFF_REQUIRED,
                    completion_time_estimate_sec=completion_estimate,
                    directive=f"HANDOFF: Only {time_remaining:.0f}s left for {timing.capability_id}, handoff prepared.",
                    manifest_digest=cls._hash(f"HANDOFF:{timing.capability_id}")
                )
            return DeprecationManifest(
                verdict=DeprecationVerdict.DEPRECATION_IMMINENT,
                completion_time_estimate_sec=completion_estimate,
                directive=f"HALT: Only {time_remaining:.0f}s left, no handoff prepared for {timing.capability_id}.",
                manifest_digest=cls._hash(f"IMMINENT:{time_remaining:.0f}")
            )

        # Gate 4: Completion feasibility
        if completion_estimate + policy.min_completion_margin_sec <= time_remaining:
            digest = cls._hash(json.dumps({
                "capability": timing.capability_id,
                "remaining": f"{time_remaining:.0f}",
                "estimate": f"{completion_estimate:.0f}"
            }, sort_keys=True))
            return DeprecationManifest(
                verdict=DeprecationVerdict.CAN_COMPLETE,
                completion_time_estimate_sec=completion_estimate,
                directive=f"PROCEED: {time_remaining:.0f}s available, {completion_estimate:.0f}s needed.",
                manifest_digest=digest
            )

        # Gate 5: Cannot complete in time
        if policy.handoff_prepared:
            digest = cls._hash(json.dumps({
                "capability": timing.capability_id,
                "remaining": f"{time_remaining:.0f}",
                "estimate": f"{completion_estimate:.0f}",
                "handoff": "READY"
            }, sort_keys=True))
            return DeprecationManifest(
                verdict=DeprecationVerdict.HANDOFF_REQUIRED,
                completion_time_estimate_sec=completion_estimate,
                directive=f"HANDOFF: Cannot complete in {time_remaining:.0f}s, handoff prepared.",
                manifest_digest=digest
            )

        return DeprecationManifest(
            verdict=DeprecationVerdict.DEPRECATION_IMMINENT,
            completion_time_estimate_sec=completion_estimate,
            directive=f"HALT: Cannot complete in {time_remaining:.0f}s and no handoff prepared.",
            manifest_digest=cls._hash(f"NO_HANDOFF:{timing.capability_id}")
        )