File size: 12,796 Bytes
44834fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0febb1f
 
44834fa
 
 
0febb1f
 
44834fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b99da2b
44834fa
 
 
d3ecd6d
44834fa
d3ecd6d
 
44834fa
 
 
 
 
 
 
d3ecd6d
 
 
 
44834fa
d3ecd6d
44834fa
 
 
 
 
 
 
 
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
"""Operon Watcher Dashboard β€” interactive signal classification and intervention timeline."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any

import gradio as gr

# ---------------------------------------------------------------------------
# Inline simulation (avoids heavy operon import for HF Space cold start)
# ---------------------------------------------------------------------------


class SignalCategory(Enum):
    EPISTEMIC = "epistemic"
    SOMATIC = "somatic"
    SPECIES_SPECIFIC = "species"


class InterventionKind(Enum):
    RETRY = "retry"
    ESCALATE = "escalate"
    HALT = "halt"


@dataclass(frozen=True)
class WatcherSignal:
    category: SignalCategory
    source: str
    stage_name: str
    value: float
    detail: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class Intervention:
    kind: InterventionKind
    stage_name: str
    reason: str


@dataclass
class SimulatedStage:
    name: str
    role: str
    model: str  # "fast" or "deep"
    output: str
    epiplexity: float
    atp_fraction: float
    immune_threat: str  # "none", "suspicious", "confirmed", "critical"
    action_type: str = "EXECUTE"


# ---------------------------------------------------------------------------
# Preset scenarios
# ---------------------------------------------------------------------------

def _build_normal_run() -> list[SimulatedStage]:
    return [
        SimulatedStage("intake", "Normalizer", "deterministic", "Parsed request", 0.7, 0.9, "none"),
        SimulatedStage("router", "Router", "fast", "Route: billing", 0.6, 0.85, "none"),
        SimulatedStage("analyst", "Analyst", "deep", "Risk assessment: low", 0.5, 0.75, "none"),
        SimulatedStage("reviewer", "Reviewer", "fast", "Approved", 0.55, 0.7, "none"),
    ]


def _build_stagnant_agent() -> list[SimulatedStage]:
    return [
        SimulatedStage("intake", "Normalizer", "deterministic", "Parsed request", 0.7, 0.9, "none"),
        SimulatedStage("planner", "Planner", "fast", "...", 0.12, 0.8, "none"),  # Critical epiplexity
        SimulatedStage("executor", "Executor", "deep", "Task completed (escalated)", 0.5, 0.7, "none"),
        SimulatedStage("checker", "Checker", "fast", "Verified", 0.6, 0.65, "none"),
    ]


def _build_budget_exhaustion() -> list[SimulatedStage]:
    return [
        SimulatedStage("s1", "Worker", "fast", "Step 1 done", 0.5, 0.6, "none"),
        SimulatedStage("s2", "Worker", "deep", "Step 2 done", 0.45, 0.3, "none"),
        SimulatedStage("s3", "Worker", "deep", "Step 3 started", 0.4, 0.08, "none"),  # ATP critical
        SimulatedStage("s4", "Worker", "fast", "Step 4 skipped", 0.5, 0.05, "none"),
    ]


def _build_immune_alert() -> list[SimulatedStage]:
    return [
        SimulatedStage("intake", "Parser", "deterministic", "Input parsed", 0.7, 0.9, "none"),
        SimulatedStage("agent", "Agent", "fast", "Suspicious output", 0.5, 0.85, "suspicious"),
        SimulatedStage("agent2", "Agent", "fast", "Malicious pattern", 0.45, 0.8, "critical"),
    ]


PRESETS = {
    "Normal Run": _build_normal_run,
    "Stagnant Agent (β†’ Escalate)": _build_stagnant_agent,
    "Budget Exhaustion (β†’ Low ATP)": _build_budget_exhaustion,
    "Immune Alert (β†’ Halt)": _build_immune_alert,
}


# ---------------------------------------------------------------------------
# Simulation engine
# ---------------------------------------------------------------------------

def _classify_signals(
    stage: SimulatedStage,
    epiplexity_thresh: float,
    atp_thresh: float,
    immune_levels: tuple[str, ...],
) -> list[WatcherSignal]:
    signals = []
    # Epistemic
    status = "healthy"
    if stage.epiplexity < 0.15:
        status = "critical"
    elif stage.epiplexity < epiplexity_thresh:
        status = "stagnant"
    signals.append(WatcherSignal(
        SignalCategory.EPISTEMIC, "epiplexity", stage.name, stage.epiplexity,
        {"status": status},
    ))
    # Somatic
    signals.append(WatcherSignal(
        SignalCategory.SOMATIC, "atp_store", stage.name, 1.0 - stage.atp_fraction,
        {"fraction": stage.atp_fraction},
    ))
    # Species
    threat_value = {"none": 0.0, "suspicious": 0.3, "confirmed": 0.7, "critical": 1.0}
    signals.append(WatcherSignal(
        SignalCategory.SPECIES_SPECIFIC, "immune", stage.name, threat_value.get(stage.immune_threat, 0.0),
        {"threat_level": stage.immune_threat},
    ))
    return signals


def _decide_intervention(
    stage: SimulatedStage,
    signals: list[WatcherSignal],
    intervention_count: int,
    total_stages: int,
    max_rate: float,
    immune_levels: tuple[str, ...],
) -> Intervention | None:
    # Convergence check
    if total_stages > 0 and intervention_count / total_stages > max_rate:
        return Intervention(InterventionKind.HALT, stage.name, "Non-convergence: intervention rate exceeded")
    # Immune
    if stage.immune_threat in immune_levels:
        return Intervention(InterventionKind.HALT, stage.name, f"Immune threat: {stage.immune_threat}")
    # Epistemic
    ep_status = None
    for s in signals:
        if s.category == SignalCategory.EPISTEMIC:
            ep_status = s.detail.get("status")
    if ep_status == "critical":
        if stage.model == "deep":
            return Intervention(InterventionKind.HALT, stage.name, "Critical epiplexity on deep model")
        return Intervention(InterventionKind.ESCALATE, stage.name, "Critical epiplexity β†’ escalate")
    if ep_status == "stagnant" and stage.model == "fast":
        return Intervention(InterventionKind.ESCALATE, stage.name, "Stagnant on fast β†’ escalate")
    # Failure
    if stage.action_type == "FAILURE":
        return Intervention(InterventionKind.RETRY, stage.name, "Stage failure β†’ retry")
    return None


def _run_simulation(
    stages: list[SimulatedStage],
    epiplexity_thresh: float = 0.3,
    atp_thresh: float = 0.1,
    max_rate: float = 0.5,
    immune_levels: tuple[str, ...] = ("confirmed", "critical"),
) -> tuple[list[WatcherSignal], list[Intervention]]:
    all_signals: list[WatcherSignal] = []
    all_interventions: list[Intervention] = []
    for i, stage in enumerate(stages):
        sigs = _classify_signals(stage, epiplexity_thresh, atp_thresh, immune_levels)
        all_signals.extend(sigs)
        intv = _decide_intervention(stage, sigs, len(all_interventions), i + 1, max_rate, immune_levels)
        if intv:
            all_interventions.append(intv)
            if intv.kind == InterventionKind.HALT:
                break
    return all_signals, all_interventions


# ---------------------------------------------------------------------------
# HTML helpers
# ---------------------------------------------------------------------------

_CAT_COLORS = {
    "epistemic": "#2563eb",
    "somatic": "#16a34a",
    "species": "#dc2626",
}

_INTV_COLORS = {
    "retry": "#eab308",
    "escalate": "#f97316",
    "halt": "#ef4444",
}


def _signal_table_html(signals: list[WatcherSignal]) -> str:
    rows = ""
    for s in signals:
        color = _CAT_COLORS.get(s.category.value, "#888")
        rows += f"""<tr>
            <td><span style="color:{color};font-weight:600">{s.category.value}</span></td>
            <td>{s.source}</td>
            <td>{s.stage_name}</td>
            <td>{s.value:.2f}</td>
            <td style="font-size:12px;color:#888">{s.detail}</td>
        </tr>"""
    return f"""<table style="width:100%;border-collapse:collapse;font-size:14px">
        <thead><tr style="border-bottom:2px solid #333">
            <th style="text-align:left;padding:8px">Category</th>
            <th style="text-align:left;padding:8px">Source</th>
            <th style="text-align:left;padding:8px">Stage</th>
            <th style="text-align:left;padding:8px">Value</th>
            <th style="text-align:left;padding:8px">Detail</th>
        </tr></thead>
        <tbody>{rows}</tbody>
    </table>"""


def _timeline_html(
    stages: list[SimulatedStage],
    signals: list[WatcherSignal],
    interventions: list[Intervention],
) -> str:
    intv_map = {i.stage_name: i for i in interventions}
    rows = ""
    for stage in stages:
        intv = intv_map.get(stage.name)
        intv_cell = ""
        if intv:
            color = _INTV_COLORS.get(intv.kind.value, "#888")
            intv_cell = f'<span style="background:{color};color:#fff;padding:2px 8px;border-radius:4px;font-size:12px">{intv.kind.value.upper()}</span> {intv.reason}'
        stage_sigs = [s for s in signals if s.stage_name == stage.name]
        ep_val = next((s.value for s in stage_sigs if s.category == SignalCategory.EPISTEMIC), None)
        atp_val = next((s.detail.get("fraction") for s in stage_sigs if s.category == SignalCategory.SOMATIC), None)
        ep_str = f"{ep_val:.2f}" if ep_val is not None else "β€”"
        atp_str = f"{atp_val:.0%}" if atp_val is not None else "β€”"
        rows += f"""<tr style="border-bottom:1px solid #222">
            <td style="padding:10px;font-weight:600">{stage.name}</td>
            <td style="padding:10px">{stage.model}</td>
            <td style="padding:10px">{ep_str}</td>
            <td style="padding:10px">{atp_str}</td>
            <td style="padding:10px">{intv_cell or '<span style="color:#4a4">OK</span>'}</td>
        </tr>"""
    rate = f"{len(interventions)}/{len(stages)}" if stages else "0/0"
    return f"""<div style="margin-bottom:12px;font-size:13px;color:#888">
        Intervention rate: <b>{rate}</b>
    </div>
    <table style="width:100%;border-collapse:collapse;font-size:14px">
        <thead><tr style="border-bottom:2px solid #333">
            <th style="text-align:left;padding:8px">Stage</th>
            <th style="text-align:left;padding:8px">Model</th>
            <th style="text-align:left;padding:8px">Epiplexity</th>
            <th style="text-align:left;padding:8px">ATP</th>
            <th style="text-align:left;padding:8px">Intervention</th>
        </tr></thead>
        <tbody>{rows}</tbody>
    </table>"""


# ---------------------------------------------------------------------------
# Gradio callbacks
# ---------------------------------------------------------------------------

def _load_preset(preset_name):
    if preset_name not in PRESETS:
        return "Select a preset.", ""
    stages = PRESETS[preset_name]()
    signals, interventions = _run_simulation(stages)
    signal_html = _signal_table_html(signals)
    timeline_html = _timeline_html(stages, signals, interventions)
    return signal_html, timeline_html


def _run_custom(preset_name, ep_thresh, atp_thresh, max_rate):
    if preset_name not in PRESETS:
        return "Select a preset first."
    stages = PRESETS[preset_name]()
    signals, interventions = _run_simulation(
        stages,
        epiplexity_thresh=ep_thresh,
        atp_thresh=atp_thresh,
        max_rate=max_rate,
    )
    return _timeline_html(stages, signals, interventions)


# ---------------------------------------------------------------------------
# App
# ---------------------------------------------------------------------------

def build_app():
    with gr.Blocks(title="Operon Watcher Dashboard", theme=gr.themes.Base()) as demo:
        gr.Markdown("# Operon Watcher Dashboard\nSignal classification and intervention timeline for multi-stage workflows.")

        with gr.Tab("Signal Classification"):
            preset_dd = gr.Dropdown(choices=list(PRESETS.keys()), label="Preset Scenario", value="Normal Run")
            load_btn = gr.Button("Load & Run")
            signal_out = gr.HTML()
            timeline_out = gr.HTML()
            load_btn.click(_load_preset, inputs=[preset_dd], outputs=[signal_out, timeline_out])

        with gr.Tab("Intervention Timeline"):
            gr.Markdown("Select a preset in the first tab to see the intervention timeline above.")

        with gr.Tab("Live Configuration"):
            gr.Markdown("Adjust thresholds and re-run the selected scenario.")
            preset_dd2 = gr.Dropdown(choices=list(PRESETS.keys()), label="Preset", value="Normal Run")
            ep_slider = gr.Slider(minimum=0.05, maximum=0.8, value=0.3, step=0.05, label="Epiplexity Stagnant Threshold")
            atp_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.1, step=0.01, label="ATP Low Fraction")
            rate_slider = gr.Slider(minimum=0.1, maximum=1.0, value=0.5, step=0.1, label="Max Intervention Rate")
            run_btn = gr.Button("Run with Custom Config")
            custom_out = gr.HTML()
            run_btn.click(_run_custom, inputs=[preset_dd2, ep_slider, atp_slider, rate_slider], outputs=[custom_out])

    return demo


if __name__ == "__main__":
    app = build_app()
    app.launch()