Skip to main content
Everything is a task: a form someone fills, a deck they go through, a micro-app with steps, a workflow, a pipeline. A component, a template, an app, a workflow’s Task Start or a pipeline IS the task. There is no separate task file: each keeps its own manifest (or, for a workflow, its Task Start) as the single home of what it is for. Every task is an MCP task (docs/mcp/MCP_TASKS.md). The switch makes it a task. Every asset with a manifest carries an Available switch on its own lane in Studio. Switched on, it is offered to every canvas in the org and lands on the map in the task category. Nothing is written twice: the task reads its title, description and selection text from the asset’s own manifest. A task is named by one URI, everywhere: on the map, in the task table, in every hand-off, on MCP. unoverse://tasks/unoverse/about-you is the about-you task, whatever it is built from; written short, unoverse/about-you. The URI is an address, not a file: it points at the asset that is the task (docs/mcp/MCP_TASKS.md § 2a, ruled 2026-10-10).

Every task has the same shape

A presentation, an application form and a chooser are built the same way. Four things make a task, whatever it is for: Every task also carries the same discovery meta: title, description and whenToUse. The skill is the only thing that changes. An Agent presenting a deck and an Agent taking a person through a form are handed the same thing: the step on screen and its props. The presenter’s skill says what is on them; the application’s skill asks for each input: true prop still empty. Nothing about the task itself says which it is. A workflow or a pipeline is a task that runs its own steps. It is loaded the same ways and takes the standard input (its Task Start’s schema). Its steps are the nodes of its workflow: nobody else fills or moves them, and it reports its progress node by node on its card. It finishes as every task does, handing back its output. Its run is its state: it is not saved to the task table, so it cannot yet be picked up again after a restart (docs/mcp/MCP_TASKS.md step 11). “Agent” no longer names a task. Until 2026-10-10 an Agent file (agents/*.yaml) listed a workflow on the map. That job is Task Start’s now, and “Agent” means someone who calls tasks: a role with instructions, ways to be reached and woken, memory, and the tasks it may call (PROPOSED, the coming Agents design).

Where it shows

A task shows wherever it is needed, and it is the same task in each place:

How it is loaded

One state

While a task is active it has one state: the step it is on and the values it holds. Everything connected to it reads and writes that one state: the screen, a voice, a chat, an LLM, the node running it. A value typed on screen is the value the voice hears; a value the voice fills shows on screen; a step moved anywhere moves everywhere. Nothing keeps its own copy. A task with a finish line is saved, however it was loaded. Each attempt at it is one row in the task table, owned by the person and naming the task’s URI. The person is who is signed in, or the guest the runtime issued; a guest’s rows become theirs when they sign in. Opening a task finds the person’s open attempt at that URI, or starts a new one, so a form half filled on a page carries on in an app, after a reload, or after a dropped call, and a task finished once can be done again. Every step handed out carries the attempt’s id as its ref, and every hand-off and submit sends it back, so a call always names the attempt it belongs to. A task with no finish line is never saved. A presentation carries its position in each call, the step marker, and the screen holds it. Each confirmed step saves the screen’s state to the row. A change made anywhere else (a voice, a node, an Agent) is published to the screen. The submit carries the screen’s answers and they win over what was saved, so nothing typed is lost. The submit completes the row, and a completed row is kept, marked completed, never deleted: a call that names a completed attempt does nothing, rather than finding no row and starting the task again.

What can be done to it

Every task offers the same features, however it is worked: A field is required unless the task marks it optional (required on its outputs, and on an input: true prop). Only a required field holds back a step or the submit. Two different ways reach them, with the same features and the same rules: An LLM may fill many fields in one go, from whatever it can reach: Spatial, the conversation, memory. What it fills is held, not final: the person sees it and may change it, and only their submit makes it the answer. Connecting a node is a choice made on the canvas: a task with no voice or LLM wired to it is worked by typing alone. Either way nothing skips an empty field and nothing finishes a task short, and every fill is saved to the row and shown on the screen.

Run by a node

The Task Runner is the node that runs a task in a workflow. It is a PromiseNode: each call is one step of the state machine. It reads the task and its state, applies what arrived, saves it, publishes where the task now is, and answers. It holds nothing between calls; the task’s row is the memory. Each call publishes the task’s position to the person’s screen as app state, tasks.<ref> = { step, stepNumber, stepsTotal }, so whatever shows that task follows it. The call that finds nothing outstanding fires done, and result carries what the task finished with: its outputs, or an Agent’s result. Wire result into the next Task Runner to start the next task with it. Nothing but the task decides when it is finished. A voice, a chat or the person typing are only ways of filling it; the task runs without any of them, and none of them can finish it short.

Its events

A task is a node, and it sends its own events at the moments it saves, however it was run: Nothing else sends a task’s events. analytics is separate: it only reports to the page’s analytics tool, when declared, and never decides whether an event is written (Analytics).

The submit

The submit is the standard one, the same as any form’s, and it is just the last call:
  1. The person presses Enter. The page sends submit with the task’s URI and every answer.
  2. The submit goes to whoever opened the task. An Agent that opened it from Spatial gets the answers as its tool call’s result. A task with a binding has its own workflow called at its trigger, with the task’s URI and the answers, as the person. A binding may name a pipeline instead, binding: { pipeline: <name> }: the answers go to the pipeline’s Input Trigger, POST /api/pipelines/<org>/<name>/execute with the app’s own org, and the run is shown live in an agent-card, as any task that runs its own steps is. The pipeline is a task in its own right too: called directly, it is started and followed the same way.
  3. The Task Runner in that call lays the answers over the saved row. If nothing is outstanding, it completes the row and fires done and result; if a field is still empty, it holds the step back and publishes it, and the page shows what is short.
Nothing waits for the submit. Every call reads and writes the row, so a task is carried across as many calls, and runs, as it takes. Five rules keep that safe: A voice still on the call hears the task is done and ends. One task may span several runs; the timeline links them through the row. A page that leaves stops its runs. When the last connection of a page’s conversation closes and does not come back within ten seconds, every run on that conversation stops: its Task Runners, its voice, all of it. The run is marked failed with the reason “the page closed”, and nothing fires done or result, so no next task starts from a page nobody is on. Ten seconds rides out a reconnect, and the task’s saved row keeps what it held.

Not built yet

The standard above is ruled. These parts of it are not yet true in the code:
  • One name. The page saves a task under its short name (unoverse/about-you), the Task Runner under what it was given, and map rows carry a unoverse://tasks/ address. All become unoverse://tasks/<org>/<name> (docs/mcp/MCP_TASKS.md § 2a, steps U1 to U9).
  • Saved by URI. A task loaded by URI is not saved today.
  • Analytics decides saving. The page saves a task, and its submit completes it, only when the manifest has an analytics action. The finish line (outputs and a submit) decides both; an analytics event on submit stays optional and changes neither.
  • Attempts. A row is found by workflow, person and task today, and a finished task cannot be started again for the same person. It becomes one row per attempt, found by the person’s open attempt at the task’s URI, and every step carries the attempt’s id.
  • Optional fields. Whether a prop can be marked required is not checked; today every collected answer holds the step back.
  • Workflow tasks saved. A workflow’s or a pipeline’s run is not saved, so it cannot be resumed after a restart (docs/mcp/MCP_TASKS.md step 11).
  • Its own events. Today seven places in the conversation code send a task’s events, the finish only when the manifest declares an analytics action, and memory keeps its own task rows beside the journey. A task run by the Task Runner on a page sends none, so Signal never sees it. All of it moves into the task’s node. Built so far (2026-09-28): the Task Runner sends opened and finished itself. Not yet: step (it needs the saved attempt to know the step changed), and removing the old senders.
  • Guest rows linked on sign-in. Designed, not checked.
  • The submit calling the binding. Built for a page’s template (2026-09-28): its submit calls the template’s binding with the answers, and the Task Runner in that call finishes it; the wait is gone. Seen live on about-you: typed, no voice, Enter fired done and result. Not built for a task in an app or on the map.
  • A page’s task starting itself. Built for a page’s template (2026-10-10): a binding with autoTrigger: true starts its task once as the page draws it, so a template that only shows what its workflow reads fills without a press. Not yet seen live.
  • Completing once. Completing a row today clears it, and nothing stops two calls both firing result.
  • The definition on every call. A Task Runner loaded by id reads the saved answers but not the task’s definition.
  • Who may submit. How a guest’s submit passes the check for starting a workflow (/api/executions needs workflow:author) is not checked.
  • The trigger rule. Nothing checks that once-per-task nodes hang off the Task Runner.
  • The voice ending on done. Not built.
  • Props handed out. A step should carry its empty input: true props as fields to fill (each with its description), its filled ones as held with the value from the row, and never an input: false prop, which is fixed content. Today the Task Runner hands out only empty outputs as fields, and passes every prop on the step as held, input: false included, valued with its Studio preview or default: a mock that looks filled, so an input: true prop is never asked for.
  • Update Task is not saved. What the Update Task node fills is published to the screen but never written to the row.
  • The MCP tools. Which tools an LLM uses to read, fill, move and submit is not checked.

Doors

A task opens onto one of three doors:
  • Interface. A component or a template the customer completes on screen: a chooser, a form, a transfer, a page.
  • Workflow. A workflow on a canvas, fired at its Input Trigger (its Task Start), or an app whose binding fires a workflow, a workflow with an interface in front. A pipeline is the same.
  • MCP. A tool on another system. Reserved.
Every task is a native MCP tool. An interface renders and elicits over MCP. A workflow is a task-augmented tool: its call creates an MCP task, progress is the task’s status, and the run’s result is the tool’s result. A task is done only when its door says so.

Agents

An Agent is not a task: it is who calls tasks. One shape for every Agent, a folder agents/<name>/ holding agent.yaml and SOUL.md: its skills, character, model, daily budget, ways in (Spatial, Slack, email, its calendar) and the tasks it may call, each by its one URI (unoverse://tasks/<org>/<name>). An Agent lives in Spatial, so a person or another Agent finds it there and wakes it. How to write one: the unoverse-create skill’s agent reference; the design: docs/architecture/signal/AGENTS.md in the platform repo.

What happens when a workflow task is called

  1. A search surfaces the task row. The harness mints it as a tool.
  2. The caller calls it. The server creates an MCP task, pushes the face into the conversation with the task’s name and message, and fires the workflow at its trigger with the caller’s own identity.
  3. As the run moves, the face shows the latest four steps: the one running now and the ones just before it. An older step drops off the top as a new one starts, and the count (12 of 40) keeps the whole run. A step that waits on the person shows as waiting, and the person answers in the same conversation.
  4. When the run completes, the output node’s text lands in the face and returns to the caller as the tool’s result. The caller carries on with it in the same turn.
Nothing is fire-and-forget. A task that has not completed is a task still running.

On the execution timeline

A call to a task is drawn as a task, in amber with a door, under the Agent that called it. An MCP tool stays purple, a skill in force stays cyan. The model chose the task among its tools like any other, and the row keeps that count; what changes is what the person reading the timeline sees first: the outcome, not the plumbing.

Where the switch lives

On the asset’s own lane in Studio: Components, Templates, Apps, and the Agents lane for the Agent files. On a canvas, a switched-on task is installed from the Content Library, exactly like a skill.
  • Components and Templates: interfaces, with the selection text on their manifest.
  • Apps: an app keeps its manifest and its binding; switched on, it is an Agent with an interface in front.
  • Agent skills: what an Agent follows while doing a task. A skill’s switch makes it a skill on the map, never a task.