vars, the values a run is given, and output, the fields it must hand back. Both are declared on the config, so the same definition can be run a thousand times with different inputs and every result comes back in the same shape.
Inputs: vars
A task declares its inputs exactly as an agent declares its variables — same field, same model, same {{vars.name}} templating. See variables for the templating rules themselves.
string
required
Letters, digits and underscores, starting with a letter or underscore. Read back in the prompt and in tools as
{{vars.name}}.string
default:""
For you and for the TaskCoPilot. Never substituted anywhere and never shown to the model.
string | null
default:"null"
What
{{vars.name}} resolves to when the run supplies no value. Null means empty unless supplied.boolean
default:"false"
Refuse the run rather than resolve this to nothing. A
default satisfies it.What a run supplies
POST /v1/tasks/{task_id}/runs takes a flat {"vars": {name: value}} map of strings. Values are matched to declarations by name:
- A name the run supplies wins over that variable’s
default. - A name the run omits falls back to the
default. - An empty string is a deliberate blank, not a request for the default.
- A
requiredvariable with neither a value nor a default is refused before anything is compiled. No model is called and no tool runs, so the failure costs nothing. - A value for a name the task does not declare is refused, not dropped.
emial would produce plausible nonsense instead of an error.
Both refusals are a 400 from the run endpoint, in the standard error shape. The undeclared-name one reads:
{{vars.email}} where it matters is what makes the value land in the right place.
Output: output
A flat list of the fields the model must produce. At least one, at most 25.
No arrays and no nested objects. A task that wants to return five talking points returns one
string containing them. That is a stated limitation, and it is what keeps a run’s result mergeable into a flat column space downstream — which is how an email batch maps a task’s fields onto a CSV’s columns.
Two rules are enforced when you save:
- A name may not appear twice in
output. Each name gets one type and one description, and the later entry would silently win. - A name may not be both a variable and an output field. A run’s inputs and its output are read side by side, so a collision would make one of the two unreachable. Rename one of each pair.
How the output is actually collected
The model does not “return” the output, and understanding what does happen is the difference between a task that finishes reliably and one that does not. When a run starts, the task gains one generated tool namedsubmit_result, whose arguments are exactly your output fields:
output, and no reply is generated afterwards. If the arguments do not match, the model is told what is wrong and may call it again. Calling it a second time after a valid result is refused.
Every field is required and nullable
In that tool, every field is required, and every field acceptsnull. The model has to say something about each one, including “I could not find it”.
An optional field would let the model silently omit the address it failed to find — and then “omitted” and “not generated yet” look identical to whoever reads the row. One of those is a fact and the other is a bug, and nothing downstream can tell them apart. Requiring the field and allowing null keeps the difference visible.
Which means the null case has to be in the description. The description is the only instruction the model gets about what belongs in that field — it becomes the argument’s description in the tool schema, so it is literally the prompt for the value.
Writing a description the model can act on
The pattern in the right-hand column: say what good looks like, say where to look, and say what to send when the answer is not findable.
In the dashboard, the Inputs and output section flags an output field whose description is blank, because a blank one is a quality bug rather than a missing note.
Next
Tools and MCP servers
Give the task somewhere to look before it fills those fields in.
Running and runs
Supply the inputs, read the output, and follow the trace.