Context for finding queries and lifecycle operations.
Read-path: strategies query active findings during init/2.
Write-path: post-converge persists raise/update/clear actions.
Subject queries use denormalized subject_kind and subject_id columns
instead of JSON operators for SQL Server 2017 compatibility.
Upserts use transactions (read-then-write) instead of ON CONFLICT
for SQL Server compatibility.
Summary
Functions
Query active findings with flexible filters.
Mode-aware query: automatically prefixes :actor and :finding_key filters
when the episode is a dry run with persist_findings enabled.
Walks the causal chain upward from a finding, returning ancestors.
Returns active child findings caused by the given finding key.
Count active findings matching the given filters. Accepts same opts as active_for/2.
Persist a finding action from a ConvergeResult.
Project a single %Finding{} into a compact, JSON-safe summary map.
Project findings into compact, JSON-safe maps for strategy context assembly.
Returns true if an active finding with the given key was updated within window_ms milliseconds.
Returns the root cause finding for the given finding key.
Functions
Query active findings with flexible filters.
Examples
Cyclium.Findings.active_for(actor: :po_status, class: "po_stalled")
Cyclium.Findings.active_for(subject: %{kind: "po", id: "PO-1955"})
Cyclium.Findings.active_for(finding_key: "po_stalled:PO-1955")
Cyclium.Findings.active_for(class: "non_responsive")Options
:limit— max rows to return:offset— rows to skip (default: 0):exclude_archived— whentrue, excludes findings with a non-nilarchived_at(default:false):env— override the env scope (defaults toCyclium.Env.current()). Pass a string to read another env's findings (demo/staging inspection or backfill), ornilto target the unset/default env explicitly.
Mode-aware query: automatically prefixes :actor and :finding_key filters
when the episode is a dry run with persist_findings enabled.
In live mode (or dry runs without persist_findings), behaves identically to active_for/2.
In dry run mode with a persist prefix, rewrites filter keys so strategies can
transparently read their own dry run findings.
Examples
# In a strategy's init/2, pass the episode to get mode-aware results:
Cyclium.Findings.active_for_mode([actor: "po_monitor"], episode)
# Dry run with persist_findings: true → queries actor: "dry_run:po_monitor"
# Live mode → queries actor: "po_monitor" (unchanged)
Walks the causal chain upward from a finding, returning ancestors.
Starting from the given finding, follows caused_by_key links up
to max_depth levels. Returns a list of findings from immediate parent
to root (oldest ancestor).
Options
:max_depth— maximum chain depth (default: 10)
Returns active child findings caused by the given finding key.
Count active findings matching the given filters. Accepts same opts as active_for/2.
Persist a finding action from a ConvergeResult.
Actions
{:raise, params}— upsert active finding (last writer wins on mutable fields){:update, key, changes}— update mutable fields on an active finding{:clear, key}— idempotent clear (set status to:cleared){:clear, key, reason}— clear with reason stored in evidence_refs
Project a single %Finding{} into a compact, JSON-safe summary map.
Project findings into compact, JSON-safe maps for strategy context assembly.
Strategies read active findings during context assembly, and that payload is
journaled by the episode runner (JSON-encoded for SQL Server via the Tds
adapter). Raw %Finding{} Ecto structs are not Jason.Encoder-able —
they carry __meta__ and association fields — so handing them to a strategy
crashes the episode at its first journal write the moment the actor has any
active finding. Always project findings through this before putting them in a
strategy's context.
The projection is also better prompt context: one summary-shaped line per
finding instead of every finding's full evidence_refs narrative (which can
run to multiple KB and would otherwise ride into every episode's first prompt
and journal row).
Returns true if an active finding with the given key was updated within window_ms milliseconds.
Useful for per-subject dedup in strategies — skip re-runs when a recent finding already exists.
Examples
Cyclium.Findings.recent?("client:advisor:123", :timer.minutes(5))
Returns the root cause finding for the given finding key.
Walks up the causal chain and returns the first finding with no caused_by_key.
Returns nil if the finding itself is not found.