agilab-streamlit-pages

Streamlit page authoring patterns for AGILAB (session_state safety, keys, rerun, UX).

SKILL.md

GitHub

Python Views Skill (AGILAB)

Use this skill when editing:

  • src/agilab/main_page.py
  • src/agilab/pages/*.py
  • src/agilab/apps-pages/*/src/*/*.py
  • custom app_args_form.py views in AGILAB-managed app repos

Retained Python views import agi_web.python_ui, typically as st. Do not import Streamlit or its internals. Tests use agi_web.testing.AppTest; direct views run with python -m agi_web.react_python_host VIEW.py --address 127.0.0.1, with view arguments after --. The existing skill name and agi_env.streamlit_args helper names remain compatibility identifiers.

Apps-Pages Versus Apps Boundary

  • Treat apps-pages as app-agnostic analysis sidecars by default. A page should inspect artifacts, manifests, metrics, or settings from the selected active app; it should not own the domain workflow.
  • Before promoting a native Python UI surface as an apps-page, check whether it generates its own dataset, trains a model, runs an optimizer, owns evidence generation, or encodes project-specific semantics. If yes, it is probably an app project with a companion analysis page, not a generic page bundle.
  • If a self-contained teaching surface must stay under apps-pages for packaging or launch reasons, document it as an explicit opt-in playground or exception. Do not describe it as a generic app-agnostic view.
  • Prefer the split architecture when the surface has both execution and review concerns: the app owns reproducible execution and artifact/evidence export; the page reads those artifacts and renders the analysis.
  • AGILAB app projects do not always need workers. Use a workerless app template for local/UI prototypes, notebook-derived tools, explainers, and reports that can write artifacts from the manager process. Use a worker template only when the app needs AGILAB Install/Execute worker deployment, Dask, worker-specific dependencies, or a distributed task plan.

App Args Forms And Sidebar Controls

  • Custom app_args_form.py views are app UI, not apps-pages. Use them when an app needs persisted execution fields in ORCHESTRATE.
  • For custom app_args_form.py views, do a field-consistency pass before closing the change: compare the app ArgsModel, seed app_settings.toml, guided-mode widgets, preview-helper inputs, and persisted candidate payload. Runtime-relevant args must either be visible in the guided form or explicitly documented as intentionally advanced; do not leave required execution fields available only through generic edit mode. Preview helpers should consume the current widget value for any exposed field, not the stale defaults_model value.
  • For compact app-specific controls, use agi_env.streamlit_args.render_form with an explicit container such as st.sidebar: render_form(defaults_model, container=st.sidebar).
  • Keep the capability generic in agi_env.streamlit_args; do not hard-code a project name or page bundle just because one app wants sidebar controls.
  • If a native Python UI surface owns training, optimization, generated data, or evidence export, make it an app project first. Keep any app-owned standalone native Python UI surface under that app project unless a separate generic analysis page reads only exported artifacts.
  • For an app-owned native Python UI surface that should appear as the app's primary ANALYSIS experience, declare [app_surface] in the app's app_settings.toml with a project-local entrypoint such as pytorch_playground/app_surface.py. Use [pages] restrict_to_view_module = true for app-surface-first projects; an empty or missing pages.view_module may be migrated to the virtual view_app_ui launcher so the app surface is selected and visible in the ANALYSIS sidebar. Do not create a generic apps-pages/view_<app> bridge for this.
  • Treat view_app_ui as an internal route key, not a user-facing label. Sidebar links for app surfaces should use [app_surface].title, route through the app surface renderer, and avoid exposing duplicate view_app_ui bridge links.
  • Keep the standard ANALYSIS view multiselect usable for app-surface projects: view_app_ui may be the default selected sidebar launcher, but users must still be able to add other installed analysis views through the same existing selector instead of a custom "additional views" control.
  • Render an ANALYSIS sidebar launcher from one place per page route. If the main page persists selected views after rendering controls, do not also render the same project label or app-surface link before the main-page body; add a regression assertion that the compact project caption and app-surface link appear exactly once.

Session State Rules (Avoid Common Crashes)

  • Never assign st.session_state["k"] = … after a widget with key="k" was created.

    • Prefer st.session_state.setdefault("k", default) before the widget.
    • Or use widget return values and compute derived state separately.
    • If a rendered widget value must be normalized into persisted settings such as app_settings["cluster"] or app_settings["args"], update only that persisted payload after render. Do not write the normalized value back into the widget key unless you do it before widget creation, behind a rerun, or through a versioned replacement key.
    • When fixing this class of bug, use agi_web.testing.AppTest or a test fake that exercises widget creation, interaction, and reruns; assert that the intended persisted value survives rather than relying on a permissive fake.
  • Do not both pre-seed st.session_state[key] and pass a widget default/value/index for the same keyed widget. Pick one source of initialization:

    • use the widget default and leave st.session_state untouched before creation, or
    • pre-seed st.session_state before creation and omit the widget default. Keep defaults and saved state consistent so widget interaction and reruns preserve the intended value. Shared widget wrappers should drop their default argument when the key already exists in session state.
  • If you need to “reset” a widget value:

    • Use a different key (versioned key pattern), or
    • Gate the reset behind a rerun and only mutate state before widget creation.
  • When native Python UI raises a duplicate-widget ID error, check whether the page or app surface is being rendered twice before only adding widget keys. Add stable keys for repeated controls, but fix duplicate entrypoint execution at the source.

Recommended Pattern

  1. Initialize defaults with setdefault at the top of the page.
  2. Render widgets.
  3. Read values from widgets, compute derived state, store under different keys.

Project-Scoped State Models

  • If a page can switch between projects/apps in one native Python UI session, any persisted widget state that belongs to a specific project must use project-scoped keys.
    • Prefer patterns like f"cluster_pool__{app_name}", not "cluster_pool".
    • Apply the same rule to toggles, text inputs, password/auth fields, scheduler/worker fields, and similar per-project controls.
  • Keep one clear source of truth for persisted settings.
    • File-backed or workspace-backed app_settings should hydrate widget keys on page init and after project switches.
    • Do not let a stale widget key silently override freshly loaded settings just because it already exists in st.session_state.
  • On project change:
    • compute any preservation/override decision before popping tracking keys from st.session_state
    • then clear the old project's widget keys explicitly
    • then rehydrate the new project's widget state from persisted settings
  • Treat app_settings["cluster"], app_settings["args"], or equivalent persisted payloads as the serializable config contract.
    • Widget keys are UI transport, not a second config layer.

Page Bootstrap And Path Drift

  • At page start, resolve the active app and apps root from the current launch context first: explicit CLI args, current source checkout, and packaged runtime location. Only then consult persisted user settings.
  • Do not let stale persisted state override the current launch root:
    • ~/.agilab/.env
    • ~/.local/share/agilab/.agilab-path
    • a previous st.session_state["env"]
    • a previous active_app or APPS_PATH
  • If the launch context says the page is running from source, UI readiness cards such as ORCHESTRATE Manager env must point at the source app path, not $HOME/agi-space.
  • When the resolved app root changes, rebuild or realign the session env object before rendering headers, buttons, or action state. Rendering first and repairing later leaves stale paths visible to users and can make action buttons operate on the wrong project.
  • Add regressions for both cold sessions and warm sessions with stale st.session_state when touching page bootstrap, sidebar project selection, or ORCHESTRATE header state.

Derived Preview Metrics

  • Treat previews as read-only explanations, not as another persisted config source.
  • Compute any preview metric from the same backend helper used by the runtime or exported summary artifact.
  • If a preview depends on existing generated files, label it explicitly as a previous-run value (for example, Last generated ...) rather than implying it reflects the current unsaved inputs.
  • When both are available, show the distinction clearly:
    • current preview from present inputs
    • last generated metric from persisted output
  • Do not write preview-only values back into app_settings.toml unless they are real app args.

Cross-Page UX and KPI Headers

  • Keep sidebar text short and action-oriented. Remove labels that only restate the active project/page or explain obvious scope such as "actions below apply to this project".
  • Keep shared page chrome minimal. Do not add global active-project labels, chips, or badges above page controls; the selected project belongs in the selector, the sidebar, or an explicitly opened context expander.
  • Before reusing or renaming a visible action label, compare its meaning across PROJECT, ORCHESTRATE, WORKFLOW, ANALYSIS, SETTINGS, sidebars, and robot click labels. Avoid same-label/different-contract collisions; scope labels by object and layer, for example Install agi-app for package/catalog install and Deploy scheduler & workers for manager/worker environment deployment.
  • Preserve stable lower-level API names when product copy changes. In ORCHESTRATE, Deploy scheduler & workers can correctly call AGI.install: the API prepares manager and worker runtime environments and reuses an already-ready local manager environment rather than forcing a reinstall.
  • For visible-label cleanup, search every render path before closing the task: page files, main_page.py, page_bootstrap.py, workflow_ui.py, page_project_selector.py, lazy-import wrappers, CSS class names, and tests. Add or update a negative regression assertion for removed text so tests do not encode newly introduced clutter as expected behavior.
  • For visible action renames, update the robot source-of-truth in the same change: tools/agilab_widget_robot.py, tools/agilab_widget_robot_matrix.py, tools/agilab_web_robot.py, tools/ui_robot_canary.py, tools/ui_robot_coverage_contract.py, and the paired robot/action-contract tests. A page-only rename can leave hosted selected-action profiles, above-fold canaries, and coverage contracts looking for the old label even when the UI tests pass.
  • Prefer page headers with a few read-only KPI cards over status banners that say ready, not set, or missing without useful context.
  • Use the same visual semantics across pages:
    • green only for verified positive values
    • amber for incomplete, missing, or no-evidence-yet values
    • neutral for identity or purely informational values
  • Do not color a value green just because it is present when the value means "no run", "not configured", or "no artifact discovered".
  • Use product-facing labels instead of internal implementation terms:
    • Workflow graph instead of DAG shape
    • Stages and dependencies for project workflow instead of graph-only jargon such as nodes and edges
    • Plan, steps, outputs, Creates, and Uses for multi-app workplans before technical DAG, artifact, node, or edge wording
    • hide generated graphs behind an explicit Show graph control when the graph can be too small to read
    • Project name when the widget selects a project
  • Derived header values must be computed locally from existing evidence when possible. For example, count ORCHESTRATE runs from run_*.log files under the app run environment instead of adding another persisted setting.
  • When a page references a machine-readable evidence file such as run_manifest.json, evidence_graph.json, workflow_run_manifest.json, notebook_export_manifest.json, or a dry-run report, do not stop at showing the file path. Render a compact human inspection surface for the contents: status, counts, schema, key rows, validation issues, and artifact links. Keep the raw path visible for audit/replay.
  • When a generic page has no app-specific semantic data, show an honest fallback such as execution stages, output files, or discovered dataframes rather than inventing a domain-specific metric.
  • Keep one navigation surface per action. If a page already exposes compact sidebar launch links, do not duplicate the same action as in-page sidecar cards, repeated Open buttons, or another selector unless the second surface adds a distinct workflow stage.
  • For lightweight page routing, prefer compact Markdown/HTML links with encoded query parameters such as current_page over a selectbox plus Open button when the user only needs to jump to a target. Add a tiny helper to construct and test the encoded URL instead of inlining query-string formatting in the render block.
  • Treat legacy “default view” UI as configuration debt when a project already has a selected view list. Persist the selected list in pages.view_module; remove stale default_view/default_views values only when that behavior is intentionally replaced by the new launcher model.
  • With st.navigation, st.switch_page targets must be the main app file or an exact file under pages/ relative to the native Python UI entrypoint. Do not hard-code stale numeric filenames such as pages/3_WORKFLOW.py; route through central page constants and add a focused test when a wizard button or deep link opens another page.
  • For wizard/deep-link actions that need the destination page to open a drawer, selector, upload area, or run action, set a non-widget intent key before navigation and consume it once on the destination page. Clear the intent before rerunning so the link cannot recursively navigate or keep the app in a loading loop.
  • Update focused page tests when changing visible labels, header cards, or sidebar structure. Grep old wording before closing the task so stale copy does not survive in tests, docs, or screenshots.
  • When replacing a single-item action with a batch action, keep button labels compact unless the extra word is needed to prevent misuse. Put selection semantics in the selector label/help text and assert the exact end-user button labels in focused page tests so stale wording such as Update selected or Remove selected cannot survive after a product copy decision.

First-Proof Onboarding UX

  • Treat the first-proof panel as a new-user wizard, not as an expert shortcut list. Every visible action must state whether it runs the built-in demo, imports AGILAB's included notebook, or uploads the user's own notebook.
  • Do not hide packaged sample assets behind vague labels such as example notebook when the user cannot know where that file is. Prefer explicit copy such as Create from built-in notebook, say that no file needs to be found or uploaded, and show the project that will be created.
  • Keep first-proof alternatives simple: the built-in demo lane should expose install, run, and analysis actions; the notebook lane should expose the included sample. Keep local notebook upload in PROJECT Create instead of adding another direct uploader to the ABOUT wizard.
  • When changing first-proof labels, update ABOUT tests, PROJECT notebook-import tests, newcomer docs, and stale-wording greps in the same change.

App-Specific Page Defaults

  • Prefer app-declared defaults in app_settings.toml over page-level hardcoded paths.
  • For apps-pages, use pages.<page_name> for app-specific defaults that should be portable across apps and machines.
  • Remember that versioned app seeds and workspace settings are different:
    • src/.../app_settings.toml is only the seed.
    • mutable user/HF settings can live under ~/.agilab/apps/<app>/app_settings.toml.
    • if a bug is caused by a stale default already persisted in the workspace, changing only the seed will not fix existing deployments.
  • When changing app analysis defaults, add a narrow migration for the stale workspace value when needed.
    • Scope migrations to the affected app and legacy value.
    • Preserve custom user defaults when the legacy value is absent.
    • Write the migrated config before widgets/options are built.
  • For app/page visibility, prefer explicit exclusions over global restrictions when the app should still see generic pages.
    • Example: for flight_project, exclude view_maps_network while keeping generic views such as view_maps, view_maps_3d, and view_barycentric available.
    • Avoid setting a broad restrict_to_view_module unless hiding every undeclared generic view is the intended product behavior.
  • For view_maps_network, supported defaults now include:
    • dataset_base_choice
    • dataset_custom_base
    • dataset_subpath
    • default_traj_globs
    • default_allocation_globs
    • default_baseline_globs
    • cloudmap_sat_path
    • cloudmap_ivdl_path
  • Keep persisted view_maps_network state for user choices, but put repo/app conventions under pages.view_maps_network.

DAG And Worker-Type UI

  • For generic ORCHESTRATE/WORKFLOW page behavior that depends on whether an app is DAG-based, use AgiEnv.base_worker_cls first. AgiEnv already populates this from worker source inspection, so page code should not import app worker classes just to decide which controls to show.
  • Treat DagWorker, known DAG-derived workers such as Sb3TrainerWorker, and custom *Dag*Worker base names as DAG-capable for UI decisions.
  • Keep name-token fallbacks narrow and explicit for planning-only or synthetic apps that intentionally have no DAG worker base yet, such as multi-app DAG draft/demo projects.
  • Reuse one helper for DAG detection across sidebars, execute controls, and distribution/workplan rendering. Do not leave page-local string checks such as endswith("dag-worker"); they drift from the AgiEnv contract and silently fail for real base names like DagWorker.
  • DAG-only planning projects should expose run/workflow actions, not dataframe load/export/delete controls, unless the app has an explicit dataframe artifact contract.

Rerun API

  • Do not use st.experimental_rerun(); use st.rerun().

Diagnostics Rendering

  • Render long diagnostics and tracebacks as code blocks, not message-box text:
    • st.error("Short actionable summary.")
    • st.caption("Full diagnostic")
    • st.code(diagnostic_text, language="text")
  • Do not embed Markdown code fences inside st.error() or st.code().
  • Do not pass traceback.format_exc() directly to st.error(); native Python UI message boxes collapse readability and can flatten newlines.
  • If a diagnostic arrives as a single long line, format a display-only copy with line breaks or wrapping before passing it to st.code; keep the original exception/message unchanged for logs and assertions that depend on exact text.
  • Add a focused helper test for display formatting and keep the repository scan guard in test/test_streamlit_diagnostic_rendering.py green when touching diagnostic rendering paths.

Action Results and Runtime Logs

  • Do not classify a page action as failed only because stderr is non-empty. AGILAB runtime helpers, package managers, and subprocess wrappers may write normal progress or warnings to stderr.
  • Treat action success/failure as a typed result contract first:
    • subprocess return code or raised exception
    • ActionResult.status
    • explicit fatal markers in logs such as tracebacks, non-zero exit status, missing imports, or worker/build failure phrases
  • Keep log classifiers narrow. Avoid broad predicates such as "failed" in line unless the surrounding phrase is part of a known fatal contract; benign warnings can contain words like failed.
  • Apply the same action-result semantics to sibling workflow actions. If INSTALL and RUN already use typed results or fatal-log heuristics, do not leave CHECK, DISTRIBUTE, LOAD, EXPORT, or service actions on older stderr == failure rules.
  • Add a regression with a realistic noisy stderr log for any action whose runtime can emit progress on stderr. The regression should prove benign stderr stays successful and a concrete fatal marker still fails.

WORKFLOW Assistant UX

  • For WORKFLOW cockpit or preflight changes, keep the decision model independent of the UI and app-agnostic. It should answer the operator questions before any run starts: can this workflow run now, which stages can reuse current outputs, which stale stages should rerun, which missing inputs or model-artifact issues block execution, and what the fastest safe plan is.
  • Treat data consistency and model compatibility as first-class preflight evidence. Missing inputs and dependency errors are blockers; reusable outputs are cached/skipped; inputs newer than outputs trigger a stale rerun; model version mismatches are blockers; missing model sidecar metadata or feature schema are warnings.
  • Render cockpit/preflight output with product-facing labels such as Run readiness, Autopilot, Recommended actions, Expected outputs, and Fastest safe plan. Avoid exposing internal DAG jargon when the user is deciding whether to run, reuse, or repair the workflow.
  • Prefer pure helper regressions for cockpit preflight logic: missing input blocker, cached output reuse, stale output rerun, dependency error, model version mismatch, and missing feature-schema warning. Add agi_web.testing.AppTest coverage only when widget state, keys, or visible page wiring changes.
  • Default generated dataframe work to a safe-action mode: the model returns a versioned JSON action contract, AGILAB validates it, and the UI displays the deterministic pandas code derived from that contract.
  • Keep raw Python generation as an explicit advanced choice with clear wording. Do not imply a container or VM is required for the normal safe-action path.
  • Persist the generation mode and action contract beside the saved stage so a reopened lab_stages.toml explains whether code came from safe actions or raw Python.
  • When generation fails validation, keep the existing stage code intact and show the contract error; do not silently replace it with partial or arbitrary code.

Long-Running Action Timers

  • For long async actions such as ORCHESTRATE install/run/serve, render a live elapsed-time placeholder before awaiting the subprocess or background task.
  • Prefer asyncio.create_task(...), yield once with await asyncio.sleep(0), then poll at a short fixed interval and update the placeholder until the task is done. Do not depend only on log callbacks; quiet processes still need a visible timer.
  • Keep the final duration visible after completion and record it in action history when that history is user-facing evidence.
  • Store timer values under non-widget session-state keys such as last_run_elapsed_seconds and last_run_elapsed_label; never mutate a key already owned by a rendered widget.
  • Add focused tests for both the formatting helper and the action path that records the elapsed label, without requiring a real long-running process.

Key Hygiene

  • Every widget must have a stable, unique key.
  • Prefer namespaced keys: f\"{page_id}:df_files\", not "df_files".