Load this skill whenever you touch fastWorkflow configuration: any LLM_* / LITELLM_API_KEY_* / SPEEDDICT_FOLDERNAME / INTENT_DETECTION_* / SESSION_STATE_* env var, the fastworkflow.env or fastworkflow.passwords.env files, a fastworkflow CLI subcommand or flag, LiteLLM proxy routing, or JWT server settings. Trigger symptoms: "env var not found" warnings, "SPEEDDICT_FOLDERNAME env var not found!", "DSPy Language Model not provided", shell exports being mysteriously ignored, refine failing with a missing-LM ValueError, or needing to add a new config knob. Do NOT use it for how to run/operate workflows end to end (fastworkflow-run-and-operate), recreating the dev environment and secrets (fastworkflow-build-and-env), or debugging non-config runtime failures (fastworkflow-debugging-playbook).
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Load this skill whenever you touch fastWorkflow configuration: any LLM_* / LITELLM_API_KEY_* / SPEEDDICT_FOLDERNAME / INTENT_DETECTION_* / SESSION_STATE_* env var, the fastworkflow.env or fastworkflow.passwords.env files, a fastworkflow CLI subcommand or flag, LiteLLM proxy routing, or JWT server settings. Trigger symptoms: "env var not found" warnings, "SPEEDDICT_FOLDERNAME env var not found!", "DSPy Language Model not provided", shell exports being mysteriously ignored, refine failing with a missing-LM ValueError, or needing to add a new config knob. Do NOT use it for how to run/operate workflows end to end (fastworkflow-run-and-operate), recreating the dev environment and secrets (fastworkflow-build-and-env), or debugging non-config runtime failures (fastworkflow-debugging-playbook).
fastWorkflow Configuration and Flags Catalog
Facts verified 2026-07-09 against v2.22.2 (commit c33b9a5). Every file:line below was
checked against the working tree on that date. If you are reading this later, run the
re-verification commands in "Provenance and maintenance" before trusting line numbers.
Jargon, defined once:
dotenv file — a KEY=value text file parsed by python-dotenv's dotenv_values(). It is
NOT automatically exported to the process environment.
LiteLLM — the multi-provider LLM client library; model strings look like
mistral/mistral-small-latest. LiteLLM Proxy — a separate gateway server; model strings
get the litellm_proxy/ prefix and calls go to LITELLM_PROXY_API_BASE.
DSPy — the LLM-programming library fastWorkflow uses for agent/planner/param-extraction
calls; dspy.LM wraps a LiteLLM model string.
LLM role — fastWorkflow assigns a separate model + API key pair per job (agent, planner,
parameter extraction, synthetic data generation, ...), so each role can use a different model.
When to use / when NOT to use
Use this skill when you need to:
Look up any env var: its default, consumer, and sharp edges (Section 3).
Understand why a shell export is ignored (Section 2 — the precedence trap).
Look up a CLI subcommand/flag, including the ones the --help text lies about (Section 5).
Add a new env var or CLI flag correctly (Section 7 checklist).
Passwords are merged AFTER the env file, so a key in the passwords file wins on conflict.
dotenv_values never touches os.environ; config lives in an in-process dict
(fastworkflow._env_vars, set at fastworkflow/__init__.py:174-176).
fastworkflow.init() must run before using framework classes (module globals like
CommandContextModel are None until then) and before importing modules that read env vars
at import time (see Section 4 traps).
init() also re-applies LOG_LEVEL from the dotenv dict (__init__.py:180-182).
When the CLI's env-file arguments are omitted, defaults resolve to
<workflow_path>/fastworkflow.env and <workflow_path>/fastworkflow.passwords.env
(cli.py:177-191). The --help text saying ".env in current directory" is WRONG — see Section 5.
For fetched examples, fastworkflow examples fetch copies the two templates to ./examples/
(not into each example folder), so you must pass them explicitly:
fastworkflow train ./examples/<name> ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env.
2. get_env_var precedence — the code default SHADOWS the OS environment
fastworkflow.get_env_var(var_name, var_type=str, default=None) at
fastworkflow/__init__.py:211-227 resolves in this exact order:
_env_vars dict (the two dotenv files passed to init())
the default= argument at the call site, if one was provided
os.getenv(var_name) — consulted ONLY when no code default exists
None + a logged warning
Consequence (the trap): for any var whose call site passes a non-None default=, a shell
export VAR=... is silently ignored — step 2 returns before os.getenv is ever consulted.
You can only override such vars via the env FILES. The shell-unoverridable vars as of v2.22.2
(call sites verified): SESSION_STATE_STORE (default "disk", session_state_store.py:123),
INTENT_DETECTION_TINY_MODEL and INTENT_DETECTION_LARGE_MODEL
(model_pipeline_training.py:892-895). Put overrides for these in fastworkflow.env, never
in the shell.
Call sites that pass default=None (e.g. SESSION_STATE_REDIS_URL/REDIS_URL at
session_state_store.py:127-129, LITELLM_PROXY_API_KEY at utils/dspy_utils.py:58) do NOT
trigger the shadow — the guard is if default is not None — so those fall through to the OS
environment normally.
Type coercion: var_type=bool accepts only true/1/false/0 (case-insensitive), else
ValueError (__init__.py:232-238). A provided default is returned as-is, uncoerced.
Masking hazard: importing litellm runs load_dotenv() over ./.env in the CWD
(verified: site-packages/litellm/__init__.py:29). The repo root has a generated .env
(make gen-env), so import-time env bugs that would crash a customer deployment are invisible
when developing in-repo. This masked the v2.21.0-2.21.3 regression fixed in 79e6986.
3. Which subcommands load env files (and which ignore them)
Subcommand
Env files loaded?
Evidence
Consequence
build
NO — fastworkflow.init(env_vars={})
build/__main__.py:326,349
LLM vars must be OS-environment exports (step 3 fallback works because call sites pass no default)
refine
NO — init(env_vars={})
refine/__main__.py:32
Same; no env-file CLI args exist at all
train
YES (both files)
train/__main__.py:269-271
—
run
YES (both files)
run/__main__.py:125-128
—
run_fastapi_mcp
YES (both files, in lifespan)
run_fastapi_mcp/__main__.py:259-264
Plus a pre-lifespan read of LOG_LEVEL for uvicorn (:1612-1617)
So before fastworkflow build / fastworkflow refine you must:
export LLM_COMMAND_METADATA_GEN=mistral/mistral-small-latest
export LITELLM_API_KEY_COMMANDMETADATA_GEN=<your key> # note: no underscore in COMMANDMETADATA
Nothing in --help tells you this; the failure mode is
ValueError: DSPy Language Model not provided. Set LLM_COMMAND_METADATA_GEN environment variable.
(raised from utils/dspy_utils.py:44-45 via build/genai_postprocessor.py:239).
File-presence probes at startup:
run HARD-FAILS if LITELLM_API_KEY_SYNDATA_GEN is absent (run/__main__.py:131-132) even
though run never generates synthetic data — it is used purely as a "did the passwords file
load?" probe. This breaks Bedrock/proxy users who legitimately have no such key; workaround:
put a dummy value in the passwords file.
train only WARNS for the same missing key ("OK if this is Bedrock",
train/__main__.py:276-277) but hard-fails on missing SPEEDDICT_FOLDERNAME (:273-275),
as does run (run/__main__.py:129-130).
4. Env var catalog
Legend: prod = production, load-bearing. dead = zero consumers in code.
"Default" = code default at the consumer (template value shown separately).
4a. LLM role variables (all resolved via dspy_utils.get_lm unless noted)
Model var (template value: mistral/mistral-small-latest)
Key var missing from the cli.py:136-139 fallback passwords stub
LLM_PARAM_EXTRACTION
LITELLM_API_KEY_PARAM_EXTRACTION
utils/signatures.py:252-255
prod
Bypasses get_lm: direct dspy.LM(model, api_key=...) at signatures.py:255, values cached in module globals after first read. Because no api_base is passed, litellm_proxy/ routing very likely does NOT work for this role (code-read inference, not runtime-verified)
Key doubles as run-time file-presence probe (Section 3)
LLM_CONVERSATION_STORE
LITELLM_API_KEY_CONVERSATION_STORE
run_fastapi_mcp/conversation_store.py:331
prod (server)
Key var missing from the cli.py fallback stub
LLM_COMMAND_METADATA_GEN
LITELLM_API_KEY_COMMANDMETADATA_GEN
build/genai_postprocessor.py:239 (used by build and refine)
prod (build-time)
(1) NOT in the packaged templates — only in docs/genai_postprocessor_readme.md and repo-local env/.env + passwords/.env; (2) key spelling is COMMANDMETADATA (no underscore) unlike every other key; (3) must be an OS export (Section 3)
LLM_RESPONSE_GEN
LITELLM_API_KEY_RESPONSE_GEN
none (rg over fastworkflow/ + tests/*.py: zero hits)
dead
Templated at examples/fastworkflow.env:8, examples/fastworkflow.passwords.env:9, and written by the cli.py:138 fallback stub — pure drift. Whether it is reserved for a future response-gen stage is an open question; do not delete without change control
LiteLLM Proxy routing (utils/dspy_utils.py:42-69): if a model string starts with
litellm_proxy/, get_lm requires LITELLM_PROXY_API_BASE (raises ValueError if unset,
:50-55), optionally uses LITELLM_PROXY_API_KEY (default None, no-auth proxies allowed,
:58), and IGNORES the per-role LITELLM_API_KEY_*. The [server] extra is NOT needed for
proxy routing. Full recipe: references/litellm-proxy-and-local-dev.md.
Roots ALL disk state; hard-required by run and train (Section 3)
SESSION_STATE_STORE
"disk"
not templated
session_state_store.py:123
prod (server)
redis value can ONLY be set via env file (default shadows shell, Section 2)
SESSION_STATE_REDIS_URL / REDIS_URL
None
not templated
session_state_store.py:127-129
prod (server, redis only)
ValueError if SESSION_STATE_STORE=redis and neither is set
INTENT_DETECTION_TINY_MODEL
google/bert_uncased_L-4_H-128_A-2
commented (fastworkflow.env:30)
model_pipeline_training.py:892-893
prod (train-time)
Env-file-only override (Section 2). Defaults chosen for transformers 4.48+/5.x compat
INTENT_DETECTION_LARGE_MODEL
distilbert-base-uncased
commented (fastworkflow.env:31)
model_pipeline_training.py:894-895
prod (train-time)
Same
SYNTHETIC_UTTERANCE_GEN_NUMOF_PERSONAS
none (int)
4
train/generate_synthetic.py:14
prod (train-time)
Import-time read trap: all three are read as module globals when generate_synthetic is first imported. Import before fastworkflow.init() captures None (a warning is logged; downstream failure inferred, not runtime-verified). Never import train modules before init()
THREE paths: utils/logging.py:53 (OS env at import, invalid value raises ValueError); __init__.py:180-182 (dotenv, via reconfigure_log_level); run_fastapi_mcp/__main__.py:1612-1617 (dotenv pre-read for uvicorn)
prod
Put it in fastworkflow.env; that covers paths 2 and 3. Shell export covers path 1 only
Set 0 to silence unserializable-artifact warnings; becomes a hard rejection in v3.0 per the docstring (turn.py:147-154)
PYTEST_RUNNING
—
—
set by tests/conftest.py:16; zero readers in fastworkflow/
dead
Safe to ignore; do not build logic on it
Not fastWorkflow config, despite appearances: repo-root config.yaml is a Dolt SQL server
config for the beads issue tracker; repo-root .env, env/.env, passwords/.env are the
local dev convention (see references/litellm-proxy-and-local-dev.md).
4c. Known drift summary (candidates for cleanup, gated by change control)
LLM_RESPONSE_GEN + key: templated but unconsumed (dead).
LLM_COMMAND_METADATA_GEN + key: consumed but untemplated, inconsistent key spelling.
cli.py:136-139 fallback passwords stub lists only 4 keys (SYNDATA_GEN, PARAM_EXTRACTION,
RESPONSE_GEN, AGENT) — omits PLANNER, CONVERSATION_STORE, COMMANDMETADATA_GEN; includes the
dead RESPONSE_GEN.
run's hard requirement on LITELLM_API_KEY_SYNDATA_GEN (probe misuse, Section 3).
5. CLI subcommands and flags
Entry point: fastworkflow = "fastworkflow.cli:main" (pyproject.toml:31). Six subcommands
(verified live with fastworkflow --help on 2026-07-09).
Subcommand
Positional args
Flags
Notes
examples list
—
—
Lists bundled examples
examples fetch
name
--force
Copies example to ./examples/<name>, copies/creates the two env templates in ./examples/ (cli.py:104-139)
Re-execs python -m fastworkflow.run_fastapi_mcp as a subprocess (cli.py:453-472); requires the [server] extra (cli.py:384-413)
Help-text lie: the env-file positionals for train/run/run_fastapi_mcp claim
"(default: .env in current directory, or bundled env file for examples)"
(cli.py:239,245,259,265,288,294). The actual default is
<workflow_path>/fastworkflow.env + <workflow_path>/fastworkflow.passwords.env
(find_default_env_files, cli.py:177-191). Trust the code.
--keep_alive sharp edge: defined with default=True and no type= (cli.py:270), so
any value you pass arrives as a string — --keep_alive False yields the truthy string
"False". Effectively this flag cannot be turned off from the CLI (empty-string workaround
untested). Downstream signature is keep_alive: bool (chat_session.py:150).
--expect_encrypted_jwt is unreachable from the wrapper: it exists only on the module
server (run_fastapi_mcp/__main__.py:358-359, action="store_true", default False), and the
CLI wrapper never forwards it (cli.py:454-469). Therefore:
# JWT signature verification DISABLED (trusted-network mode) — the only mode the wrapper offers:
fastworkflow run_fastapi_mcp ./examples/hello_world ./examples/fastworkflow.env ./examples/fastworkflow.passwords.env
# JWT signature verification ENABLED — must bypass the wrapper:
python -m fastworkflow.run_fastapi_mcp --workflow_path ./examples/hello_world \
--env_file_path ./examples/fastworkflow.env \
--passwords_file_path ./examples/fastworkflow.passwords.env \
--port 8000 --expect_encrypted_jwt
Note the argument style changes: the module form uses --workflow_path/--env_file_path
flags (load_args, run_fastapi_mcp/__main__.py:346-360), not positionals.
6. JWT constants and jwt_keys/ — hardcoded, not env vars
run_fastapi_mcp/jwt_manager.py:22-26 hardcodes (comment admits "can be made configurable via
env vars" — acknowledged unfinished work):
Constant
Value
JWT_ALGORITHM
RS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES
60
JWT_REFRESH_TOKEN_EXPIRE_DAYS
30
JWT_ISSUER / JWT_AUDIENCE
fastworkflow-api / fastworkflow-client
Keys live at ./jwt_keys/private_key.pem (mode 600) and public_key.pem (mode 644),
relative to CWD (jwt_manager.py:29-31, save at :88-100) — launch the server from a
stable directory or you'll silently generate a fresh keypair. jwt_keys/.gitignore excludes
the PEMs; never commit them.
Module default EXPECT_ENCRYPTED_JWT = True (jwt_manager.py:38) is ALWAYS overwritten at
startup by set_jwt_verification_mode(ARGS.expect_encrypted_jwt)
(run_fastapi_mcp/__main__.py:267-268), i.e. effective default is False/unverified.
Unsigned mode issues alg="none" tokens (jwt_manager.py:203).
Deployment security posture (unauthenticated /admin endpoints, open CORS) is covered in
fastworkflow-run-and-operate.
7. How to add a config axis (checklist)
Work through ALL of these; a var added to code but not the templates (or vice versa) becomes
the next LLM_RESPONSE_GEN / LLM_COMMAND_METADATA_GEN drift entry.
Consumer: read via fastworkflow.get_env_var("MY_VAR", ...) — never a scattered
os.environ read (sanctioned exceptions: LOG_LEVEL at logger import, and
FW_EAGER_ARTIFACT_VALIDATION). Never call it at module import time — read lazily inside
functions/properties (the 79e6986 lesson; litellm's cwd-.env load will hide the bug in-repo).
Decide the precedence tier consciously: passing default= to get_env_var makes
the var shell-unoverridable (Section 2). If ops must be able to override via shell, pass no
default and enforce requiredness at the CLI entry instead.
LLM role? Add BOTH LLM_<ROLE> and LITELLM_API_KEY_<ROLE> and resolve through
dspy_utils.get_lm("LLM_<ROLE>", "LITELLM_API_KEY_<ROLE>") so litellm_proxy/ routing
works. Keep the _ spelling consistent (do not imitate COMMANDMETADATA).
Templates: add to fastworkflow/examples/fastworkflow.env (settings) or
fastworkflow/examples/fastworkflow.passwords.env (secrets), with a comment block.
CLI fallback stub: if it's an API key, add it to the stub writer at cli.py:134-139.
build/refine reachability: if the var is consumed on the build/refine path, document
that it must be an OS export (those subcommands init with env_vars={}).
This catalog: add a row to Section 4 (or 4a) with consumer file:line and sharp edges,
plus a re-verification grep in the Provenance section.
Docs: update docs/genai_postprocessor_readme.md / relevant doc of record if applicable
(see fastworkflow-docs-and-positioning).
Tests: remember tests init from repo-local ./env/.env + ./passwords/.env
(tests/test_command_executor.py:24-27); add your var there if tests need it, and re-run
make gen-env mentally — it merges every *.env in the tree into root .env.
Change control: renaming/removing an existing var is a breaking change
(deployed env files reference old names — e.g. the COMMANDMETADATA spelling is frozen by
existing installs until proven otherwise). Route through fastworkflow-change-control.
Do NOT commit or push any of this without the developer'sexplicit request in that turn.
Run scripts/audit_env_catalog.sh (read-only) after any config change: it re-derives consumed
vs templated vars and prints drift.
Provenance and maintenance
Facts verified 2026-07-09 against v2.22.2 (commit c33b9a5). Re-verification one-liners (run from repo root):
Import-time reads: sed -n '14,16p' fastworkflow/train/generate_synthetic.py; sed -n '19,23p' fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py
Intent model defaults: sed -n '890,896p' fastworkflow/model_pipeline_training.py
CLI flags + help-text lie + env defaults: sed -n '177,302p' fastworkflow/cli.py and fastworkflow <subcommand> --help