vike
Research

Studies

What a study is, where the interpreted and compiled tiers live under user_data, and how studies and the runs they produce are listed with every failure named rather than skipped.

A study is a function, not an object. crates/vike-user-research/src/contract.rs's StudyFn states the whole contract: called once with a StudyContext and a recipe (&toml::Value), it returns a StudyOutcome — named numbers and named text artifacts — or a StudyError. The header honours a design verdict that refused a Study trait in as many words — wrong tool for one implementation; extract it from two — and carries the reason: a strategy needs one because it is driven (on_bar is called thousands of times and the object between calls is where its state lives), while a study has no between-calls. The signature is the same on both tiers; a Rhai study spells it fn run(ctx, params) with the types erased. Rhai studies and Rust studies cover each tier's own surface.

Where a study lives

<project>/user_data/research/studies/
├─ rhai/<name>/<name>.rhai      interpreted — runs on a shipped binary
└─ rust/<name>/<name>.rs        compiled — meaningful only in a source checkout

The layout is spelled once, in crates/vike-model/src/state_path.rs: RESEARCH_SUBDIR because a study is not a strategy, STUDIES_SUBDIR because the tiers are the strategy tree's own RHAI_SUBDIR/RUST_SUBDIR rather than a second pair, artifact first and language second so a third language later adds a sibling rather than a new root. user_rust_studies_dir's doc carries the compiled tier's limit: a .rs file is an input to cargo, so the tier is meaningful only in a source checkout — but it still exists on a binary install and a listing still shows what is in it, because an empty directory is an answer while a tier that silently disappears on half the installs makes "I dropped a file in and nothing happened" unanswerable.

A study is a folder, and the entry file's stem must equal the folder name. Beside it sit *.toml recipes — named configurations of the same study. crates/vike-studio-core/src/study_dispatch.rs's RECIPE_EXT notes that both tiers document that layout and neither runner reads a recipe; the caller loads one, and recipes lists a folder's recipes in name order, answering an absent or unreadable folder with an empty list, because a study with no recipe is the ordinary state — both tiers run with no configuration at all.

A study places no orders, so there is no arming flag

crates/vike-user-research/src/codegen.rs records that a study folder carries no study.toml and therefore no live flag. The strategy tier has such a default-deny opt-in because mounting a strategy on a venue signs real orders; a study places none, and the surface it is handed reaches no network — enforced in the Rhai tier, a sanctioned convention in the Rust one. So a flag would be a settings key nothing reads — the defect class this workspace names as worse than an unimplemented feature.

Listing: why it was the thing that was missing

crates/vike-studio-core/src/listing.rs opens with the reason it exists: a result that is not listed does not exist to the person who produced it. Research results used to go to a ClickHouse an end user does not have, and a strategy backtest printed to stdout and was gone with the scrollback. crates/vike-model/src/runs.rs is the half that made a run leave something behind; this is the half that finds it again.

list_studies takes <project>/user_data/research/studies — resolved by the binary through user_studies_dir; the library resolves no path — and scans both tiers, rhai/ then rust/, in name order. It is enumeration only: nothing is compiled, nothing is run, no entry-file rule is applied — a folder is a study. The header argues the boundary rather than apologising for it: a compiled study resolves through a registry generated at build time, so "would this folder run" is not a question a directory scan can answer; a folder added since the binary was compiled is real, listable and not in the registry, and listing it is the honest answer. A name claimed in both tiers lists twice, one row per tier. Each ListedStudy carries its name, its folder and its StudyTier — the difference a user acts on.

list_runs takes <project>/user_data/runs and reads the common manifest and nothing else. Every top-level field of RunManifest is identical for every producer, so one row renders a backtest, a study run and a kind invented next year; the kind-specific detail subtree is carried through untouched. Nothing opens the report: its name is common and its content is the producer's business, so the listing reports whether it is there. Runs sort by directory name, which for a minted id (<unix-seconds>-<pid>-<seq>) is chronological, and ListedRun::run_id is deliberately the directory name rather than the manifest's own run_id, which is duplicated into the file and can disagree with it — the path is what a caller opens, so the path decides.

Nothing is skipped in silence

Every failure is a ListingDiagnostic carrying its path and its fix, rendered as one line that names what to do:

  • An absent root is silent; an unreadable one is RootUnreadable. A fresh install has no user_data/ at all, so "nothing there" is ordinary; a root that exists and cannot be listed is a permissions bug wearing the "not configured yet" answer, which looks exactly like a correct fresh install while every run silently vanishes. The header names the credential store as drawing the same line for the same reason.
  • RunUnfinished is a run directory holding no manifest. The manifest is written last, so this is a run being written right now or one that stopped between its two writes — a Severity::Warning carrying whether the report is there, which is the whole question an operator has about the row.
  • RunUnreadable collapses unreadable, not-JSON and missing-common-field into one variant, because to the user they are one event, with the reader's own words attached.
  • RunIdMismatch lists the run under its directory and reports the disagreement.
  • StrayStudy is a script directly in a tier root instead of in a folder — the same first-time mistake the strategy loader reports — and the message names where to move it.

severity splits these in two: the two warnings leave something listed or recoverable; everything else means work the user believes is there and is not. Both listings sort before returning, because a results surface whose rows move between two calls is one nobody can read, and both import their layout vocabulary from the strategy loader rather than re-spelling it. The Studio's Research pane is one list over both tiers; Running a study is what the row it hands you reaches, and Runs on disk is the manifest itself.

On this page