CodeGym /Courses /ChatGPT Apps /Multistep workflows as a way to reduce cognitive load

Multistep workflows as a way to reduce cognitive load

ChatGPT Apps
Level 11 , Lesson 0
Available

1. What is a workflow in the context of a ChatGPT App

Well, if you’ve sorted out authentication, you’ve earned a reward. Let’s move on to a very interesting topic — the workflow in a ChatGPT App. The word “workflow” often triggers flashbacks to BPMN diagrams and dreary enterprise software. Rest assured: in the context of a ChatGPT App, we’re interested in a much lighter version.

In our course, by workflow we mean a multistep scenario in which:

  • there is a clear goal (for example, choose a gift and drive to purchase),
  • there are sequential steps (survey → generate options → refine → finish),
  • each step has its own roles for GPT, the widget, and tools.

An important point: a workflow is not “one method in an MCP server.” It’s a composition of:

  • the model’s reasoning (which questions to ask, which tool to call),
  • tool invocations (MCP/Agents),
  • UI steps in the widget,
  • state on the backend.

In other words, you don’t have one “super tool” solve_everything, but several simple ones that are used at different stages. And not one “mega-widget,” but a small set of screens/states, each for its own subtask.

The “triangle of responsibility” in a workflow

It’s helpful to think of a workflow as a dance of three participants:

Role Task in the workflow Example in GiftGenius
GPT Brain. Understands the user’s intent, decides when a step is complete and what comes next. Can call tools. Understands “I want something for a geek” and decides to call search_items(category="geek").
Widget Face. Renders the current step, shows only what’s relevant, collects clicks and input. Stores UI state. Shows a form “Who is the gift for?”, then gift cards, then a “Buy” button.
MCP/Agent Hands. Does heavy and structured work, validates data, stores business state. Stores the recipient’s profile, queries the gift catalog, filters by budget.

These three roles together implement the same scenario, but at different levels: GPT decides “what’s next,” the widget shows “what’s now,” MCP is responsible for what actually happens with the data.

2. Example workflow based on GiftGenius

Let’s take the familiar GiftGenius scenario — a gift selection assistant. It can be described as a simple linear wizard.

The sequence of steps might be:

  1. Collect basic information about the recipient.
  2. Determine budget and constraints.
  3. Generate and filter gift ideas.
  4. Show candidates and allow like/hide.
  5. Proceed to checkout or save the selection.

The same scenario can be represented as a small “state machine”:

stateDiagram-v2
    [*] --> Profiling
    Profiling --> ProfilingDone: profile completed
    ProfilingDone --> Browsing: ideas generated
    Browsing --> Refining: user refined filters
    Refining --> Browsing: updated list
    Browsing --> Checkout: gift selected
    Checkout --> Success: order placed
    Success --> [*]

Here:

  • Profiling — the step of collecting answers about the recipient,
  • Browsing/Refining — working with the list of candidates,
  • Checkout — checkout,
  • Success — final confirmation.

Note: there isn’t a single button or a single fetch on the diagram. These are logical steps, and you attach concrete UI screens, tools, and API calls on top of them.

3. Why split the task into steps at all

If you’ve ever made “a 25-question form on a single screen,” you already know why. But let’s break it down step by step.

User cognitive load

People have limited attention resources. Psychologists like to recall Miller’s law about 7±2 objects in short-term memory. In UX design, this translates into a very practical rule: the more fields and options you show at once, the higher the chance the user will stall out, get tired, or close the tab.

A 12-field form on one screen inside a small inline ChatGPT widget is almost guaranteed rage-quit: the user drops everything and just closes the tab. The user came to “chat,” not to take an exam.

If you split the task into steps:

  • “Step 1 of 4: tell us about the person,”
  • “Step 2 of 4: choose a budget,”
  • “Step 3 of 4: browse options,”
  • “Step 4 of 4: confirm your choice,”

then each moment feels manageable. A progress bar or step labels provide a sense of control: it’s clear what’s happening and how much is left.

Model cognitive load

Surprise: the model has a similar problem. An LLM isn’t a human, of course, but it also has limited “attention” and a context window. If you ask GPT in a single pass to:

  • find out everything about the recipient,
  • handle the budget,
  • take delivery details into account,
  • pick 10 options,
  • explain why these options,

then the model spends part of its attention and tokens on each of these sub-tasks. The more unrelated tasks in one request, the higher the risk that some parts will be done superficially or with errors.

