> ## Documentation Index
> Fetch the complete documentation index at: https://fal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Queue and steering

> Send messages while the agent works, queue follow-ups, stop a running chain, and cancel generations.

export const QueueDrawerComp = () => {
  const FAL_COMP_W = 754;
  const FAL_COMP_H = 338;
  const falEases = {
    lin: t => t,
    out: t => 1 - Math.pow(1 - t, 3),
    expo: t => t >= 1 ? 1 : 1 - Math.pow(2, -10 * t),
    inOut: t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
    back: t => 1 + 3.1 * Math.pow(t - 1, 3) + 2.1 * Math.pow(t - 1, 2)
  };
  const falEvalNum = (tr, t) => {
    const k = tr.keys;
    const n = k.length;
    if (t <= k[0][0]) return k[0][1];
    if (t >= k[n - 1][0]) return k[n - 1][1];
    for (let i = 0; i < n - 1; i++) {
      const t0 = k[i][0];
      const t1 = k[i + 1][0];
      if (t >= t0 && t <= t1) {
        const u = t1 === t0 ? 1 : (t - t0) / (t1 - t0);
        return k[i][1] + (k[i + 1][1] - k[i][1]) * tr.ease(u);
      }
    }
    return k[n - 1][1];
  };
  const falMakeTimeline = root => {
    const tracks = [];
    const q = sel => !sel ? [] : typeof sel === "string" ? [...root.querySelectorAll(sel)] : [sel];
    const track = (sel, prop, keys, ease = "out", fmt = null) => {
      const made = q(sel).map(el => ({
        el,
        prop,
        keys,
        ease: falEases[ease],
        fmt
      }));
      tracks.push(...made);
      return made;
    };
    const enter = (sel, at, o = {}) => {
      const {y = 10, x = 0, s = 1, b = 0, d = 0.55, e = "expo"} = o;
      track(sel, "--o", [[at, 0], [at + d * 0.6, 1]]);
      if (y) track(sel, "--y", [[at, y], [at + d, 0]], e);
      if (x) track(sel, "--x", [[at, x], [at + d, 0]], e);
      if (s !== 1) track(sel, "--s", [[at, s], [at + d, 1]], e);
      if (b) track(sel, "--b", [[at, b], [at + d * 0.8, 0]], e);
    };
    const exit = (sel, at, d = 0.45) => track(sel, "--o", [[at, 1], [at + d, 0]], "inOut");
    const typewrite = (el, str, at, rate) => {
      const end = at + str.length * rate;
      track(el, "text", [[at, 0], [end, str.length]], "lin", n => str.slice(0, Math.round(n)));
      return end;
    };
    const sample = t => {
      for (const tr of tracks) {
        const v = falEvalNum(tr, t);
        if (tr.prop === "text" && tr.fmt) {
          const s = tr.fmt(v);
          if (tr.el.textContent !== s) tr.el.textContent = s;
        } else if (tr.prop === "opacity") {
          tr.el.style.opacity = String(v);
        } else {
          tr.el.style.setProperty(tr.prop, String(v));
        }
      }
    };
    return {
      track,
      enter,
      exit,
      typewrite,
      sample
    };
  };
  const useFalComp = build => {
    const stageRef = useRef(null);
    const rootRef = useRef(null);
    useEffect(() => {
      const stage = stageRef.current;
      const root = rootRef.current;
      if (!stage || !root || typeof window === "undefined") return;
      const ro = new ResizeObserver(([entry]) => {
        const width = entry && entry.contentRect.width;
        if (width) stage.style.setProperty("--fal-comp-scale", String(width / FAL_COMP_W));
      });
      ro.observe(stage);
      const tl = falMakeTimeline(root);
      const {duration, still, onReady} = build(tl, root);
      const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      let disposed = false;
      const ready = Promise.all([...[...root.querySelectorAll("img")].map(im => (im.decode ? im.decode() : Promise.resolve()).catch(() => undefined)), document.fonts ? document.fonts.ready : Promise.resolve()]);
      if (reduced) {
        if (onReady) onReady();
        tl.sample(still);
        ready.then(() => {
          if (disposed) return;
          if (onReady) onReady();
          tl.sample(still);
        });
        return () => {
          disposed = true;
          ro.disconnect();
        };
      }
      tl.sample(0);
      let isReady = false;
      let running = false;
      let inView = true;
      let raf = 0;
      let t = 0;
      let clockMs = 0;
      const step = now => {
        const dt = Math.min(0.05, (now - clockMs) / 1000);
        clockMs = now;
        t += dt;
        if (t >= duration) t -= duration;
        tl.sample(t);
        raf = requestAnimationFrame(step);
      };
      const sync = () => {
        const shouldRun = isReady && !disposed && inView && document.visibilityState === "visible";
        if (shouldRun && !running) {
          running = true;
          clockMs = performance.now();
          raf = requestAnimationFrame(step);
        } else if (!shouldRun && running) {
          running = false;
          cancelAnimationFrame(raf);
        }
      };
      const io = new IntersectionObserver(([entry]) => {
        inView = entry ? entry.isIntersecting : true;
        sync();
      });
      io.observe(root);
      document.addEventListener("visibilitychange", sync);
      ready.then(() => {
        if (disposed) return;
        if (onReady) onReady();
        isReady = true;
        sync();
      });
      return () => {
        disposed = true;
        running = false;
        cancelAnimationFrame(raf);
        io.disconnect();
        ro.disconnect();
        document.removeEventListener("visibilitychange", sync);
      };
    }, []);
    return {
      stageRef,
      rootRef
    };
  };
  const FAL_COMP_BASE_CSS = `
@property --x { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --y { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --s { syntax: "<number>"; inherits: false; initial-value: 1; }
@property --o { syntax: "<number>"; inherits: false; initial-value: 1; }
@property --b { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --h { syntax: "<number>"; inherits: false; initial-value: 150; }
.fal-comp-frame { position: relative; width: 100%; aspect-ratio: 754 / 338; border-radius: 12px; overflow: hidden; margin: 1.5rem 0; }
.fal-comp-stage { position: absolute; inset: 0; overflow: hidden; }
.fal-comp-artboard { position: absolute; left: 0; top: 0; width: 754px; height: 338px; transform-origin: 0 0; transform: scale(var(--fal-comp-scale, 1)); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; font-size: 14px; -webkit-font-smoothing: antialiased; color: #202022; }
.fal-comp-artboard * { box-sizing: border-box; margin: 0; }
.fal-comp-artboard img { max-width: none; }
.fal-comp-a { transform: translate3d(calc(var(--x, 0) * 1px), calc(var(--y, 0) * 1px), 0) scale(var(--s, 1)); opacity: var(--o, 1); filter: blur(calc(var(--b, 0) * 1px)); will-change: transform, opacity, filter; }
`;
  const falFrame = ({comp, label, css, plate, children}) => <div className="fal-comp-frame">
    <style>{FAL_COMP_BASE_CSS + css}</style>
    <div ref={comp.stageRef} role="img" aria-label={label} className="fal-comp-stage" style={{
    background: plate
  }}>
      <div ref={comp.rootRef} aria-hidden="true" className="fal-comp-artboard">
        {children}
      </div>
    </div>
  </div>;
  const falStroke = (d, color = "#494950", w = 1.2, size = 12, cap = "square") => <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none" stroke={color} strokeWidth={w} strokeLinecap={cap} strokeLinejoin="round">
    <path d={d} />
  </svg>;
  const STOP = {
    closed: 8,
    header: 36,
    row1: 86,
    row2: 118,
    row3: 150
  };
  const build = tl => {
    tl.enter("[data-assembly]", 0.1, {
      y: 12,
      s: 0.99,
      b: 4,
      d: 0.62
    });
    const A = 0.8;
    tl.track("[data-drawer]", "--h", [[A, STOP.closed], [A + 0.34, STOP.header], [A + 0.42, STOP.header], [A + 0.9, STOP.row1], [A + 0.99, STOP.row1], [A + 1.39, STOP.row2], [A + 1.48, STOP.row2], [A + 1.88, STOP.row3]], "expo");
    const OUT = A + 1.88 + 6;
    tl.exit("[data-center]", OUT, 0.42);
    return {
      duration: OUT + 0.48,
      still: OUT - 1
    };
  };
  const comp = useFalComp(build);
  const I = {
    queue: falStroke("M0.6 0.6V7.27C0.6 8.37 1.5 9.27 2.6 9.27H10.6M9.27 10.6L10.6 9.27L9.27 7.93M4.6 1.93H8.6M4.6 4.6H7.27", "#787881"),
    pencil: falStroke("M5.85 2.35L7.35 0.85L9.6 3.1L8.1 4.6M5.85 2.35L0.6 7.6V9.85H2.85L8.1 4.6M5.85 2.35L8.1 4.6", "#494950", 1.2, 11, "round"),
    trash: falStroke("M1.35 2.6L1.85 9.85H7.85L8.35 2.6M3.85 4.85V7.35M5.85 4.85V7.35M0.6 2.35H9.1M2.89 2.22C3.06 1.3 3.87 0.6 4.85 0.6C5.83 0.6 6.64 1.3 6.81 2.22", "#494950", 1.2, 11),
    info: falStroke("M4.6 5.1H5.1V7.1M5.1 3.1V2.97M9.6 5.1C9.6 7.58 7.58 9.6 5.1 9.6C2.61 9.6 0.6 7.58 0.6 5.1C0.6 2.61 2.61 0.6 5.1 0.6C7.58 0.6 9.6 2.61 9.6 5.1Z", "#494950", 1.2, 11),
    chevron: falStroke("M4 5 6 7 8 5"),
    check: falStroke("M2.6 6.2 4.9 8.6 9.4 3.6", "#FAFAFA"),
    plus: falStroke("M6 2.5v7M2.5 6h7"),
    folder: falStroke("M1.5 9.5V2h3.1l1.2 1.6h4.7v5.9z"),
    auto: falStroke("M8 0C8 1.39 7.39 2 6 2C7.39 2 8 2.61 8 4M8 0C8 1.39 8.61 2 10 2C8.61 2 8 2.61 8 4M8 0V4M0 5.75C2.95 5.75 4.25 4.45 4.25 1.5C4.25 4.45 5.55 5.75 8.5 5.75C5.55 5.75 4.25 7.05 4.25 10C4.25 7.05 2.95 5.75 0 5.75Z", "#494950", 1, 10),
    send: falStroke("M8 12.7V4.2M4.4 7.8 8 4.2l3.6 3.6", "#2D2D2F", 1.5, 16),
    dots: <svg width={12} height={12} viewBox="0 0 12 12" fill="#494950"><circle cx="3.35" cy="6" r=".6" /><circle cx="6" cy="6" r=".6" /><circle cx="8.65" cy="6" r=".6" /></svg>
  };
  const Btn = ({icon}) => <span className="fc-ibtn">{I[icon]}</span>;
  const css = `
.fc-center { position: absolute; left: 45px; top: 50%; transform: translateY(-50%); width: 664px; opacity: var(--o, 1); }
.fc-assembly { width: 664px; }
.fc-drawer { width: 664px; height: calc(var(--h, 150) * 1px); overflow: hidden; margin-bottom: -8px; background: #f4f4f5; border-radius: 8px 8px 0 0; box-shadow: 0 -1px 0 0 rgba(18,18,22,.1), -1px 0 0 0 rgba(18,18,22,.1), 1px 0 0 0 rgba(18,18,22,.1), 0 20px 25px -5px rgba(0,0,0,.1), 0 10px 10px -5px rgba(0,0,0,.04); }
.fc-qhead { height: 28px; border-bottom: 1px solid rgba(18,18,22,.05); display: flex; align-items: center; gap: 8px; padding: 2px 4px 2px 12px; }
.fc-qhead-label { flex: 1; font-size: 12px; font-weight: 600; line-height: 120%; letter-spacing: -.02em; color: #202022; }
.fc-qrow { display: flex; gap: 8px; }
.fc-qrow-lead { height: 50px; align-items: flex-start; padding: 8px 4px 8px 12px; }
.fc-qrow-slim { height: 32px; align-items: center; padding: 4px 4px 4px 12px; }
.fc-qicon { width: 16px; height: 16px; flex: none; display: grid; place-items: center; }
.fc-qinfo { flex: 1; min-width: 0; display: flex; gap: 8px; align-items: center; }
.fc-leadgrid { flex: 1; min-width: 0; display: grid; grid-template-columns: auto auto 1fr; column-gap: 8px; row-gap: 2px; align-items: center; }
.fc-leadgrid .fc-qicon { grid-area: 1 / 1; }
.fc-leadgrid .fc-badge { grid-area: 1 / 2; }
.fc-leadgrid .fc-qtitle { grid-area: 1 / 3; justify-self: start; }
.fc-leadgrid .fc-qsub { grid-area: 2 / 3; justify-self: start; }
.fc-qtitle { font-size: 12px; font-weight: 600; line-height: 120%; letter-spacing: -.02em; color: #494950; white-space: nowrap; }
.fc-qsub { font-size: 12px; font-weight: 400; line-height: 120%; letter-spacing: -.02em; color: #787881; white-space: nowrap; }
.fc-qrow-slim .fc-qtitle { flex: 1; }
.fc-badge { display: inline-flex; align-items: center; justify-content: center; gap: 2px; flex: none; height: 14px; padding: 2px 4px; border-radius: 4px; font-size: 10px; font-weight: 600; line-height: 12px; letter-spacing: -.01em; white-space: nowrap; }
.fc-badge-next { background: rgba(133,83,255,.1); color: #5718c0; }
.fc-badge-num { background: rgba(18,18,22,.05); color: #19191a; }
.fc-badge-warn { background: rgba(249,143,7,.1); color: #b74806; }
.fc-acts { display: flex; align-items: center; gap: 4px; padding-left: 8px; flex: none; }
.fc-ibtn { width: 24px; height: 24px; border-radius: 4px; display: grid; place-items: center; flex: none; }
.fc-approve { display: inline-flex; align-items: center; justify-content: center; gap: 4px; flex: none; height: 24px; padding: 2px 8px; background: #202022; border-radius: 4px; font-size: 12px; font-weight: 600; line-height: 14px; letter-spacing: -.01em; color: #fafafa; }
.fc-approve-ico { display: flex; align-items: center; justify-content: flex-end; width: 10px; flex: none; }
.fc-prompt { position: relative; z-index: 1; width: 664px; height: 88px; display: flex; flex-direction: column; background: #fff; border-radius: 8px; box-shadow: 0 0 0 .5px rgba(18,18,22,.1), 0 20px 25px -5px rgba(0,0,0,.1), 0 10px 10px -5px rgba(0,0,0,.04); }
.fc-pfield { position: relative; height: 48px; display: flex; flex-direction: column; justify-content: center; padding: 16px 64px 16px 16px; }
.fc-ph { font-size: 14px; line-height: 160%; letter-spacing: -.02em; color: #787881; white-space: nowrap; }
.fc-send { position: absolute; right: 8px; top: 8px; width: 32px; height: 32px; display: grid; place-items: center; background: #d4d4d8; opacity: .5; border-radius: 4px; }
.fc-ptools { height: 40px; display: flex; align-items: center; gap: 8px; padding: 8px 10px 8px 8px; border-top: .5px solid rgba(18,18,22,.1); }
.fc-chip { display: inline-flex; align-items: center; justify-content: center; gap: 4px; flex: none; height: 24px; padding: 2px 8px; background: rgba(18,18,22,.05); border-radius: 4px; font-size: 12px; font-weight: 600; line-height: 14px; letter-spacing: -.01em; color: #19191a; }
.fc-chip-ico { display: flex; align-items: center; justify-content: flex-end; width: 10px; flex: none; }
`;
  return falFrame({
    comp,
    css,
    plate: "linear-gradient(135deg, #b9cfdc 0%, #a9c1d0 55%, #9fb6c6 100%)",
    label: "The fal Agent composer with the queue drawer rising behind it to reveal three queued requests",
    children: <div data-center className="fc-center" style={{
      "--o": 0
    }}>
        <div data-assembly className="fal-comp-a fc-assembly">
          <div data-drawer className="fc-drawer">
            <div className="fc-qhead"><span className="fc-qhead-label">3 queued requests</span><span className="fc-acts"><Btn icon="chevron" /></span></div>
            <div className="fc-qrow fc-qrow-lead">
              <div className="fc-leadgrid">
                <span className="fc-qicon">{I.queue}</span>
                <span className="fc-badge fc-badge-next">Up next</span>
                <span className="fc-qtitle">Upscale the hero shot to 4K</span>
                <span className="fc-qsub">Runs after the current work</span>
              </div>
              <span className="fc-acts"><Btn icon="pencil" /><Btn icon="trash" /><Btn icon="dots" /></span>
            </div>
            <div className="fc-qrow fc-qrow-slim">
              <span className="fc-qicon">{I.queue}</span>
              <span className="fc-badge fc-badge-num">2</span>
              <span className="fc-qinfo"><span className="fc-qtitle">Generate B-roll variations</span><span className="fc-badge fc-badge-warn">Needs approval</span></span>
              <span className="fc-acts"><span className="fc-approve"><span className="fc-approve-ico">{I.check}</span>Approve</span><Btn icon="info" /><Btn icon="trash" /><Btn icon="dots" /></span>
            </div>
            <div className="fc-qrow fc-qrow-slim">
              <span className="fc-qicon">{I.queue}</span>
              <span className="fc-badge fc-badge-num">3</span>
              <span className="fc-qinfo"><span className="fc-qtitle">Animate the opening sequence</span></span>
              <span className="fc-acts"><Btn icon="pencil" /><Btn icon="trash" /><Btn icon="dots" /></span>
            </div>
          </div>
          <div className="fc-prompt">
            <div className="fc-pfield"><span className="fc-ph">Describe what you want to create...</span><span className="fc-send">{I.send}</span></div>
            <div className="fc-ptools">
              <Btn icon="plus" />
              <span className="fc-chip"><span className="fc-chip-ico">{I.folder}</span>Sporelight</span>
              <span className="fc-chip"><span className="fc-chip-ico">{I.auto}</span>Auto</span>
            </div>
          </div>
        </div>
      </div>
  });
};

