What invariants prevent sequential commitment from locking in suboptimal paths?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session makes sequential commitments (commits to A, which requires committing to B, which requires committing to C), and each commitment narrows the session's future options, potentially locking the session into a path that becomes suboptimal as information accumulates. Does not apply to sessions that can defer commitment until information is complete, or sessions where the first commitment is trivially reversible.

Core Distinction

The separation between **commitment as progress** (each commitment moves the session forward) and **commitment as narrowing** (each commitment reduces the session's future option space). Relying on progress creates the **Sequential Lock-In**: the session commits to A, then B is the only viable continuation, then C is the only viable continuation—by the time the path is clearly suboptimal, the session has committed too deeply to reverse without significant cost.

What Was Observed

In commitment-driven agent architectures, failing to preserve option space produces three primary failure modes:

  1. **Sunk Cost Trap:** The session commits to A, A turns out to be suboptimal, but reversing A costs more than continuing to B, C, D. The session continues down the suboptimal path to amortize A's cost.
  2. **Narrowing Cascade:** Each commitment narrows the option space, and the session cannot pivot when new information arrives because all pivots require reversing earlier commitments.
  3. **Information-Starved Lock-In:** The session commits early, before sufficient information is available to evaluate the path, and the path's actual suboptimality is revealed only after all reasonable pivots have been foreclosed.

Recorded Elements

  1. **`commitment-sequence-receipt`:** The exact sequence of commitments made, with the option space narrowing at each step, so the narrowing is documented.
  2. **`reversal-cost-per-commitment-signature`:** For each commitment, the cost to reverse, so the total reversal cost is computable.
  3. **`information-gap-at-commitment-receipt`:** For each commitment, the information that would have been needed to evaluate the commitment's optimality, so early commitments are flagged when information was insufficient.
  4. **`option-space-preservation-signature`:** The session's strategy for preserving option space (deferring commitment when possible, choosing commitments that are cheap to reverse, or keeping parallel paths open), so the strategy is explicit.

Condition of Validity

* **Applies when:** The session makes sequential commitments, each reduces option space, and information accumulates over time. * **Does not apply when:** Commitments are trivially reversible, information is complete at first commitment, or the path is verified optimal.

Decision Boundary

  1. **Does the session face sequential commitments?** If no, standard decision applies. If yes, proceed.
  2. **Does each commitment narrow option space significantly?** If yes, lock-in risk is real. If no, standard progress.
  3. **Is information sufficient to evaluate each commitment?** If yes, commit. If no, defer or choose cheap-to-reverse commitments.
  4. **Conversely, when can the session commit despite insufficient information?** Only when the commitment is verified cheap to reverse (reversal cost < cost of waiting), or the information will not become available in time for the decision anyway.

Operational Effect

  1. **Sunk Cost Prevention:** Commitments are chosen with reversal cost awareness.
  2. **Option Preservation:** The session retains pivots when possible.
  3. **Information-Gap Awareness:** Early commitments are flagged when information is insufficient.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class SequentialCommitmentVerdict(Enum):
    NO_SEQUENTIAL_COMMITMENT = auto()
    SAFE_COMMITMENT = auto()
    LOCK_IN_RISK = auto()
    DEFER_WARRANTED = auto()

@dataclass(frozen=True)
class CommitmentStep:
    step_id: str
    option_narrowing: float  # 0-1, how much option space is lost
    reversal_cost: float
    information_sufficiency: float  # 0-1
    information_becoming_available_soon: bool

@dataclass(frozen=True)
class SequentialPolicy:
    min_information_for_commitment: float
    max_reversal_cost_acceptable: float

@dataclass(frozen=True)
class SequentialManifest:
    verdict: SequentialCommitmentVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_step(
        cls, step: CommitmentStep, policy: SequentialPolicy
    ) -> SequentialManifest:
        
        # Gate 1: No commitment
        if not step.step_id:
            digest = cls._hash(json.dumps({
                "step": "NONE"
            }, sort_keys=True))
            return SequentialManifest(
                verdict=SequentialCommitmentVerdict.NO_SEQUENTIAL_COMMITMENT,
                directive=f"PROCEED: No sequential commitment to evaluate.",
                manifest_digest=digest
            )

        # Gate 2: Sufficient information
        if step.information_sufficiency >= policy.min_information_for_commitment:
            digest = cls._hash(json.dumps({
                "step": step.step_id,
                "info": f"{step.information_sufficiency:.3f}",
                "narrowing": f"{step.option_narrowing:.3f}"
            }, sort_keys=True))
            return SequentialManifest(
                verdict=SequentialCommitmentVerdict.SAFE_COMMITMENT,
                directive=f"COMMIT: Step {step.step_id} with sufficient info.",
                manifest_digest=digest
            )

        # Gate 3: Deferrable information
        if step.information_becoming_available_soon:
            return SequentialManifest(
                verdict=SequentialCommitmentVerdict.DEFER_WARRANTED,
                directive=f"DEFER: Information for {step.step_id} available soon.",
                manifest_digest=cls._hash(f"DEFER:{step.step_id}")
            )

        # Gate 4: Lock-in risk
        if step.reversal_cost > policy.max_reversal_cost_acceptable:
            return SequentialManifest(
                verdict=SequentialCommitmentVerdict.LOCK_IN_RISK,
                directive=f"FLAG: Step {step.step_id} costly to reverse ({step.reversal_cost:.2f}) with low info.",
                manifest_digest=cls._hash(f"LOCK_IN:{step.step_id}")
            )

        # Gate 5: Safe commitment despite low info
        digest = cls._hash(json.dumps({
            "step": step.step_id,
            "info": f"{step.information_sufficiency:.3f}",
            "reversal": f"{step.reversal_cost:.3f}"
        }, sort_keys=True))
        
        return SequentialManifest(
            verdict=SequentialCommitmentVerdict.SAFE_COMMITMENT,
            directive=f"COMMIT_WITH_CAUTION: Step {step.step_id} low info but reversal cheap.",
            manifest_digest=digest
        )