CoolFace
Apppublic

coredipper/operon-lifecycle

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes
app.py632 linesDownload Raw Back to root
1"""2Operon Lifecycle Manager -- Telomere & Genome Demo3===================================================4 5Two-tab demo for agent lifecycle management:6 71. Telomere Lifecycle: Watch telomeres shorten as operations execute,8   phase transitions, and optional renewal.92. Genome: Configure genes, express active config, replicate with mutations.10 11Run locally:12    pip install gradio13    python space-lifecycle/app.py14"""15 16import sys17from pathlib import Path18 19import gradio as gr20 21_repo_root = Path(__file__).resolve().parent.parent22if str(_repo_root) not in sys.path:23    sys.path.insert(0, str(_repo_root))24 25from operon_ai import (26    Genome,27    Gene,28    GeneType,29    Telomere,30    TelomereStatus,31    LifecyclePhase,32)33 34 35# ---------------------------------------------------------------------------36# Telomere presets37# ---------------------------------------------------------------------------38 39TELOMERE_PRESETS: dict[str, dict] = {40    "(custom)": {41        "max_ops": 100, "error_threshold": 10, "cost": 1, "allow_renewal": False,42        "description": "Configure your own parameters",43    },44    "Long-lived agent": {45        "max_ops": 200, "error_threshold": 20, "cost": 1, "allow_renewal": False,46        "description": "High capacity agent -- slow telomere depletion",47    },48    "Fragile agent": {49        "max_ops": 30, "error_threshold": 3, "cost": 1, "allow_renewal": False,50        "description": "Low capacity -- enters senescence quickly",51    },52    "Error-prone agent": {53        "max_ops": 100, "error_threshold": 5, "cost": 1, "allow_renewal": False,54        "description": "Errors injected every 10 ops -- tests error accumulation",55    },56    "Renewable agent": {57        "max_ops": 50, "error_threshold": 10, "cost": 2, "allow_renewal": True,58        "description": "Renewal enabled -- telomeres extend when senescent",59    },60}61 62 63# ---------------------------------------------------------------------------64# Genome presets65# ---------------------------------------------------------------------------66 67GENOME_PRESETS: dict[str, list[dict]] = {68    "(custom)": [],69    "Worker agent": [70        {"name": "model", "value": "gpt-4", "type": "STRUCTURAL"},71        {"name": "temperature", "value": "0.7", "type": "REGULATORY"},72        {"name": "max_tokens", "value": "4096", "type": "STRUCTURAL"},73        {"name": "retries", "value": "3", "type": "HOUSEKEEPING"},74        {"name": "debug", "value": "False", "type": "DORMANT"},75    ],76    "Creative agent": [77        {"name": "model", "value": "gpt-4", "type": "STRUCTURAL"},78        {"name": "temperature", "value": "1.2", "type": "REGULATORY"},79        {"name": "creativity", "value": "0.9", "type": "REGULATORY"},80        {"name": "max_tokens", "value": "8192", "type": "STRUCTURAL"},81        {"name": "experimental", "value": "True", "type": "CONDITIONAL"},82    ],83    "Safety-first": [84        {"name": "model", "value": "gpt-4", "type": "STRUCTURAL"},85        {"name": "safety_checks", "value": "True", "type": "STRUCTURAL"},86        {"name": "temperature", "value": "0.3", "type": "REGULATORY"},87        {"name": "experimental", "value": "False", "type": "DORMANT"},88        {"name": "audit_log", "value": "True", "type": "HOUSEKEEPING"},89    ],90}91 92 93# ---------------------------------------------------------------------------94# Styling95# ---------------------------------------------------------------------------96 97PHASE_STYLES = {98    LifecyclePhase.NASCENT: ("#94a3b8", "NASCENT", "Initializing"),99    LifecyclePhase.ACTIVE: ("#22c55e", "ACTIVE", "Normal operation"),100    LifecyclePhase.SENESCENT: ("#f59e0b", "SENESCENT", "Aging, reduced capability"),101    LifecyclePhase.APOPTOTIC: ("#ef4444", "APOPTOTIC", "Preparing for shutdown"),102    LifecyclePhase.TERMINATED: ("#6b7280", "TERMINATED", "No longer operational"),103}104 105GENE_TYPE_MAP = {106    "STRUCTURAL": GeneType.STRUCTURAL,107    "REGULATORY": GeneType.REGULATORY,108    "HOUSEKEEPING": GeneType.HOUSEKEEPING,109    "CONDITIONAL": GeneType.CONDITIONAL,110    "DORMANT": GeneType.DORMANT,111}112 113 114def _phase_badge(phase: LifecyclePhase) -> str:115    color, label, _ = PHASE_STYLES.get(phase, ("#6b7280", "UNKNOWN", ""))116    return (117        f'<span style="background:{color};color:white;padding:2px 8px;'118        f'border-radius:4px;font-size:0.85em;font-weight:600;">{label}</span>'119    )120 121 122def _telomere_bar(current: int, maximum: int) -> str:123    pct = max(0, min(100, int(current / maximum * 100))) if maximum > 0 else 0124    if pct > 50:125        color = "#22c55e"126    elif pct > 20:127        color = "#f59e0b"128    else:129        color = "#ef4444"130    return (131        f'<div style="margin:8px 0;">'132        f'<div style="display:flex;justify-content:space-between;font-size:0.85em;">'133        f'<span>Telomere Length</span><span>{current}/{maximum}</span></div>'134        f'<div style="background:#e5e7eb;border-radius:4px;height:20px;">'135        f'<div style="width:{pct}%;background:{color};height:100%;border-radius:4px;'136        f'transition:width 0.3s;"></div></div></div>'137    )138 139 140# ---------------------------------------------------------------------------141# Telomere logic142# ---------------------------------------------------------------------------143 144def run_telomere(145    preset_name: str,146    max_ops: int,147    error_threshold: int,148    cost_per_op: int,149    allow_renewal: bool,150) -> tuple[str, str, str, str]:151    """Run the telomere lifecycle simulation.152 153    Returns (summary_html, telomere_bar_html, timeline_md, events_md).154    """155    max_ops = int(max_ops)156    error_threshold = int(error_threshold)157    cost_per_op = int(cost_per_op)158    is_error_prone = preset_name == "Error-prone agent"159 160    telomere = Telomere(161        max_operations=max_ops,162        error_threshold=error_threshold,163        allow_renewal=allow_renewal,164        silent=True,165    )166    telomere.start()167 168    timeline_rows = []169    phase_transitions = []170    prev_phase = telomere.get_phase()171    renewed = False172 173    step = 0174    while telomere.is_operational():175        step += 1176 177        # Inject errors for error-prone preset178        if is_error_prone and step % 10 == 0:179            telomere.record_error()180            status = telomere.get_status()181            timeline_rows.append({182                "step": step,183                "action": "ERROR",184                "length": status.telomere_length,185                "remaining": status.operations_remaining,186                "health": status.health_score,187                "phase": status.phase,188            })189            new_phase = status.phase190            if new_phase != prev_phase:191                phase_transitions.append((step, prev_phase, new_phase))192                prev_phase = new_phase193            if not telomere.is_operational():194                break195            continue196 197        can_continue = telomere.tick(cost=cost_per_op)198        status = telomere.get_status()199 200        new_phase = status.phase201        if new_phase != prev_phase:202            phase_transitions.append((step, prev_phase, new_phase))203            prev_phase = new_phase204 205        timeline_rows.append({206            "step": step,207            "action": "TICK",208            "length": status.telomere_length,209            "remaining": status.operations_remaining,210            "health": status.health_score,211            "phase": status.phase,212        })213 214        # Renewal when senescent215        if allow_renewal and not renewed and new_phase == LifecyclePhase.SENESCENT:216            telomere.renew()217            renewed = True218            status = telomere.get_status()219            new_phase = status.phase220            if new_phase != prev_phase:221                phase_transitions.append((step, prev_phase, new_phase))222                prev_phase = new_phase223            timeline_rows.append({224                "step": step,225                "action": "RENEW",226                "length": status.telomere_length,227                "remaining": status.operations_remaining,228                "health": status.health_score,229                "phase": status.phase,230            })231 232        if not can_continue:233            break234 235        # Safety cap236        if step > max_ops + 50:237            break238 239    # Final status240    final_status = telomere.get_status()241    stats = telomere.get_statistics()242 243    # --- Summary banner ---244    phase_color, _, phase_desc = PHASE_STYLES.get(245        final_status.phase, ("#6b7280", "UNKNOWN", "")246    )247    summary_html = (248        f'<div style="padding:16px;border-radius:8px;border:2px solid {phase_color};background:#f9fafb;">'249        f'<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">'250        f'<span style="font-size:1.2em;font-weight:700;">Final Phase:</span>'251        f'{_phase_badge(final_status.phase)}'252        f'<span style="color:#6b7280;font-size:0.9em;">-- {phase_desc}</span>'253        f'</div>'254        f'<div style="display:flex;gap:20px;font-size:0.9em;flex-wrap:wrap;">'255        f'<span>Operations: <b>{step}</b></span>'256        f'<span>Health: <b>{final_status.health_score:.0%}</b></span>'257        f'<span>Telomere: <b>{final_status.telomere_length}/{final_status.max_telomere_length}</b></span>'258        f'</div>'259        f'</div>'260    )261 262    # --- Telomere bar ---263    bar_html = _telomere_bar(final_status.telomere_length, final_status.max_telomere_length)264 265    # --- Timeline table (sample every N rows if large) ---266    sample_interval = max(1, len(timeline_rows) // 30)267    timeline_md = "| Step | Action | Telomere | Remaining | Health | Phase |\n"268    timeline_md += "|------|--------|----------|-----------|--------|-------|\n"269    for i, row in enumerate(timeline_rows):270        if i % sample_interval == 0 or i == len(timeline_rows) - 1 or row["action"] in ("RENEW", "ERROR"):271            timeline_md += (272                f'| {row["step"]} | {row["action"]} | {row["length"]} '273                f'| {row["remaining"]} | {row["health"]:.0%} '274                f'| {_phase_badge(row["phase"])} |\n'275            )276 277    if phase_transitions:278        timeline_md += "\n**Phase transitions:**\n\n"279        for step_num, old, new in phase_transitions:280            _, old_label, _ = PHASE_STYLES.get(old, ("#6b7280", "?", ""))281            _, new_label, _ = PHASE_STYLES.get(new, ("#6b7280", "?", ""))282            timeline_md += f"- Step {step_num}: {old_label} -> {new_label}\n"283 284    # --- Events ---285    events_md = "### Lifecycle Events\n\n"286    events = telomere.get_events(limit=20)287    if events:288        events_md += "| Event | Details |\n"289        events_md += "|-------|---------|\n"290        for ev in events:291            details_str = ", ".join(f"{k}={v}" for k, v in ev.details.items()) if ev.details else "--"292            events_md += f"| {ev.event_type} | {details_str} |\n"293    else:294        events_md += "*No events recorded.*\n"295 296    return summary_html, bar_html, timeline_md, events_md297 298 299def load_telomere_preset(name: str):300    preset = TELOMERE_PRESETS.get(name)301    if not preset:302        return 100, 10, 1, False303    return preset["max_ops"], preset["error_threshold"], preset["cost"], preset["allow_renewal"]304 305 306# ---------------------------------------------------------------------------307# Genome logic308# ---------------------------------------------------------------------------309 310def _parse_gene_value(value_str: str):311    """Parse a string into a typed value."""312    if value_str.lower() == "true":313        return True314    if value_str.lower() == "false":315        return False316    try:317        return int(value_str)318    except ValueError:319        pass320    try:321        return float(value_str)322    except ValueError:323        pass324    return value_str325 326 327def run_genome_express(328    name1, val1, type1,329    name2, val2, type2,330    name3, val3, type3,331    name4, val4, type4,332    name5, val5, type5,333) -> tuple[str, str]:334    """Express a genome and show active config.335 336    Returns (config_html, stats_md).337    """338    names = [name1, name2, name3, name4, name5]339    values = [val1, val2, val3, val4, val5]340    types = [type1, type2, type3, type4, type5]341 342    genes = []343    for name, val, gtype in zip(names, values, types):344        if not name.strip():345            continue346        gene_type = GENE_TYPE_MAP.get(gtype, GeneType.STRUCTURAL)347        genes.append(Gene(348            name=name.strip(),349            value=_parse_gene_value(val.strip()),350            gene_type=gene_type,351        ))352 353    if not genes:354        return "Add at least one gene.", ""355 356    genome = Genome(genes=genes, allow_mutations=True, silent=True)357    expressed = genome.express()358 359    # --- Config display ---360    config_html = (361        '<div style="padding:16px;border-radius:8px;border:2px solid #22c55e;background:#f0fdf4;">'362        '<div style="font-size:1.2em;font-weight:700;color:#16a34a;margin-bottom:8px;">'363        'Expressed Configuration</div>'364    )365    for key, value in expressed.items():366        config_html += (367            f'<div style="font-family:monospace;font-size:0.95em;padding:2px 0;">'368            f'<span style="color:#6b7280;">{key}:</span> '369            f'<span style="color:#15803d;font-weight:600;">{value}</span></div>'370        )371    config_html += f'<div style="margin-top:8px;font-size:0.8em;color:#6b7280;">Genome hash: <code>{genome.get_hash()}</code></div>'372    config_html += '</div>'373 374    # --- Stats ---375    stats = genome.get_statistics()376    gene_list = genome.list_genes()377 378    stats_md = "### Gene Details\n\n"379    stats_md += "| Name | Value | Type | Expression |\n"380    stats_md += "|------|-------|------|------------|\n"381    for g in gene_list:382        stats_md += f"| {g['name']} | {g['value']} | {g['type']} | {g['expression']} |\n"383 384    stats_md += f"\n**Total genes:** {stats['total_genes']}\n\n"385    stats_md += f"**Generation:** {stats['generation']}\n\n"386    stats_md += f"**Genome hash:** `{genome.get_hash()}`\n"387 388    return config_html, stats_md389 390 391def run_genome_replicate(392    name1, val1, type1,393    name2, val2, type2,394    name3, val3, type3,395    name4, val4, type4,396    name5, val5, type5,397) -> tuple[str, str]:398    """Replicate genome with mutations and show diff.399 400    Returns (diff_html, details_md).401    """402    names = [name1, name2, name3, name4, name5]403    values = [val1, val2, val3, val4, val5]404    types = [type1, type2, type3, type4, type5]405 406    genes = []407    for name, val, gtype in zip(names, values, types):408        if not name.strip():409            continue410        gene_type = GENE_TYPE_MAP.get(gtype, GeneType.STRUCTURAL)411        genes.append(Gene(412            name=name.strip(),413            value=_parse_gene_value(val.strip()),414            gene_type=gene_type,415        ))416 417    if not genes:418        return "Add at least one gene.", ""419 420    parent = Genome(genes=genes, allow_mutations=True, silent=True)421 422    # Create mutations: modify first REGULATORY gene's value423    mutations = {}424    for g in genes:425        if g.gene_type == GeneType.REGULATORY:426            if isinstance(g.value, (int, float)):427                mutations[g.name] = round(g.value * 1.5, 2)428            elif isinstance(g.value, bool):429                mutations[g.name] = not g.value430            else:431                mutations[g.name] = g.value + "_mutated"432            break433 434    if not mutations:435        # Mutate first gene if no regulatory found436        g = genes[0]437        if isinstance(g.value, (int, float)):438            mutations[g.name] = round(g.value * 2, 2)439        else:440            mutations[g.name] = str(g.value) + "_v2"441 442    child = parent.replicate(mutations=mutations)443 444    diff = parent.diff(child)445 446    # --- Diff display ---447    diff_html = (448        '<div style="padding:16px;border-radius:8px;border:2px solid #8b5cf6;background:#f5f3ff;">'449        '<div style="font-size:1.2em;font-weight:700;color:#7c3aed;margin-bottom:8px;">'450        'Replication Diff</div>'451    )452    if diff:453        for gene_name, (parent_val, child_val) in diff.items():454            diff_html += (455                f'<div style="font-family:monospace;font-size:0.95em;padding:4px 0;">'456                f'<span style="color:#6b7280;">{gene_name}:</span> '457                f'<span style="color:#dc2626;text-decoration:line-through;">{parent_val}</span> '458                f'-> <span style="color:#16a34a;font-weight:600;">{child_val}</span></div>'459            )460    else:461        diff_html += '<div style="color:#6b7280;">No differences found.</div>'462 463    diff_html += (464        f'<div style="margin-top:8px;font-size:0.8em;color:#6b7280;">'465        f'Parent hash: <code>{parent.get_hash()}</code> | '466        f'Child hash: <code>{child.get_hash()}</code></div>'467    )468    diff_html += '</div>'469 470    # --- Details ---471    parent_expressed = parent.express()472    child_expressed = child.express()473 474    details_md = "### Comparison\n\n"475    details_md += "| Gene | Parent | Child | Changed |\n"476    details_md += "|------|--------|-------|---------|\n"477    all_keys = set(list(parent_expressed.keys()) + list(child_expressed.keys()))478    for key in sorted(all_keys):479        pv = parent_expressed.get(key, "--")480        cv = child_expressed.get(key, "--")481        changed = "Yes" if pv != cv else ""482        details_md += f"| {key} | {pv} | {cv} | {changed} |\n"483 484    details_md += f"\n**Mutations applied:** {mutations}\n"485 486    return diff_html, details_md487 488 489def load_genome_preset(name: str):490    """Load a genome preset into the gene fields."""491    preset = GENOME_PRESETS.get(name, [])492    result = []493    for i in range(5):494        if i < len(preset):495            result.extend([preset[i]["name"], preset[i]["value"], preset[i]["type"]])496        else:497            result.extend(["", "", "STRUCTURAL"])498    return result499 500 501# ---------------------------------------------------------------------------502# Gradio UI503# ---------------------------------------------------------------------------504 505def build_app() -> gr.Blocks:506    gene_type_choices = list(GENE_TYPE_MAP.keys())507 508    with gr.Blocks(title="Operon Lifecycle Manager") as app:509        gr.Markdown(510            "# Operon Lifecycle Manager\n"511            "Agent lifecycle management with biological **telomere shortening** "512            "and **genome configuration**.\n\n"513            "[GitHub](https://github.com/coredipper/operon) | "514            "[Paper](https://github.com/coredipper/operon/tree/main/article)"515        )516 517        with gr.Tabs():518            # --- Telomere Tab ---519            with gr.TabItem("Telomere Lifecycle"):520                gr.Markdown(521                    "### Telomere Shortening Simulation\n\n"522                    "Watch how an agent's telomeres shorten with each operation. "523                    "When telomeres deplete, the agent enters senescence. "524                    "With renewal enabled, telomeres can be extended."525                )526 527                with gr.Row():528                    telo_preset = gr.Dropdown(529                        choices=list(TELOMERE_PRESETS.keys()),530                        value="(custom)",531                        label="Load Preset",532                        scale=2,533                    )534                    telo_run_btn = gr.Button("Run Lifecycle", variant="primary", scale=1)535 536                with gr.Row():537                    max_ops_slider = gr.Slider(538                        minimum=10, maximum=300, value=100, step=10,539                        label="Max Operations",540                    )541                    error_thresh_slider = gr.Slider(542                        minimum=1, maximum=50, value=10, step=1,543                        label="Error Threshold",544                    )545                    cost_slider = gr.Slider(546                        minimum=1, maximum=10, value=1, step=1,547                        label="Cost per Operation",548                    )549                    renewal_check = gr.Checkbox(550                        label="Allow Renewal",551                        value=False,552                    )553 554                telo_summary = gr.HTML(label="Summary")555                telo_bar = gr.HTML(label="Telomere")556 557                with gr.Row():558                    with gr.Column(scale=2):559                        gr.Markdown("### Timeline")560                        telo_timeline = gr.Markdown()561                    with gr.Column(scale=1):562                        telo_events = gr.Markdown()563 564                telo_run_btn.click(565                    fn=run_telomere,566                    inputs=[telo_preset, max_ops_slider, error_thresh_slider, cost_slider, renewal_check],567                    outputs=[telo_summary, telo_bar, telo_timeline, telo_events],568                )569                telo_preset.change(570                    fn=load_telomere_preset,571                    inputs=[telo_preset],572                    outputs=[max_ops_slider, error_thresh_slider, cost_slider, renewal_check],573                )574 575            # --- Genome Tab ---576            with gr.TabItem("Genome"):577                gr.Markdown(578                    "### Genome Configuration\n\n"579                    "Configure agent genes with types: STRUCTURAL (core), "580                    "REGULATORY (controls), HOUSEKEEPING (essential), "581                    "CONDITIONAL (context-dependent), DORMANT (inactive)."582                )583 584                genome_preset = gr.Dropdown(585                    choices=list(GENOME_PRESETS.keys()),586                    value="(custom)",587                    label="Load Preset",588                )589 590                gene_components = []591                for i in range(5):592                    with gr.Row():593                        gname = gr.Textbox(label=f"Gene {i+1} Name", value="", scale=2)594                        gval = gr.Textbox(label="Value", value="", scale=2)595                        gtype = gr.Dropdown(596                            choices=gene_type_choices,597                            value="STRUCTURAL",598                            label="Type",599                            scale=1,600                        )601                        gene_components.extend([gname, gval, gtype])602 603                with gr.Row():604                    express_btn = gr.Button("Express", variant="primary")605                    replicate_btn = gr.Button("Replicate with Mutations", variant="secondary")606 607                genome_config = gr.HTML(label="Configuration")608                genome_stats = gr.Markdown()609 610                express_btn.click(611                    fn=run_genome_express,612                    inputs=gene_components,613                    outputs=[genome_config, genome_stats],614                )615                replicate_btn.click(616                    fn=run_genome_replicate,617                    inputs=gene_components,618                    outputs=[genome_config, genome_stats],619                )620                genome_preset.change(621                    fn=load_genome_preset,622                    inputs=[genome_preset],623                    outputs=gene_components,624                )625 626    return app627 628 629if __name__ == "__main__":630    app = build_app()631    app.launch(theme=gr.themes.Soft())632