discover_params
Read back the tunable param() knobs a Rhai strategy declares, with their defaults, in declaration order — offline, with no server of any kind.
discover_params compiles a Rhai strategy and tells you which knobs it exposes: the exact keys a
profile's [strategy.params] or a [sweep] grid may override, in declaration order. It is how an
agent finds those names without asking the human who wrote the script.
Call it on a script you wrote, too. The description used to read as an inspection tool for
somebody else's code, and an agent that had just authored a strategy answered "which parameters can
I tune" from memory of what it had written instead of calling this. It happened to be right and had
no way to know it was: validate_strategy answers
{ok: true} and says nothing about params, and this tool runs the top level only — so the one
mistake worth catching, a param() reached from inside a hook, is invisible to the author as well
as to the reader. The tool's own text now says both things (tools_spec in
crates/vike-cli/src/cmd/mcp.rs).
Offline. tools_spec annotates it readOnlyHint: true, idempotentHint: true and
openWorldHint: false — the only one of the three that is unusual is the last: this tool touches
no datahub and no node, so a session with neither configured can still call it.
Arguments
| Parameter | Type | Required | Notes |
|---|---|---|---|
script | string | yes | Rhai strategy source |
A missing or non-string script is a tool error before anything is compiled — the message names
the argument it wanted.
What comes back
{ "params": [ { "name": "fast", "default": 5.0 }, { "name": "slow", "default": 20.0 } ] }tool_discover_params calls vike_script::discover_params and maps each (name, default) pair
into that object. Defaults are always numbers — ScriptCtx::params_seen is an
IndexMap<String, f64>, so a knob is an f64 and nothing else.
Order is declaration order, not alphabetical. discover_params's own doc says "in declaration
order", and the IndexMap behind params_seen is what makes that true rather than incidental.
How the knobs are found
vike_script::discover_params builds the engine, compiles the source, and runs its top level
exactly once with an empty scope and no overrides. It is broker-agnostic: no Strategy<B> is
mounted, so nothing trades and nothing needs a venue.
The recording happens inside the host function itself. build_engine registers
param(name, default) to record the pair into params_seen — first seen wins — and return the
injected override if one is present, else the default. Discovery injects no overrides, so what
comes back is the declared default in every case.
param() takes an integer default too
The registration comment is explicit that the default argument is a rhai::Dynamic, not an f64,
and says why: registered as f64 it accepted param("fast", 5.0) and rejected
param("fast", 5) with a Rhai "Function not found" naming an i64 — a knob that worked or did not
depending on whether the author typed a decimal point. Every shipped template happens to write
5.0, which is why it went unnoticed.
Because only the top level runs, only the param() calls the top level reaches are recorded. That
is the shape the engine documents for the call — let fast = param("fast", 5.0); at the top level,
so the one-time run bakes the value in — and a knob buried inside a hook is invisible to discovery.
That is not only a reporting gap: a [sweep] grid built from what this tool answers can never name
that knob, so it is driven only by somebody who already knows it is there. The override map itself
is not the limit — crates/vike-script/src/engine.rs registers param to read overrides on
every call, so a name you do type into the grid reaches a hook-level param() like any other.
A compile error is a tool error here
This is the one place discover_params and
validate_strategy differ, and they run the same compile
path. validate_strategy turns a bad script into an ok: false answer the agent reads and
fixes; discover_params returns a tool error, rhai compile error: …. If you are checking whether
a script is sound, call validate_strategy; call this one when you want the knobs.
Compiling is a much weaker signal than it looks
Rhai resolves a registered function when its line runs, not at compile time, and this tool runs
only the top level — so every call inside on_bar is unchecked. Three mistakes land in that blind
spot: an unbound name (a typo, or any name outside the host-bound set), a wrong arity, and
a wrong argument type on a concretely-typed binding — Rhai's resolve_fn hashes each argument's
TypeId and performs no int-to-float coercion for a registered function, so market(1, 1) misses
as hard as a typo, while the indicators and param take a rhai::Dynamic and accept either. Each
raises a runtime error on every bar — function-not-found for the first two, and
crates/vike-script/src/engine.rs's bad_arg message for a non-numeric indicator argument.
RhaiStrategy's hook runner swallows it (fail-safe: zero orders that bar) and self-disables after
10 consecutive errors. The strategy looks mounted and silently never trades. Check the names
against list_indicators, which reports the set the host
actually binds.
Where it sits in a session
Start from list_templates or your own source, call this to see
the knobs, then name those knobs in a [sweep] table and hand the profile to
run_sweep — each key there overrides strategy.params.<key> across
a value grid. The language itself is documented under
Scripting, and the engine's registered surface in
The engine.
Here is my Rhai strategy — what knobs does it expose, and what are their defaults?