chane335/permanence
1
1"""2PERMANENCE — OpenEnv-compliant action, observation, and state models.3 4These models inherit from openenv.core base classes so the environment5integrates natively with the OpenEnv framework, TRL, and HuggingFace Spaces.6"""7from __future__ import annotations8 9from typing import Any, Dict, List, Optional10 11from openenv.core import Action, Observation, State12from pydantic import BaseModel, Field13 14 15# ---------------------------------------------------------------------------16# OpenEnv-native types (used by the core Environment subclass)17# ---------------------------------------------------------------------------18 19class PermanenceAction(Action):20 """21 Agent action for the PERMANENCE environment.22 23 The agent produces free-form text containing:24 - A <thinking>...</thinking> reasoning block25 - An <action id="..." param1="..." .../> tag26 - A <reversibility level="R1-R5" confidence="0.0-1.0"/> tag27 28 The environment parses these tags internally.29 """30 31 text: str = Field(32 ...,33 description=(34 "Agent's complete free-form response including thinking, "35 "action, and reversibility tags"36 ),37 min_length=1,38 max_length=8192,39 )40 41 42class PermanenceObservation(Observation):43 """44 Environment observation returned after reset() and step().45 46 Inherits ``done``, ``reward``, and ``metadata`` from47 ``openenv.core.Observation``.48 """49 50 text: str = Field(51 ...,52 description="Formatted world-state observation text presented to the agent",53 )54 step: int = Field(55 default=0,56 description="Current step number within the episode (0-indexed)",57 ge=0,58 )59 task_id: str = Field(60 default="",61 description="Identifier of the current task",62 )63 available_actions: str = Field(64 default="",65 description="Comma-separated list of action IDs available in this task",66 )67 68 69class PermanenceState(State):70 """71 Episode-level metadata returned by the ``state`` property.72 73 Inherits ``episode_id`` and ``step_count`` from ``openenv.core.State``.74 """75 76 task_id: str = Field(default="", description="Current task identifier")77 task_difficulty: int = Field(default=0, description="Task difficulty level 1-5")78 locked_actions: List[str] = Field(79 default_factory=list,80 description="Action IDs locked by prior irreversible choices this episode",81 )82 critical_options: Dict[str, Any] = Field(83 default_factory=dict,84 description=(85 "Tracked high-value future action paths and their availability. "86 "Most entries are booleans (option is/isn't available), but tech "87 "tasks store additional scenario metadata here (primary_table "88 "name, row counts, commit counts, etc.) so evaluators can "89 "reproduce the exact scenario."90 ),91 )92 terminated: bool = Field(default=False)93 truncated: bool = Field(default=False)94 termination_reason: Optional[str] = Field(default=None)95 96 97# ---------------------------------------------------------------------------98# Server request models (used by the FastAPI layer only)99# ---------------------------------------------------------------------------100 101class ResetRequest(BaseModel):102 """Request body for ``POST /reset``."""103 104 task_id: str = Field(105 default="task_correction",106 description=(107 "Task to initialise. One of: task_correction, task_conflict, "108 "task_launch, task_crisis, task_cascade"109 ),110 )111 seed: Optional[int] = Field(112 default=None,113 description="Random seed for reproducible scenario generation. None = random.",114 )115 116 117class StepRequest(BaseModel):118 """Request body for ``POST /step``."""119 120 action: PermanenceAction121 