If you build a chain of steps — essentially the same chain-of-thought, just explicitly laid out in the interface — the model first solves the narrow task “extract the profile,” then “adjust the budget,” then “select candidates.” The quality of the model’s reasoning at each stage is noticeably higher.

Maintainability and debugging

When everything is stuffed into one tool and one screen, debugging turns into a quest: “At exactly which point did it go wrong?”

In a multistep workflow you almost automatically get:

  • logging points: step_started, step_completed, step_failed,
  • clear places to measure conversion (how many people reached step 3),
  • localized problems: “it only fails on the idea generation step.”

All this will be useful in the module on workflow analytics, but it’s already helpful to get used to thinking in steps.

4. Types of steps in a workflow and how they look in the UI

We’ve already discussed why to split a task into steps. Now let’s organize the steps themselves and see which common “building blocks” most often appear in a ChatGPT App. To avoid devolving into a chaotic set of screens, it’s helpful to have a “library” of step types. Your app will almost always reuse a few patterns.

Here’s a basic table:

Step type Goal How it usually looks in a ChatGPT App Example in GiftGenius
Data collection (wizard) Fill a complex object in parts Small form, chips, option selection, progress indicator “Who is the gift for?”, “Age?”, “Interests?”
Branching Decide which path to take next A question in chat + simple options in the UI “Gift for a child → children’s categories”
Review/confirmation Let the user verify the outcome Summary card + “Back” / “Confirm” buttons “Here’s what I inferred about her — is it all correct?”
Final step Finish the scenario and offer next actions Final screen with the result + follow-ups in chat “Here are your gifts — would you like to place an order?”

It’s important to remember: the same logical step can show up both in the UI and in a purely textual dialog. For example, the “collect interests” step can be:

  • a form with tags “sports,” “board games,” “cooking,”
  • or a conversation in which GPT politely clarifies: “What are they into?”

Often the optimal option is a hybrid: GPT asks a question, the user answers with some text, and can also click on chips in the widget at the same time.

5. Who “leads” the workflow: GPT, the widget, or the server?

It’s intuitive to say: “Of course the widget — we’re front-enders, we control everything through state.” But in the ChatGPT App world it doesn’t work that way. A workflow is a joint effort of all three participants.

GPT as the orchestrator

GPT:

  • drives the dialog and asks questions,
  • decides when a step can be considered complete,
  • chooses when to call a tool (for example, “it’s time to generate gifts”).

For GPT, your workflow looks like a set of subtasks. In the system prompt you can describe which subtasks exist and in which order they’re usually performed, while leaving the model some freedom to slightly adapt the sequence.

Example of a mini-instruction inside the system prompt for GiftGenius (pseudocode, without exact syntax):

1. First clarify the recipient’s profile (age, relationship, interests).
2. Then clarify the budget.
3. When you have enough data — call the suggest_gifts tool.
4. After receiving candidates — help the user choose.

The key point: GPT doesn’t know (and shouldn’t know) the details of your React components. It operates in step terms tied to goals: “collect the profile,” “generate ideas.”

The widget as the “face” of a step

The widget:

  • displays exactly the step that’s relevant now,
  • stores UI state (selected card, open tab, local form fields),
  • can show a step progress indicator.

Simplest representation of a UI workflow in code:

type GiftWorkflowStep =
  | "profiling"
  | "budget"
  | "candidates"
  | "checkout";

type GiftWidgetState = {
  step: GiftWorkflowStep;
  selectedGiftId?: string;
};

Inside the React widget, you can store this state either in a regular useState or, if you want to bind it to the ChatGPT widget’s lifecycle, use useWidgetState from the Apps SDK.

const [widgetState, setWidgetState] = useState<GiftWidgetState>({
  step: "profiling",
});

Event handlers in the widget won’t directly “buy a gift,” but will change the step and pass the necessary data back to the model/backend.

MCP tools as the “hands” of the workflow

The MCP server:

  • stores business state (profile, choice history),
  • validates steps (“you can’t go to Checkout if no gift is selected”),
  • does heavy work: catalog search, price calculation, integration with ACP.

For example, it’s more correct for the decision “which gifts to show” to be made not in the widget, but in an MCP tool like suggest_gifts, so the model can call it repeatedly during refinement.

This gives you a separation of concerns:

  • GPT — text and sequence,
  • the widget — the visual representation of the current step,
  • MCP — data and invariants.

6. How to describe a workflow in code: a mini state machine

Remember the GiftGenius state diagram from the beginning of the lecture? Now we’ll write the same logic as simple types and functions — a mini state machine in code. We’re not going to turn your app into a theoretical course on finite automata, but a couple of simple types and functions makes life much easier.

