Comparisons

    Background tasks or blocking tool calls

    Dispatch anything over 30 seconds. Blocking tools return request IDs in the same turn; background tasks collect finished media from the task list.

    Versely Team7 min read

    A blocking tool call returns when the function returns, not when the video is done. generate_videos comes back with generation_started and a request_id. The clip is still rendering. If the next thing you need is that finished file (captions, a merge, a post), waiting inside the same agent turn is the wrong collection model. Anything that will sit there for more than about 30 seconds should be dispatched as a background task and collected later.

    That threshold is not a vibe. It is the cutoff spawn_background_task is written for: movie-plus-music, slideshow-to-reel, bulk generation, generate-then-post, or any chain that combines several generations. Under 30 seconds, and with no downstream step that needs the finished URL, stay in the turn. Over it, spawn.

    What a blocking call actually hands you

    Inside a chat turn the agent calls tools one after another. Each call occupies the turn until it returns. For image, video, and music tools that return is a dispatch receipt, not an asset:

    What you called What comes back in the turn What is still outstanding
    generate_images / generate_videos status: "generation_started", tasks, request_ids The pixels. Poll or wait.
    generate_music { taskId } The mix.
    spawn_background_task status: "task_created", task_id, task_name, total_steps Every step after step zero.

    The blocking path is the right one when the receipt is the result you needed: a single still, a one-shot clip you will look at in the thread, a model lookup, a credit check. The agent can keep talking, the generation finishes on its own, and the app merges the finished media into the conversation later.

    It is the wrong path the moment step two needs the file from step one. Captioning a video that is still queued, stitching four clips that have not landed, posting a carousel whose slides are still generating: those are not "one more tool call." They are a pipeline. If you leave them in the turn, the stream stays open while the agent either guesses at URLs that do not exist yet or sits in a wait loop that should have been a wait_generation step on a background task.

    Automating a multi-step content task is the shape this is built for. A multi-scene movie is the textbook case: several generations, a wait after each, then a merge. None of that belongs in one chat round-trip.

    How result collection changes when you go async

    Spawn is fire-and-forget. The tool inserts the row, kicks executeTaskAsync, and returns task_id immediately. The chat turn can close. Collection moves off the reply and onto three surfaces:

    1. The task itself. GET /api/v1/agentic/tasks lists your recent background tasks (default 20, cap 50) with status, current_step_index, total_steps, error_message, timestamps, and a derived inbox card: up to three media_urls plus an optional result_summary. There is no outbound webhook. Versely is poll-only.
    2. The conversation thread. GET /api/v1/agentic/conversation/:id/tasks is the server-owned list for that chat. The app merges those rows into the message list by created_at and subscribes to them, so a completion that lands after the stream closed still appears in the thread.
    3. Inside the pipeline. Later steps do not read the chat. They read prior step results through $step_N.field. After a generate you put a wait_generation step that polls the table (user_images, user_videos, user_music, slideshows, ai_movies) until is_generating is false. Default wait is 15 minutes per step if you omit timeout_seconds; the poll interval is 10 seconds. When it returns, downstream args can use $step_N.media_url, $step_N.audio_url, or $step_N.all_urls.

    That third surface is the actual reason to spawn. A blocking turn cannot honestly say "now caption the video I just started," because it does not have the URL yet. A background task can, because wait_generation is a first-class step, not a hope that the model will poll check_generation_status for you before the turn ends.

    Statuses you will see on the task row: pending, running, scheduled, completed, failed, cancelled. Five tasks may be pending or running at once per user; scheduled ones do not count against that cap. Spawn number six and you get an error telling you to wait or cancel.

    The 30-second rule in practice

    Use wall-clock of the chain, not the first tool.

    Stay blocking when:

    • You asked for one image or one short clip and you will judge it in the thread before doing anything else.
    • The next action depends on your taste, not on a URL. "Make it warmer" is a new turn, not a step.
    • The call is metadata: estimate_cost, check_credits, get_model_input_schema, list_user_workflows.

    Spawn when:

    • Two or more generations have to finish before a later step can run.
    • You are going to post, caption, merge, or notify, and those tools need finished media.
    • The job can start later (scheduled_at as an ISO 8601 UTC datetime). A blocking call cannot wait until 9am for you.
    • You already know you will run it again. Every spawn auto-saves a reusable workflow keyed on task_name.

    A useful prompt is explicit about both the dispatch and the collection:

    Run this as a background task named "Kitchen Product Reel". Theme: warm daylight home-kitchen product demo, 9:16, captions on the lower third. Steps: generate four stills, wait until they are done, animate the first, wait, add captions, notify me. Do not sit in this chat turn waiting for the renders.

    You keep talking. The task list is where the files show up. If a run looks stuck, check or resume it rather than re-issuing the same blocking chain. Credit-wise, check the balance before you dispatch: a five-step task that dies on step four still spent steps one through three. There is no free allowance to absorb a half-finished pipeline; every generation costs credits, and pricing is the live tariff.

    What does not change

    Spawn does not make generation faster. It changes who is allowed to wait. The pixels still take as long as the model takes. You still poll. You still pay per generation.

    It also does not replace judgement gates. If step two is "look at the four stills and pick one," do not encode a pick you have not made. Spawn the generations, collect from the task list, then start a new task (or a new blocking call) with the chosen URL. Background tasks will happily proceed past a decision you wanted to make.

    The nearby habit of keeping the conversation going while a long job runs is the other half of this. This post is the dispatch rule and the collection model. That one is what you do in the foreground once the job is gone.

    FAQ

    Can I chain generate_videos and add_video_captions in one blocking turn?

    Not reliably. The generate call returns request_ids, not a video_url. Captioning needs the finished file. Put a wait_generation step between them on a background task, and point the caption step at $step_N.media_url. If you need to approve the clip first, stop after the generate and caption in a later turn.

    Where do I look when the chat reply just says the task started?

    GET /api/v1/agentic/tasks for the inbox card (status, step index, up to three media URLs). For the same job in the thread you were in, GET /api/v1/agentic/conversation/:id/tasks. There is no customer webhook to register. If you close the app, the task keeps running; the list is the source of truth, not the scrollback.

    What if I already have five tasks running?

    Spawn returns an error: maximum is five pending or running tasks per user. Cancel one that you no longer want, or wait. Scheduled tasks do not count toward the five until they start.

    Does spawn wait for the first tool the way a blocking call does?

    No. The spawn call returns as soon as the row is inserted and execution is kicked off. Even step zero has not necessarily finished. If you need the first still in the same breath as the spawn receipt, you spawned the wrong job: generate that still as a blocking call, then spawn the rest with the URL as a media_assets input.