179 lines
7.6 KiB
Python
179 lines
7.6 KiB
Python
"""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",
|
|
]
|