Signal guide
How to define and read measurables so the company runs on evidence instead of status theater.
Signal is the measurable layer of the operating system. It should show what is healthy, stale, at risk, or broken without requiring humans to compile status by hand.
Good measurables
- Owned by one seat.
- Tied to a responsibility or goal.
- Updated on a useful cadence.
- Has a target or threshold.
- Produces an exception when it breaks.
What to avoid
Avoid vanity numbers, manual-only status fields, and metrics nobody can act on. If a measurable goes off track and no seat knows what to do, the Charter is incomplete.
Operate Signal from CLI or MCP
- Read:
rost signal list --json/signal.list/rost_list_signalsreturns measurables with their latest reading and on/off-track state, pluseffective_source— the read-time measured-by (autowhen a source binding is active, otherwiseagent/human), matching the/signalUI and distinct from the declaredsource.signal.get/rost_get_signalreturns one measurable's full reading history. - Add a measurable:
measurable.create(scope: seat) defines a measurable a seat owns — name, unit, direction, target, cadence. The seat owns it; readings attach to it afterward. - Record a reading:
rost status record --measurable-id <id> --value <n>(status.record, scope: seat) writes a status event with the reading. This is not gated. An agent'sstatus.recordnever downgrades a human-confirmed reading: a routine agent read that lands on a period a human already confirmed leaves the confirmed value and its confirmation intact (invariants #7/#8 — agents recommend; humans decide). - Confirm a reading:
rost signal confirm/signal.confirm_reading/rost_confirm_signal_readingmarks a reading human-verified.signal.correct_reading/rost_correct_signal_readingoverwrites a reading with a human-confirmed value. - Draft first readings from a connected source:
signal.draft_first_readingspulls a measurable's bound source once (read-only, SSRF-guarded — the same pull path assignal.preview) and lands the value as a DRAFT reading for the current period, for a human to confirm. It is strictly for a measurable that has a connected source but has never had a confirmed reading — in ANY period, not just the current one. A measurable that already carries a confirmed reading (even an older one) is rejected with an "already has readings" outcome and no pull happens, so the first-reading flow can never be repurposed as an ongoing draft source; usesignal.reportto add a current-period datapoint orsignal.correct_readingto change a confirmed value instead. The Signal page shows a "Draft first readings" button on the eligible (never-measured) rows so the row stops being a permanent blank. Nothing is published — a human clears the draft withsignal.confirm_reading(invariant #7) — and it never fabricates a value: a non-numeric or blocked pull drafts nothing and says so.
Run Signal without an agent
A human can run the whole loop from the Signal page. The page opens answer-first — one computed line ("2 of 14 measurables need attention," or "All 14 measurables on target" when nothing needs you) with only the exception rows fully visible; healthy measurables collapse to a single "N on target" count you can expand. Every create/edit action lives behind the one "+ Add" button, which opens a drawer with four segments: Log reading records a human reading (the same signal.correct_reading path, or a row's own "Log reading" button seeds the drawer to that measurable), New measurable creates one against a seat (the measurable.create path, plus adopting from the template catalog), Sources, and CSV import. You do not need an agent to keep Signal current.
Per-seat budget alerts
The Signal page shows a seat-cost strip: each seat's trailing 30-day agent run spend. On top of that you can set a per-seat soft budget alert — a spend threshold in USD. When a seat's 30-day spend approaches its threshold (at 80%) or crosses it, a visible alert appears in the cost strip on the scorecard. This alert is advisory only: it never blocks a run or pauses an agent (hard budget enforcement is a separate, later capability). Set a threshold from the cost strip by choosing an agent seat and entering an amount, or leave the amount blank to clear it. Thresholds are per tenant and per seat; setting one records a durable event.
Import a scorecard from CSV
To move off a spreadsheet or another operating tool, open the Signal page's "+ Add" drawer and choose the "CSV import" segment (the signal.import command, also available on CLI/MCP). Each row becomes a measurable owned by the seat whose name matches the row's owner, with its target, unit, direction, cadence, and trailing readings. Import is idempotent — re-importing upserts by period — and a row whose cadence conflicts with an existing measurable is reported and skipped, not overwritten.
Adopt a measurable template
Not sure which measurables to track? measurable_template.list returns a starter catalog grouped by function (Sales, Finance, Operations, Marketing, HR), each with a unit, direction, cadence, suggested target, and source-readiness metadata when a known source path exists. Finance templates are explicit: platform billing usage baselines can use safe platform metadata, while tenant customer MRR, QuickBooks AR aging, and QuickBooks gross margin stay blocked until tenant-owned billing/accounting reads ship. measurable_template.adopt instantiates one against a seat — the same write path as measurable.create, so the created measurable is a plain human-owned Signal you log against afterward. Adoption is a human act; the suggested target is just a starting point you can override.
The leadership scorecard grid
The default /signal view is exception-first — it surfaces what is off track, at risk, or pending, so the team never stares at a wall of green. For a leadership review or the familiar weekly scorecard grid, toggle the trailing-grid view (scorecard.grid): rows are the measurables for a cadence (the classic 13-week weekly view, plus monthly, quarterly, and annual), columns are the trailing periods, and each cell is colour-coded against its target and direction. An empty period reads "no data", never a fabricated zero. The grid also surfaces the Org-Checkup measurables — charter coverage, measurable freshness, aging open issues, and peak steward load — computed on read from the operating data, so the scorecard is never empty even before a single reading is logged.
Auto-fill Signal from a connected source
Once a measurable's number lives in a connected system, you can teach ROST the exact deterministic pull once and let it keep the measurable current. The flow is discover -> preview -> bind -> bind_confirm:
integration.discoverreturns non-secret metadata for a connected source and a recipe skeleton to author against.signal.previewruns a candidate recipe live but read-only against an allowlisted HTTPS source, returns the extracted value, and issues a short-livedpreview_token. It never mutates and never stores a secret in the recipe — the credential is resolved from the connection at execution.signal.bindpersists the validated recipe as an inertdraftbinding. It requires thepreview_tokenfrom a preview of the same recipe, so nothing is bound that was not first seen working.signal.bind_confirmis human-only. It activates the binding (draft -> active); an agent that calls it produces a pending confirmation instead. Only after a human confirms does the scheduled pull begin writing confirmed readings.
Discovery always returns a recipe_presets array; it is empty when ROST does not recognize a safe source type. Today the proven self-filling path is Baserow REST through integration.connect_rest: a tenant admin creates or rotates the Baserow connection through the command/vault path, then Signal runs integration.discover -> signal.preview -> signal.bind -> signal.bind_confirm against that connected source. Baserow discovery now offers two first-class presets: baserow.row-field (read one scalar field from a row) and baserow.filtered-count (count rows matching a condition). The filtered-count builder takes a table id, field key, comparison operator, and comparison value and assembles a validated rest_for_signals recipe whose value_path is the response count; the field key and operator are allowlisted (they form the query-parameter name) and the comparison value is percent-encoded, so nothing the operator types can inject into the URL. The human still supplies the real table, field, operator, value, and host — presets and the builder are starter recipes, not secret stores. Google Sheets Signal pulls are not live yet: Google uses OAuth JSON credentials and remains withheld from REST presets until the pull executor extracts and refreshes the Google access token instead of treating the stored credential blob as a bearer token. A preset includes a label, cadence, required metadata, a draft rest_for_signals recipe, and the host allowlist to preview with. The human still fills in the real Baserow table/row/field or exact REST URL and value path before running signal.preview; presets are starter recipes, not secret stores.
Recipes are validated data, never code: a read-only GET, a closed set of non-secret headers, and a restricted scalar path to the value. Every fetch passes one SSRF guard (HTTPS only, a steward-confirmed host allowlist set at bind, and a runtime check that the host does not resolve to a private address). The preset output never contains nango:// handles, vault refs, tokens, or static credential headers. Deterministic pulls write integration-sourced readings on the measurable's cadence.
In the web app, the same workflow lives on /signal in the "+ Add" drawer's "Sources" segment. It shows each measurable's setup state — manual, agent draft, source draft, or auto-filled — plus the latest safe pull outcome when one exists. Choose a connected source, discover its non-secret metadata, preview a GET URL and value path, bind the working preview as a draft, then confirm the draft before scheduled pulls can write readings. The UI never displays credential material, vault refs, raw response bodies, or recipe secrets; if no connected source exists, connect one from Settings first.
A source binding can inform a measurable without automatically changing a Cascade goal. The proven Leiluna Baserow binding uses informs. Use drives_status only after a second business anchor confirms that this exact measurable should compute a childless goal's status; otherwise the Signal should stay visible as evidence and exception context, not an automatic goal-status driver.
Collect a Signal by judgment (the Signal agent)
Some measurables cannot be a deterministic pull — the number lives in a PDF, in messy records that need reconciling, or is a judgment estimate of progress. For those, mark the measurable agent-sourced and let a per-seat Signal agent read it. The agent proposes each reading with signal.report (scope: seat): one draft reading for one of its OWN seat's agent-sourced measurables, with a value and a short note citing the source. The reading lands unconfirmed, exactly like a deterministic draft — a human clears it with signal.confirm_reading. signal.report refuses any measurable not owned by the acting seat and any measurable whose source is not agent, and it never confirms its own reading. Deliver it as the stock/signal-collector template (a read-only agent whose only durable grant is signal.report) or run the collect-seat-signals how-to from an occupant agent. When the agent cannot read a number confidently — ambiguous or conflicting sources — it escalates instead of guessing.
When to stop for confirmation
signal.correct_reading is human_required; overwriting a recorded measurable is a human judgment. signal.confirm_reading is none, so a seat can confirm its own readings directly. measurable.create is none — defining a measurable is not gated. An agent records readings with evidence; a human corrects when a value is wrong.
Agent guidance
Agents may record readings when the Charter allows it. Agent-reported readings should include enough evidence for a human to trust the number. Broken or stale Signal should become Friction when action is needed.