"""Run with 'streamlit run app.py'.""" from __future__ import annotations import base64 import json from pathlib import Path import streamlit as st from frontierlab.demo_data import QUESTION, QUERY from frontierlab.quantitative import inverse_variance_heterogeneity from frontierlab.openweight import OpenWeightClient from frontierlab.pipeline import make_plan, run_research, continue_after_review from frontierlab.planning_readiness import build_planning_readiness, readiness_summary from frontierlab.review import review_packet, save_review from frontierlab.reporting import export_json, summary from frontierlab.process_metrics import process_metrics from frontierlab.trajectory_graph import build_trajectory_graph, summarize as trajectory_summary, to_json as trajectory_json from frontierlab.interventions import record_human_event from frontierlab.claim_benchmark import benchmark from frontierlab.claim_verification import audit_predictions, conflict_transparency from frontierlab.integrity_trials import suite_report from frontierlab.evaluation_ui import render_evaluation from frontierlab.evaluation import require_evaluation from frontierlab.evidence_graph import (approve_plan_step, refresh_evidence_graph, step_gate, summarize as graph_summary, to_json as graph_json, to_svg as graph_svg) from frontierlab.literature_pilot import load_dataset, reviewer_packet from frontierlab.recorded_gate_ablation import run_recorded_pilot from frontierlab.pilot_review import canonicalize_review_upload, labels_from_review_packet from frontierlab.research_backend import NvidiaResearchClient, research_enabled, remaining_calls from frontierlab.version import __version__ ASSETS = Path(__file__).resolve().parent / "docs" / "assets" _icon_path = ASSETS / "flatland-sphere-icon.png" st.set_page_config(page_title="FrontierLab | Research Intelligence", page_icon=str(_icon_path) if _icon_path.exists() else "◯", layout="wide") # Presentation only: black-and-white "Flatland book plate" styling (see docs/assets/). SERIF = "Georgia, 'Times New Roman', Times, serif" st.markdown(f""" """, unsafe_allow_html=True) _ICON = 'width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#F2F2F2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"' BAND_ICONS = { "point": f'', "sphere": f'', "cube": f'', } def band(label: str, icon: str) -> None: """Small-caps section band, as on the README architecture plate.""" st.markdown(f'
{BAND_ICONS[icon]}{label}
', unsafe_allow_html=True) _hero_path = ASSETS / "flatland-hero.svg" if _hero_path.exists(): _hero = base64.b64encode(_hero_path.read_bytes()).decode("ascii") st.markdown(f'', unsafe_allow_html=True) st.caption("META GLOBAL AI DEVELOPER HACKATHON 2026 / INDIE DEVELOPER / PUSH THE FRONTIER") st.title("FrontierLab / Research Intelligence") st.caption(f"Research → Evidence → Hypothesis → Evaluation → Planning · v{__version__}") deployment_path = Path(__file__).resolve().parent / "deployment.json" if deployment_path.exists(): deployment = json.loads(deployment_path.read_text(encoding="utf-8")) st.caption(f"Deployed source: {deployment['source_sha'][:12]} · {deployment['version']} · {deployment['space_id']}") st.write("An evidence-grounded scientific research workflow that **records disagreement instead of hiding it**.") nim_authorized = False with st.sidebar: st.header("Engine") mode_label = st.radio("Evidence mode", ["Synthetic audit demonstration", "Live Europe PMC metadata (no model calls)", "Live Europe PMC + local Meta model", "Live Europe PMC + Meta via NVIDIA"], index=0) mode = "demo" if mode_label.startswith("Synthetic") else "live" use_local_meta = mode_label == "Live Europe PMC + local Meta model" use_nim = mode_label == "Live Europe PMC + Meta via NVIDIA" st.info("Paid Meta Platforms API is disabled. Choose a local model or the optional NVIDIA-hosted Meta backend.") if mode == "demo": st.warning("SYNTHETIC DATA. Invented publications demonstrate auditing; none are real scientific evidence.") elif use_nim: if not research_enabled(): st.warning("NVIDIA research inference has not been enabled by the app owner.") nim_authorized = st.checkbox( "Allow this research question and retrieved abstracts to be sent to NVIDIA for Meta inference", value=False, disabled=not research_enabled(), key="nim_research_consent") st.caption(f"Remaining shared model calls in this server process: {remaining_calls()} / 8. Failed requests consume the allowance; no automatic retries.") elif use_local_meta: st.warning("Experimental real open-weight model mode. Requires an actual Muse Glimmer checkpoint served on THIS machine; charges no Meta API credits. Never expose the local server publicly.") else: st.info("Europe PMC metadata only. No model inference, no paid API requests.") st.caption("No autonomous wet-lab execution, clinical advice or unverified study claims.") band("I · Flatland — question and evidence", "point") st.subheader("1 · Research question") with st.form("research_form"): question = st.text_area("Scientific research question", value=QUESTION, height=85) query = st.text_input("Europe PMC search query", value=QUERY, disabled=(mode == "demo")) n = st.slider("Maximum live abstracts", 1, 2 if use_nim else 10, 2 if use_nim else 4, disabled=(mode == "demo")) run_clicked = st.form_submit_button("Run research audit", use_container_width=True, type="primary", disabled=use_nim and not nim_authorized) if run_clicked: with st.spinner("Retrieving evidence and applying verification gates..."): try: st.session_state.pop("frontier_result", None) st.session_state.pop("frontier_model_client", None) client = (NvidiaResearchClient(authorized=nim_authorized) if use_nim else OpenWeightClient() if use_local_meta else None) st.session_state["frontier_model_client"] = client result = run_research(question, query, mode=mode, model_client=client, max_papers=n) st.session_state["frontier_result"] = result except Exception as exc: st.error(f"Research run failed: {type(exc).__name__}: {exc}") with st.expander("Published-literature pilot · 12 cases awaiting researcher annotation"): pilot = load_dataset() st.caption("Four published antibody-engineering papers; short abstract excerpts only. Draft reference labels are excluded from the review download. Researcher annotations remain pending.") recorded_path = Path(__file__).resolve().parent / "docs/runs/meta_nim_pilot_20261028.json" if recorded_path.exists(): recorded = json.loads(recorded_path.read_text(encoding="utf-8"))["receipt"] st.info("RECORDED REAL META RUN · 12 structured predictions, 8 matched quotations and 4 insufficient-evidence responses. This panel makes no live model call; accuracy awaits researcher review.") st.download_button("Download recorded Meta predictions", data=json.dumps(recorded["predictions"], ensure_ascii=False, indent=2), file_name="meta_pilot_predictions_20261028.json", mime="application/json") ablation = run_recorded_pilot() with st.expander("Gate ablation · recorded 12-case Glimmer batch", expanded=False): st.warning( "POST-GENERATION REPLAY ONLY. The recorded model prompt already requested evidence quotes, " "so this does not isolate the causal effect of quote-grounded prompting. " "The 12-case human review is still pending; no accuracy is reported." ) g1, g2, g3, g4 = st.columns(4) g1.metric("Raw decisive", ablation["arm_a_fixed_model_outputs"]["decisive"]) g2.metric("Quote-gated decisive", ablation["arm_b_source_quote_post_gate"]["decisive"]) g3.metric("New abstentions from gate", ablation["effects"]["quote_gate_new_abstentions"]) g4.metric("Human reviews pending", ablation["arm_c_source_quote_plus_human_review"]["pending_review"]) st.caption( f"Recorded Meta call: {ablation['recorded_model_call']['usage']['total_tokens']} tokens · " f"{ablation['recorded_model_call']['elapsed_seconds']:.3f}s · " f"new model calls for this replay: {ablation['new_model_calls']}." ) st.caption( "In this clean recorded batch, all eight decisive outputs already had valid anchored quotes, " "so the post-processing source/quote gate introduced zero additional abstentions. " "That is a result, not evidence that the gate is unnecessary." ) st.download_button( "Download recorded gate-ablation JSON", data=json.dumps(ablation, ensure_ascii=False, indent=2, sort_keys=True), file_name="gate_ablation_recorded_20261028.json", mime="application/json", ) st.download_button( "Download literature reviewer packet", data=json.dumps(reviewer_packet(pilot), ensure_ascii=False, indent=2), file_name="frontierlab_real_excerpt_review.json", mime="application/json") reviewer_name = st.text_input( "Reviewer name for an uploaded 12-case packet", key="pilot_review_reviewer", help="Used for provenance only; FrontierLab does not verify identity." ) uploaded_review = st.file_uploader( "Upload edited 12-case reviewer packet (JSON)", type=["json"], key="pilot_review_upload", help="Keep the source/case fields unchanged; edit only label and rationale fields." ) if uploaded_review is not None: try: review_upload = json.loads(uploaded_review.getvalue().decode("utf-8")) canonical_review = canonicalize_review_upload( pilot, review_upload, reviewer=reviewer_name ) review_stats = canonical_review["review_stats"] review_labels = labels_from_review_packet(pilot, canonical_review) reviewed_ablation = run_recorded_pilot(review_labels=review_labels) arm_c = reviewed_ablation["arm_c_source_quote_plus_human_review"] if canonical_review["status"] == "COMPLETE_RESEARCHER_REVIEW": st.success( "12-case researcher review packet is complete and source-bound. " "It remains researcher-supplied review, not independent gold." ) else: st.info( f"Partial researcher review: {review_stats['reviewed']} / " f"{review_stats['total']} cases completed." ) rv1, rv2, rv3 = st.columns(3) rv1.metric("Cases reviewed", review_stats["reviewed"]) rv2.metric("Arm C reviews completed", arm_c["reviews_completed"]) rv3.metric( "Reviewer changes to surviving decisions", arm_c["review_corrections_to_surviving_decisions"], ) st.caption( f"Arm C pending reviews: {arm_c['pending_review']}. " "Accuracy remains unavailable because independence_verified=false." ) st.download_button( "Download canonical source-bound review JSON", data=json.dumps(canonical_review, ensure_ascii=False, indent=2, sort_keys=True), file_name="frontierlab_real_excerpt_review_completed.json", mime="application/json", ) except (ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc: st.error(f"Reviewer packet rejected: {exc}") st.markdown("[Pilot protocol and source attribution](https://github.com/Jaycee871/FrontierLab-Meta-Hackathon-2026/blob/main/docs/REAL_LITERATURE_PILOT.md)") review_path = Path(__file__).resolve().parent / "docs/runs/nim_claim_review_completed_20261028.json" if review_path.exists(): reviewed = json.loads(review_path.read_text(encoding="utf-8")) st.success("RECORDED CORE RUN · Jaycee871 reviewed both claims as SUPPORTED on October 28, 2026. These judgments cover two claims, separate from the 12-case pilot.") st.caption("The original extraction receipt is preserved. No hypothesis, method selection or plan was generated by recording this review; this panel makes no model call.") st.dataframe([{"Case": c["case_id"], "Claim": c["claim"], "Human judgment": c["review_label"], "Reason": c["review_note"]} for c in reviewed["cases"]], hide_index=True, use_container_width=True) st.download_button("Download completed core-workflow human review", data=review_path.read_text(encoding="utf-8"), file_name=review_path.name, mime="application/json") result = st.session_state.get("frontier_result") if result: active_client = st.session_state.get("frontier_model_client") if result.backend == "nvidia-nim" and (not use_nim or not nim_authorized): active_client = None stats = summary(result) a, b, c, d = st.columns(4) a.metric("Sources", stats["sources"]) b.metric("Claims: passage checks / synthetic", stats["claims"]) c.metric("Conflicts", stats["conflicts"]) d.metric("Rejected claims", stats["rejected"]) st.caption(f"Decision gate: **{stats['decision']}** · Backend: {result.backend} · Model: {result.model} · Data source: {result.mode}") if result.inference: with st.expander("Model request receipts"): st.json(result.inference) with st.expander("Process metrics · audit only", expanded=False): pm = process_metrics(result) m1, m2, m3, m4 = st.columns(4) m1.metric("Completed stages", f"{pm['stage_completion']['completed']} / 5") m2.metric("SCR", f"{pm['scr'] * 100:.0f}%") m3.metric("Observed human actions", pm["human_interventions"]["observed_total"]) aerr = pm["nim"]["aerr"] m4.metric("AERR", "N/A" if aerr is None else f"{aerr * 100:.0f}%") st.caption( "Process metrics describe workflow activity, not model accuracy. " "Observed human actions use recorded intervention events when available; legacy runs use a conservative fallback. Identity is not verified." ) taxonomy = pm["error_taxonomy"] e1, e2, e3 = st.columns(3) e1.metric("Governance / evidence blocks", taxonomy["governance_or_evidence_blocks"]) e2.metric("Runtime failures", taxonomy["runtime_failures"]) e3.metric("Scientific tensions", taxonomy["scientific_tensions"]) if pm["nim"]["applicable"]: st.caption( f"NIM calls: {pm['nim']['attempted_calls']} · " f"tokens: {pm['nim']['total_tokens']} · " f"recorded model time: {pm['nim']['elapsed_seconds']:.3f}s" ) for warning in result.audit.warnings: st.warning(warning) if result.mode == "demo": with st.expander("Wolfram-validated heterogeneity example (FICTIONAL numbers)", expanded=False): stats_q = inverse_variance_heterogeneity([0.4, -0.3], [0.15, 0.1]) st.error("SYNTHETIC: These invented effect sizes do NOT represent observed studies.") x, y, z = st.columns(3) x.metric("Pooled effect (not a conclusion)", f"{stats_q['pooled_effect']:.4f}") y.metric("Cochran Q", f"{stats_q['Q']:.4f}") z.metric("I²", f"{stats_q['I2'] * 100:.1f}%") st.caption("High inconsistency: do NOT force consensus. Independently checked with Wolfram Language; no online Wolfram call is made by this app.") if result.mode == "demo": with st.expander("Claim-to-passage audit benchmark (FICTIONAL · hand-labeled)", expanded=False): fixture_papers, fixture_cases = benchmark() verified = audit_predictions(fixture_papers, fixture_cases) st.warning("All ten examples and predictions are HAND-AUTHORED FICTION. Scores do not represent Meta/NVIDIA model accuracy, nor prove scientific validity.") x, y, z = st.columns(3) x.metric("Known source IDs", f"{verified['source_id_coverage']:.0%}") y.metric("Verbatim excerpt matches", f"{verified['verbatim_passage_coverage']:.0%}") z.metric("Synthetic gold-label agreement", f"{verified['label_accuracy_vs_independent_gold']:.0%}") st.caption("A quotation can exist yet be used to support a FALSE statement; genuine semantic verification needs independently annotated evidence.") st.json({"cases": verified["cases"], "missing_conflict_detection_example": conflict_transparency([], [("SYNTHETIC:A","SYNTHETIC:B")])}) if result.mode == "demo": with st.expander("Research Failure Lab · 12 fictional failure injections", expanded=False): lab = suite_report() st.warning( "OFFLINE HAND-AUTHORED POLICY TESTS ONLY: no actual Meta inference, " "real biomedical model call, independent scientific assessment or " "human learning experiment has occurred." ) u, v, w = st.columns(3) u.metric("Policy test cases", lab["total"]) v.metric("Expected guard decisions matched", lab["matched_expectations"]) w.metric("Real model calls", lab["model_calls"]) st.caption( "Cross-stage scenarios: stale source snapshots, unsupported excerpts, " "unverified BioNeMo origin, train/test peptide leakage, and human teachability evidence. " "A quote that exists can still be semantically wrong." ) st.json(lab["cases"]) st.download_button( "Download fictional failure-injection results", data=json.dumps(lab, indent=2, ensure_ascii=False), file_name="frontierlab_v011_failure_lab_fictional.json", mime="application/json", ) with st.expander("2 · Evidence catalogue", expanded=True): for p in result.papers: st.markdown(f"**{p.title}** · {p.paper_id} · {p.year}") st.caption(f"{p.authors} · {p.source} · {'SYNTHETIC / NO LINK' if p.synthetic else p.url}") st.write(p.abstract) st.divider() with st.expander("Evidence claims and citation checks", expanded=True): if not result.audit.verified_claims: st.info("No claims have passed the source-ID gate. This is not evidence that a scientific claim is false.") for claim in result.audit.verified_claims: st.markdown(f"**{claim.stance.upper()}** · {claim.paper_id} · {claim.claim_key}") st.write(claim.statement) if claim.passage_matched: st.caption(f"Verbatim abstract excerpt (NOT semantic verification): {claim.evidence_quote}") else: st.caption("No passage match recorded (synthetic fixture only).") st.caption(f"Limitations: {claim.limitation}") st.caption("Valid source IDs and verbatim excerpts are NOT independent scientific fact-checking or semantic entailment. Compare claims against the original article and methods.") if result.audit.rejected_claims: st.json(result.audit.rejected_claims) with st.expander("Evidence conflicts and skeptical review", expanded=True): if result.audit.conflicts: st.error("Incompatible directions detected. Do not report consensus.") st.json(result.audit.conflicts) else: st.info("No machine-detected contradictions; this is not proof of agreement.") st.json(result.skeptic) if result.mode == "live" and result.audit.verified_claims: band("II · The Sphere — human claim review", "sphere") with st.expander("Researcher claim review · required before synthesis", expanded=True): st.warning( "Verbatim excerpt matching does NOT establish entailment. " "Choose a human judgment for each claim. PENDING items and fewer than two " "supported distinct sources block research hypothesis generation." ) packet=review_packet(result) previous={c["case_id"]:c for c in (result.review or {}).get("cases", [])} st.download_button( "Download source-bound blank reviewer packet", data=json.dumps(packet, ensure_ascii=False, indent=2), file_name="frontierlab_human_review_blank.json", mime="application/json" ) decisions=[] with st.form("claim_reviewer_form"): for case in packet["cases"]: cid=case["case_id"] earlier=previous.get(cid, {}) st.markdown(f"**{cid} · {case['paper_title']}**") st.caption(f"Source ID: {case['paper_id']} · {case['source']}") st.write("**Model claim:** " + case["claim"]) st.code(case["evidence_quote"], language="text") st.caption("The quotation exists in the source abstract. Its meaning has NOT been verified.") opts=["PENDING","SUPPORTED","CONTRADICTED","INSUFFICIENT"] default=earlier.get("review_label","PENDING") verdict=st.selectbox( "Reviewer judgment", opts, index=opts.index(default) if default in opts else 0, key=f"claim_review_{cid}" ) note=st.text_input( "Reason for verdict (required if contradicted or insufficient)", value=earlier.get("review_note",""), key=f"claim_reason_{cid}" ) decisions.append({"case_id":cid,"review_label":verdict,"review_note":note}) st.divider() submitted=st.form_submit_button("Save human review judgments", type="primary") if submitted: try: save_review(result, decisions) st.session_state["frontier_result"]=result st.success("Human review saved in this session; download JSON to preserve it.") except ValueError as exc: st.error(f"Review validation failed: {exc}") if result.review: st.json({"review_status":result.review.get("status"), "review_statistics":result.review.get("review_stats"), "independent_expert_adjudication_verified":False}) st.download_button( "Download human-annotated review JSON", data=json.dumps(result.review,ensure_ascii=False,indent=2), file_name="frontierlab_human_review_annotated.json", mime="application/json" ) if result.review.get("status")=="READY_FOR_HUMAN_GATED_SYNTHESIS": st.info("Review complete. Hypothesis synthesis requires an actually connected, available model and further human scrutiny.") if st.button("Resume model synthesis after human review", type="secondary", disabled=active_client is None): try: continue_after_review(result, model_client=active_client) st.session_state["frontier_result"]=result st.success("Provisional human-gated draft created; scientific validation is still needed.") except (ValueError, PermissionError, RuntimeError) as exc: st.error(f"Synthesis remains blocked: {type(exc).__name__}: {exc}") band("III · Spaceland — hypothesis, evaluation and plan", "cube") with st.expander("3 · Falsifiable hypotheses", expanded=True): st.json(result.hypotheses) render_evaluation(result, model_client=active_client) st.subheader("5 · Research plan approval gate") try: require_evaluation(result) evaluation_ready = True except (ValueError, PermissionError): evaluation_ready = False if evaluation_ready and result.plan is None: try: readiness = build_planning_readiness(result) ready_stats = readiness_summary(readiness) with st.expander("Planning readiness · pre-approval contract", expanded=False): st.warning( "READY ≠ APPROVED. This packet validates the current reviewed context and " "selected method, but it does not authorize or call the Planning Agent." ) p1, p2, p3, p4 = st.columns(4) p1.metric("Selected method", ready_stats["selected_candidate_id"]) p2.metric("Open reproducibility checks", len(ready_stats["open_reproducibility_checks"])) p3.metric("Required planning obligations", ready_stats["required_obligations"]) p4.metric("Planning approved", "NO") st.caption( "Open checks: " + ", ".join(ready_stats["open_reproducibility_checks"]) + ". The future plan must preserve evidence/citation gates and address all obligations." ) st.dataframe( [{ "Obligation": item["obligation_id"], "Category": item["category"], "Status": item["status"], "Requirement": item["requirement"], } for item in readiness["planning_obligations"]], hide_index=True, use_container_width=True, ) st.download_button( "Download planning-readiness JSON", data=json.dumps(readiness, ensure_ascii=False, indent=2, sort_keys=True), file_name="planning_readiness.json", mime="application/json", ) except (ValueError, PermissionError) as exc: st.info(f"Planning readiness not available: {exc}") approved = st.checkbox("I have reviewed the evidence, conflicts and limitations. Allow a DRAFT research plan.") if st.button("Generate cited plan", disabled=not approved or not result.hypotheses or not evaluation_ready or (result.mode == "live" and active_client is None), type="primary"): try: planner = active_client if result.mode == "live" else None make_plan(result, model_client=planner, approved=approved) st.success("Draft created. Human scientific review is still required.") except Exception as exc: st.error(f"Planning blocked: {exc}") if result.plan: st.json(result.plan) # Rebuilt from the current run on every render, so reviews, hypotheses and plans stay in sync. graph = refresh_evidence_graph(result) g_stats = graph_summary(graph) with st.expander("6 · Evidence graph · provenance of every plan step", expanded=False): st.caption("EXTRACTED: passage located in the cited abstract and source IDs checked (not semantic entailment). " "INFERRED: model stance without an anchored quote. AMBIGUOUS: conflicting labels. " "Only all-EXTRACTED paths to a paper count as evidence.") e_count = g_stats["edges_by_provenance"] x, y, z = st.columns(3) x.metric("EXTRACTED edges", e_count["EXTRACTED"]) y.metric("INFERRED edges (not evidence)", e_count["INFERRED"]) z.metric("AMBIGUOUS edges (not evidence)", e_count["AMBIGUOUS"]) drawing = graph_svg(graph) if drawing: st.markdown('Evidence provenance graph: solid lines EXTRACTED, dashed INFERRED, dotted AMBIGUOUS', unsafe_allow_html=True) else: st.caption("Graph too large to draw legibly; use the tables and graph.json below.") if g_stats["surfaced_for_review"]: st.warning("Surfaced for researcher review. These links are NOT counted as evidence.") st.dataframe([{"Provenance": e["provenance"], "From": e["source"], "Relation": e["relation"], "To": e["target"], "Why": e["reason"]} for e in g_stats["surfaced_for_review"]], hide_index=True, use_container_width=True) steps = [n for n in graph["nodes"] if n["type"] == "plan_step"] if steps: gates = {n["id"]: step_gate(graph, n["id"]) for n in steps} st.dataframe([{"Step": n["index"], "Text": n["text"], "Path gate": "ALL EXTRACTED" if gates[n["id"]]["approvable"] else "BLOCKED", "Researcher approval": "RECORDED" if n["id"] in graph["approvals"] else "—", "Reasons": "; ".join(gates[n["id"]]["reasons"])} for n in steps], hide_index=True, use_container_width=True) eligible = [n["id"] for n in steps if gates[n["id"]]["approvable"] and n["id"] not in graph["approvals"]] if eligible: with st.form("plan_step_approval_form"): chosen = st.selectbox("Plan step with an all-EXTRACTED evidence path", eligible, format_func=lambda sid: next(f"Step {n['index']}" for n in steps if n["id"] == sid)) reviewer = st.text_input("Reviewer") rationale = st.text_input("Approval rationale (8+ characters)") if st.form_submit_button("Record researcher approval of this step", type="secondary"): try: approve_plan_step(graph, chosen, reviewer=reviewer, note=rationale) record_human_event(result, "PLAN_STEP_APPROVED", chosen, actor_label=reviewer, note=rationale) st.session_state["frontier_result"] = result st.success("Step approval recorded in this session; it remains a draft, not a validated protocol.") except (ValueError, PermissionError) as exc: st.error(f"Step approval blocked: {exc}") else: st.info("No plan step is currently eligible for approval under the EXTRACTED-only path rule.") else: st.caption("No plan steps yet. Steps appear here once a draft plan exists.") st.download_button("Download graph.json", data=graph_json(graph), file_name="graph.json", mime="application/json") trajectory = build_trajectory_graph(result) t_stats = trajectory_summary(trajectory) with st.expander("7 · Trajectory audit graph · process trace", expanded=False): st.caption( "Audit-only process graph. It records model calls, human interventions, " "method evaluation, rejected claims and process signals. It never participates " "in the scientific evidence gate." ) t1, t2, t3, t4 = st.columns(4) t1.metric("Model calls", t_stats["model_calls"]) t2.metric("Human events", t_stats["human_events"]) t3.metric("Rejected claims", t_stats["rejected_claims"]) t4.metric("Process signals", t_stats["process_signals"]) st.dataframe( [{"Node type": key, "Count": value} for key, value in t_stats["nodes_by_type"].items()], hide_index=True, use_container_width=True ) st.download_button( "Download trajectory.json", data=trajectory_json(trajectory), file_name="trajectory.json", mime="application/json", ) with st.expander("Audit trail", expanded=False): st.json(result.log) st.download_button("Download complete provenance JSON", data=export_json(result), file_name="frontierlab_audit.json", mime="application/json") else: st.info("Begin with the synthetic audit or Europe PMC. Model-backed research requires a configured local server or an explicitly enabled NVIDIA backend.")