A chat runs one turn at a time. Everything else waits in a queue that you can see and edit. The queue has two lanes.

| Lane              | How rows get there                                                               | Priority                   |
| :---------------- | :------------------------------------------------------------------------------- | :------------------------- |
| **Your messages** | You press `Enter` while a turn runs                                              | Always run first, in order |
| **Queued work**   | You press **Add to queue**, or the agent schedules a continuation or a plan step | Run after your messages    |

## Send while the agent works

Press `Enter` while a turn is running. The message appears in the chat with an **Up next** badge and runs as soon as the current turn settles. The agent reads the queue before it continues, and it reconciles queued work that your new message makes stale. It can edit, remove, or reorder its own queued rows. It never touches your messages.

## Add to queue

Press `Alt+Enter` (`Option+Enter`) or click **Add to queue**. The message goes to the queued work lane. Use this for follow-ups that must wait for the current result, such as "then export everything as a zip".

## The queue drawer

The queue drawer sits behind the composer and lists **Queued requests**. It rises as rows are added.

<QueueDrawerComp />

For each row you can:

* Drag it to reorder, or use move up and move down.
* Edit its text.
* Cancel it.

A separator, **Runs after your requests**, marks where your pending messages end and queued work begins.

Each row shows a phase. **Waiting for source** means the row needs a previous run to land. **Approval waiting** means a checkpoint blocks it. **Chain halted** means you stopped the chain. **Ready** rows dispatch next.

## Plan steps in the queue

When a [plan](/docs/documentation/agent/plan-cards) runs, every remaining step becomes a queue row. Removing a step removes the steps that depend on it. Approval checkpoints hold their row until you approve.

## Stop

The send button becomes a stop button while a turn runs. `Escape` triggers it. Press `Escape` twice to confirm.

Stop escalates in two presses:

1. **Halt the chain.** Queued rows stay in the drawer but do not dispatch. Continuations from landed runs wait.
2. **Cancel generations.** In-flight runs are cancelled. Their cells show **Cancelled**. Dependent rows are parked with **Source run stopped** rather than deleted.

To resume a halted chain, send a message or click resume in the drawer. Resume clears the halt on every waiting row at once.

## Cancel one generation

Hover an in-flight cell and click the cancel button. Only that run stops. The rest of the batch continues.

## Cost approvals

A run that exceeds your [spending cap](/docs/documentation/agent/spending-caps) waits for approval. While it waits, the agent cannot submit more runs in that chat. Approve or reject it in the chat.
