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