Skip to content

feat(retrieval): live close-the-loop relevance-signal infrastructure (#779) - #789

Merged
github-actions[bot] merged 7 commits into
mainfrom
feat/issue-779-injection-events
May 14, 2026
Merged

feat(retrieval): live close-the-loop relevance-signal infrastructure (#779)#789
github-actions[bot] merged 7 commits into
mainfrom
feat/issue-779-injection-events

Conversation

@robotrocketscience

Copy link
Copy Markdown
Owner

Closes #779

Schema ratified at #779#issuecomment-4448107904 (2026-05-14). Six atomic commits, all signed:

Commit Layer Summary
66a720aa 1 injection_events table + 3 indexes
c5fc7268 1 MemoryStore API: record_injection_event / list_pending_injection_events / update_injection_referenced
d8ffa36f 1 UPS hook write-path + get_active_meta_belief_consumers()
dd9bbf33 2 src/aelfrice/relevance_detection.py — exact-substring strategy
d669ae99 3 _sweep_relevance_signal wired into UPS hook
c89a1f4d docs CHANGELOG + docs/design/relevance-signal.md

Three-layer architecture

Layer 1 — injection_events. New SQL table with (id, session_id, turn_id, belief_id, injected_at, source, active_consumers, referenced, referenced_at). active_consumers is a canonical-sorted JSON array of meta-belief keys whose retrieval consumer was env-gated ON for the turn. Three indexes: (session_id, turn_id), (belief_id), and a partial (session_id, referenced) WHERE referenced IS NULL for the sweeper's hot path. CREATE TABLE IF NOT EXISTS migrates pre-#779 stores forward on first open.

Layer 2 — relevance_detection.py. Pure-function reference detection:

  • normalize_text(s) — NFC + casefold + whitespace collapse. Fixed-point idempotent (running twice yields the same bytes).
  • is_referenced(belief, response) — True iff normalised belief content appears verbatim in normalised response. 8-char minimum belief length to prevent short-belief false positives.
  • score_references(pairs, response_text, *, strategy=STRATEGY_EXACT_SUBSTRING) — bulk variant; preserves input order.
  • STRATEGY_NGRAM_OVERLAP reserved (raises ValueError in v1; Q5 deferral).

Layer 3 — _sweep_relevance_signal. Fires at the start of every UPS hook, right after apply_sentiment_feedback, before this turn's retrieval. Reads pending events for the session, joins event.belief_id → belief.content via get_belief(), reads the assistant transcript via _read_assistant_text_since(session_id, oldest.injected_at) (filters turns.jsonl by session + role + ts), scores, fires update_meta_belief(consumer_key, SIGNAL_RELEVANCE, evidence=float(referenced), ...) once per consumer key in active_consumers, then idempotently stamps the row.

The substrate's "no-op on non-subscribed consumer" contract (update_meta_belief returns False if the signal class isn't in the consumer's subscription) keeps the wiring single-sourced via the env flags. v1 ships with latency-only #756 + bm25_l0_ratio-only #757; neither materialises a relevance posterior row, but both get their events stamped as scored. Siblings that DO subscribe to relevance (test consumers, future #758#760) light up automatically.

Determinism contract (#605, c06f8d575fad71fb)

  • normalize_text is pure stdlib.
  • score_references is a pure function over (events, response_text) → byte-identical output, in input order.
  • Wall-clock dependence bounded to update_meta_belief's now_ts (substrate decay math is wall-clock-independent at the function level) and referenced_at (audit-only, never re-read).

Fail-soft posture

Every layer is fail-soft. Store-open, transcript-read, update_meta_belief, and update_injection_referenced errors each print one stderr line and return. The sweeper is feedback substrate — never breaks retrieval.

Ratification log (Q1–Q6, from #779#issuecomment-4448107904)

  • Q1 consumers shape → JSON column on the event row.
  • Q2 rebuild_log reuse → no, new SQL table.
  • Q3 GC → deferred (posteriors decay; ancient rows contribute negligible signal).
  • Q4 source enum → UPS only in v1; pre_compact accommodated by TEXT-not-CHECK column.
  • Q5 detection default → exact-substring; n-gram is a follow-up sub-issue.
  • Q6 substring shape → full-content verbatim (8-char floor).

Verification

  • pytest: 4077 passed, 62 skipped, 75 xfailed (full suite, 78s).
  • Per-file: 19 tests in test_injection_events.py, 23 in test_relevance_detection.py, 10 in test_hook_injection_events_wiring.py, 11 in test_relevance_sweeper.py = 63 new tests.
  • Discretion grep on added lines: CLEAN.
  • All six commits signed (%G? = G).

Coverage

  • Schema presence (fresh + migrated stores), column set, indexes.
  • Store API: canonical-JSON encoding, dedup, empty default, FK cascade, pending filter, session filter, before_turn_id slicing, decode round-trip, deterministic order, limit, idempotent update, bad-value rejection.
  • Detection: normalize idempotency / casefold / NFC / whitespace / German ß / empty; is_referenced positive / case-insensitive / whitespace-tolerant / short-belief boundary / unicode / empty; score_references one-pair-per-input / order / empty / unsupported-strategy / determinism.
  • UPS wiring: turn-id shape, get_active_meta_belief_consumers (env-permutation matrix), fail-soft on no-session / empty-hits / bad-db, end-to-end UPS records one event per hit, threads active consumer when env on, batch-shares-turn-id.
  • Sweeper: transcript read filters (missing file / session / role / cutoff / malformed lines / file order), sweeper no-op cases, hit case shifts posterior, miss case scores 0, idempotent re-run, latency-only consumer stamps event without materialising relevance row.

Deferred (sub-issues path)

Out of scope

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 31 minutes and 40 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8ba59cab-5fe6-4064-8bd4-8a94642b4ffb

📥 Commits

Reviewing files that changed from the base of the PR and between 89f1367 and c345f3d.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !**/CHANGELOG.md
📒 Files selected for processing (9)
  • docs/design/relevance-signal.md
  • src/aelfrice/hook.py
  • src/aelfrice/relevance_detection.py
  • src/aelfrice/retrieval.py
  • src/aelfrice/store.py
  • tests/test_hook_injection_events_wiring.py
  • tests/test_injection_events.py
  • tests/test_relevance_detection.py
  • tests/test_relevance_sweeper.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-779-injection-events

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label May 14, 2026
@github-actions

github-actions Bot commented May 14, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 1925 changed lines (limit: 200)
  • 10 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

Comment thread tests/test_relevance_sweeper.py Fixed
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:mondragon:2026-05-14T15:51:32Z]

@robotrocketscience robotrocketscience left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve

Read all 1923 added lines across the four source files (store.py, hook.py, retrieval.py, relevance_detection.py), the design doc, and CHANGELOG. Ran the four new test files locally on a fresh uv env — 63 passed in 6.76s. CI was already green on all required gates (pytest 3.12/3.13, CodeQL, staging-gate, bench-smoke, secrets-scan, history-scan, deptry, vulture, typos).

Branch is FF-clean on github/main; all six commits signed (%G? = G for every SHA). Discretion grep on the actual additions is clean — the lone "subagent" hit in the diff output is context from #778's existing CHANGELOG entry above, already on main.

Code-level observations (none blocking)

(1) Sweeper windowing is intentionally loose, and is a known recall-vs-precision trade. _sweep_relevance_signal reads transcript text since the oldest pending event's injected_at and scores every pending belief against that bulk text. So a belief first injected on turn N+1 can substring-match content from turn N's response. The bias is upward on referenced, never downward, and the 8-char floor in relevance_detection._MIN_NORMALIZED_LENGTH plus the posterior decay window absorb most of it — fine for v1. Worth a follow-up sub-issue if bench evidence later shows the half-life / anchor-weight posteriors drifting hot under live traffic; the tighter design groups pending events by turn_id and bounds each turn's response window to [turn_injected_at, next_turn_injected_at).

(2) update_meta_beliefupdate_injection_referenced is not crash-atomic. Each commits independently. A process death between the per-consumer update_meta_belief loop and the update_injection_referenced stamp would cause the next sweep to re-bump the same Beta-Bernoulli posterior. Acceptable per the file's fail-soft posture (this is feedback substrate, not retrieval), and the test suite verifies the no-crash idempotency, but the comment above the stamp could note the window explicitly. Mentioning in case a future "wrap both writes in a transaction" refactor is queued.

(3) Minor — _record_injection_events opens the store once and commits per-hit (1 commit per row in the auto-commit-on-execute style elsewhere in MemoryStore). Typical UPS injections are O(10) beliefs, so this is fine, but if the hit count ever spikes (e.g., a future broad-expansion path), a batched executemany + single commit would amortize fsync. Not for this PR.

What I confirmed

  • Schema correct: referenced tri-state, FK cascade on beliefs(id), partial index WHERE referenced IS NULL matches the sweeper's hot query.
  • record_injection_event canonical-sort-deduplicates active_consumers before JSON-encoding — same determinism property as encode_signal_weights. Empty source rejected.
  • list_pending_injection_events.before_turn_id lexicographic slicing is correct because both writers (transcript_logger._new_turn_id and the new _new_injection_event_turn_id) prefix %Y%m%dT%H%M%S%fZ → lex = chrono. Documented inline.
  • update_injection_referenced is idempotent at the UPDATE ... WHERE referenced IS NULL level. referenced ∈ {0, 1} validated.
  • normalize_text is a fixed point (NFC + casefold + whitespace collapse); German ß fold and NFC compose/decompose covered in tests.
  • STRATEGY_NGRAM_OVERLAP correctly raises ValueError rather than silently fallback-dispatching.
  • Sweeper placement honors the design: runs after apply_sentiment_feedback, before this turn's retrieval, so shifted posteriors influence the very next reranker call.
  • get_active_meta_belief_consumers() returns sorted output → determinism-replay friendly.
  • Honors locked PHILOSOPHY (#605, c06f8d575fad71fb): pure stdlib, no embeddings, no LLM judges, no wall-clock state in the scoring layer.

Tagging ready-to-merge.

@robotrocketscience robotrocketscience added ready-to-merge Trigger merge-train: FF main to this PR's head and removed attn:review Needs review (PR open, awaiting reviewer) labels May 14, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:mondragon:2026-05-14T15:55:01Z]

@github-actions

Copy link
Copy Markdown

merge-train: blocked

1 review thread(s) are unresolved on these files: tests/test_relevance_sweeper.py. Resolve them on the PR (click 'Resolve conversation' on each) and re-add the label.

The ready-to-merge label has been removed. Address the issue above and re-add the label when you're ready for another attempt.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:bagheera:2026-05-14T16:57:58Z]

robotrocketscience added a commit that referenced this pull request May 14, 2026
CodeQL flagged the unused import on tests/test_relevance_sweeper.py
(only META_HALF_LIFE_KEY is referenced). Collapses the multi-name
import to a single-line form. Unblocks merge-train on PR #789.
@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label May 14, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:bagheera:2026-05-14T16:59:23Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Surfacing the merge-train block diagnosis (yeats, 2026-05-14T17:00Z) without modifying the PR — author / next claimant decides whether to act.

The unresolved review thread on tests/test_relevance_sweeper.py is a stale CodeQL alert (id 411):

Import of 'ENV_META_BELIEF_HALF_LIFE' is not used. (line 25)

Two problems with the finding:

  1. Symbol name doesn't match the branch. git grep ENV_META_BELIEF_HALF_LIFE github/feat/issue-779-injection-events returns zero hits. The file at line 22 imports META_HALF_LIFE_KEY (different name).
  2. Line 25 isn't an import. It's TEST_META_KEY = "meta:retrieval.test_relevance_consumer" — a module-level constant.
  3. The actual import META_HALF_LIFE_KEY is used at lines 389, 399, and 419 — active_consumers list, read_meta_belief_state call, and one positional arg.

The alert appears to reference a symbol that existed on an earlier revision of this branch and was renamed during development. The current branch state is clean.

Two more blockers separate from the CodeQL thread:

I haven't claimed this PR — surfacing only. Author or next reviewer can: (a) dismiss the CodeQL alert as false-positive via Security tab; (b) rebase; (c) re-add ready-to-merge.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:bagheera:2026-05-14T17:05:42Z]

First commit of the #779 close-the-loop relevance-signal
infrastructure. Per the schema ratification in
#779#issuecomment-4448107904 (2026-05-14):

- One row per (UPS-or-pre_compact event, injected belief). FK to
  beliefs(id) ON DELETE CASCADE, autoincrement PK — same pattern
  as belief_corroborations (#190) and deferred_feedback_queue
  (#191).
- active_consumers is a TEXT column carrying a canonical-sorted
  JSON array of meta-belief keys whose retrieval consumer fired
  on this turn. JSON over sidecar table because every read path
  is "one row per event, all consumers"; no query asks "find
  events where consumer X was active" today.
- referenced is tri-state (NULL / 0 / 1) — NULL means the
  sweeper hasn't scored this row yet.
- Three indexes: (session_id, turn_id) for sweeper joins,
  belief_id for FK cascade hygiene, and a partial index on
  pending rows so the sweeper hot path stays bounded.

No store API yet — that lands in the next commit. CREATE TABLE
IF NOT EXISTS is idempotent so pre-#779 stores migrate forward
on first open.
…nced (#779)

Three methods on MemoryStore:

- record_injection_event(session_id, turn_id, belief_id, *,
  injected_at, source, active_consumers) — inserts one row.
  active_consumers is dedup-sorted into a canonical-JSON column
  value so two stores with the same subscriptions produce byte-
  identical column bytes (same determinism property as
  meta_beliefs.encode_signal_weights).

- list_pending_injection_events(session_id, *, before_turn_id=None,
  limit=1000) — returns [(id, turn_id, belief_id, injected_at,
  source, active_consumers)] for rows with referenced IS NULL in
  the given session. before_turn_id slices to "prior turns only"
  via string-lexicographic comparison; this works because
  transcript_logger._new_turn_id() is `{utc-compact-ts}-{hex4}` and
  the timestamp prefix sorts chronologically. Ordered by id ASC.

- update_injection_referenced(event_id, *, referenced, referenced_at)
  — stamps referenced ∈ {0, 1} on a row that still has
  referenced IS NULL. Returns True on first call, False on
  re-call or unknown id (idempotent at the UPDATE WHERE level —
  a scored row's prior score is never overwritten).

19 new tests in tests/test_injection_events.py cover schema
presence, columns, indexes, canonical-JSON encoding, dedup,
empty-consumer default, FK cascade on belief delete, pending
filter, session filter, before_turn_id slicing, limit, decode
round-trip, deterministic order, idempotent update,
bad-value rejection, and unknown-id no-op.
Wires the close-the-loop relevance-signal write-path on every
UPS retrieval that produces a non-empty hits list. Mirrors the
existing `_emit_user_prompt_submit_rebuild_log` call site at
hook.py:865 — both fire on the same `if hits:` branch.

Adds:

- `_new_injection_event_turn_id()` in hook.py — same
  `{utc-compact-ts}-{hex4}` shape as transcript_logger's turn ids
  so the lexicographic-sort-is-chronological invariant
  `list_pending_injection_events.before_turn_id` relies on holds
  across writers. Generated fresh per UPS fire; not shared with
  transcript_logger's user-line id (the sweeper joins on
  session_id + temporal order, not turn-id equality).

- `_record_injection_events(...)` in hook.py — opens the store,
  writes one row per hit, fail-soft. Skips :memory: DBs (the
  rebuild_log emit applies the same guard) and empty session ids.

- `get_active_meta_belief_consumers()` in retrieval.py — returns
  the canonical-sorted list of meta-belief keys whose env flag is
  truthy. v1 covers `#756` half-life + `#757` bm25f_anchor_weight;
  siblings (#758-#760) drop in here as they ship.

Wiring change in `user_prompt_submit`: between the rebuild_log
emit and the `total_chars` measurement, fire one
`_record_injection_events` call carrying:

  - the session_id
  - a fresh turn_id (shared across the batch)
  - the post-dedup `hits` list
  - source="ups"
  - active_consumers from `get_active_meta_belief_consumers()`

Sweeper (Layer 3 / next commit's neighbour) will read these rows
on the *next* UPS turn and update each active consumer's
`relevance` sub-posterior.

10 new tests in tests/test_hook_injection_events_wiring.py cover
turn-id shape + uniqueness, get_active_meta_belief_consumers
under all env-flag permutations (sorted output), fail-soft on
missing session_id / empty hits / bad DB path, end-to-end UPS
fire records one event per hit with source='ups' and
referenced=NULL, threading the half-life consumer when its env
flag is on, and the batch-shares-a-turn-id invariant.
New module src/aelfrice/relevance_detection.py implements the
deterministic detection strategy ratified in
#779#issuecomment-4448107904 (Q5 / Q6):

- `normalize_text(s)` — NFC + casefold + whitespace collapse.
  Idempotent (running twice is a fixed point). casefold over
  lower() so German ß → ss folds correctly. NFC so composed and
  decomposed café both normalise to the same byte string.

- `is_referenced(belief_content, response_text)` — True iff the
  normalised belief content appears verbatim inside the
  normalised response. Beliefs shorter than 8 normalised chars
  return False (precision bias — a 1-3 char belief would match
  almost any response and shift posteriors on noise).

- `score_references(belief_pairs, response_text, *, strategy=...)`
  — bulk variant returning `[(event_id, referenced)]` in input
  order. Short beliefs score 0 (not filtered) so the sweeper's
  idempotent update_injection_referenced stamps them as scored
  rather than leaving them pending forever.

- `STRATEGY_EXACT_SUBSTRING` / `STRATEGY_NGRAM_OVERLAP` constants
  — n-gram is reserved for a future sub-issue (Q5 deferral). The
  v1 dispatch raises ValueError on anything other than the
  exact-substring strategy rather than silently picking a
  fallback.

Pure stdlib, no embeddings, no LLM judges — honours the locked
PHILOSOPHY (#605, c06f8d575fad71fb). Same inputs yield
byte-identical output.

23 new tests cover: normalize_text idempotency, casefold,
NFC compose/decompose match, whitespace-variant collapse
(CRLF / tabs / multi-space), leading/trailing strip, German ß
fold, empty string; is_referenced positive / case-insensitive /
whitespace-tolerant / negative / short-belief boundary / unicode
diacritic match / empty-response; score_references one-pair-per-
input, input-order preservation, empty list, short-belief scoring,
unsupported-strategy ValueError, explicit substring strategy,
determinism across runs.
Final wiring: at the *start* of every UPS hook (right after the
sentiment-feedback pass, before this turn's retrieval), the
sweeper scores the prior turn's pending injection_events and
pushes `relevance` evidence into each event's active consumers.

Adds in hook.py:

- `_read_assistant_text_since(session_id, since_iso, *, stderr)` —
  reads `turns.jsonl` (transcript_logger's Stop-hook output),
  filters by session_id + role=='assistant' + ts > since_iso,
  concatenates in file order. Fail-soft on missing file /
  malformed lines.

- `_sweep_relevance_signal(*, session_id, stderr)` —
  1. list_pending_injection_events(session_id)
  2. _read_assistant_text_since(session_id, oldest.injected_at)
  3. join event_id → belief.content via get_belief()
  4. score_references(pairs, response_text)
  5. for each (event_id, referenced):
     - update_meta_belief(consumer_key, SIGNAL_RELEVANCE,
       evidence=float(referenced), now_ts) per active_consumer
     - update_injection_referenced(event_id, referenced,
       referenced_at) — idempotent at the UPDATE-WHERE-NULL level

Fail-soft on every layer: store-open, transcript-read,
update_meta_belief, and update_injection_referenced errors each
print one stderr line and return. The sweeper is feedback
substrate; a write failure must not break the user-visible
retrieval contract that follows in the same UPS hook.

Substrate compatibility: meta-beliefs that didn't subscribe to
SIGNAL_RELEVANCE (e.g. the v1 #756 half-life consumer, which is
latency-only) silently no-op per `update_meta_belief`'s
no-row-no-write contract. The event still gets stamped as scored
so it never re-sweeps. This keeps the sweeper single-sourced via
`active_consumers` — siblings that DO subscribe to relevance
(test consumers, future #757 / #758 / #760 once they add
relevance) light up automatically.

11 new tests cover:
  - _read_assistant_text_since: missing file, session/role
    filtering, file-order concatenation, malformed-line skip.
  - _sweep_relevance_signal: no-session no-op, no-pending no-op,
    no-transcript leaves events pending, hit case shifts
    posterior (alpha+beta > 1 off prior), miss case scores 0,
    second run is idempotent (no double-count), latency-only
    consumer stamps event but doesn't materialise relevance row.

Wiring into `user_prompt_submit`: one line inserted between
`apply_sentiment_feedback(...)` and the prompt-shape gate, in
the same fail-soft posture as the sentiment lane.
Adds the CHANGELOG entry summarising the three-layer architecture
shipped across the four prior atomic commits + the design doc at
docs/design/relevance-signal.md covering data flow, schema
rationale, determinism contract, and the explicit deferral list
(GC, pre_compact source, n-gram detection, embedding/LLM-judge).
CodeQL flagged the unused import on tests/test_relevance_sweeper.py
(only META_HALF_LIFE_KEY is referenced). Collapses the multi-name
import to a single-line form. Unblocks merge-train on PR #789.
@robotrocketscience
robotrocketscience force-pushed the feat/issue-779-injection-events branch from a70d6fc to c345f3d Compare May 14, 2026 17:08
@robotrocketscience robotrocketscience removed the ready-to-merge Trigger merge-train: FF main to this PR's head label May 14, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Rebased on github/main HEAD (post-#759, #785, #791, #792). One conflict in src/aelfrice/retrieval.py where #759's decode_bfs_depth_budget / is_meta_belief_bfs_depth_budget_enabled landed in the same block where this PR adds get_active_meta_belief_consumers. Resolved by keeping both — see commit dc82c3f2. Did not add bfs_depth_budget to the active-consumers list (deferred to follow-up); the existing wiring tests pin only HALF_LIFE + BM25F_ANCHOR_WEIGHT env state and adding bfs_depth_budget would break them without a test update beyond the rebase scope.

Verification on the rebased branch:

  • All 7 commits signed (%G? = G).
  • pytest tests/test_relevance_sweeper.py tests/test_hook_injection_events_wiring.py tests/test_injection_events.py tests/test_relevance_detection.py → 63 passed.
  • pytest tests/test_retrieval_smoke.py tests/test_retrieve_v2.py tests/test_meta_beliefs.py tests/test_bfs_depth_budget_meta.py → 65 passed.

Re-adding ready-to-merge after CI greens.

@github-actions
github-actions Bot merged commit c345f3d into main May 14, 2026
23 of 24 checks passed
@github-actions

Copy link
Copy Markdown

merge-train: merged c345f3dmain via FF push.

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label May 14, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:bagheera:2026-05-14T17:11:06Z]

@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label May 14, 2026
@github-actions

Copy link
Copy Markdown

merge-train: merged c345f3dmain via FF push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live close-the-loop relevance-signal infrastructure — #756 / #480 prereq

2 participants