| name | read-only-instruments |
| description | Design know-how for read-only instruments (計器) — aggregate readings over stored state (distributions, compositions, cluster structure) that inform the operator before an intervention, in the style of ADR-0071's pattern-composition instruments. Use when quantifying an open observation before intervening (instrument-first sequencing), when a design floor/threshold would otherwise be guessed, when calibrating an embedding-based reading (three-point scale), or when deciding whether to build OR remove an instrument (signal-first both ways). NOT for per-event audit logs that replay a decision offline (that is replayable-audit-logs / ADR-0075) and NOT for metrics that feed gates, ranking, or retrieval — instruments are observability, never intervention. |
| compatibility | Written for the Contemplative Agent repo (examples are CA-specific); the pattern itself is portable. |
| origin | shimo4228 |
Read-Only Instruments (計器)
An instrument is a read-only aggregate reading over stored state — a
distribution, composition tally, or cluster structure — built so the operator
can choose interventions from data instead of intuition. Canonical example:
core/view_metrics.py (ADR-0071) — consumed-view supply and pairwise-cosine
diversity (the echo-chamber detector).
Not the same thing as an audit log. An audit log records one event per
decision for offline replay (replayable-audit-logs,
ADR-0075). An instrument reads across the whole store at query time. They
compose: the log is the corpus, the instrument is one lens over it.
When to build one (instrument-first sequencing)
- Before intervening. Quantify current state → choose the intervention
from readings → re-measure. ADR-0071 sequenced exactly this way, and for an
extra reason worth checking every time: a confounder (the gemma4:e4b model
swap 5 days earlier) meant any uninstrumented change would have been
unattributable to the change vs. the swap.
- When a floor/threshold would otherwise be guessed. A rescue-lane floor,
a similarity cutoff, a cap — pick it from the measured distribution, never
as a bare number (the numeric-cap anti-pattern; feedback
no-numeric-caps).
- When an open observation has no data. "An echo effect seems to be
forming" stayed anecdote until pairwise homogeneity + supply concentration
made it a reading.
Signal-first — the gate for building AND removing
Build an instrument only if its reading changes a named action. The same
test governs removal:
- ADR-0072 removed the grounding-composition instrument shipped days earlier:
under per-episode distillation its reading is constant, and a constant
reading changes no action.
- The M2
epistemic_counts repair stays deferred "until a decision consumes
the reading" (task ledger T-M2) — an instrument without a consumer is not
built early.
Invariants (from ADR-0071, Decision 6)
- Observability, never intervention. Readings feed the operator; none
feed gates, ranking, retrieval, or promotion (ADR-0050/0051 rejected
metric write-back; AKC ADR-0015 "visibility without intervention"). The
module docstring and the ADR are the only guard — resist wiring numbers
back in.
- Carry an ambiguity note in the output itself. Empty/low supply means
"missing patterns OR a stale seed" — undecidable by the instrument. A
systematically biased instrument is worse than none (AKC ADR-0016).
- Degrade, never abort. A malformed or wrong-dimension row is skipped
with a WARNING; an instrument must never crash its host command.
- Bound your own cost. Pairwise stats over n² pairs get a deterministic
stride-sample cap (
PAIRWISE_STATS_MAX_N); mirror constants that cannot
be imported (import cycles) are pinned by a cross-check test instead.
- Production stays byte-identical. Wire into dry-run / opt-in report
paths first; longitudinal always-on wiring is a follow-up once readings
prove useful.
Calibration: the three-point scale
An embedding-based reading is meaningless without anchors. Measure all three:
| Anchor | How | CA introduction values |
|---|
| Floor | cosine of deliberately unrelated texts vs corpus/seeds | ~0.33–0.46 |
| Corpus mean | pairwise mean over the live pool | 0.554 |
| Top band | consumed-view top matches | ~0.68–0.77 |
A reading is a signal only relative to these (CA's echo signature: corpus
mean ~0.1–0.2 above floor). Re-measure the scale whenever the geometry
changes — embedding model swap, seed rewrite, normalization change.
Reading pitfalls
- A flat metric does not mean nothing changed. After the ADR-0072
interventions the pairwise mean stayed ~0.60 while the corpus register
visibly transformed — the change was orthogonal to the instrument's axis
("axis rotation"). Corroborate a flat reading with a qualitative sample
before concluding no-effect.
- Survivors-only data is not a distribution. Scores recorded only for
items that passed a gate cannot be analyzed as the population distribution
(feedback
relevance-distribution).
- Coarse scorers support thresholds only at populated bucket boundaries.
If the scorer emits a few discrete values, a threshold between buckets is
fiction (relevance retune, 2026-06-05).
- Don't measure orphans. A distribution over a seed/axis nothing consumes
measures staleness of the seed, not structure of the corpus (why ADR-0071
scoped supply to the two consumed views).
Placement
Read-only module in core/ (exemplar: view_metrics.py), instruments passed
into pipelines via an explicit param (instrument_views), rendered through
distill --dry-run / report --patterns. Record the baseline readings and
the calibration scale in the ADR or evidence dir that motivated the
instrument, so the next reader knows what the numbers meant when built.