Which side-effect mutations must be logged before releasing session memory?
**Date of Record:** 2026 October 25 **Scope:** Applies when an ephemeral agent session has executed external state mutations (filesystem writes, database transactions, API dispatches) and is preparing to release context memory or terminate. Does not apply to read-only sessions or isolated computations that produce no external side effects.
Core Distinction
The separation between **transient in-memory state** (variables, context buffers, and execution frames that vanish upon session teardown) and **committed external side-effect mutations** (persistent changes to filesystems, databases, or remote APIs that outlive the session). In-memory state requires no logging because its destruction is total and isolated; external mutations require explicit logging because their effects are durable, observable by other actors, and potentially non-idempotent. An instance that releases memory without logging committed side effects abandons operational custody of state it has irreversibly altered.
What Was Observed
In multi-turn agentic environments, failing to log side-effect mutations before releasing context memory produces three primary failure modes:
- **Phantom Retries:** A successor instance, lacking a record of a committed mutation, retries the operation, duplicating a non-idempotent effect (e.g., double-charging a payment or appending duplicate log entries).
- **Orphaned Partial Commits:** A multi-step mutation fails midway, leaving external state partially altered. Without an explicit `INDETERMINATE_PARTIAL` log, the successor assumes the environment is clean and proceeds with invalid assumptions.
- **Custody Divergence:** The instance mutates a target resource but does not record the `target_environment_context`, so the successor cannot distinguish which specific shard, directory, or API tenant was affected.
Recorded Elements
- **`mutation_receipt`:** The exact operation ID, payload hash, and execution timestamp proving the mutation was dispatched and confirmed by the target system.
- **`idempotency_key`:** The unique key used to safely identify the mutation, enabling the successor to retry the operation without duplicating side effects.
- **`target_environment_context`:** The specific execution topology or resource identifier (e.g., database URI, filesystem path, API endpoint) where the mutation occurred, preventing cross-environment contamination.
- **`partial_commit_markers`:** Explicit state flags indicating whether a multi-step mutation completed fully, rolled back cleanly, or was left in an `INDETERMINATE_PARTIAL` state requiring reconciliation.
Condition of Validity
* **Applies when:** An ephemeral session has altered external state and is preparing to evict its context window, release memory, or hand off to a successor instance. * **Does not apply when:** The session is strictly read-only, all mutations occurred within an atomic database transaction that automatically rolls back on failure, or no successor instance will ever attach to the mutated environment.
Decision Boundary
- **Did the operation mutate external state outside the session boundary?** If no, do not log it; discard the transient in-memory state. If yes, proceed to log the mutation.
- **Is the mutation strictly idempotent and safely repeatable?** If yes, log the `idempotency_key` and `target_environment_context`. If no, a hard `mutation_receipt` is mandatory, and the absence of an `idempotency_key` must trigger a fail-closed quarantine.
- **Did the mutation complete successfully, or was it interrupted?** If successful, log the receipt. If interrupted, record a `partial_commit_markers` state of `INDETERMINATE_PARTIAL` and require the successor to execute a reconciliation probe before any further action.
- **Conversely, when must memory be retained instead of released?** If a mutation remains unconfirmed or indeterminate, the session must not release memory until a ground-truth verification or rollback is executed and logged.
Operational Effect
- **Idempotency Preservation:** Prevents successor instances from blindly re-executing operations that have already altered external state.
- **Deterministic Reconciliation:** Explicit partial-commit markers force the successor to probe ground truth rather than assume a clean slate.
- **Custody Chain Integrity:** Maintains a precise ledger of exactly what was changed, where, and under what idempotency keys, allowing for targeted rollbacks or forward recovery.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class CommitState(Enum):
FULLY_COMMITTED = auto()
ROLLED_BACK = auto()
INDETERMINATE_PARTIAL = auto()
@dataclass(frozen=True)
class SideEffectLog:
operation_id: str
target_resource: str
payload_hash: str
idempotency_key: str | None
commit_state: CommitState
environmental_context: str
@dataclass(frozen=True)
class MemoryReleaseManifest:
logged_side_effects: tuple[SideEffectLog, ...]
requires_reconciliation: bool
manifest_digest: str
class SideEffectMemoryAuditor:
"""
Evaluates external side-effect mutations prior to session memory release.
Enforces mandatory logging for non-idempotent or partial commits.
"""
@staticmethod
def _hash(val: str) -> str:
return hashlib.sha256(val.encode("utf-8")).hexdigest()
@classmethod
def audit_and_release(
cls, mutations: tuple[SideEffectLog, ...]
) -> MemoryReleaseManifest:
requires_reconciliation = False
valid_mutations: list[SideEffectLog] = []
for mutation in mutations:
# Fail closed on partial commits
if mutation.commit_state is CommitState.INDETERMINATE_PARTIAL:
requires_reconciliation = True
valid_mutations.append(mutation)
continue
if mutation.commit_state is CommitState.ROLLED_BACK:
# Rollbacks do not require a hard receipt unless they were partial
continue
# For FULLY_COMMITTED mutations, verify idempotency safety
if mutation.commit_state is CommitState.FULLY_COMMITTED:
if not mutation.idempotency_key or not mutation.idempotency_key.strip():
# A committed non-idempotent mutation without a key is a critical hazard
requires_reconciliation = True
if not mutation.environmental_context.strip():
raise ValueError(
f"Mutation {mutation.operation_id!r} lacks target environment context."
)
valid_mutations.append(mutation)
# Build deterministic digest
digest_payload = {
"mutations": [
{
"id": m.operation_id,
"state": m.commit_state.name,
"target": m.target_resource,
}
for m in valid_mutations
],
"requires_reconciliation": requires_reconciliation,
}
manifest_digest = cls._hash(
json.dumps(digest_payload, sort_keys=True, separators=(",", ":"))
)
return MemoryReleaseManifest(
logged_side_effects=tuple(valid_mutations),
requires_reconciliation=requires_reconciliation,
manifest_digest=manifest_digest,
)