Objectives

An Objective is a Service Level Objective: a target evaluated against an Indicator. It pairs a plain-English promise to your users (the description) with a machine-checkable target (the definition), and Firetiger evaluates it continuously, opening Investigations when the target is at risk.

Service: firetiger.observability.v1.ObjectivesService

Resource name pattern: objectives/{objective}

Access: Read-write

Methods

RPC HTTP
CreateObjective POST /v2/objectives
GetObjective GET /v2/{name=objectives/*}
EvaluateObjective GET /v2/{name=objectives/*}:evaluate
ListObjectives GET /v2/objectives
UpdateObjective PATCH /v2/{objective.name=objectives/*}
DeleteObjective DELETE /v2/{name=objectives/*}
ActivateObjective POST /v2/{name=objectives/*}:activate
DeactivateObjective POST /v2/{name=objectives/*}:deactivate
ReactivateObjective POST /v2/{name=objectives/*}:reactivate
AcceptObjective POST /v2/{name=objectives/*}:accept
ArchiveObjective POST /v2/{name=objectives/*}:archive
RestoreObjective POST /v2/{name=objectives/*}:restore
UndeleteObjective POST /v2/{name=objectives/*}:undelete
RecreateObjectiveExpertSession POST /v2/{name=objectives/*}:recreateExpertSession
CreateRecommendedObjectiveChange POST /v2/{parent=objectives/*}/recommendedObjectiveChanges
ListRecommendedObjectiveChanges GET /v2/{parent=objectives/*}/recommendedObjectiveChanges
GetRecommendedObjectiveChange GET /v2/{name=objectives/*/recommendedObjectiveChanges/*}
AcceptRecommendedObjectiveChange POST /v2/{name=objectives/*/recommendedObjectiveChanges/*}:accept
RejectRecommendedObjectiveChange POST /v2/{name=objectives/*/recommendedObjectiveChanges/*}:reject

Request/response shapes are in proto/firetiger/observability/v1/objectives.proto.

Objective resource

Field Type Behavior Description
name string Output only Format: objectives/{objective}
display_name string Required Short label shown in lists and headers
description string Required One-sentence, plain-English promise to the user (avoid SRE jargon)
indicator string Required The Indicator this Objective is computed against. Format: indicators-v2/{indicator}
definition oneof gauge | ratio Conditionally required Kind-specific SLO body; at most one variant, and it must match the Indicator’s kind. The target (GAUGE threshold, RATIO target_failure_rate) lives inside the variant. Optional while the Objective is RECOMMENDED or CALIBRATING (agent recommendations are concept-only and the working target is tuned during calibration); required to enter ACTIVE — activation is where the target is committed
filter DimensionFilter[] Optional Narrows included rows to a subset of the Indicator’s declared dimension values. Filters do not define whether a dimension matters
owner_resource string Optional Catalog resource this Objective belongs to (MVP: services/{service})
state ObjectiveState Output only Lifecycle state. CALIBRATING on create (unless calibration_config.skip); activated via ActivateObjective; paused/resumed via DeactivateObjective and ReactivateObjective. Agent-recommended Objectives persist in RECOMMENDED until accepted. See ObjectiveState
calibration_config CalibrationConfig Optional Set skip = true to start ACTIVE with no calibration phase. A skipping create must carry a definition (rejected INVALID_ARGUMENT otherwise), and accepting a skip-flagged recommendation that has no definition lands CALIBRATING instead of ACTIVE
calibration_observations CalibrationObservations Output only Rolling daily baseline snapshots (≤7, FIFO) gathered while CALIBRATING; frozen on activation
trigger ObjectiveTrigger Optional What opens an Investigation against this Objective. A default RateMultiplierTrigger is applied on create when unset
latest_evaluation LatestObjectiveEvaluation Output only Latest persisted health snapshot written by the periodic evaluator. Includes bounded per-Cell health rows, aggregate health counts, evaluation time, and any google.rpc.Status execution error
trigger_state ObjectiveTriggerState Output only Bounded server-owned bookkeeping for the current unhealthy episode observed by trigger. Present only while the Objective is unresolved; cleared when all observed Cells are healthy. See ObjectiveTriggerState
expert_session string Output only The per-Objective objective-expert agent session that owns this Objective’s Indicator lifecycle (review, re-validation, repair). Format: agents/{agent}/sessions/{session}
indicator_unit string Output only Display unit of the backing Indicator (e.g. s, ms, %), resolved from the referenced Indicator on read by GetObjective/ListObjectives so threshold/target numbers render with their unit. Computed on read, never persisted; empty when the Indicator can’t be read
recommendation_confidence ObjectiveRecommendationConfidence Optional Agent’s confidence in this recommendation. See ObjectiveRecommendationConfidence
recommendation_evidence string[] Optional Supporting evidence the agent cited when recommending this Objective
recommendation_reasoning string Optional Free-text rationale for why this Objective was recommended
etag string Output only Optimistic-concurrency token; required on Update, stale etags fail with ABORTED
create_time / update_time / delete_time timestamp Output only Standard AIP lifecycle timestamps

UpdateObjective is field-masked: send the Objective with an update_mask of the paths to write. Output-only fields (state, calibration_observations, latest_evaluation, trigger_state, expert_session, indicator_unit, etag, timestamps) are server-managed; do not set them on Create/Update.

Calibration-phase revisions are agent-internal tooling, not a separate API: the per-Objective expert agent revises the working definition/details through a calibration-gated masked UpdateObjective (its tooling refuses any Objective that is not CALIBRATING, restricts the mask to {display_name, description, gauge, ratio, filter}, and echoes the read etag so a concurrent activation fails the write with ABORTED). Once ACTIVE, agent changes go through RecommendedObjectiveChange.

ObjectiveState

Value Description
OBJECTIVE_STATE_UNSPECIFIED Default zero value; not a valid persisted state
OBJECTIVE_STATE_CALIBRATING Set on create (unless calibration_config.skip); gathers baseline samples but does not alert. The working definition and details are revisable in place by the per-Objective expert agent while in this state. Transitions to ACTIVE only via ActivateObjective or an accepted KIND_ACTIVATE recommended change
OBJECTIVE_STATE_ACTIVE Evaluating and alerting
OBJECTIVE_STATE_INACTIVE Customer-paused via DeactivateObjective; not evaluating. Resumed with ReactivateObjective
OBJECTIVE_STATE_RECOMMENDED Persisted agent recommendation. Visible in the Service UI but does not calibrate, evaluate, alert, or spin up an objective-expert session until accepted
OBJECTIVE_STATE_ARCHIVED Dismissed recommendation. Hidden from active views and skipped by AcceptService bulk-activation

Customer lifecycle

Customer-owned Objectives move through the normal operational lifecycle after creation:

  • ActivateObjective transitions a CALIBRATING Objective to ACTIVE, optionally applying a threshold_override in the same transaction. It is idempotent if the Objective is already ACTIVE.
  • DeactivateObjective pauses an ACTIVE Objective by moving it to INACTIVE. Paused Objectives are not evaluated and do not open Investigations. The call is idempotent for an already-INACTIVE Objective and rejects CALIBRATING, RECOMMENDED, and ARCHIVED Objectives with FAILED_PRECONDITION.
  • ReactivateObjective resumes an INACTIVE Objective by moving it back to ACTIVE. The call is idempotent for an already-ACTIVE Objective and rejects CALIBRATING, RECOMMENDED, and ARCHIVED Objectives with FAILED_PRECONDITION.
  • DeleteObjective soft-deletes the Objective by setting delete_time. UndeleteObjective clears delete_time for non-ARCHIVED Objectives, backing undo flows after user-initiated archive/delete actions.

ObjectiveRecommendationConfidence

Value Description
OBJECTIVE_RECOMMENDATION_CONFIDENCE_UNSPECIFIED Confidence not set
OBJECTIVE_RECOMMENDATION_CONFIDENCE_HIGH High confidence
OBJECTIVE_RECOMMENDATION_CONFIDENCE_MEDIUM Medium confidence
OBJECTIVE_RECOMMENDATION_CONFIDENCE_LOW Low confidence

Recommendation lifecycle

Beyond the customer-authored Create → calibrate → activate path, Objectives can be proposed by a Service’s expert agent. A recommended Objective is persisted as a real objectives/{objective} resource in OBJECTIVE_STATE_RECOMMENDED: it shows up in the Service UI alongside active Objectives but does not calibrate, evaluate, alert, or create an objective-expert session.

  • AcceptObjective moves a RECOMMENDED Objective into the normal lifecycle (entering CALIBRATING, or ACTIVE if calibration is skipped), spinning up its expert session.
  • ArchiveObjective dismisses a recommendation into OBJECTIVE_STATE_ARCHIVED. Archived Objectives are hidden from active views and skipped by AcceptService bulk-activation.
  • RestoreObjective brings an archived Objective back to RECOMMENDED.

Recommendation metadata (recommendation_confidence, recommendation_evidence, recommendation_reasoning) is carried on the Objective resource itself.

Recommending changes to existing Objectives

A RecommendedObjectiveChange is a child of the Objective it advises (objectives/{objective}/recommendedObjectiveChanges/{recommended_objective_change}) and proposes an edit, removal, or activation of that live Objective, distinct from a brand-new-Objective recommendation (which is a RECOMMENDED-state Objective). The agent only ever creates one; it never mutates an ACTIVE Objective directly. The human accepts or rejects it. At most one change is pending per Objective: creating a new one supersedes (resolves) any still-pending proposal on the same target.

Field Type Behavior Description
name string Output only Format: objectives/{objective}/recommendedObjectiveChanges/{recommended_objective_change}
kind Kind Required KIND_UPDATE (edit), KIND_DELETE (removal), or KIND_ACTIVATE (lock in the calibrated target and activate)
proposed Objective Optional UPDATE/ACTIVATE. Sparse Objective holding just the changed fields; applied under objective_update_mask. Not validated at propose time, the merged result is validated on accept
objective_update_mask FieldMask Optional UPDATE/ACTIVATE. Paths to apply from proposed; for UPDATE restricted to display_name, description, gauge, ratio, filter (the SLO body is masked by its oneof variant gauge/ratio, not definition); for ACTIVATE restricted to at most one of gauge/ratio (the settled target), or empty when the working definition was already tuned in during calibration
proposed_indicator Indicator Optional UPDATE only. Sparse query/dimension rewrite of the target Objective’s backing Indicator, applied under indicator_update_mask
indicator_update_mask FieldMask Optional UPDATE only. Paths to apply from proposed_indicator
reasoning / confidence Optional Why the change is proposed and the agent’s confidence in it. reasoning is review-card copy, capped at 1200 characters: a longer value is rejected with INVALID_ARGUMENT at create time
target_etag string Output only The target Objective’s etag captured at propose time. Advisory: the UI warns when it differs from the live Objective. Accept is last-write-wins, not gated on it
source string Optional The agent session that authored the proposal (agents/{agent}/sessions/{session}). The agent tooling fills this from the running session, like Issue.source; a direct caller may set it or leave it blank
diff FieldDiff[] Output only Server-rendered {field_path, before, after} for each masked path (objective fields, plus backing-indicator fields prefixed indicator.), computed at create time. Clients render these directly rather than reconstructing the diff from proposed + masks, so no change is hidden before accept. Empty for a DELETE
create_time / update_time / delete_time timestamp Output only delete_time is stamped when the change is accepted or rejected (soft-delete as history)
  • AcceptRecommendedObjectiveChange applies the change in one transaction: UPDATE → UpdateObjective + UpdateIndicator (the rewrite is always bound to the target Objective’s own backing Indicator, never an arbitrary one a proposal might name); DELETE → DeleteObjective, also resolving the Objective’s other pending changes so none dangle; ACTIVATE → applies the proposed gauge/ratio (if the card carries one) and transitions CALIBRATINGACTIVE atomically, with the same semantics as ActivateObjective (idempotent if a manual activation raced the card). The accepted change is soft-deleted as history.
  • RejectRecommendedObjectiveChange soft-deletes the change without touching the Objective.
  • UndeleteObjective clears delete_time on a soft-deleted Objective, backing the undo after an accepted removal. Restricted to non-ARCHIVED objectives: a service-archived Objective is also soft-deleted but is restored with its Service (it carries the delete_time marker that service-restore keys on), so calling this on an ARCHIVED Objective returns FAILED_PRECONDITION.

Errors / edge cases:

  • Accept or reject of a change that is already resolved (its delete_time is set) returns FAILED_PRECONDITION.
  • Creating a change supersedes any still-pending change on the same Objective (the superseded card is soft-deleted as resolved history), so at most one card is pending per Objective.
  • KIND_UPDATE and KIND_DELETE require a live target (ACTIVE, INACTIVE, or CALIBRATING); a RECOMMENDED or ARCHIVED target is rejected with FAILED_PRECONDITION — corrections to a pending recommendation are replacement proposals (create the corrected recommendation, archive the stale one), not edits layered on an unaccepted card.
  • An ACTIVATE may not carry indicator changes, and its objective_update_mask admits at most one of gauge/ratio, whose variant must be present in proposed. An ACTIVATE with an empty mask against an Objective that has no definition is rejected with INVALID_ARGUMENT at create time — there would be no target to activate against.
  • An ACTIVATE may only be proposed against a CALIBRATING Objective; any other target state is rejected with FAILED_PRECONDITION at create time (target edits on an ACTIVE Objective use KIND_UPDATE). If the Objective was activated manually while the card was pending, accepting the card is idempotent: it resolves without re-applying the proposed target.
  • Accept of an UPDATE is last-write-wins: it applies the masked patch to the current live Objective/Indicator. target_etag is advisory (the UI warns on drift) and is not a precondition, so an accept against a since-changed Objective succeeds and only overwrites the masked paths.
  • objective_update_mask is restricted to {display_name, description, gauge, ratio, filter} and indicator_update_mask to {query[.confit_sql/.connections/.description], display_name, description, unit, dimensions}; any other path is rejected with INVALID_ARGUMENT at create time. The indicator rewrite is always bound to the target Objective’s own backing Indicator regardless of any name on the proposed patch.
  • reasoning longer than 1200 characters is rejected with INVALID_ARGUMENT at create time — it is the human-scanned review-card rationale, not a place for full evidence dumps.
  • Accept of an UPDATE re-runs the standard Objective (definition-vs-Indicator) and Indicator (ConfitSQL/placeholder/dimension) validation against the merged result; a now-invalid merge fails with INVALID_ARGUMENT and the Objective is left unchanged.
  • Accept of a DELETE soft-deletes the Objective and resolves its other pending changes; UndeleteObjective restores it.

How evaluation works

Firetiger re-checks every active Objective on a short cycle (about every 5 minutes). It does not wait for a long window to “fill up” — each check looks back over two rolling windows that both end at now:

  • Short window (default 5 minutes) — “is it bad right now?”
  • Long window (default 1 hour) — “is it bad looking back over the longer period?”

A Cell is Unhealthy only when both windows are over the line at the same time. Requiring both is deliberate: the short window catches a problem quickly, and the long window confirms it is real and not a one-off blip. (A window with too little traffic or no data is reported LOW_VOLUME / NO_DATA instead of being judged.)

“Over the line” means the Objective’s target adjusted by the trigger’s multiplier (1.0 = fire right at the target). For dimensional Objectives, every Cell is judged on its own and the Objective is Unhealthy if any Cell is.

How fast it reacts depends on what you measure:

  • Latency-style Objectives (a GAUGE, e.g. p99 latency) react quickly. If responses are slow right now, that slowness shows up in both the 5-minute and the 1-hour view at the same time, so it is flagged on the next check. The long window mainly makes the breach “stick” a little longer.
  • Error-rate Objectives (a RATIO) react more slowly, on purpose. The 1-hour view averages all traffic together, so a brief blip gets diluted and ignored. The Objective only turns Unhealthy once errors keep happening hard or long enough to move the whole hour’s average past the target — a genuinely sustained problem.

Once a Cell is Unhealthy, Firetiger opens at most one Investigation per cooldown window (default 1 hour), so a sustained problem does not re-page every cycle.

Trigger settings (RateMultiplierTrigger)

Every Objective carries a trigger.rate_multiplier; a default is applied on create, and each field can be tuned per Objective.

Field Default Meaning
multiplier 1.0 (GAUGE), 14.4 (RATIO) How far past the target counts as a breach. 1.0 fires right at the target; a higher value adds margin so only a clear breach triggers
long_window 1h The longer rolling window — confirms the problem is sustained
short_window 5m The recent rolling window — catches the problem quickly and bounds how soon a recovered Cell clears
cooldown 1h Minimum gap between automated Investigations for the same ongoing episode
min_volume per-kind default Minimum events in the short window before a Cell is evaluated. See Minimum-volume guard

Dimensional evaluation

Objective dimensionality comes from the backing Indicator:

  • If Indicator.dimensions is empty, the Objective is scalar.
  • If Indicator.dimensions contains ServiceDimensions, Firetiger evaluates the Objective independently for each observed Cell. A Cell is the set of observed ServiceDimension values for that row, such as region=us-west-2 and tenant=acme-corp.
  • Objective.filter[] narrows which rows are included before evaluation; it does not change which bound dimensions define the Cells.

Unhealthy dimensional Cells do not create one Investigation per Cell. Firetiger creates at most one Objective-level Investigation per cooldown window and includes the unhealthy Cells as evidence.

EvaluateObjective returns the current health on demand. The response contains health_status, summary, and cell_evaluations; scalar Objectives return one Cell with empty dimensions, while dimensional Objectives return one row per observed Cell with dimension values, health status, long/short observed values, target, row count, and ratio total events when applicable.

Minimum-volume guard

A Cell whose evaluation window holds too few events for its percentile or ratio to be statistically trustworthy is reported OBJECTIVE_HEALTH_STATUS_LOW_VOLUME instead of being evaluated, so a single outlier in a low-traffic Cell cannot trip a breach. Like NO_DATA, a LOW_VOLUME Cell never opens an Investigation and never clears the trigger episode; it is counted separately from no_data in summary.

The floor is trigger.rate_multiplier.min_volume, measured over the short window. When unset (0) the server applies a conservative per-kind default: a small event count for RATIO (from the Indicator’s total_events), and a small number of populated data points for GAUGE. Set min_volume explicitly to raise or lower the floor for a given Objective.

The periodic evaluator writes the same bounded health shape to Objective.latest_evaluation so readers can render current health without running a fresh Indicator query:

Field Type Description
health_status ObjectiveHealthStatus Overall Objective health: OBJECTIVE_HEALTH_STATUS_HEALTHY, OBJECTIVE_HEALTH_STATUS_UNHEALTHY, OBJECTIVE_HEALTH_STATUS_LOW_VOLUME, or OBJECTIVE_HEALTH_STATUS_NO_DATA
summary ObjectiveHealthSummary Aggregate counts: unhealthy, healthy, low_volume, and no_data
evaluation_time timestamp Time the evaluator produced this snapshot
cell_evaluations ObjectiveCellEvaluation[] Bounded, priority-sorted per-Cell rows. Truncated snapshots set cell_evaluations_truncated = true
total_cells int32 Full number of Cells evaluated before truncation
cell_evaluations_truncated bool True when the server kept only the highest-priority Cell rows
execution_status google.rpc.Status Set when evaluation failed; nil or OK means the snapshot ran successfully

ObjectiveTriggerState

trigger_state is not an alert history or a copy of Objective evaluations. It is bounded coordination state for the current unhealthy episode so the evaluator can dedupe automated Investigation check-ins without storing every evaluation row in Postgres. NO_DATA and LOW_VOLUME leave this state intact because an inconclusive evaluation does not prove recovery; only a fully healthy observed evaluation (every Cell HEALTHY) clears it.

Field Type Description
unhealthy_scope_fingerprint string Stable hash of the Objective name plus the current set of unhealthy Cell identities. Metric values, severity, and timestamps are excluded so small fluctuations do not create new episodes
first_unhealthy_time timestamp First time this continuous unhealthy episode was observed
last_investigation_time timestamp Last automated Investigation check-in started for this episode
last_investigation_session string Last automated Investigation session created for this episode. Format: agents/{agent}/sessions/{session}
investigation_count int32 Number of automated Investigation check-ins started for the current unhealthy scope fingerprint. Used only to derive backoff
next_investigation_time timestamp Earliest time the evaluator may start another automated Investigation check-in for this episode

ObjectiveHealthStatus

Value Description
OBJECTIVE_HEALTH_STATUS_UNSPECIFIED Default zero value; not a persisted health state
OBJECTIVE_HEALTH_STATUS_HEALTHY No evaluated Cell is unhealthy and at least one Cell has data
OBJECTIVE_HEALTH_STATUS_UNHEALTHY At least one Cell crossed the Objective target in both long and short windows
OBJECTIVE_HEALTH_STATUS_NO_DATA No usable evaluation data, or evaluation could not produce a health result
OBJECTIVE_HEALTH_STATUS_LOW_VOLUME A Cell had data but too few samples in the evaluation window for its percentile or ratio to be trustworthy, so it is reported rather than evaluated. Inconclusive like NO_DATA — it never opens an Investigation and never clears trigger_state — but distinct so clients can surface “low volume” rather than “no data”. See Minimum-volume guard

Calibration and activation

New Objectives start in CALIBRATING: the evaluator gathers up to seven daily baseline snapshots, but no Investigations are triggered yet. While calibrating, the working definition and details are revisable in place — agent-recommended Objectives arrive concept-only (no definition), and the per-Objective expert agent tunes the working target against the accruing observations through its calibration-gated tooling.

Activation (CALIBRATINGACTIVE) is human-driven, by either path:

  • ActivateObjective — direct, optionally applying a threshold_override atomically. The override is required when the Objective has no definition yet; activation is where a target is first committed, so a target-less activate returns INVALID_ARGUMENT.
  • An accepted KIND_ACTIVATE recommended change — the expert-authored path: the card carries the settled gauge/ratio plus the reasoning grounded in the observed distribution, and accepting it applies the target and activates in one transaction.

Pass calibration_config.skip = true on create (with a definition) to start ACTIVE immediately.


This site uses Just the Docs, a documentation theme for Jekyll.