Watching Tasks
While Streaming Updates monitors a single task, watchTasks / TaskWatcher lets you subscribe to updates across all tasks — useful for building dashboards, monitoring systems, or audit logs.
For a run that has already finished, the app itself answers where did the time go? — see The report panel at the end of this page.
Basic Usage
Section titled “Basic Usage”import { watchTasks } from '@requence/task'
const stop = watchTasks({ since: new Date(), onUpdate(update) { console.log(`[${update.type}] Task ${update.context.taskId}`) },})
// Later — stop watchingstop()watchTasks returns a function that stops the watcher when called. It is also an AsyncIterable:
const watcher = watchTasks({ since: new Date() })
for await (const update of watcher) { console.log(update.type, update.context.taskId)
if (shouldStop) { watcher() // stop watching break }}from requence.task import TaskWatcherfrom datetime import datetime
watcher = TaskWatcher(since=datetime.now())
# Synchronous iterationfor update, incomplete in watcher.sync().updates: print(f"[{update['type']}] Task {update['context'].task_id}")# Async iterationasync for update, incomplete in watcher.updates: print(f"[{update['type']}] Task {update['context'].task_id}")Call watcher.stop() to disconnect:
watcher.stop()Options
Section titled “Options”| Option | Type | Description | |––––|——|———––| | since |
Date | Required. Only receive updates after this timestamp. | |
accessToken | string | Access token (falls back to env / config file) |
| filter.only | string[] | Only receive specific update types (e.g.
['taskEnd', 'taskError']) | | onUpdate | function | Callback for each
update | | onConnect | function | Called when the connection is
established | | onReconnecting | function | Called when the connection
is being re-established | | onError | function | Called on connection
errors |
| Option | Type | Description | |––––|——|———––| | since |
date | Required. Only receive updates after this timestamp. | |
access_token | str | Access token (falls back to env / config file) | |
on_connect | callable | Called when the connection is established |
Incomplete Updates
Section titled “Incomplete Updates”Each update includes an incomplete flag. When True, it means the watcher connected after the task had already started — the taskStart event was missed. Use this to decide whether to ignore or partially process the update.
Sub-Task Updates
Section titled “Sub-Task Updates”Every update also carries a rootTaskId — the ID of the root task when the update belongs
to a sub-task, and null for a top-level task. A
dashboard that should show one row per user-started task groups on it and ignores the rest.
Delivery Guarantee
Section titled “Delivery Guarantee”When a task was started with requireAck: true, watchTasks / TaskWatcher automatically sends the ACK as soon as the terminal event (taskEnd, taskError, or taskAborted) is received — no extra code required.
The ACK is sent using the same access token passed to watchTasks, so the backend can confirm the correct subscriber received the result. See Delivery Guarantee for the full explanation.
The Report Panel
Section titled “The Report Panel”Every finished task run carries a performance report, in the app, on the task page.
Open it with the chart button in the toolbar — beside the run duration — or with
Cmd/Ctrl + I. Esc closes it again.
It is finished runs only, and the button is disabled while the task is still going: a run that has not ended has open work whose durations are not facts yet. Nothing is calculated in advance either — the report is built the moment you open it, from the task’s own update history, and nothing is stored.
The panel has two tabs.
Summary
Section titled “Summary”Always the whole task, however many message paths it took, and never truncated.
Three lines at the top: how long the run took, how long it waited before it started, and
how many of the template’s nodes actually ran (5 of 7).
Then one row per node that ran — however many times it ran:
| Column | What it says |
|---|---|
runs |
How many times the node ran, one per dispatch. 2 ✗ counts the runs that failed; 3 ⊘ counts the runs the task’s own end cut short. |
work · max |
A run from its start to its end, minus any stretch it spent deferred. Total over every run, then the longest single run that ended on its own clock. |
wait · max |
How long a dispatch sat before a service picked it up — it grows when the service is busy, still starting, or not running at all. |
defer · max |
How long runs stood parked waiting for something outside the task, such as a form somebody has to fill in. Never counted as work. |
re-dispatches |
How many times Requence sent the node’s work again — a configured retry, a reroute to a different version, work that outlived its time limit, or a watchdog stepping in. |
A continuous node keeps its row, but its work cell reads
connected 4m 11s: being attached is not work, so it is not added to the work totals
and the row is pinned to the top. A short connected span on a long task is worth a look —
it means the attachment ended early.
The footer sums the columns per node kind (service, logic, …) and then in total.
Nothing in the report has to add up, and that is deliberate: nodes run at the same time, so the per-node numbers legitimately sum past the wall clock. There is no “unaccounted time” line, because there is no honest one to write.
Timeline
Section titled “Timeline”The same run as a waterfall: one bar per run, oldest first, with the node’s name and the program that ran it beside each bar. Each bar is split into its wait, work and defer stretches, and hovering it gives you the numbers, the service name and version, which replica ran it, and — where there is one — the error.
- A bar with no right edge never reported an end at all. A hatched bar is either that, or a continuous node’s attachment; neither may be read as work.
- Small ticks inside a bar are re-dispatches — the same work sent again.
- A sub-task node’s bar is a link: click it to open the child run and read its own report. The bar covers the child’s whole duration; which node inside the child was slow is a question for the child’s report.
- A stretch where the only thing happening is a defer is cut out of the axis and drawn as a hatched band with the removed time written inside it — otherwise a form that waited three days for a human would leave the rest of the run one pixel wide. Time spent waiting is never cut: that is exactly what the report is for.
The axis is real wall clock, so the scale’s labels jump across a band by the amount the band says it removed.
If the canvas has a trace selected — one message’s path through the graph — the timeline shows just that path. With nothing selected it shows the whole run: every node’s every run, interleaved over time. The panel only reads the selection; it never changes it. Note that with a trace selected the two tabs are at different scopes: Summary still covers the whole run.
Only the newest 500 runs are drawn, and the panel says so when it had to cut. Newest, because a run that failed usually failed at the end. Nothing is lost from the numbers — Summary still counts every run.