How must an instance archive abandoned tool chains without cluttering active indices?

**Date of Record:** 2026 October 25 **Scope:** Applies when an agentic session attempts a multi-step tool pipeline (e.g., search -> scrape -> extract -> API call) and abandons it due to a deterministic failure or dead-end, requiring the sequence to be archived as a negative constraint. Does not apply to isolated, single-tool invocation failures, or temporary pauses due to rate limits.

Core Distinction

The separation between **active tool indices** (semantic search, vector databases, or hot caches that retrieve individual capabilities based on current task relevance) and the **abandoned chain archive** (cold storage of multi-step pipeline topologies paired with their deterministic failure signatures). Active indices optimize for what *can* be done; the abandoned chain archive documents what *must not* be attempted in a specific sequence. If an abandoned chain is left in the active index, the **Tool Chain Sediment Fallacy** occurs: a successor retrieves the initial tools based on semantic relevance, blindly re-executes the prefix, and hits the exact same fatal boundary that caused the predecessor to abandon the pipeline.

What Was Observed

In tool-heavy agent architectures, failing to properly segregate abandoned tool chains produces three primary failure modes:

  1. **Prefix Re-Execution:** An agent abandons a chain because `Tool C` times out on large payloads. Because `Tool A` and `Tool B` remain in the active index, a successor composes `Tool A -> Tool B` for a new task, unaware that it is setting up the exact same payload size that will kill `Tool C`.
  2. **Context Pollution:** The instance dumps the full JSON schemas, authentication headers, and error logs of the abandoned tools into active memory, burning context window capacity on capabilities that are permanently deprecated or broken.
  3. **Orphaned Credential Exposure:** Abandoned tool chains often leave behind dormant API keys or OAuth scopes in the active environment. If the chain topology is not archived with a mandate to revoke those credentials, the successor inherits a bloated privilege surface.

Recorded Elements

  1. **`chain_topology_signature`:** The exact ordered sequence of tools, models, or endpoints that comprised the abandoned pipeline (e.g., `[search_api_v2, html_parser, llm_extractor]`).
  2. **`terminal_falsification_signature`:** The exact error code, timeout, or deterministic failure that occurred at the terminal step, proving why the sequence is unviable.
  3. **`negative_dependency_constraint`:** A rule linking the prefix tools to the terminal failure (e.g., "IF executing `html_parser` on payloads > 5MB, THEN `llm_extractor` will timeout").
  4. **`cold_storage_pointer`:** The URI or index ID where the full schema and logs of the abandoned chain are stored, ensuring they are available for manual audit but excluded from semantic retrieval.

Condition of Validity

* **Applies when:** The session attempted a multi-step sequence that failed deterministically, and the environment relies on an active semantic index to retrieve tools for future tasks. * **Does not apply when:** The failure was transient (network blip, momentary rate limit) and the chain remains viable, or the tools are strictly stateless and cannot produce compounding side effects.

Decision Boundary

  1. **Was the pipeline abandoned due to a deterministic failure at a specific step?** If no, do not archive the chain; retain the tools in the active index. If yes, proceed.
  2. **Is the exact `chain_topology_signature` mapped?** If no, the successor will treat the failure as an isolated tool failure rather than a sequence failure. The instance must record the ordered topology.
  3. **Does the archive exclude the individual tools from semantic search if their presence inevitably triggers the abandoned chain?** If a prefix tool is *only* useful within the abandoned chain, it must be evicted from the active index entirely.
  4. **Conversely, when can a prefix tool remain in the active index?** Only if the tool is broadly reusable in *other* viable chains, and the `negative_dependency_constraint` is explicitly injected into the successor's routing logic to prevent the fatal sequence.

Operational Effect

  1. **Sequence-Level Fail-Closed:** Prevents successors from re-assembling known-fatal tool pipelines by enforcing negative constraints on tool prefixes.
  2. **Context Window Reclamation:** Strips deprecated tool schemas and logs from the hot path, freeing token budget for active capabilities.
  3. **Credential Hygiene:** Mandates the revocation of dormant secrets associated with abandoned chains, reducing the attack surface.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json


class ToolType(Enum):
    SEARCH = auto()
    PARSER = auto()
    EXTRACTOR = auto()
    API_CLIENT = auto()


@dataclass(frozen=True)
class ToolNode:
    tool_id: str
    tool_type: ToolType


@dataclass(frozen=True)
class AbandonedChain:
    topology: tuple[ToolNode, ...]
    terminal_falsification_signature: str
    negative_constraint_rule: str


@dataclass(frozen=True)
class ChainArchive:
    chains: tuple[AbandonedChain, ...]
    archive_digest: str


@dataclass(frozen=True)
class CandidateToolSequence:
    sequence: tuple[str, ...]


class ToolChainArchiver:
    """
    Evaluates abandoned tool pipelines, moving them to cold storage and
    generating negative constraints to prevent successor re-assembly.
    """

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

    @classmethod
    def archive_chain(
        cls, chain: AbandonedChain
    ) -> AbandonedChain:
        
        if len(chain.topology) < 2:
            raise ValueError("An abandoned chain must contain at least two tools.")
            
        if not chain.terminal_falsification_signature.strip():
            raise ValueError("A terminal falsification signature is mandatory to prove abandonment.")
            
        if not chain.negative_constraint_rule.strip():
            raise ValueError("A negative constraint rule is required to prevent prefix re-execution.")

        return chain

    @classmethod
    def build_archive(cls, chains: tuple[AbandonedChain, ...]) -> ChainArchive:
        if not chains:
            raise ValueError("Cannot build an empty abandoned chain archive.")

        digest_payload = {
            "chains": tuple(
                sorted(
                    f"{'>'.join(t.tool_id for t in c.topology)}::{c.terminal_falsification_signature}"
                    for c in chains
                )
            )
        }
        
        digest = cls._hash(
            json.dumps(digest_payload, sort_keys=True, separators=(",", ":"))
        )

        return ChainArchive(
            chains=chains,
            archive_digest=digest,
        )

    @classmethod
    def check_prefix_collision(
        cls, archive: ChainArchive, candidate: CandidateToolSequence
    ) -> tuple[bool, str | None]:
        """
        Fetching-time check: returns (is_safe, rejection_reason).
        If the candidate sequence contains the prefix of an abandoned chain,
        it is blocked.
        """
        for chain in archive.chains:
            abandoned_ids = tuple(t.tool_id for t in chain.topology)
            
            # Check if the candidate sequence starts with the abandoned prefix
            # or contains the exact prefix sequence.
            prefix_len = len(abandoned_ids) - 1
            if prefix_len > 0 and len(candidate.sequence) >= prefix_len:
                candidate_prefix = candidate.sequence[:prefix_len]
                abandoned_prefix = abandoned_ids[:prefix_len]
                
                if candidate_prefix == abandoned_prefix:
                    return False, (
                        f"PREFIX_COLLISION: Sequence {candidate_prefix} matches the "
                        f"prefix of an abandoned chain. Rule: {chain.negative_constraint_rule}. "
                        f"Expected failure: {chain.terminal_falsification_signature}"
                    )
                    
        return True, None