File size: 6,147 Bytes
4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 eb2d131 4058302 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | """Pydantic schemas for the Incident Command Center environment.
These are the wire types shared by the HTTP server and the client. They are
designed to be:
- **Forwards-compatible**: new observation fields have default values so old
clients keep working.
- **Strict on the server**: every action field has a validator that ensures
the server never receives malformed data.
- **Self-documenting**: every field has a `description` that renders into
the OpenAPI schema at `/docs`.
"""
from __future__ import annotations
from typing import Dict, List, Literal, Optional
from openenv.core.env_server import Action, Observation, State
from pydantic import ConfigDict, Field, field_validator
# ----- Constants shared with server code -----------------------------------
ActionType = Literal[
"inspect_logs",
"inspect_metrics",
"consult_kb",
"negotiate_handoff",
"apply_fix",
"close_incident",
"escalate",
"rollback",
"submit_postmortem",
]
RoleName = Literal[
"triage_agent",
"investigator_agent",
"ops_manager_agent",
]
CustomerTier = Literal["free", "standard", "premium", "enterprise"]
# ---------------------------------------------------------------------------
# Action
# ---------------------------------------------------------------------------
class IncidentAction(Action):
"""Structured action payload accepted by the environment.
Validators reject obviously malformed input (empty targets, invalid roles)
and trim whitespace so training-time and inference-time JSON is normalised
identically.
"""
model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)
action_type: ActionType = Field(
..., description="Selected action from the supported action space."
)
actor: RoleName = Field(
"triage_agent",
description="Specialist role acting in the environment during this turn.",
)
target: Optional[str] = Field(
None,
description=(
"Service id for inspect_logs/inspect_metrics, KB id for consult_kb, "
"team name for negotiate_handoff/escalate."
),
)
root_cause: Optional[str] = Field(
None, description="Predicted root cause for close_incident."
)
resolution_summary: Optional[str] = Field(
None,
description="Human-readable fix summary for apply_fix, rollback and close_incident.",
)
postmortem_note: Optional[str] = Field(
None,
description="Postmortem text for submit_postmortem actions.",
)
confidence: Optional[float] = Field(
None,
ge=0.0,
le=1.0,
description="Optional self-reported confidence of the agent in this action.",
)
reason: Optional[str] = Field(
None,
description="Optional free-text rationale for audit logs and traceability.",
)
@field_validator("target", "root_cause", "resolution_summary", "postmortem_note", "reason")
@classmethod
def _empty_string_to_none(cls, value: Optional[str]) -> Optional[str]:
if value is None:
return None
value = value.strip()
return value or None
# ---------------------------------------------------------------------------
# Observation
# ---------------------------------------------------------------------------
class IncidentObservation(Observation):
"""Observation returned to the agent after each action.
All newly added fields carry defaults so older clients continue to
deserialize this type correctly.
"""
model_config = ConfigDict(extra="ignore")
incident_id: str = ""
incident_title: str = ""
incident_description: str = ""
incident_category: str = ""
incident_difficulty: str = "easy"
customer_tier: CustomerTier = "standard"
affected_users_estimate: int = 0
revenue_impact_usd_per_min: int = 0
postmortem_required: bool = False
available_actions: List[str] = Field(default_factory=list)
available_teams: List[str] = Field(default_factory=list)
allowed_actors_by_action: Dict[str, List[str]] = Field(default_factory=dict)
visible_signals: List[str] = Field(default_factory=list)
investigation_targets: Dict[str, List[str]] = Field(
default_factory=dict,
description="Per-tool list of known investigation ids (logs/metrics/kb).",
)
playbook_hints: List[str] = Field(default_factory=list)
terminal_output: str = ""
budget_remaining: int = 0
sla_minutes_remaining: int = 0
incidents_remaining: int = 0
episode_step: int = 0
incident_step: int = 0
clues_found: int = 0
mitigation_applied: bool = False
postmortem_submitted: bool = False
reward_components: Dict[str, float] = Field(default_factory=dict)
last_action_notes: List[str] = Field(default_factory=list)
# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
class IncidentState(State):
"""Full environment state exposed at `/state` for observability."""
model_config = ConfigDict(extra="ignore")
task_id: str = "easy"
seed: int = 0
version: str = "3.0.0"
current_incident_index: int = 0
incidents_resolved: int = 0
incidents_failed: int = 0
budget_remaining: int = 0
sla_minutes_remaining: int = 0
cumulative_reward: float = 0.0
mitigation_applied: bool = False
postmortem_submitted: bool = False
clue_keywords_used: List[str] = Field(default_factory=list)
investigation_keys_used: List[str] = Field(default_factory=list)
handoff_history: List[str] = Field(default_factory=list)
action_trace: List[str] = Field(default_factory=list)
per_incident_steps: Dict[str, int] = Field(default_factory=dict)
reward_trace: List[Dict[str, float]] = Field(default_factory=list)
terminated_reason: Optional[str] = None
|