picoflow.io

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.
  • getTool keeps the toolset tiny each turn (aligns with the “restrict tools per step” guidance in basic-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_chat simply returns EndStep for 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.