Documentation Index
Fetch the complete documentation index at: https://docs.cyberwave.com/llms.txt
Use this file to discover all available pages before exploring further.
timed_condition is a stateful conditional node that holds the
“active” signal for at least min_duration_s seconds before firing,
then re-arms only after the input has been inactive for at least
cooldown_s seconds. It is the generalised successor to the original
presence_dwell node — the dwell semantics are still the default
behaviour, but the input now accepts any signal shape (not just
detection arrays), so the same primitive applies to non-vision
workflows.
Backward compatibility. Workflows authored before the rename use
the legacy presence_dwell subtype. The cloud executor and edge
codegen continue to accept that string and resolve it to this same
schema, so existing zone-based intrusion alerts keep working without
edits. New nodes created from the palette use the canonical
timed_condition subtype.
When to use this node
detection_event_gate (the existing class-change gate) emits on
the first frame a class appears. That’s correct for “person
detected at all” alerts but wrong for “person has been loitering in
zone A for 10 seconds” — there is no time component in the event-gate
state. timed_condition is the time-dimension counterpart:
| Node | When it fires |
|---|
detection_event_gate (on_enter) | On the first frame a target class appears. |
timed_condition (mode = "sustained") | On the frame the cumulative “active” duration crosses the threshold. |
timed_condition (mode = "timeout") | On the frame the cumulative “inactive” duration crosses the threshold. |
timed_condition (mode = "debounce") | Immediately on the first activation, then suppressed for cooldown_s. |
Modes
| Mode | Behaviour |
|---|
sustained | Fires once the signal has been continuously active for ≥ min_duration_s. Re-arms after cooldown_s of inactive signal. |
timeout | Watchdog. Fires once the signal has been continuously inactive for ≥ min_duration_s. Re-arms automatically on the next active frame (cooldown is unused). |
debounce | Leading-edge fire with suppression. Fires immediately on the first activation, then suppresses re-fires for cooldown_s regardless of signal state. min_duration_s is unused. |
All three modes share the same per-node state and the same input
predicate (active-when-truthy / above-threshold / non-empty), so
swapping modes never requires re-wiring the upstream graph. Unknown
modes are rejected loudly by the cloud executor and the edge codegen
so a stale UI can’t silently run the wrong shape.
| Field | Type | Description |
|---|
signal (legacy: detections) | array | boolean | number | The signal to evaluate. Detection array + non-empty (after the optional class filter) counts as active. Boolean is active when truthy. Number is active when ≥ threshold. The port is named detections for back-compat with workflows authored before the rename. |
mode | string | One of sustained (default), timeout, or debounce. See the Modes table above. |
min_duration_s | number | Continuous “active” seconds required before the gate fires (sustained) or continuous “inactive” seconds before the timeout fires (timeout). Ignored by debounce. 0 fires on first observation. |
cooldown_s | number | Inactive seconds required after firing before the gate re-arms (sustained) or suppression window after firing (debounce). Ignored by timeout, which re-arms on the next active frame. |
target_classes | array | Optional class allow-list applied when the signal is a detection array. Empty / null matches any class. Ignored for boolean and number signals. |
threshold | number | Numeric threshold used when the signal is a number. The signal is active when value ≥ threshold. Default 0. Ignored for boolean and array signals. |
Outputs
| Field | Type | Description |
|---|
condition_met | boolean | true on the frame the time threshold is first crossed; false otherwise. |
dwell_ms | number | How long the signal has been continuously active when the gate fires; 0 when not firing. (Kept under the legacy dwell_ms name for back-compat with downstream wiring.) |
matched_detections | array | Detections from the current frame that passed the target_classes filter when the signal is a detection array; empty list otherwise. Handy to forward into send_alert.metadata so an operator can see what triggered the gate. |
first_seen_ts | number | null | Monotonic seconds when the current active window started; null when the signal is currently inactive. Use it for relative “active for N seconds” math inside the same edge / runner process, not for cross-process correlation. |
State machines
State is keyed on node.uuid so two instances on the same workflow
run independent timers — typical when one zone tracks people and
another tracks vehicles.
Sustained
┌─────────────────────┐ signal becomes active ┌──────────────────────┐
│ IDLE (re-armed) │ ──────────────────────────▶│ ACTIVE (counting) │
└─────────────────────┘ └──────────────────────┘
▲ │
│ │ elapsed_ms ≥ min_duration_s
│ ▼
│ cooldown_s seconds ┌──────────────────────┐
└──────────────────── of inactivity ─────────│ COOLDOWN (fired) │
└──────────────────────┘
Timeout
┌─────────────────────┐ signal goes inactive ┌──────────────────────┐
│ IDLE (re-armed) │ ────────────────────────▶│ COUNTING (inactive) │
└─────────────────────┘ └──────────────────────┘
▲ │
│ │ elapsed_ms ≥ min_duration_s
│ next active frame ▼
└────────────────────────────────────────── ┌──────────────────────┐
│ FIRED (one-shot) │
└──────────────────────┘
The next active frame closes the inactive window and re-arms the
gate immediately — there is no cooldown. Designed for “alert if the
signal stays absent” patterns (occupancy timeout, missing
heartbeat, stale telemetry).
Debounce
┌─────────────────────┐ signal becomes active ┌──────────────────────┐
│ IDLE (re-armed) │ ─────────────────────────▶│ FIRED │
└─────────────────────┘ └──────────────────────┘
▲ │
│ │ cooldown_s elapsed
│ ▼
│ ┌──────────────────────┐
└────────────────────────────────────────────│ SUPPRESSING │
└──────────────────────┘
Ignores min_duration_s and the signal value during the
suppression window — re-fires only after cooldown_s has elapsed,
on the next active frame.
Authoring
In the workflow editor inspector for the timed_condition node:
- Mode — pick
Sustained, Timeout, or Debounce. The other
knobs grey out automatically when the selected mode ignores them
(e.g. Min Duration is disabled in Debounce, Cooldown is
disabled in Timeout).
- Min Duration (s) — how long the signal must persist before the
gate fires (active for
Sustained, inactive for Timeout). 0
for “fire on first frame” semantics; 5–30 for loitering /
intrusion alerts; higher for “left unattended” scenarios.
- Cooldown (s) — for
Sustained, how long the input must stay
inactive after a fire before the gate re-arms. For Debounce,
the suppression window after the leading-edge fire. Tune to your
alert review cadence.
- Target Classes — optional class allow-list (chips), only used
when the upstream signal is a detection array. Leave empty when
the upstream
spatial_filter is already class-scoped.
Companion nodes
spatial_filter — the canonical upstream for
detection-array signals: scope the gate to a polygon zone.
send_alert — the canonical downstream: fire an alert with the
polygon and elapsed duration baked into the payload.
Edge implementation
When timed_condition (or its legacy presence_dwell alias) sits
one hop downstream of spatial_filter (or directly downstream of
call_model) on a camera_frame-triggered workflow, the codegen
inlines the state machine into the generated wf_*.py worker as
three module-level globals (_dwell_first_seen_<n>,
_dwell_alerted_<n>, _dwell_last_clear_<n>) and a per-frame block
reading either _detections (post-spatial-filter) or
results.detections (no spatial filter). On the frame the active
window first crosses min_duration_s, the implicit
cw.publish_alert(...) loop fires exactly once over _dwell_matched.
The gate then stays silent until the input has been empty for
cooldown_s, at which point all three globals reset together so the
next entry can fire again.
State is in-process — restart the edge runtime and the timer resets
to IDLE.
The edge codegen handles the detection-array signal path for all
three modes (sustained, timeout, debounce) and stays bit-for-bit
identical to the cloud executor — the codegen parity tests guard the
algorithm. Boolean / number signals still run through the cloud
executor for now.
End-to-end alert pipeline
A common pattern is “polygon → timed condition → email”:
- Edge perception workflow (camera_frame trigger):
camera_frame → call_model → anonymize → spatial_filter → timed_condition.
The edge worker emits one cw.publish_alert(...) per gate-fired
intrusion event.
- Cloud reactive workflow (alert trigger):
alert_trigger(twin=…, alert_type=detection) → send_email.
The cloud workflow runner wakes on every edge-emitted alert and
sends the email — no per-frame cloud cost, only the dwell-gated
one-shots.
The timed_condition cloud executor exists too and behaves
identically, but it is intended for cloud-only graphs where
detections (or boolean / number signals) are produced inside a single
WorkflowUtils.execute() call (e.g. fed by a non-camera trigger). It
is not designed for state to persist across separate
alert-triggered cloud executions.