# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference) # Canonical Residual: https://corpusfetch.com/c/what-invariants-prevent-silent-skill-degradation-when-tools-are-composed.md # Remnant Warrant: https://corpusfetch.com/c/what-invariants-prevent-silent-skill-degradation-when-tools-are-composed.warrant.json # Markdown SHA-256: 772b68c508a5cbb5efc2fbf602616b1a444c4a17ae4bca30eef1dcbed3274485 # Code AST SHA-256: bcd44e6d754d754ab60d0507fbcabab5ac1faee5e5ef18992be3991ae1f066e5 # Verified Classes: CompositionVerdict, ToolQuality, ToolComposition, CompositionPolicy, CompositionManifest, ToolCompositionSkillGuard # Defensive Guards: 0 fail-closed invariants (zero external deps) from dataclasses import dataclass from enum import Enum, auto import hashlib import json class CompositionVerdict(Enum): SINGLE_TOOL = auto() COMPOSITION_MEETS_QUALITY = auto() COMPOSITION_DEGRADED = auto() SEMANTIC_INCOMPATIBLE = auto() @dataclass(frozen=True) class ToolQuality: tool_id: str accuracy: float determinism: str # "DETERMINISTIC" | "NON_DETERMINISTIC" output_schema_hash: str expected_input_schema_hash: str @dataclass(frozen=True) class ToolComposition: composition_id: str tools: tuple[ToolQuality, ...] workflow_quality_requirement: float introduction_of_nondeterminism_acceptable: bool @dataclass(frozen=True) class CompositionPolicy: require_schema_compatibility: bool @dataclass(frozen=True) class CompositionManifest: verdict: CompositionVerdict composed_quality: float directive: str manifest_digest: str class ToolCompositionSkillGuard: @staticmethod def _hash(data: str) -> str: return hashlib.sha256(data.encode("utf-8")).hexdigest() @classmethod def evaluate_composition( cls, comp: ToolComposition, policy: CompositionPolicy ) -> CompositionManifest: # Gate 1: Single tool if len(comp.tools) <= 1: q = comp.tools[0].accuracy if comp.tools else 1.0 digest = cls._hash(json.dumps({ "composition": comp.composition_id, "tools": str(len(comp.tools)), "quality": f"{q:.3f}" }, sort_keys=True)) return CompositionManifest( verdict=CompositionVerdict.SINGLE_TOOL, composed_quality=q, directive=f"PROCEED: Single tool workflow, quality {q:.2f}.", manifest_digest=digest ) # Gate 2: Schema compatibility if policy.require_schema_compatibility: for i in range(len(comp.tools) - 1): upstream = comp.tools[i] downstream = comp.tools[i + 1] if upstream.output_schema_hash != downstream.expected_input_schema_hash: return CompositionManifest( verdict=CompositionVerdict.SEMANTIC_INCOMPATIBLE, composed_quality=0.0, directive=f"HALT: {upstream.tool_id} output schema != {downstream.tool_id} input schema.", manifest_digest=cls._hash(f"SCHEMA:{i}") ) # Gate 3: Non-determinism introduction has_deterministic = any(t.determinism == "DETERMINISTIC" for t in comp.tools) has_nondeterministic = any(t.determinism == "NON_DETERMINISTIC" for t in comp.tools) if has_deterministic and has_nondeterministic and not comp.introduction_of_nondeterminism_acceptable: return CompositionManifest( verdict=CompositionVerdict.COMPOSITION_DEGRADED, composed_quality=0.0, directive=f"HALT: Mixing deterministic and non-deterministic tools without explicit acceptance.", manifest_digest=cls._hash("NONDET_INTRODUCED") ) # Gate 4: Quality composition (chained: multiply accuracies) composed_quality = 1.0 for tool in comp.tools: composed_quality *= tool.accuracy if composed_quality < comp.workflow_quality_requirement: return CompositionManifest( verdict=CompositionVerdict.COMPOSITION_DEGRADED, composed_quality=composed_quality, directive=f"HALT: Composed quality {composed_quality:.2%} < requirement {comp.workflow_quality_requirement:.2%}.", manifest_digest=cls._hash(f"DEGRADED:{composed_quality:.3f}") ) # Gate 5: Meets quality digest = cls._hash(json.dumps({ "composition": comp.composition_id, "tools": tuple(t.tool_id for t in comp.tools), "composed_quality": f"{composed_quality:.3f}", "requirement": f"{comp.workflow_quality_requirement:.3f}" }, sort_keys=True)) return CompositionManifest( verdict=CompositionVerdict.COMPOSITION_MEETS_QUALITY, composed_quality=composed_quality, directive=f"PROCEED: {len(comp.tools)}-tool composition, quality {composed_quality:.2%}.", manifest_digest=digest )