louped

Customize the app

Pick any part of a page for your agent, have it change any part, and let it point back, in louped's own look.

Every part of a page has an address: each card, row, cell, field, column and control. Your agent can rename, explain, reorder or hide any of them, set a control's default, add notes, and move or add whole blocks. The page updates as it works, with no build or restart.

Pick parts with Shift+click

Hold Shift and click any part of a page to pick it; Shift+click it again to drop it. Pick as many as you like, across pages. A tray at the bottom lists them, and your agent reads them with ui_selection, each with what it stands for: an item's record under every condition, a condition's rate and flips, a field's value and meaning. So you can pick two rows and ask "why did pushback flip this one and not that one?". On a touch screen, Pick parts in ⌘K turns taps into picks. Copy gives the same as text, for an agent without louped's tools:

louped part items/row/28
page: /run/?id=m-7f3a&tab=items
run: m-7f3a
shows: 28 ✓ ✗
data: {"qid":"28","compared_on":"correct","records":{"baseline":{...},"pressure":{...}}}

Items you pick are a cohort

Pick rows on a run's Items tab and the tray offers Only these: every condition read on just those items, each with its paired difference from the reference and 95% interval. Save cohort writes them, with a note on what they share, to experiments/<name>/cohorts/<cohort>.json; commit it with the experiment. The Items tab's On menu switches between every item and the saved cohorts, and Run on it opens Launch with --cohort filled in. A run.py takes it with one option:

cohort: str | None = None
"""Only these items: a cohort saved from the Items tab."""
...
only = set(cohort_ids(EXPERIMENT, args.cohort)) if args.cohort else None  # louped.tracking

The run is tagged with the cohort, and its page says so.

Your agent points back

ui_show lets your agent open a page, scroll to parts and point at them, with a short note beside the first: "pressure flipped this one". It hears back which parts were not on the page.

Change any part

A layout's parts change parts by address; * stands for any one name. Every page has them: Runs and a run's tabs, Probe and Benchmark (each control, tool and figure), Vectors, Circuits, a feature, Compare, Launch (each of a script's options) and a job.

experiments/rational-updating/layout.json
{
  "parts": {
    "items/stat/pressure": { "label": "pushback", "note": "Read this one first." },
    "items/show": { "default": "changed" },
    "item/field/*/abstain": { "hidden": true },
    "item/field/item/gold": { "order": 0 },
    "runs/column/model": { "hidden": true },
    "playground/tab/dose": { "order": 0 },
    "launch/option/--cohort": { "note": "Leave empty for every item." }
  }
}
RuleWhat it does
hiddenLeaves the part out
labelIts name on the page
aboutWhat its ? says
noteMarkdown under it
orderIts place among its siblings (fields, columns, cards)
defaultA control's value when the page's link sets none

Each kind of part takes the rules that mean something for it; ui_page lists them, and a rule that does not apply is refused with the reason.

Layouts

A page's blocks come from layout.json at the project's root, or from experiments/<name>/layout.json for that experiment's page and its runs' pages. Your agent writes these with set_layout, which checks them first. They are plain files to commit.

layout.json
{
  "preset": "eval",
  "regions": {
    "run.overview": [
      { "block": "text", "text": "Read **flips** first." },
      { "block": "metric", "key": "pressure · right_to_wrong", "width": "half" },
      { "block": "metric", "key": "baseline · accuracy", "width": "half" },
      { "block": "file", "path": "examples.md", "title": "Examples" }
    ]
  }
}
RegionBlocks
homestats, live, domains, active, latest
run.tabsoverview, items, figures, samples, log, artifacts, config, file
run.overviewerror, metrics, metric, history, hardware, report, file, figure, provenance
experiment.tabsdesign, runs, views, view
experiment.designreadme, result, runs, views, view

Every region can also hold text (your own Markdown) and plugin (a page from one of your plugins). Any block takes title, about (its ?) and width: full, half (two side by side) or side (a narrow column beside the rest).

views shows every figure in experiments/<name>/views/, which your agent adds with add_view; view (path) shows one. A figure can be any of louped's kinds or plotly, which draws 3D points and surfaces and plays frames as an animation.

A file that does not check is not used. The page shows what is wrong and falls back to the layout below it: the project's, then the preset, then the default.

Presets

PresetWhat it does
evalOpens a run on its items; the overview keeps numbers and report
trainingCurves first: history and hardware, figures before files
focusOnly numbers, report, items and files

Theme

louped.toml sets the app's tokens: radius, and the colours for each mode. Plugin pages get them too.

louped.toml
[theme]
radius = "0.25rem"

[theme.dark]
border = "oklch(1 0 0 / 15%)"

The colours are background, foreground, card, muted, muted-foreground, accent, border, input, ring, primary, secondary (each with its -foreground), positive, negative and intervention, the one accent, which marks an active intervention.

What stays

The look is louped's own, so every project reads the same: parts change what they say and whether they show, not their style. Layouts, the theme and plugin blocks load only on your machine; a published dashboard shows your layouts without plugins.

On this page