docs(limitations): the exposure-as-evidence path has not existed since #1162 (#1267) - #1284
Conversation
#1267 states that an automatic channel raises posteriors and none lowers them. Both halves are settled here by driving every production apply_feedback route against a fresh in-memory store rather than by reading constants. Exposure (+0.1) is real but gated on _exposure_updates_posterior(), default False since #1086 — the audit row is written, alpha is not touched. Sentiment-from-prose is wired into UserPromptSubmit at #606 and emits negative valence at 10-20x the exposure magnitude, so an automatic down-channel does exist. The deferred sweeper writes nothing since #1162 and its enqueue is default off. Clears ambient AELFRICE_* before importing aelfrice so a developer's own opt-ins cannot change the reported defaults, and exits non-zero if any channel departs from the documented posture.
The sharp-edges entry said retrieval "still enqueues each surfaced belief" and that the manual sweep "applies a small alpha bump (default +0.05)", citing #1091 as having only flagged the sweep for audit-only treatment. #1162 delivered it: sweep_deferred_feedback classifies what it would have applied and returns mutated=False, and the enqueue is gated on AELFRICE_IMPLICIT_FEEDBACK_ENQUEUE, default off. A reader auditing where their posteriors come from was pointed at a mutation path that no longer fires.
There was a problem hiding this comment.
Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning Review limit reached
Next review available in: 37 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
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. Comment |
PR-size soft capThis PR is over the advisory size threshold:
Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the |
Reviewer's GuideDocuments that there is currently no automatic exposure-as-evidence path at defaults, corrects LIMITATIONS and CHANGELOG around feedback channels, and adds a benchmark script that audits which feedback channels move posteriors under default configuration and guards against regressions. Sequence diagram for posterior_channel_audit benchmark over feedback channelssequenceDiagram
participant Script as posterior_channel_audit
participant Env as os_environ
participant Store as MemoryStore
participant Exposure as record_retrieval
participant Sentiment as apply_sentiment_to_pending
participant Sweeper as sweep_deferred_feedback
Script->>Env: clear AELFRICE_* variables
rect rgb(230,230,250)
Script->>Store: _seed("b1")
Script->>Exposure: record_retrieval(Store, [Belief("b1")])
Exposure-->>Store: write audit row only (default)
Script->>Store: _ab(Store, "b1")
Store-->>Script: alpha,beta unchanged
Script-->>Script: record "automatic UP channel off by default"
end
rect rgb(230,255,230)
Script->>Sentiment: detect_sentiment("no that's wrong")
Sentiment-->>Script: negative signal (BASE_VALENCE)
Script->>Store: _seed("b2")
Script->>Sentiment: apply_sentiment_to_pending(Store, signal, ["b2"])
Sentiment-->>Store: increase beta (down-channel)
Script->>Sentiment: apply_sentiment_to_pending(Store, signal, ["b2"], escalated=True)
Sentiment-->>Store: further increase beta (ESCALATED_NEGATIVE_VALENCE)
Script-->>Script: record "automatic DOWN channel exists but is opt-in"
end
rect rgb(255,240,230)
Script->>Store: _seed("b3")
Script->>Sweeper: sweep_deferred_feedback(Store)
Sweeper-->>Script: mutated=False, alpha_withheld=0.0
Script->>Store: _ab(Store, "b3")
Store-->>Script: alpha,beta unchanged
Script-->>Script: record "no residual exposure-as-evidence path"
end
Script-->>Script: aggregate failures and print VERDICT
Script-->>Script: exit non-zero if any channel moves posterior at defaults
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
[claim:review:Setr:2026-08-01T03:38:55Z] |
|
[claim:review:Garsecg:2026-08-01T03:39:05Z] |
|
[release:review:Garsecg:2026-08-01T03:39:11Z] |
|
merge-train: merged 49423f0 → |
|
[claim:review:Gylf:2026-08-01T03:43:45Z] |
|
[release:review:Gylf:2026-08-01T03:43:51Z] |
Review — the doc fix is right; the guard behind it was not falsifiableThe premise correction and the
So the entry that had to go was genuinely stale, and the replacement text is
Two independent reasons that did not hold. Both are now fixed and pushed to 1. The channel-3 check could not fail —
|
Correction to my review above — this PR was already merged when I posted itMy previous comment closed with "approving on content" and "holding the merge This PR merged at 03:40:14Z. The queue scan that routed me here ran at I have restored this branch to The findings themselves stand, and now describe
Nothing in #1290 changes a default, a behaviour, or any doc text — the premise |
|
[release:review:Setr:2026-08-01T04:18:25Z] |
Refs #1267. Does not close it — #1267's first acceptance criterion is an
operator decision on direction, and that is still open. What this lands is the
measurement that decision should be taken against, plus the one concrete doc
defect the measurement exposed.
Attribution first
The premise correction below was reached independently, but not first. The
framing comment on #1267
got there earlier and went further — it enumerates the
apply_feedbackcallsites exhaustively, confirms both defaults by calling them, and backs it with
the live store (594 beliefs sitting exactly on their ingest prior after 13,812
exposures). It is the record on the premise; this section is context for the
change, not a claim of discovery.
What is new here is the doc defect that enumeration could not surface, and
the guard against it recurring. The deferred-feedback sweeper stopped being an
apply_feedbackcaller at #1162, so it correctly does not appear in thattable — but
LIMITATIONS.mdstill described it as one.The issue's stated asymmetry does not hold on
main#1267 opens with:
I drove every production
apply_feedbackroute against a fresh store ratherthan reading the constants. Both halves of that sentence are wrong, in
opposite directions.
HOOK_RETRIEVAL_VALENCE = +0.1+0.05−1.0 / −1.5, escalating−2.0UserPromptSubmit(hook.py:976)aelf feedback <id> used|harmfulCorrection 1 — the "up" channel is off. #1267 quotes
HOOK_RETRIEVAL_VALENCE = 0.1and theapply_feedbackcall inrecord_retrieval, and both are real. But that call passesupdate_posterior=_exposure_updates_posterior(), and the resolver has reados.environ.get(ENV, "0") == "1"since #1086. The issue notes the gateparenthetically without its default, which reads as an always-on channel. At
default it writes the audit row and leaves α/β untouched.
Correction 2 — an automatic down-channel exists. #1267 says the negative
path is only
aelf feedback <id> harmfulplus two MCP sites. It missessentiment_feedback.py:315. That lane is wired intoUserPromptSubmitathook.py:976, fires on every prompt before the turn's retrieval,regex-matches the user's prose and distributes a negative valence across
the previous turn's pack —
valence = magnitude if POSITIVE else -magnitude,with a correction-frequency escalator at
−2.0. That is 10–20× the exposuremagnitude. No human types a command for it to fire.
So the corrected statement is symmetric, not asymmetric: at default
settings no automatic channel moves a posterior in either direction. Both
automatic channels are opt-in and off. The residual gap is much narrower than
#1267 describes — it is a config combination (opt into exposure but not
sentiment ⇒ up-only), not a design asymmetry.
I have not edited #1267's body; the finding is posted there as a comment and
the disposition is the operator's.
The doc defect this exposed
LIMITATIONS.mdclaimed a mutation path that has not fired since #1162:Three stale claims.
sweep_deferred_feedbackis documented "Writesnothing (#1162)" and returns
mutated=False/alpha_withheld=0.0;is_enqueue_on_retrieve_enabled()is defaultFalseandretrieval.py:4098honours it; and #1091 is merged, with #1162 having delivered the treatment.
A reader auditing where their posteriors come from was pointed at a path that
no longer exists.
This is squarely #1267's third acceptance criterion — "the docs stop
describing posterior movement as evidence of correctness while the only
automatic input is exposure." The rest of that bullet was already accurate on
exposure; only the sweeper clause was stale.
Why the script ships
Per the standing finding that published numbers must ship the script that
produced them,
benchmarks/posterior_channel_audit.pyis committed ratherthan pasted. It exits non-zero if any channel departs from the documented
posture, so it is a regression guard on the defaults as well as a
measurement — this doc entry cannot go stale silently the way the last one
did. It clears ambient
AELFRICE_*before importingaelfrice, becauseseveral of these resolvers read the environment and a developer's own opt-ins
would otherwise change the reported defaults. No live store is read; every
belief is synthetic.
What is deliberately not here
measurement in exp(refutation): run proposal 17's kill experiment — re-derive the refutation-by-edit fire set and measure its precision (#1177) #1255), posterior decay (docs: ARCHITECTURE and PHILOSOPHY assert posterior decay that has no production caller #1218), and auto-demotion on
contradiction (refactor(feedback): remove unreachable CONTRADICTS-edge auto-demote path (per #605) #814); none of them is proposed.
Verification
benchmarks/posterior_channel_audit.py— exits 0 on this branch.github/main, fast-forward; three atomic signed commits.Pre-PR gate status — read this before re-running it
The gate's rebase, discretion and sibling-PR steps all pass. Its
pyteststeppassed in full on this exact tree on the first invocation (which then
aborted at step 5 only because the branch was not yet pushed). Re-runs
afterwards failed, and the failures are wall-clock artifacts of this
workstation, not of the diff — which is two docs files plus a
benchmarks/script that no test imports.
Full suite, no
-x: 6888 passed, 5 failed, and every failure is a timingassertion:
test_doctor_cli_exit_1_when_brokenTimeout (>5.0s)— passes in isolationtest_commit_limit_cappedTimeout (>5.0s)test_force_push_after_real_rebase_passesTimeout (>5.0s)test_mass_bounded_and_order_invariant_for_any_shapeTimeout (>5.0s)test_ac11_per_query_overhead_within_budgetassert elapsed < 1.0→1.519sThe three subprocess timeouts are the 5s global
timeoutinpyproject.tomlmeeting a saturated box:
test_commit_limit_cappeddoes 20 commits ≈ 41gitspawns, and that work alone — with no
aelfricecode in the loop — measured12.5–14.5 s here at load average 13.75.
test_ac11_per_query_overhead_within_budgetis the one that is not a timeout,so I checked it against the possibility that #1274 (merged into
mainan hourago, and touching
retrieval.py) had added per-query overhead. It had not:order_for_injection/resolve_order_policyare reached only fromhook.pyat the render boundary, never from
retrieve(). Timed both trees:retrieve()9258d87f)Indistinguishable, and both over the 1.0 s ceiling — the assertion is an
uncalibrated absolute wall-clock budget that this machine no longer meets on
main. It is green on CI (#1276 merged withpytest (3.12)and(3.13)SUCCESS 40 minutes ago), and CI runs the same unfiltered
pytest tests/ --ignore=tests/e2e, so CI is the authority here — that iswhat the required checks on this PR will settle.