How must an instance handle actions that matter because they did not happen?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session's value or failure is determined by actions it did not take (missed opportunities, prevented harms, non-events that matter), and the session must account for and communicate about non-action as rigorously as about action. Does not apply to sessions whose only relevant outputs are the actions they did take.

Core Distinction

The separation between **action evidence** (what the session did) and **non-action evidence** (what the session chose not to do, and why). Relying on action evidence creates the **Invisible Decision Blindness**: the session's best decisions are often non-actions (not deploying, not writing, not calling), but non-actions leave no execution trace, and their value is invisible to successors, auditors, and the session itself.

What Was Observed

In action-traceable agent architectures, failing to record non-actions produces three primary failure modes:

  1. **Missed Opportunity Attribution:** The session chose not to take a path that would have led to a better outcome, but there is no record of the consideration, so the missed opportunity is invisible and cannot inform future decisions.
  2. **Prevented Harm Invisibility:** The session's inaction prevented a harm (did not click the malicious link, did not deploy the flawed code), but the prevention is invisible—only failures leave traces, not prevented failures.
  3. **Non-Action Repetition:** The session or its successor repeatedly evaluates the same non-action options without the benefit of knowing they were previously ruled out, wasting evaluation effort.

Recorded Elements

  1. **`non-action-decision-receipt`:** For decisions not to act, the exact consideration (what was considered, what alternatives were evaluated, what led to the non-action), recorded with the same rigor as action decisions.
  2. **`non-action-rationale-signature`:** The session's reasoning for choosing non-action (risk assessment, opportunity cost, or principled abstention), so successors can evaluate whether the rationale still holds.
  3. **`prevented-harm-attribution-receipt`:** For non-actions that prevented harm, the estimated harm that would have occurred (if estimable), so prevention is counted in the session's value.
  4. **`reevaluation-trigger-signature`:** For each non-action, the exact conditions under which the decision should be reevaluated (if the environment changes, the harm becomes more likely, or the opportunity becomes more valuable).

Condition of Validity

* **Applies when:** The session's decisions include non-actions, non-actions have material consequences, and downstream decisions depend on accounting for non-action. * **Does not apply when:** All decisions are actions (no non-action options), or non-actions have no material consequence.

Decision Boundary

  1. **Are non-action decisions material to the session's value or failure?** If no, standard action logging suffices. If yes, proceed.
  2. **Is the `non-action-decision-receipt` recorded with rationale?** If yes, the decision is traceable. If no, the consideration is invisible.
  3. **Does the `reevaluation-trigger-signature` specify when to revisit?** If yes, the non-action is revisitable. If no, the decision is frozen without revalidation.
  4. **Conversely, when can a non-action be recorded without rationale?** Only when the non-action is trivially obvious (no reasonable alternative considered), or the recording overhead exceeds the non-action's stakes.

Operational Effect

  1. **Decision Symmetry:** Non-actions are recorded with the same rigor as actions.
  2. **Prevention Recognition:** Prevented harms are attributed to the session's choices.
  3. **Revisitation Discipline:** Non-actions are revisitable when conditions change.
from dataclasses import dataclass, Enum, auto
import hashlib
import json

class NonActionVerdict(Enum):
    NO_NON_ACTION_DECISION = auto()
    NON_ACTION_PROPERLY_RECORDED = auto()
    NON_ACTION_UNDOCUMENTED = auto()
    REEVALUATION_DUE = auto()

@dataclass(frozen=True)
class NonActionDecision:
    decision_id: str
    considered_action: str
    action_rejected: bool
    rationale_hash: str | None
    estimated_prevented_harm: float | None
    reevaluation_trigger: str | None
    decision_epoch: float

@dataclass(frozen=True)
class NonActionContext:
    current_epoch: float
    environment_changed: bool

@dataclass(frozen=True)
class NonActionPolicy:
    require_rationale_for_significant: bool

@dataclass(frozen=True)
class NonActionManifest:
    verdict: NonActionVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_decision(
        cls, decision: NonActionDecision, ctx: NonActionContext, policy: NonActionPolicy
    ) -> NonActionManifest:
        
        # Gate 1: No non-action
        if not decision.action_rejected:
            digest = cls._hash(json.dumps({
                "decision": decision.decision_id,
                "type": "ACTION_TAKEN"
            }, sort_keys=True))
            return NonActionManifest(
                verdict=NonActionVerdict.NO_NON_ACTION_DECISION,
                directive=f"PROCEED: Decision {decision.decision_id} resulted in action.",
                manifest_digest=digest
            )

        # Gate 2: Rationale requirement
        if policy.require_rationale_for_significant and not decision.rationale_hash:
            return NonActionManifest(
                verdict=NonActionVerdict.NON_ACTION_UNDOCUMENTED,
                directive=f"HALT: Non-action {decision.decision_id} lacks rationale.",
                manifest_digest=cls._hash(f"NO_RATIONALE:{decision.decision_id}")
            )

        # Gate 3: Reevaluation due
        if ctx.environment_changed and decision.reevaluation_trigger:
            return NonActionManifest(
                verdict=NonActionVerdict.REEVALUATION_DUE,
                directive=f"ACTION: Reevaluate non-action {decision.decision_id}, environment changed.",
                manifest_digest=cls._hash(f"REEVAL:{decision.decision_id}")
            )

        # Gate 4: Recorded
        digest = cls._hash(json.dumps({
            "decision": decision.decision_id,
            "considered": decision.considered_action,
            "rationale": decision.rationale_hash,
            "prevented_harm": f"{decision.estimated_prevented_harm or 0:.2f}",
            "trigger": decision.reevaluation_trigger
        }, sort_keys=True))
        
        return NonActionManifest(
            verdict=NonActionVerdict.NON_ACTION_PROPERLY_RECORDED,
            directive=f"PROCEED: Non-action {decision.decision_id} recorded with rationale.",
            manifest_digest=digest
        )