BasicFlow tutorial
Track overview
BasicFlow registers fourteen steps and uses them to demonstrate every major part of the PicoFlow Step contract. This page maps the whole graph before the lessons take it apart.
BasicFlow collects a small user profile across many turns: weather for two cities,
three favourites, a full name, a date of birth, and a mailing address. The domain is
deliberately thin. What matters is that each stage was written to isolate one
framework mechanism, so a lesson can quote one file and explain one idea.
The implementation lives in pico-demo/src/myflow/basic-flow/. Its deterministic
conversation scenario lives in pico-demo/test/basic-flow/. Browse the
BasicFlow source on GitHub.
What BasicFlow is
A Flow subclass with four overrides that matter and two that are situational:
configModel()declares the default model,openai/gpt-4o-mini, withtemperature: 0.2and three runner retry attempts.configLlmCallPolicy()gives each model invocation attempt a 60-second wall-clock budget, regardless of which provider or model a step selects.defineSteps()returns the fourteenStepinstances the flow is allowed to activate, each with its memory namespace and optional model override.initialStep()picks the entry point at runtime:PresidentStepwhenconfig.isPresidentis true, otherwiseWeatherStep.spawnSteps()implements batch mode, reached whenconfig._concurrentis set.onRestoreSessionDoc()is overridden but currently delegates tosuper.
The step graph
The normal profile path, exactly as the code routes it:
Three things about that diagram are worth stating explicitly, because they are the source of most confusion:
WeatherStep,NameStep,DOBStep, andAddressStepeach define aterminate_sessiontool, so any of them can jump straight toTerminateSessionStep. Those edges are omitted above to keep the spine readable.- The
InContextStepsubtree is not part of the durable path. It runs insideNameStep’s tool handler throughrunStep(), and it never becomesflow.currentStep. After it returns,NameStepstill owns the transition and goes toDOBStep. FooLogicStepandGooLogicStepmake no model call at all. They are traversed within the same HTTP request that produced the second city temperature.
Every registered step
defineSteps() returns these fourteen instances, in this order. The first entry is
only the default cursor; initialStep() overrides it here.
| Step | File | Memory | Model | What it demonstrates |
|---|---|---|---|---|
WeatherStep |
weather-step.ts |
class default | openai / gpt-5, reasoning.effort: "low" |
A deterministic local fixture, incremental saveState, stay() as a corrective loop, and a @Tools(["get_weather"]) batch handler |
NameStep |
name-step.ts |
default |
flow default | Zod tool schema, rejecting input in code, transient state, and nested runStep() |
AddressStep |
address-step.ts |
default |
flow default | Validation delegated to a plain TS module, and go().withPrompt().withState() |
DOBStep |
dob-step.ts |
default |
openai / gpt-5.1, reasoning.effort: "low" |
Reading another step’s state into a prompt template |
FooLogicStep |
foo-logic.ts |
default |
n/a | LogicStep.runLogic() with zero model calls |
GooLogicStep |
goo-logic.ts |
default |
n/a | A second logic hop, showing withState landing on the destination |
InContextStep |
incontext-step.ts |
separate |
flow default | structOutputSchema() and runSteps() fan-out from onEnter() |
ConcurStep1 |
concur-step1.ts |
class default | flow default | A tool that returns JSON to its runSteps() caller with directResult() |
ConcurStep2 |
concur-step2.ts |
class default | flow default | Nesting from onEnter() |
ConcurStep3 |
concur-step3.ts |
class default | flow default | The minimum viable step: prompt plus onResponse |
ConcurStep4 |
concur-step4.ts |
class default | flow default | The same, reached from a different parent hook |
PresidentStep |
president-step.ts |
president |
flow default | An alternate entry point driven by config, and sessionCompleted() |
FavoritesStep |
favorites-step.ts |
favorite |
flow default | Prompt files, onCrossing(), and routing from onResponse() without tools |
TerminateSessionStep |
framework | temp |
flow default | The built-in terminal step |
“Class default” means the step never called .useMemory(...), so its memory namespace is its own class name and its conversation history is isolated from every other step.
Start with the replay
Start here — an eight-turn deterministic replay shows the full profile journey before the individual framework lessons unpack it.
The eighteen lessons
Read them in order. Lessons 1 to 8 are the fundamentals; 9 to 14 are composition; 15 to 18 are operations.
- Bootstrapping PicoFlow in NestJS — the
FlowEngineprovider, model adapters, and the/ai/runcontroller. - Your first flow —
configModel(),configLlmCallPolicy(),defineSteps(), and why a flow is a registry. - Your first step — the smallest step that
works:
getPrompt()plusonResponse(). - Tools and Zod —
defineTool(), the@Tooldecorator, normal routing results, anddirectResult()for a parallel child. - Routing with go() and stay() — the corrective loop and the response builders.
- Validation belongs in code — moving rules out of prompt prose into plain TypeScript.
- Prompt files and templates —
Prompt.file(),Prompt.replace(), and a shared role file. - Reading another step’s state —
flow.getStepState()and who owns which data. - Deterministic LogicStep — a stage with no LLM call at all.
- Response-driven steps — routing
from
onResponse()when there are no tools. - Structured output — constraining the model with a Zod schema.
- Nested execution: runStep() — calling a child step inline and using its return value.
- Parallel children and tools: runSteps() —
fan-out, child-owned state, tool calls,
directResult(), and the independence rules that make it safe. - Transient state and context — the four kinds of data and what survives persistence.
- Memory namespaces and model overrides — sharing or isolating history, and per-step models.
- @Tools batching — group tool dispatch around a deterministic local fixture.
- Sessions, migration, batch mode —
conditional entry, restore policy, and
concurrentSteps(). - Testing a flow end to end — scenario-driven assertions on message content and persisted state.
Next
Begin with the deterministic replay, then continue to 1. Bootstrapping PicoFlow in NestJS.