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 checkoutThe 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 nouser_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. RunUnfinishedis 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 — aSeverity::Warningcarrying whether the report is there, which is the whole question an operator has about the row.RunUnreadablecollapses 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.RunIdMismatchlists the run under its directory and reports the disagreement.StrayStudyis 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.
Research
The research loop in Vike Trader — a study reads the history store, may fit a model, scores its own numbers and leaves a run behind — and the headless crate that hosts every step of it.
Running a study
How a surface runs the study a listing handed it — one entry point for both tiers, the order of operations that mints a run, and what the manifest records so a study run is never mistaken for a backtest.