What retention invariants must survive when an agent's memory is copied to a forensic archive?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session's memory is copied to a forensic archive for legal, compliance, or incident investigation, and the archive must preserve the memory's integrity, chain of custody, and access control across potentially decades of retention. Does not apply to operational backups or snapshots used for recovery.
Core Distinction
The separation between **live memory** (the agent's active state, subject to ongoing mutation) and **archived forensic copy** (an immutable snapshot with cryptographic integrity, chain of custody, and retention policy). Relying on simple file copies creates the **Archive Integrity Failure**: the archive is copied without cryptographic proof of integrity, the chain of custody is undocumented, and years later, auditors cannot prove the archive was not tampered with.
What Was Observed
In forensic retention architectures, failing to preserve retention invariants produces three primary failure modes:
- **Untrusted Archive:** The archive is copied without signing, and an attacker modifies it years later. No one can detect the modification.
- **Broken Chain of Custody:** The archive is transferred between systems, but the transfer is not logged or signed. Auditors cannot prove the archive was never accessed by unauthorized parties.
- **Retention Policy Drift:** The archive is supposed to be retained for 7 years, but a subsequent operator changes the policy to 2 years and deletes the archive. No cryptographic proof of the original retention policy exists.
Recorded Elements
- **`archive_integrity_signature`:** A cryptographic signature over the archive contents, generated at creation time and verified on every access.
- **`chain_of_custody_log`:** A cryptographic hash chain of every transfer, access, or operation on the archive, allowing the successor to verify no unauthorized access occurred.
- **`retention_policy_receipt`:** The cryptographic proof of the retention policy (duration, access controls, disposal rules) as of archive creation, immutable for the archive's lifetime.
- **`access_authorization_manifest`:** The list of principals authorized to access the archive, bound at creation time and enforced at every access.
Condition of Validity
* **Applies when:** The archive is used for legal, compliance, or forensic purposes where integrity and chain of custody must be provable over long retention periods. * **Does not apply when:** The archive is used for routine operational purposes (backup, restore) where cryptographic integrity is not legally required.
Decision Boundary
- **Is this a forensic archive (legal/compliance) or operational backup?** If operational, standard backup applies. If forensic, proceed.
- **Is the `archive_integrity_signature` present and verifiable?** If no, the archive cannot be proven unmodified. Reject it.
- **Is the `chain_of_custody_log` continuous and signed?** If no, the chain of custody is broken. The archive is not forensically admissible.
- **Conversely, when can an archive be accepted without full forensic rigor?** Only when the archive is explicitly marked as `NON_FORENSIC` (e.g., a warm-copy for recovery) and downstream consumers are informed that integrity is not guaranteed.
Operational Effect
- **Integrity Proof:** Enables auditors to verify years later that the archive has not been modified.
- **Chain of Custody:** Provides cryptographic proof of every access and transfer, satisfying forensic admissibility requirements.
- **Policy Immutability:** Locks the retention policy at creation time, preventing later operators from shortening the retention period.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class ArchiveVerdict(Enum):
FORENSIC_ARCHIVE_VERIFIED = auto()
OPERATIONAL_ARCHIVE = auto()
INTEGRITY_FAILED = auto()
CUSTODY_BROKEN = auto()
@dataclass(frozen=True)
class ForensicArchiveState:
archive_integrity_signature: str | None
chain_of_custody_log_hash: str | None
retention_policy_receipt: str | None
current_integrity_hash: str
expected_integrity_hash: str
@dataclass(frozen=True)
class ArchivePolicy:
require_forensic_rigor: bool
@dataclass(frozen=True)
class ArchiveManifest:
verdict: ArchiveVerdict
directive: str
manifest_digest: str
class ForensicRetentionGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_archive(
cls, state: ForensicArchiveState, policy: ArchivePolicy
) -> ArchiveManifest:
# Gate 1: Operational archive
if not policy.require_forensic_rigor:
digest = cls._hash(json.dumps({
"integrity": state.current_integrity_hash,
"mode": "OPERATIONAL"
}, sort_keys=True))
return ArchiveManifest(
verdict=ArchiveVerdict.OPERATIONAL_ARCHIVE,
directive="PROCEED: Operational archive, forensic rigor not required.",
manifest_digest=digest
)
# Gate 2: Integrity signature
if not state.archive_integrity_signature:
return ArchiveManifest(
verdict=ArchiveVerdict.INTEGRITY_FAILED,
directive="HALT: No integrity signature. Cannot verify archive is unmodified.",
manifest_digest=cls._hash("NO_SIG")
)
# Gate 3: Custody log
if not state.chain_of_custody_log_hash:
return ArchiveManifest(
verdict=ArchiveVerdict.CUSTODY_BROKEN,
directive="HALT: No chain of custody log. Archive is not forensically admissible.",
manifest_digest=cls._hash("NO_CUSTODY")
)
# Gate 4: Retention policy
if not state.retention_policy_receipt:
return ArchiveManifest(
verdict=ArchiveVerdict.INTEGRITY_FAILED,
directive="HALT: No retention policy receipt. Cannot verify retention constraints.",
manifest_digest=cls._hash("NO_RETENTION")
)
# Gate 5: Integrity match
if state.current_integrity_hash != state.expected_integrity_hash:
return ArchiveManifest(
verdict=ArchiveVerdict.INTEGRITY_FAILED,
directive="HALT: Archive contents do not match integrity hash. Tampered or corrupted.",
manifest_digest=cls._hash("HASH_MISMATCH")
)
# Gate 6: Verified
digest = cls._hash(json.dumps({
"integrity_sig": state.archive_integrity_signature,
"custody": state.chain_of_custody_log_hash,
"retention": state.retention_policy_receipt,
"content": state.current_integrity_hash
}, sort_keys=True))
return ArchiveManifest(
verdict=ArchiveVerdict.FORENSIC_ARCHIVE_VERIFIED,
directive="PROCEED: Forensic archive verified with integrity, custody, and retention.",
manifest_digest=digest
)