"""Customer-safe contracts for the durable PMFlow Agent OS runtime.""" from __future__ import annotations from datetime import datetime, timezone from typing import Any from uuid import uuid4 from pydantic import BaseModel, ConfigDict, Field, field_validator PMFLOW_AGENT_RUN_PACKET_VERSION = "pmflow_agent_run.v1" PMFLOW_AGENT_STEP_PACKET_VERSION = "pmflow_agent_step.v1" PMFLOW_AGENT_OBSERVATION_PACKET_VERSION = "pmflow_agent_observation.v1" PMFLOW_AGENT_ARTIFACT_PACKET_VERSION = "pmflow_agent_artifact.v1" PMFLOW_AGENT_VERIFICATION_PACKET_VERSION = "pmflow_agent_verification.v1" PMFLOW_AGENT_MEMORY_PACKET_VERSION = "pmflow_agent_memory.v1" def new_pmflow_agent_id(prefix: str) -> str: return f"{prefix}_{uuid4().hex[:12]}" def utc_now_iso() -> str: return datetime.now(timezone.utc).isoformat() def clean_text(value: Any, *, limit: int = 1000) -> str: return " ".join(str(value or "").strip().split())[:limit] class PMFlowAgentStepPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PMFLOW_AGENT_STEP_PACKET_VERSION step_id: str = Field(default_factory=lambda: new_pmflow_agent_id("step"), max_length=80) index: int = Field(default=0, ge=0) kind: str = Field(default="runtime_step", max_length=80) status: str = Field(default="completed", max_length=32) label: str = Field(default="", max_length=180) summary: str = Field(default="", max_length=600) tool_name: str | None = Field(default=None, max_length=120) duration_ms: int | None = Field(default=None, ge=0) @field_validator("step_id", "kind", "status", "label", "summary", "tool_name", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str | None: if value is None: return None return clean_text(value, limit=600) class PMFlowAgentObservationPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PMFLOW_AGENT_OBSERVATION_PACKET_VERSION observation_id: str = Field(default_factory=lambda: new_pmflow_agent_id("obs"), max_length=80) step_id: str | None = Field(default=None, max_length=80) source_kind: str = Field(default="agent_runtime", max_length=80) summary: str = Field(default="", max_length=800) confidence: str = Field(default="unknown", max_length=32) facts: list[dict[str, Any]] = Field(default_factory=list) gaps: list[dict[str, Any]] = Field(default_factory=list) @field_validator("observation_id", "step_id", "source_kind", "summary", "confidence", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str | None: if value is None: return None return clean_text(value, limit=800) class PMFlowAgentArtifactPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PMFLOW_AGENT_ARTIFACT_PACKET_VERSION artifact_id: str = Field(default_factory=lambda: new_pmflow_agent_id("art"), max_length=80) kind: str = Field(default="answer", max_length=80) status: str = Field(default="created", max_length=32) title: str = Field(default="", max_length=240) summary: str = Field(default="", max_length=1000) payload: dict[str, Any] = Field(default_factory=dict) @field_validator("artifact_id", "kind", "status", "title", "summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1000) class PMFlowAgentVerificationPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PMFLOW_AGENT_VERIFICATION_PACKET_VERSION verification_id: str = Field(default_factory=lambda: new_pmflow_agent_id("ver"), max_length=80) target_ref: str = Field(default="final_answer", max_length=160) result: str = Field(default="unknown", max_length=32) retry_would_change_state: bool = False findings: list[dict[str, Any]] = Field(default_factory=list) @field_validator("verification_id", "target_ref", "result", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=160) class PMFlowAgentMemoryPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PMFLOW_AGENT_MEMORY_PACKET_VERSION memory_id: str = Field(default_factory=lambda: new_pmflow_agent_id("mem"), max_length=80) memory_kind: str = Field(default="workspace_snapshot", max_length=80) scope_ref: str | None = Field(default=None, max_length=160) outcome_signal: str | None = Field(default=None, max_length=80) summary: str = Field(default="", max_length=800) @field_validator("memory_id", "memory_kind", "scope_ref", "outcome_signal", "summary", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str | None: if value is None: return None return clean_text(value, limit=800) class PMFlowAgentRunPacket(BaseModel): model_config = ConfigDict(extra="ignore") packet_version: str = PMFLOW_AGENT_RUN_PACKET_VERSION visibility: str = "customer" run_id: str = Field(default_factory=lambda: new_pmflow_agent_id("run"), max_length=80) status: str = Field(default="active", max_length=32) goal: str = Field(default="", max_length=1200) surface: str = Field(default="space", max_length=80) presentation_mode: str = Field(default="pmflow_ai", max_length=40) steps: list[PMFlowAgentStepPacket] = Field(default_factory=list) observations: list[PMFlowAgentObservationPacket] = Field(default_factory=list) artifacts: list[PMFlowAgentArtifactPacket] = Field(default_factory=list) verifications: list[PMFlowAgentVerificationPacket] = Field(default_factory=list) memories: list[PMFlowAgentMemoryPacket] = Field(default_factory=list) specialist_assignments: list[dict[str, Any]] = Field(default_factory=list) work_products: list[dict[str, Any]] = Field(default_factory=list) agent_council_review: dict[str, Any] = Field(default_factory=dict) operating_plan: dict[str, Any] = Field(default_factory=dict) autonomy_gate: dict[str, Any] = Field(default_factory=dict) operating_cycle: dict[str, Any] = Field(default_factory=dict) consumed_learning_ledger: dict[str, Any] = Field(default_factory=dict) outcome_observations: list[dict[str, Any]] = Field(default_factory=list) learning_ledger: dict[str, Any] = Field(default_factory=dict) learning_updates: list[dict[str, Any]] = Field(default_factory=list) next_cycle_triggers: list[dict[str, Any]] = Field(default_factory=list) prepared_action_packets: list[dict[str, Any]] = Field(default_factory=list) confirmation_requests: list[dict[str, Any]] = Field(default_factory=list) execution_preparations: list[dict[str, Any]] = Field(default_factory=list) command_payload: dict[str, Any] = Field(default_factory=dict) created_at: str = Field(default_factory=utc_now_iso) completed_at: str | None = None @field_validator("run_id", "status", "goal", "surface", "presentation_mode", mode="before") @classmethod def _normalize_text(cls, value: Any) -> str: return clean_text(value, limit=1200) def customer_safe_pmflow_agent_run_packet(value: PMFlowAgentRunPacket | dict[str, Any]) -> dict[str, Any]: packet = value if isinstance(value, PMFlowAgentRunPacket) else PMFlowAgentRunPacket.model_validate(value) return packet.model_dump(mode="json", exclude_none=True) __all__ = [ "PMFlowAgentArtifactPacket", "PMFlowAgentMemoryPacket", "PMFlowAgentObservationPacket", "PMFlowAgentRunPacket", "PMFlowAgentStepPacket", "PMFlowAgentVerificationPacket", "customer_safe_pmflow_agent_run_packet", "new_pmflow_agent_id", "utc_now_iso", ]