DevTool Setup
How to get the DevTool running against your own flows.
Prerequisites
You need:
- the
@flow-state-dev/fsdevpackage installed (it provides thefsdevcommand) @flow-state-dev/devtoolinstalled (provides the pre-built UI assets)- At least one flow definition in a conventional location
pnpm add -D @flow-state-dev/fsdev @flow-state-dev/devtool
Project structure
The CLI discovers flows from standard directories. A minimal project looks like this:
my-project/
├── src/
│ └── flows/
│ └── my-flow/
│ └── flow.ts ← exports a FlowInstance as default
├── package.json
└── .env.local ← optional, loaded automatically
The flow file should default-export a FlowInstance created by defineFlow:
import { defineFlow, handler } from "@flow-state-dev/core";
const echo = handler({
name: "echo",
execute: async (input) => input,
});
export default defineFlow({
kind: "my-flow",
actions: {
echo: { block: echo },
},
})({ id: "default" });
Starting the server
Run from your project root:
fsdev dev
Output looks like:
DevTool server running at http://localhost:4200
Flows: my-flow
API: http://localhost:4200/api/flows
Data: .fsdev/data/
The DevTool opens in your browser. Select your flow from the navigator, create a session, and dispatch actions.
Custom flow directories
If your flows live somewhere non-standard, use --flow-dir:
fsdev dev --flow-dir ./lib/workflows
This flag is repeatable. When specified, the default src/flows/ and flows/ discovery is skipped.
Model overrides
During development you might want a faster or cheaper model. --model overrides the model for every generator that runs in the server's own process:
fsdev dev --model openai/gpt-5.4-mini
Background work handed to a queue runs in another process, under that process's own model configuration. See Model overrides.
Connecting to a secured flow
Some flows authenticate every request with a bearer secret. Their principal resolver rejects anything that arrives without a valid token. DevTool has no token to send by default, so those flows look inert: you dispatch an action and nothing happens.
Declare a devtool block in your fsdev.config.ts:
export default createFlowState({
// ...flows, stores...
devtool: {
userId: "owner",
bearerToken: process.env.MY_FLOW_SECRET,
},
});
fsdev dev reads it and wires DevTool from it. userId becomes the session identity DevTool creates sessions and dispatches as. bearerToken is sent as Authorization: Bearer on every flow request. Use the identity the flow's resolver expects: a bearer flow that resolves to a fixed owner needs userId: "owner", so the session and the action agree on who is acting.
The wiring is local-only. The token is injected into the loopback page fsdev dev serves and nowhere else. fsdev serve and deploy paths ignore the devtool block.
For a one-off you can skip the config and set a bearer token ad hoc in the Settings sheet (the gear icon). Precedence is straightforward: a userId from the config wins over a previously-saved Settings value on load.
Environment variables
The CLI loads .env.local files automatically, walking up from your working directory. Put API keys and configuration there:
# .env.local
OPENAI_API_KEY=sk-...
Data persistence
By default, session data persists to .fsdev/data/ relative to your working directory. Delete this directory to start fresh. The directory is created automatically on first use.
This is the no-config default. When your project ships an fsdev.config.ts, stores come from the app's own wiring instead, whatever profile that config declares. See App Configuration.
Monorepo support
In monorepos, fsdev dev scans one level under packages/, examples/, apps/, and labs/ for flow directories. Run it from the monorepo root to discover flows across all packages.