Step types and configuration

Let’s start with a declarative description of the steps. We’ll take the familiar GiftWorkflowStep type (repeat it here for clarity) and define a configuration for it:

type GiftWorkflowStep =
  | "profiling"
  | "budget"
  | "candidates"
  | "checkout";

type StepConfig = {
  label: string;
  isFinal?: boolean;
};

export const GIFT_WORKFLOW_STEPS: Record<GiftWorkflowStep, StepConfig> = {
  profiling: { label: "Recipient" },
  budget: { label: "Budget" },
  candidates: { label: "Candidates" },
  checkout: { label: "Checkout", isFinal: true },
};

Now we can add a simple transition function:

export function getNextStep(
  current: GiftWorkflowStep
): GiftWorkflowStep | null {
  switch (current) {
    case "profiling":
      return "budget";
    case "budget":
      return "candidates";
    case "candidates":
      return "checkout";
    default:
      return null; // final
  }
}

This already gives you:

  • a centralized list of steps,
  • explicit transition rules,
  • the ability to quickly change order and logic.

Using it in the widget

The simplest version of a “wizard” in your widget can look like this:

function GiftWizard() {
  const [step, setStep] = useState<GiftWorkflowStep>("profiling");

  const handleStepComplete = () => {
    const next = getNextStep(step);
    if (next) setStep(next);
  };

  return (
    <div>
      <ProgressBar step={step} />
      <StepContent step={step} onComplete={handleStepComplete} />
    </div>
  );
}

The StepContent component can render different subforms depending on the step:

function StepContent(props: {
  step: GiftWorkflowStep;
  onComplete: () => void;
}) {
  const { step, onComplete } = props;

  if (step === "profiling") {
    return <ProfilingStep onNext={onComplete} />;
  }
  if (step === "budget") {
    return <BudgetStep onNext={onComplete} />;
  }
  if (step === "candidates") {
    return <CandidatesStep onNext={onComplete} />;
  }
  return <CheckoutStep />;
}

Note: here we aren’t yet touching how GPT chooses the step — this is local UI logic. Later you can synchronize this step with server state or messages from tools, but this is sufficient to understand multistep flow.

7. Evolving the training app: from “mega-form” to wizard

Let’s imagine that before this lecture your GiftGenius widget looked like a “big form”:

  • recipient’s name,
  • age,
  • interests,
  • budget,
  • event type,
  • checkboxes “delivery needed” and five more fields,
  • and one big “Select a gift” button at the bottom.

This is often fine for a prototype, but as soon as you want a product-grade scenario — it’s time to split it into steps.

What the “before” looked like

A caricature example:

// Anti-pattern: one huge form
function GiftFormAllInOne() {
  return (
    <form>
      {/* 10+ mixed fields */}
      {/* ... */}
      <button type="submit">Select a gift</button>
    </form>
  );
}

Typical problems:

  • the user doesn’t understand which fields are required,
  • it’s unclear how long it will take,
  • it’s harder for GPT to explain to the user what happened and do a follow-up.

How to do the “after”: a three-screen wizard

Step 1 — separate profile from budget:

function ProfilingStep(props: { onNext: () => void }) {
  const [recipientType, setRecipientType] = useState("");
  const [interests, setInterests] = useState<string[]>([]);

  const handleSubmit = () => {
    // you can call a profile-saving tool here
    props.onNext();
  };

  return (
    <div>
      <h3>Who are we shopping for?</h3>
      {/* pairs of radio buttons / chips for type and interests */}
      <button onClick={handleSubmit}>Next</button>
    <div>
  );
}

Step 2 — budget:

function BudgetStep(props: { onNext: () => void }) {
  const [budget, setBudget] = useState<number | null>(null);

  const handleSubmit = () => {
    // you can call a budget-validation tool
    props.onNext();
  };

  return (
    <div>
      <h3>What’s your budget?</h3>
      {/* a slider or an input */}
      <button onClick={handleSubmit} disabled={!budget}>
        Find options
      </button>
    </div>
  );
}

Step 3 — list of candidates:

function CandidatesStep(props: { onNext: () => void }) {
  const [selectedId, setSelectedId] = useState<string | null>(null);

  // here you already show gift cards
  // and allow selecting one

  return (
    <div>
      <h3>Choose a suitable option</h3>
      {/* cards with onClick = setSelectedId */}
      <button onClick={props.onNext} disabled={!selectedId}>
        Proceed to checkout
      </button>
    </div>
  );
}

