Skip to content
ƒ FlowscriptDocs
ftw.trade docs ↗
Documentation
Build with it / Flowscript

Run Flowscript locally

Install the standalone package, declare your own typed sources, evaluate JSONL files or producer pipes, and save and resume a local run.

Flowscript runs independently of ftw.trade. Declare your own sources, supply observations and inspect the program's results on your machine. The local CLI, JavaScript package and native Rust library use the same compiler and evaluator; ftw.trade supplies one application host for them.

UseEntry pointYour application supplies
Local files or a producer pipeThe packaged flowscript CLIA host JSON file and JSONL observations
A JavaScript or TypeScript service@flowscript/coreA host object, retained run, observations and output consumers
A native serviceflowscript-coreA Host, retained live::Run and application bindings

The examples below use an arbitrary prices namespace. There are no injected market names, FTW sources, accounts or credentials.

Build and install the local package#

There is currently no public package download or npm registry release. With authorized access to the source, copy or check out flowscript/ alone. Building needs Node.js 22.18 or newer, npm, Rust and its wasm32-unknown-unknown target. No other repository directory, application server or root Justfile is needed.

From flowscript/npm:

Terminal
npm ci
npm test
npm pack

npm test builds the JavaScript and WebAssembly and tests the package. At the current version, npm pack creates flowscript-core-0.1.0.tgz. The filename follows package.json; packing does not publish it or change the repository's license.

In a separate project directory, install that tarball. Replace the source path with your own checkout or copy:

Terminal
npm init -y
npm install /path/to/source/flowscript/npm/flowscript-core-0.1.0.tgz
./node_modules/.bin/flowscript --help

The tarball contains compiled JavaScript, the CLI and flowscript.wasm. Running the installed package needs Node.js; it does not need Rust, TypeScript, the source checkout or a connection to ftw.trade.

Declare sources and a program#

Save these three files in your project. Their values illustrate the input contract; they are not a market dataset or strategy performance evidence.

host.json:

JSON
{
  "sources": [
    { "name": "prices.mid", "type": "number" },
    { "name": "prices.status", "type": { "record": { "venue": "text", "asOf": "number" } } }
  ],
  "capabilities": [],
  "documentation": {
    "sources": {
      "prices.mid": "Observed midpoint in the producer's declared price units.",
      "prices.status": "Venue identity and original observation time in seconds."
    }
  }
}

strategy.flow:

Flowscript
flowscript "0.3";
uses { prices: "^1" }
const mid = prices.mid();
const status = prices.status();
const average = isNa(mid[2]) ? na : sma(mid, 3);
metric("mid", mid);
metric("average", average);
metric("asOf", status.asOf);
metric("venue", status.venue);

observations.jsonl (one complete JSON object per line):

jsonl
{"time":1700000000,"values":{"prices.mid":100,"prices.status":{"venue":"demo","asOf":1700000000}}}
{"time":1700000001,"values":{"prices.mid":102,"prices.status":{"venue":"demo","asOf":1700000001}}}
{"time":1700000002,"values":{"prices.mid":104,"prices.status":{"venue":"demo","asOf":1700000002}}}

Check and run:

Terminal
./node_modules/.bin/flowscript check strategy.flow --host host.json
./node_modules/.bin/flowscript run strategy.flow --host host.json --input observations.jsonl

The first JSONL output record has type: "program", the artifact hash, language version, declared reads, requirements, warnings and actionMode: "unsent-intents". Each following type: "step" record includes the input line, time, bar, metrics, texts and decisions. The third step's metrics.average is 102; the program explicitly leaves earlier averages unknown until three observations are present. Unknown numeric metrics encode as JSON null. Each step's texts.venue is "demo".

Errors go to stderr and exit with status 1; invalid command usage exits with status 2. Nothing substitutes fabricated observations or restarts implicitly.

The host file is the same Host JSON accepted by compile(source, host). Source declarations provide types, key shapes and intervals; they do not fetch data. There are no default sources or capability manifests. Declare each read and action the program uses. The embedding guide covers the API; libraries and tools covers imports, typed external model outputs, custom actions and integrations.

Connect your own producer#

Your producer can read a file, subscribe to a venue, collect sensor values or run an external model in its own process. It writes the same JSONL frames to stdout; application diagnostics belong on stderr. Pipe it into the evaluator:

Terminal
node producer.mjs | ./node_modules/.bin/flowscript run strategy.flow --host host.json --input -

producer.mjs is your adapter, not a built-in subscription service. The CLI does not connect to a data provider, poll a URL or infer symbol names. Choose the producer's authentication, ordering, reconnect policy and freshness checks explicitly. For an existing observation file, the equivalent pipe is:

Terminal
cat observations.jsonl | ./node_modules/.bin/flowscript run strategy.flow --host host.json --input -

For a live producer, supervise both processes or enable your shell's pipefail so a collection failure remains visible. EOF means the input stream ended; it does not certify provider health. A producer must report unavailable data explicitly or stop with an error, rather than quietly dropping failed reads.

Each frame has exactly time and values. Time is a nonnegative safe integer in seconds and must not decrease; equal timestamps allow several observations within one second. Preserve finer source time and sequence in declared fields when you need them. values maps source names to their declared observations. An omitted source holds its previous observation; explicit null makes it unknown. A clock-only frame such as {"time":1700000003,"values":{}} still steps the evaluator, including any deadlines. Build bars or sampling policies in your adapter rather than assuming the CLI aggregates the feed. Blank lines and extra frame fields are refused. End each appended file row with a newline; appending inside an already consumed final row is an error on resume. The CLI bounds source text to 1,000,000 UTF-8 bytes, the host document to 4 MiB, an input line to 1 MiB and its checkpoint document to 32 MiB.

Save and resume a file#

For a file input, save the retained evaluator and its consumed input cursor:

Terminal
./node_modules/.bin/flowscript run strategy.flow --host host.json --input observations.jsonl --checkpoint state.json

Append further complete observations to that same file. Resume explicitly:

Terminal
./node_modules/.bin/flowscript run strategy.flow --host host.json --input observations.jsonl --checkpoint state.json --resume

Resume verifies the program and host identity and the exact consumed file prefix before it evaluates the suffix. A changed prefix, incompatible program or host, corrupt or missing checkpoint, or conflicting checkpoint ownership is an error. Starting a new run over an existing checkpoint is refused. The CLI does not silently discard state and replay from the beginning.

The runner holds a sibling state.json.lock while it owns the checkpoint and removes it on normal exit. A killed process can leave that lock behind. Confirm that no writer remains before removing a retained lock and resuming.

Checkpoint replacement atomically saves the evaluator snapshot, line cursor and prefix hash together after each successful step and before its output. It is available only with file input, not stdin. Stdout and checkpoint storage do not form one transaction: a crash can leave emitted output and the saved cursor out of step. Consumers that require durable exactly-once effects need an application-owned transactional input/output journal and reconciliation; redirecting stdout is not an outbox.

Add execution in your application#

A program can declare custom action capabilities and produce decisions. The CLI prints these as unsent intents; it does not place orders, sign, send transactions or report simulated fills. A manifest alone does not implement an executor. Use the JavaScript or Rust embedding API to authorize and consume decisions and report authoritative outcomes. Keep keys, limits, durable submission identity and settlement reconciliation in that host. The local runner refuses async/await actions and any behavior its runtime reports as unmodelled. Embed Flowscript with a request and settlement coordinator when a venue completes an action later.

The outputs guide demonstrates application consumers. The performance guide explains retained execution, queue and allocation budgets and why evaluator timing alone is not order latency.