""" Operon Bi-Temporal Memory — Explorer (Gradio Demo) =================================================== Three-tab demo: 1. Fact Timeline — load preset scenarios, inspect facts and corrections 2. Point-in-Time — query belief state at any (valid, record) coordinate 3. Diff & Audit — compare time points, view full audit trails Run locally: pip install gradio operon-ai python space-bitemporal/app.py """ import sys from datetime import datetime, timedelta from pathlib import Path import gradio as gr # Allow importing operon_ai from the repo root when running locally _repo_root = Path(__file__).resolve().parent.parent if str(_repo_root) not in sys.path: sys.path.insert(0, str(_repo_root)) from operon_ai import BiTemporalMemory # ── Time helpers ──────────────────────────────────────────────────── _BASE = datetime(2026, 3, 15, 9, 0, 0) def _t(day: int, hour: int = 12) -> datetime: return _BASE + timedelta(days=day - 1, hours=hour - 9) def _fmt(dt: datetime) -> str: return dt.strftime("Day %d %H:%M").replace("Day 0", "Day ").lstrip("0") def _day_fmt(dt: datetime) -> str: delta = dt - _BASE day = delta.days + 1 return f"Day {day} {dt.strftime('%H:%M')}" # ── Preset scenarios ─────────────────────────────────────────────── def _build_compliance_audit() -> BiTemporalMemory: """Financial product approval with retroactive correction.""" mem = BiTemporalMemory() # Day 1: Quantitative model outputs risk score mem.record_fact( subject="product:BOND-7Y", predicate="risk_score", value=0.42, valid_from=_t(1), recorded_from=_t(1), source="quant_model", tags=("quantitative",), ) # Day 2: Liquidity assessed mem.record_fact( subject="product:BOND-7Y", predicate="liquidity_class", value="B", valid_from=_t(2), recorded_from=_t(2), source="liquidity_engine", tags=("quantitative",), ) # Day 3: Regulatory category assigned mem.record_fact( subject="product:BOND-7Y", predicate="regulatory_category", value="standard", valid_from=_t(1), recorded_from=_t(3), source="compliance_team", tags=("regulatory",), ) # Day 5: Post-approval audit reveals risk was higher risk_facts = mem.retrieve_valid_at(at=_t(5), subject="product:BOND-7Y", predicate="risk_score") if risk_facts: mem.correct_fact( old_fact_id=risk_facts[0].fact_id, value=0.68, valid_from=_t(1), recorded_from=_t(5), source="audit_review", tags=("correction", "audit"), ) return mem def _build_client_onboarding() -> BiTemporalMemory: """Client onboarding with progressive fact discovery.""" mem = BiTemporalMemory() # Day 1: Initial KYC data mem.record_fact( subject="client:ACME-42", predicate="kyc_status", value="pending", valid_from=_t(1), recorded_from=_t(1), source="onboarding_portal", ) mem.record_fact( subject="client:ACME-42", predicate="revenue_band", value="mid-market", valid_from=_t(1), recorded_from=_t(1), source="crm_sync", ) # Day 2: KYC approved mem.record_fact( subject="client:ACME-42", predicate="kyc_status", value="approved", valid_from=_t(2), recorded_from=_t(2), source="compliance_team", ) # Day 3: Revenue reclassified (retroactive to day 1) rev_facts = mem.retrieve_valid_at(at=_t(3), subject="client:ACME-42", predicate="revenue_band") if rev_facts: mem.correct_fact( old_fact_id=rev_facts[0].fact_id, value="enterprise", valid_from=_t(1), recorded_from=_t(3), source="finance_team", tags=("correction",), ) # Day 4: Tier assigned based on corrected revenue mem.record_fact( subject="client:ACME-42", predicate="tier", value="gold", valid_from=_t(4), recorded_from=_t(4), source="tier_engine", ) return mem def _build_incident_response() -> BiTemporalMemory: """Incident timeline with evolving root cause analysis.""" mem = BiTemporalMemory() # Hour 0: Alert fires mem.record_fact( subject="incident:INC-1337", predicate="severity", value="P2", valid_from=_t(1, 9), recorded_from=_t(1, 9), source="alerting_system", tags=("auto",), ) mem.record_fact( subject="incident:INC-1337", predicate="root_cause", value="database_timeout", valid_from=_t(1, 9), recorded_from=_t(1, 10), source="oncall_engineer", ) # Hour 3: Escalated to P1 sev_facts = mem.retrieve_valid_at(at=_t(1, 12), subject="incident:INC-1337", predicate="severity") if sev_facts: mem.correct_fact( old_fact_id=sev_facts[0].fact_id, value="P1", valid_from=_t(1, 9), recorded_from=_t(1, 12), source="incident_commander", tags=("escalation",), ) # Hour 5: Root cause updated rc_facts = mem.retrieve_valid_at(at=_t(1, 14), subject="incident:INC-1337", predicate="root_cause") if rc_facts: mem.correct_fact( old_fact_id=rc_facts[0].fact_id, value="network_partition_causing_db_timeout", valid_from=_t(1, 9), recorded_from=_t(1, 14), source="sre_team", tags=("correction", "root_cause"), ) # Hour 8: Resolved mem.record_fact( subject="incident:INC-1337", predicate="status", value="resolved", valid_from=_t(1, 17), recorded_from=_t(1, 17), source="incident_commander", ) return mem PRESETS = { "Compliance Audit (BOND-7Y)": { "description": "Financial product approval with post-approval risk correction.", "build_fn": _build_compliance_audit, "subjects": ["product:BOND-7Y"], }, "Client Onboarding (ACME-42)": { "description": "Progressive KYC and revenue reclassification during onboarding.", "build_fn": _build_client_onboarding, "subjects": ["client:ACME-42"], }, "Incident Response (INC-1337)": { "description": "Evolving severity and root cause during an incident.", "build_fn": _build_incident_response, "subjects": ["incident:INC-1337"], }, } # Module-level state: the currently loaded memory instance _current_mem: BiTemporalMemory | None = None _current_subjects: list[str] = [] # ── HTML helpers ──────────────────────────────────────────────────── def _badge(text, color="#6366f1"): return ( f'' f'{text}' ) def _fact_row_html(f, show_status=True): status = "" if show_status: if f.recorded_to is not None: status = _badge("CLOSED", "#ef4444") else: status = _badge("ACTIVE", "#22c55e") supersedes = "" if f.supersedes: supersedes = f' [corrects {f.supersedes[:8]}]' tags = "" if f.tags: tags = " ".join(_badge(t, "#64748b") for t in f.tags) valid_to = _day_fmt(f.valid_to) if f.valid_to else "ongoing" rec_to = _day_fmt(f.recorded_to) if f.recorded_to else "current" return ( f'' f'{f.fact_id[:8]}' f'{status}' f'{f.subject}' f'{f.predicate}' f'{f.value}' f'{_day_fmt(f.valid_from)} — {valid_to}' f'{_day_fmt(f.recorded_from)} — {rec_to}' f'{f.source}{supersedes}' f'{tags}' f'' ) def _fact_table_html(facts, title="Facts", show_status=True): if not facts: return f'
{title}: No matching facts.
' header = ( '' '' '' '' '' '' '' '' '' '' '' '' ) rows = "".join(_fact_row_html(f, show_status) for f in facts) return ( f'
{title}
' + header + rows + '
IDStatusSubjectPredicateValueValid TimeRecord TimeSourceTags
' ) def _summary_card(label, value, color="#6366f1"): return ( f'
' f'
{label}
' f'
{value}
' f'
' ) # ── Tab 1: Fact Timeline ─────────────────────────────────────────── def _load_preset(preset_name): global _current_mem, _current_subjects preset = PRESETS.get(preset_name) if not preset: return "Select a preset.", "" _current_mem = preset["build_fn"]() _current_subjects = preset["subjects"] all_facts = _current_mem._facts active = [f for f in all_facts if f.recorded_to is None] closed = [f for f in all_facts if f.recorded_to is not None] corrections = [f for f in all_facts if f.supersedes is not None] summary = ( '
' + _summary_card("Total Facts", len(all_facts), "#3b82f6") + _summary_card("Active", len(active), "#22c55e") + _summary_card("Closed", len(closed), "#ef4444") + _summary_card("Corrections", len(corrections), "#9333ea") + '
' ) timeline = _fact_table_html( sorted(all_facts, key=lambda f: f.recorded_from), title=f"Full Timeline ({preset_name})", ) return summary, timeline # ── Tab 2: Point-in-Time Query ───────────────────────────────────── def _run_query(query_type, valid_day, record_day, subject_filter): if _current_mem is None: return "Load a scenario first (Tab 1)." subject = subject_filter.strip() or None valid_time = _t(int(valid_day)) record_time = _t(int(record_day)) if query_type == "Valid-Time Only": facts = _current_mem.retrieve_valid_at(at=valid_time, subject=subject) title = f"Valid at {_day_fmt(valid_time)} (active records only)" explanation = ( '
' 'Valid-time query: Returns facts whose valid interval contains ' f'{_day_fmt(valid_time)}, considering only currently active records. ' 'This answers: "What is true in the world at this time?"' '
' ) elif query_type == "Record-Time Only": facts = _current_mem.retrieve_known_at(at=record_time, subject=subject) title = f"Known by {_day_fmt(record_time)} (includes closed records)" explanation = ( '
' 'Record-time query: Returns facts the system had recorded by ' f'{_day_fmt(record_time)}, including later-closed records. ' 'This answers: "What did the system know at this time?"' '
' ) else: facts = _current_mem.retrieve_belief_state( at_valid=valid_time, at_record=record_time, ) title = f"Belief state at (valid={_day_fmt(valid_time)}, record={_day_fmt(record_time)})" explanation = ( '
' 'Belief-state query: Intersects both axes — returns facts ' f'valid at {_day_fmt(valid_time)} AND recorded by {_day_fmt(record_time)}. ' 'This answers: "What did the system believe was true at this world-time, ' 'given only what it knew by this record-time?"' '
' ) if subject: facts = [f for f in facts if f.subject == subject] return explanation + _fact_table_html(facts, title=title) # ── Tab 3: Diff & Audit ─────────────────────────────────────────── def _run_diff(axis, t1_day, t2_day): if _current_mem is None: return "Load a scenario first (Tab 1)." t1 = _t(int(t1_day)) t2 = _t(int(t2_day)) diff = _current_mem.diff_between(t1, t2, axis=axis.lower().replace("-time", "")) axis_label = axis.lower().replace("-time", "") return _fact_table_html( diff, title=f"New facts on {axis_label} axis between {_day_fmt(t1)} and {_day_fmt(t2)}", ) def _run_audit(subject): if _current_mem is None: return "", "" subject = subject.strip() if not subject: subject = _current_subjects[0] if _current_subjects else "" if not subject: return "Enter a subject.", "" history = _current_mem.history(subject) timeline = _current_mem.timeline_for(subject) hist_html = _fact_table_html( history, title=f"History (by record time): {subject}", ) time_html = _fact_table_html( timeline, title=f"Timeline (by valid time): {subject}", ) return hist_html, time_html # ── Gradio UI ────────────────────────────────────────────────────── def build_app() -> gr.Blocks: with gr.Blocks(title="Bi-Temporal Memory Explorer", theme=gr.themes.Soft()) as app: gr.Markdown( "# Bi-Temporal Memory Explorer\n" "Explore **dual time axes**: valid time (when a fact is true) vs " "record time (when the system learned it). Corrections are " "append-only --- old records are closed, never mutated.\n\n" "Start by loading a **preset scenario** in the first tab." ) with gr.Tabs(): # ── Tab 1: Fact Timeline ───────────────────────────── with gr.TabItem("Fact Timeline"): gr.Markdown( "Load a preset scenario to populate the bi-temporal " "memory. Each scenario demonstrates corrections and " "retroactive knowledge updates." ) with gr.Row(): preset_dd = gr.Dropdown( choices=list(PRESETS.keys()), value="Compliance Audit (BOND-7Y)", label="Preset Scenario", scale=3, ) load_btn = gr.Button("Load Scenario", variant="primary", scale=1) summary_html = gr.HTML() timeline_html = gr.HTML() load_btn.click( fn=_load_preset, inputs=[preset_dd], outputs=[summary_html, timeline_html], ) # ── Tab 2: Point-in-Time Query ─────────────────────── with gr.TabItem("Point-in-Time Query"): gr.Markdown( "Query the memory at specific time coordinates. " "Compare how **valid-time**, **record-time**, and " "**belief-state** queries produce different results " "for the same data." ) with gr.Row(): query_type = gr.Dropdown( choices=[ "Valid-Time Only", "Record-Time Only", "Belief State (both axes)", ], value="Belief State (both axes)", label="Query Type", ) subject_input = gr.Textbox( value="", label="Subject Filter (optional)", placeholder="e.g. product:BOND-7Y", ) with gr.Row(): valid_slider = gr.Slider( minimum=1, maximum=7, value=2, step=1, label="Valid-Time Day", ) record_slider = gr.Slider( minimum=1, maximum=7, value=4, step=1, label="Record-Time Day", ) query_btn = gr.Button("Query", variant="primary") query_output = gr.HTML() query_btn.click( fn=_run_query, inputs=[query_type, valid_slider, record_slider, subject_input], outputs=[query_output], ) # ── Tab 3: Diff & Audit ────────────────────────────── with gr.TabItem("Diff & Audit"): gr.Markdown( "Compare what changed between two time points, or " "inspect the full audit trail for a subject." ) gr.Markdown("### Temporal Diff") with gr.Row(): diff_axis = gr.Dropdown( choices=["Valid-Time", "Record-Time"], value="Record-Time", label="Axis", ) diff_t1 = gr.Slider(minimum=1, maximum=7, value=1, step=1, label="From Day") diff_t2 = gr.Slider(minimum=1, maximum=7, value=5, step=1, label="To Day") diff_btn = gr.Button("Compute Diff", variant="primary") diff_output = gr.HTML() diff_btn.click( fn=_run_diff, inputs=[diff_axis, diff_t1, diff_t2], outputs=[diff_output], ) gr.Markdown("### Subject Audit Trail") with gr.Row(): audit_subject = gr.Textbox( value="", label="Subject", placeholder="e.g. product:BOND-7Y", scale=3, ) audit_btn = gr.Button("Audit", variant="primary", scale=1) audit_history = gr.HTML() audit_timeline = gr.HTML() audit_btn.click( fn=_run_audit, inputs=[audit_subject], outputs=[audit_history, audit_timeline], ) return app if __name__ == "__main__": app = build_app() app.launch()