Lesson 3: SymptomsStep — Intake & triage
Prompt & intent
src/myflow/medical-flow/symptoms-step.ts turns the model into a receptionist:
public getPrompt(): string {
return "You are a helpful medical receptionist. Ask the user for their symptoms and reason for visit. Once you have a clear reason, use the `capture_symptoms` tool.";
}
- Clear persona and single objective.
- The step stays active until a tool call completes.
Tool surface
public defineTool(): ToolType[] {
return [
{
name: 'capture_symptoms',
description: 'Capture user symptoms to find an appropriate doctor',
schema: z.object({ symptoms: z.string().describe('The reported symptoms or reason for visit') }),
},
];
}
public getTool(): string[] {
return ['capture_symptoms', 'end_chat'];
}
- Zod schema forces the LLM to return a string reason.
getToolkeeps the toolset tiny each turn (aligns with the “restrict tools per step” guidance inbasic-flow-techniques-detail.md).
Handler logic
protected async capture_symptoms(tool: ToolCall): Promise<ToolResponseType> {
const { symptoms } = tool.args;
this.saveState({ symptoms });
const doctors = [
{ name: 'Dr. Smith', specialty: 'General Practice', availableTimes: ['10:00 AM', '2:00 PM'] },
{ name: 'Dr. Jones', specialty: 'Specialist', availableTimes: ['11:00 AM', '3:00 PM'] }
];
return { step: BookingStep, state: { doctors } };
}
- Persists symptoms into step state (so later prompts can reuse it).
- Returns a transition + state payload: this is the Picoflow “tool decides the next step” pattern.
end_chatsimply returnsEndStepfor early exits.
[!Why] Picoflow treats each step as a local conversational workspace. Saving state and transitioning here keeps the model focused and mirrors the state-machine behavior described in
picoflow-engine.md.