Yes, there’s a bit more code, but the logic is simpler:

  • each step solves a small task,
  • the model can separately comment on transitions between steps,
  • you can log/measure each step independently.

8. Anti-patterns: how not to turn a workflow into a monster

Practice and observations from similar apps show several common mistakes you really want to avoid.

First, don’t try to “draw everything” with a complex BPMN diagram with 30 states, 40 arrows, and a giant A0 sheet. In the context of a ChatGPT App, an intuitively understandable staircase of steps is more important than a formal notation. Simple diagrams like the one we drew for GiftGenius are enough.

Second, don’t turn the app into one huge form, especially inside an inline widget. The user is already in chat; adding a dense UI block should reduce, not increase, cognitive load. If you catch yourself thinking “well, there are 12 fields here, but they’re all important,” it’s almost always a sign you need to split the task.

Third, don’t add steps “for looks.” Each step must have a clear goal: either collect data, narrow the choice, or let the person confirm something. An empty screen like “almost there” with a single “next” button rarely helps.

Finally, don’t try to expose every capability of the app in the very first steps. Details like “advanced filters” or “special delivery conditions” can be added as additional steps only for those who actually need them.

9. A simple workflow design exercise

To reinforce the material, try doing the following on paper (or in an IDE, but without code).

Pick one task. It could be:

  • gift selection (GiftGenius),
  • trip booking,
  • building a study plan for something.

Split it into 3–5 steps. For each step, describe:

  • the goal: what should be known/done after this step,
  • the format: what’s more appropriate here — pure GPT text, a widget, or a combination.

For example, for a simple “TypeScript study plan”:

  1. Step “Level assessment” — a dialog (GPT asks a couple of questions) + a short self-assessment form.
  2. Step “Goals” — textual discussion + goal checkboxes in the widget.
  3. Step “Plan” — plan generation (a list) + “make harder/easier” buttons.
  4. Step “Confirmation” — a brief summary and a “save plan” button.

Then try to estimate which tools might be used at each step, but don’t go into detail: tools, their enablement/disablement, and state storage are topics for the next lectures in this module.

10. Common mistakes when working with multistep workflows

Mistake #1: trying to solve everything with one step and one tool.
It’s very tempting to make “one big smart tool” that asks, analyzes, selects, and places the order. In practice, this worsens both UX (one heavy screen) and the model’s reasoning quality — too many responsibilities in one call. It’s simpler, more reliable, and cheaper to maintain to break the task into a chain of 3–5 simple steps.

Mistake #2: implicit steps hidden in the developer’s head.
Sometimes the code seems to have a sequence of actions, but it’s not explicitly described anywhere: no step types, no configuration, no diagram. As a result, no one on the team can clearly answer “what happens in this app from start to finish.” Minimal declarative descriptions of steps and transitions save hours of debugging.

Mistake #3: mixing UI steps and business logic.
If the logic of transitions between steps is buried deep inside React components (e.g., if (isValid && hasBudget && !needsShipping) on a button’s onClick), it becomes hard to reuse and test. It’s better to have a relatively explicit “state machine” or at least a getNextStep function, and the UI just calls it and renders the result.

Mistake #4: ignoring GPT’s role as an orchestrator.
Sometimes a developer tries to control the entire scenario from the widget: “I’ll ask everything myself; let the model just select.” As a result, ChatGPT stops feeling like a live assistant and turns into a compute engine behind a form. It’s much nicer when GPT actively communicates, nudges to the next step, and initiates tool calls itself — and you support it with step design and instructions.

Mistake #5: steps without a clear goal.
Sometimes “extra” steps appear in the wizard — honestly, just because the designer thought it looked nicer. The user sees “Step 2 of 5,” but nothing really happens on that step and nothing is required of them. Such empty screens only increase perceived complexity. If you can’t formulate a step as “after it we definitely know X” or “after it the user did Y,” it probably isn’t needed.

Mistake #6: missing progress and the lack of a sense of journey.
Multistep flows without visual support turn into a black box: the user doesn’t understand where they are and how much is left. Even a simple text indicator “Step 2 of 4” or a horizontal list of steps in the widget header noticeably reduces anxiety. Ignoring this effect is one reason people “drop off” midway through the scenario, even though there may not be any real complexity there.

1
Task
ChatGPT Apps, level 11, lesson 0
Locked
Mini state machine for steps and a text progress bar
Mini state machine for steps and a text progress bar
1
Task
ChatGPT Apps, level 11, lesson 0
Locked
Wizard instead of a “mega-form” with state persisted in widgetState
Wizard instead of a “mega-form” with state persisted in widgetState
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION