How HQ rows get a timeline on the Gantt

[Gallery, Gantt, and Calendar views](/help/hq-decisions-views) and [The four HQ decision sources and how they map into one log](/help/hq-decisions-source-kinds) both describe the Gantt on Action Pl…

12 min read·Updated July 14, 2026
On this page

What this article covers

Gallery, Gantt, and Calendar views and The four HQ decision sources and how they map into one log both describe the Gantt on Action Plans (hq.verinode.ai/actions) from the outside: which checkpoints each source shows and what each button does. This article goes one layer deeper, into the actual data structure that draws the bar. Every row on the Gantt, whether it is a decision plan, an intervention, a consent request, or a program violation, is drawn from one small piece of JSON attached to that row called agent_plan. On Verinode IQ's operator side, that JSON is written by the AI agent the moment an operator acts on a signal. None of HQ's four network-level systems have an agent that writes one, so a separate piece of code builds a stand-in version for every row before it reaches the board. This is the article about that stand-in: what shape it has to match, how each source's real timestamps get folded into it, and a few things it does to a bar that are not obvious just from looking at the screen.

Why the Gantt needs this at all

The Gantt is not a bespoke HQ view. It is the same DecisionTimelineView component Verinode IQ uses on the operator side, reused as-is. That component only knows how to read one thing off a row: an evidence.agent_plan object holding a plan_started_at timestamp and a steps array, each step carrying a title, a day_offset (the number of days after plan_started_at it falls due), and a completed flag. On the operator side, that object is real: it is the actual step-by-step plan an AI agent drafted, often with an email template or script attached to each step, and plan_started_at is stamped the moment the operator presses Start.

None of HQ's four source systems, the network data, or the network data / the network data, run anything through that agent. A flagged at-risk location was never "planned" by an AI; it was flagged by an HQ admin. A consent request is a form HQ sent, not a task list. Without something filling in that gap, Action Plans had nothing to draw for any HQ-sourced row: no agent_plan object means no timeline, full stop, and the surface read as broken rather than empty. The fix is a small builder function for each of the four sources that produces a JSON object shaped exactly like an agent's own plan, anchored on the row's real dates, so the same Gantt code that draws an operator's AI-authored plan draws an HQ row's real-world lifecycle instead.

Note

This only affects Action Plans. Decisions (hq.verinode.ai/decisions) is a gallery, and a gallery tile does not need an agent_plan at all, it just needs a title, a status, and a severity. The synthesis described here exists purely to feed the Gantt and Calendar views on Action Plans.

The anchor: plan_started_at

Every stand-in plan needs one anchor date that every step's day_offset counts forward from. That anchor is not "today," it is the moment the row's real lifecycle began, which is different for each source:

| Source | Anchor (plan_started_at) | |---|---| | Decision plans | The plan's activated_at timestamp, not its created_at | | Interventions | flagged_at, the moment the location was flagged | | Consent requests | created_at, the moment HQ sent the request | | Program violations | detected_at, the moment the check (or another detection path) turned it up |

Decision plans are the one exception worth calling out. A plan's stand-in timeline is not anchored on when it was drafted, it is anchored on when it went active. That matters because a plan sitting in draft has no activated_at value yet, and without one the builder returns nothing at all rather than a plan with a guessed anchor. A draft or paused plan simply has no agent_plan object, which is exactly why it never appears on Action Plans in the first place, only Decisions. See Network playbooks and directives as decision rows for the rest of that lifecycle.

The four step tracks

Each builder produces a fixed list of steps, in order, with a day_offset counted from that source's anchor above. The completed flag on every step, critically, is never computed from the calendar, it is read straight off the row's real status field. Day offsets only decide where a checkpoint sits left-to-right on the ruler; whether it is lit up green comes from what actually happened in core.*, not from how much time has passed.

Decision plans, anchored on activation:

| Step | Day offset | Marked complete when | |---|---|---| | Drafted | 0 | Always | | Activated | 0 | Always | | Adoption check | The gap to Completed, capped at 14 days, always at least a day before it | Once the plan's completed_at is set | | Completed | The actual day count from activation to completion, or 30 days out as a running placeholder while still active | Once the plan's completed_at is set |

Interventions, anchored on flagging:

| Step | Day offset | Marked complete when | |---|---|---| | Flagged | 0 | Always | | Contacted | 3 | Status is contacted, in_progress, or resolved | | In progress | 7 | Status is in_progress or resolved | | Resolved | The actual day count from flagging to resolution, or 21 days out as a placeholder | Status is resolved |

Consent requests, anchored on send:

| Step | Day offset | Marked complete when | |---|---|---| | Sent | 0 | Always | | Approved | The actual day count from send to approval, or 7 days out as a placeholder | The request's approved_at is set | | Closed | 3 days after Approved | Never. This step has no completion condition in the builder at all |

Program violations, anchored on detection:

| Step | Day offset | Marked complete when | |---|---|---| | Detected | 0 | Always | | Escalated | 3 | Escalation state is escalated | | Resolved | The actual day count from detection to resolution, or 14 days out as a placeholder | The violation's resolved_at is set |

Program audits get no track at all. There is no builder function for audits, no agent_plan object is ever attached to one. That is consistent with an audit's status never becoming Acted or Resolved on the shared board (see The four HQ decision sources): a row that can never qualify for Action Plans has no reason to carry a synthesized timeline. An audit only ever shows as a gallery tile on Decisions.

Heads up

Every offset above except the first and last step's real-outcome dates is a fixed guess, 3 days, 7 days, 14 days, and so on, not a measured duration. None of the four source tables records a timestamp for every status change, only for the events that actually get stamped (flagged, activated, resolved, and so on). Read a bar's overall shape and which checkpoint is next; do not read the exact day count between two checkpoints as a historical fact.

How the JSON becomes a bar

Once a row has an agent_plan with a plan_started_at, the Gantt turns each step into a due date (plan_started_at plus that step's day_offset, in whole days) and draws one colored segment per step inside a single rounded bar, separated by a thin grout line. Two things about that drawing are easy to miss just from looking at a bar:

The very first checkpoint on every source never draws a visible segment. Drafted, Flagged, Sent, and Detected all sit at day offset 0, the same day as the anchor itself. A segment's width on the bar is the gap between its own due date and the previous checkpoint's due date; when a checkpoint's due date is identical to the plan's own start (there is no "previous" checkpoint before the first one), that gap is zero, and a zero-width segment is dropped from the drawing entirely rather than rendered as a sliver. It still counts in the "X/Y steps" readout under the row's title, and it is always marked complete, but there is nothing on the bar itself to hover. For a decision plan specifically, both Drafted and Activated share day offset 0, so the visible bar effectively starts at Adoption check, the first checkpoint that actually has daylight between it and the plan's start.

A green segment can sit to the right of today's line. Because completed comes from the row's real status rather than from whether its day offset has passed, an intervention contacted the same afternoon it was flagged still shows its Contacted checkpoint at the fixed day-3 position, just already green, even though only a few hours have passed. That is not a rendering glitch: the checkpoint's position on the ruler is a heuristic placeholder, but its color is a fact about what your team has actually done. A pending checkpoint (not yet complete) is colored by how its due date compares to today instead: red once its due date has passed, full copper on the day it is due, a lighter copper the day before, and a faint copper tint further out.

The label cell to the left of the bar carries the row's title, the anonymized franchisee label or program name underneath where the row has one, and the "X/Y steps" readout with its own thin copper progress fill, all independent of which segments actually rendered on the bar. Hovering a rendered segment opens a small card with the checkpoint's name, its status in plain words, and its due date; clicking the bar, a segment, or the row's title all open the same workspace slider. See Gallery, Gantt, and Calendar views for the full ruler, legend, and Calendar-view walkthrough, none of which differs for HQ beyond what is described here.

Heads up

The hover card's Mark complete button belongs to the shared component and writes to the operator side's own your operator data table. It has no path back to the network data, or the network data, so clicking it on an HQ row does nothing to that row. A checkpoint moves forward only when the underlying record's real status changes, contact a flagged location, approve or let a consent request run its course, escalate or resolve a violation, activate or complete a plan, using that source's own Act, Park, or Ignore button on its tile, or from the workspace slider.

Two states an HQ row never reaches

The operator-side Gantt has two extra row states this synthesis never produces for HQ:

  • Planning, a pulsing badge shown while an AI agent is actively drafting a plan in the background. HQ rows have no background agent run behind them, so this state cannot occur.
  • Ready to start, shown when an agent has saved a plan with steps but the operator has not yet pressed Start, so there is no plan_started_at yet to anchor a bar. Every HQ builder sets plan_started_at in the same step that creates the steps array, there is no in-between moment where a stand-in plan exists without an anchor. A row either has no agent_plan yet (and does not appear on Action Plans at all) or it has one with its anchor already set (and draws a full bar).

In practice this means an HQ row on Action Plans is always either a full bar or entirely absent, never a placeholder badge waiting on someone to press a button.

What never makes it onto the Gantt

Because Action Plans only ever shows rows whose shared status is Acted or Resolved, several rows that do carry a synthesized agent_plan never actually reach the board:

  • A decision plan that has been paused drops off Action Plans the moment its status leaves Active, even though its agent_plan object is still attached underneath.
  • An intervention still queued, not yet contacted, stays Pending and does not appear.
  • A consent request that was declined, withdrawn, or expired stops appearing once it leaves the Approved state.
  • A program violation still open, not yet escalated, stays Pending and does not appear.

All four are still visible as gallery tiles on Decisions; they simply have nothing to draw on Action Plans until (or unless) their status crosses into Acted or Resolved.

Reading a bar in practice

  1. 1Look at the "X/Y steps" line first, it tells you how far along a row is even for the invisible first checkpoint you cannot hover.
  2. 2Find where the visible segments sit relative to today's copper line. A segment past the line that is still not green is late; treat it the same way you would treat an overdue item anywhere else on the platform.
  3. 3Remember a green segment ahead of today's line is real progress reported early, not a bug, since color tracks the actual record, not the ruler.
  4. 4Use the row's own Act, Park, Ignore, or Discuss button, on the tile from Decisions or from the workspace slider, to actually move a checkpoint forward. The Gantt is a read, not an input surface, for every HQ-sourced row.
  5. 5If a plan you expect to see is missing entirely, check its status on Decisions first. Paused plans, queued interventions, non-escalated violations, and anything but an approved consent request are excluded from Action Plans by design, not lost.

The privacy boundary on a Gantt row

The synthesizer never reads a franchisee's own business data to build a step or a date, only the timestamps already sitting on the network-level row itself (when it was flagged, contacted, resolved, and so on). Where a row does name a specific location, an intervention or a consent request, that name is anonymized to a stable label everywhere the row appears, including its Gantt label cell, unless your network's entity model is configured as a single legal entity. Decision plans and program violations carry no franchisee identity in the first place; a plan is about the network, a violation is about a program. No HQ source estimates a dollar figure, so unlike the operator side, you will never see a dollar amount on an HQ Gantt bar.

Empty states

If the current Action Plans filter has no rows with a bar to draw, the card shows a plain message rather than an empty ruler, the same messages covered in full in Gallery, Gantt, and Calendar views: "Nothing overdue," "Nothing due today," "No completed plans yet," and so on depending on which bucket is selected, or "Nothing matches this filter" when nothing in the current status qualifies at all.

Data sources

Data sources

  1. 1.Decision plans. the network data.
  2. 2.Interventions queue. the network data.
  3. 3.Consent requests. the network data.
  4. 4.Program violations. the network data.
Was this helpful?