Ryker_Feng d1d0958bea
feat(skills): add guarded evolution host APIs (#5645)
* feat(skills): add guarded evolution host APIs

* docs(agents): avoid growing saturated guidance chains

* fix(skills): address mutation host review findings

* fix(skills): order name fence before projection lock

* fix(skills): preserve composite toggle lock order

* fix(skills): address catalog and pagination review feedback

* docs: keep subagent guidance within merged instruction budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-18 21:17:02 +08:00

162 lines
4.4 KiB
Python

"""Host-owned, conditional updates of existing custom skills; no learning policy."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class AssetRevision:
incarnation_id: str
mutation_seq: int
content_digest: str
@dataclass(frozen=True)
class SkillRevisionView:
target_ref: str
owner_id: str
name: str
revision: AssetRevision
content: str
enabled: bool
@dataclass(frozen=True)
class MutationCapabilities:
operations: tuple[str, ...]
protocol_version: str = "skill-mutations-v1"
max_main_bytes: int = 131072
max_package_bytes: int = 16777216
max_package_files: int = 256
max_sources: int = 20
max_pending_proposals: int = 20
max_model_scan_files: int = 32
max_model_scan_bytes: int = 1048576
max_model_scan_file_bytes: int = 131072
max_concurrent_scans: int = 4
max_owner_plugin_concurrent_scans: int = 2
max_owner_plugin_scans_per_hour: int = 20
check_timeout_seconds: int = 120
proposal_ttl_seconds: int = 86400
check_ttl_seconds: int = 900
rollback_ttl_seconds: int = 2592000
topology: str = "posix-single-host-local-filesystem"
def __post_init__(self):
object.__setattr__(self, "operations", tuple(self.operations))
@dataclass(frozen=True)
class Proposal:
proposal_id: str
owner_id: str
target_ref: str
name: str
base_revision: AssetRevision
candidate_hash: str
state: str
expires_at: str
source_refs: tuple[str, ...]
operation_id: str | None = None
def __post_init__(self):
object.__setattr__(self, "source_refs", tuple(self.source_refs))
@dataclass(frozen=True)
class BundleFile:
path: str
content: bytes
sha256: str
executable: bool
def __post_init__(self):
object.__setattr__(self, "content", bytes(self.content))
@dataclass(frozen=True)
class ProposalBundle:
proposal_id: str
baseline: tuple[BundleFile, ...]
candidate: tuple[BundleFile, ...]
base_revision: AssetRevision
candidate_hash: str
def __post_init__(self):
object.__setattr__(self, "baseline", tuple(self.baseline))
object.__setattr__(self, "candidate", tuple(self.candidate))
@dataclass(frozen=True)
class HostCheckResult:
proposal_id: str
decision: str
candidate_hash: str
base_revision: AssetRevision
policy_version: str
expires_at: str
reason_code: str
@dataclass(frozen=True)
class AssessmentRef:
candidate_hash: str
base_revision: AssetRevision
evaluator: str
evaluator_version: str
report_id: str
@dataclass(frozen=True)
class Operation:
operation_id: str
owner_id: str
name: str
publication: str
views: str
before_revision: AssetRevision
after_revision: AssetRevision
generation: int
proposal_id: str | None = None
reverts_operation_id: str | None = None
error_code: str | None = None
superseded_by_generation: int | None = None
class SkillMutationService(Protocol):
@property
def capabilities(self) -> MutationCapabilities:
raise NotImplementedError
async def read_skill(self, *, source_ref: str, name: str) -> SkillRevisionView:
raise NotImplementedError
async def stage(self, *, source_refs: tuple[str, ...], target_ref: str, expected_revision: AssetRevision, content: str, idempotency_key: str) -> Proposal:
raise NotImplementedError
async def get_proposal(self, *, proposal_id: str) -> Proposal:
raise NotImplementedError
async def read_proposal_bundle(self, *, proposal_id: str, max_bytes: int = 33554432) -> ProposalBundle:
raise NotImplementedError
async def check(self, *, proposal_id: str) -> HostCheckResult:
raise NotImplementedError
async def commit(self, *, proposal_id: str, idempotency_key: str, assessment_ref: AssessmentRef | None = None) -> Operation:
raise NotImplementedError
async def discard(self, *, proposal_id: str) -> Proposal:
raise NotImplementedError
async def revert(self, *, operation_id: str, expected_current_revision: AssetRevision, idempotency_key: str) -> Operation:
raise NotImplementedError
async def get_operation(self, *, operation_id: str) -> Operation:
raise NotImplementedError
async def find_operation(self, *, idempotency_key: str, method: str = "commit") -> Operation | None:
raise NotImplementedError