-- pgmnemo--0.9.2--0.9.3.sql -- Incremental upgrade: pgmnemo 0.9.2 → 0.9.3 -- SPDX-License-Identifier: Apache-2.0 -- -- THEME: D1 — Base-rate-adjusted reinforce() deltas + GUC-configurable -- -- PROBLEM: Shipped deltas (+0.10/-0.15) caused confidence to saturate at the ceiling -- under typical success rates, destroying discriminability. -- Base-rate-adjusted defaults (+0.02/-0.12) restore discriminability and show -- consistent positive correlation with actual outcome. -- -- FIX: -- 1. Change default deltas: -- success: +0.10 → +0.02 -- failure: -0.15 → -0.12 -- 2. Make GUC-configurable: -- pgmnemo.reinforce_success_delta (default 0.02, range [0.0, 0.5]) -- pgmnemo.reinforce_fail_delta (default 0.12, range [0.0, 0.5], applied negative) -- Both clamp to [0.0, 0.5]; applied via GREATEST/LEAST per existing pattern. -- -- ITEMS: -- #1 reinforce(BIGINT, TEXT) — scalar form: add GUC reads, use new deltas -- #2 reinforce(BIGINT[], TEXT) — batch form: add GUC reads, use new deltas -- -- Upgrade: ALTER EXTENSION pgmnemo UPDATE TO '0.9.3'; \echo Use "ALTER EXTENSION pgmnemo UPDATE TO '0.9.3'" to load this file. \quit -- ══════════════════════════════════════════════════════════════════════════════ -- #1: reinforce(BIGINT, TEXT) — scalar form (D1) -- ══════════════════════════════════════════════════════════════════════════════ -- -- Changes vs 0.9.2: -- + DECLARE: _success_delta DOUBLE PRECISION, _fail_delta DOUBLE PRECISION -- + GUC read block for pgmnemo.reinforce_success_delta (default 0.02, clamp [0.0, 0.5]) -- + GUC read block for pgmnemo.reinforce_fail_delta (default 0.12, clamp [0.0, 0.5]) -- ~ success branch: hardcoded 0.10 → _success_delta::REAL -- ~ failure branch: hardcoded 0.15 → _fail_delta::REAL -- + COMMENT updated. -- -- Signature unchanged — CREATE OR REPLACE is safe, no DROP needed. -- ────────────────────────────────────────────────────────────────────────────── CREATE OR REPLACE FUNCTION pgmnemo.reinforce( p_lesson_id BIGINT, p_outcome TEXT ) RETURNS REAL LANGUAGE plpgsql AS $func$ #variable_conflict use_column DECLARE _row pgmnemo.agent_lesson%ROWTYPE; _new_conf REAL; _success_delta DOUBLE PRECISION; _fail_delta DOUBLE PRECISION; BEGIN -- D1: read base-rate-adjusted delta GUCs -- pgmnemo.reinforce_success_delta default 0.02, clamp [0.0, 0.5] BEGIN _success_delta := GREATEST(0.0, LEAST(0.5, COALESCE( NULLIF(current_setting('pgmnemo.reinforce_success_delta', TRUE), '')::DOUBLE PRECISION, 0.02))); EXCEPTION WHEN OTHERS THEN _success_delta := 0.02; END; -- pgmnemo.reinforce_fail_delta default 0.12, clamp [0.0, 0.5], applied negative BEGIN _fail_delta := GREATEST(0.0, LEAST(0.5, COALESCE( NULLIF(current_setting('pgmnemo.reinforce_fail_delta', TRUE), '')::DOUBLE PRECISION, 0.12))); EXCEPTION WHEN OTHERS THEN _fail_delta := 0.12; END; SELECT * INTO _row FROM pgmnemo.agent_lesson WHERE id = p_lesson_id FOR UPDATE; IF NOT FOUND THEN RAISE EXCEPTION 'pgmnemo.reinforce: lesson_id % not found', p_lesson_id; END IF; CASE p_outcome WHEN 'success' THEN _new_conf := LEAST(1.0, _row.confidence + _success_delta::REAL); UPDATE pgmnemo.agent_lesson SET confidence = _new_conf, success_count = _row.success_count + 1, last_outcome = 'success', last_outcome_at = NOW() WHERE id = p_lesson_id; WHEN 'failure' THEN _new_conf := GREATEST(0.0, _row.confidence - _fail_delta::REAL); UPDATE pgmnemo.agent_lesson SET confidence = _new_conf, fail_count = _row.fail_count + 1, last_outcome = 'failure', last_outcome_at = NOW() WHERE id = p_lesson_id; WHEN 'neutral' THEN _new_conf := _row.confidence; ELSE RAISE EXCEPTION 'pgmnemo.reinforce: unknown outcome ''%'' -- expected ''success'', ''failure'', or ''neutral''', p_outcome; END CASE; RETURN _new_conf; END; $func$; COMMENT ON FUNCTION pgmnemo.reinforce(BIGINT, TEXT) IS 'Outcome-learning update (v0.9.3 D1): adjusts confidence for lesson p_lesson_id. ' 'Exact case required: ''success'' | ''failure'' | ''neutral''. ' 'success: confidence += pgmnemo.reinforce_success_delta (default 0.02, clamped to 1.0); ' ' increments success_count; sets last_outcome/at. ' 'failure: confidence -= pgmnemo.reinforce_fail_delta (default 0.12, clamped to 0.0); ' ' increments fail_count; sets last_outcome/at. ' 'neutral: no-op -- returns current confidence without any write. ' 'Unknown outcome string: RAISE EXCEPTION. ' 'Row-locked (SELECT ... FOR UPDATE) for concurrent-safe update on hot lessons. ' 'GUCs: pgmnemo.reinforce_success_delta [0.0,0.5] default 0.02; ' ' pgmnemo.reinforce_fail_delta [0.0,0.5] default 0.12 (applied negative). ' 'Rationale: base-rate-adjusted defaults restore confidence discriminability. ' ' +0.02/-0.12 restore discriminability (base-rate-adjusted).'; -- ══════════════════════════════════════════════════════════════════════════════ -- #2: reinforce(BIGINT[], TEXT) — batch form (D1) -- ══════════════════════════════════════════════════════════════════════════════ -- -- Changes vs 0.9.2: -- + DECLARE: _success_delta DOUBLE PRECISION, _fail_delta DOUBLE PRECISION -- + GUC read blocks before loop (read once, apply N times consistently) -- ~ success branch: hardcoded 0.10 → _success_delta::REAL -- ~ failure branch: hardcoded 0.15 → _fail_delta::REAL -- + COMMENT updated. -- -- Signature unchanged — CREATE OR REPLACE is safe, no DROP needed. -- ────────────────────────────────────────────────────────────────────────────── CREATE OR REPLACE FUNCTION pgmnemo.reinforce( p_lesson_ids BIGINT[], p_outcome TEXT ) RETURNS INT LANGUAGE plpgsql AS $func$ DECLARE _id BIGINT; _row pgmnemo.agent_lesson%ROWTYPE; _new_conf REAL; _updated INT := 0; _success_delta DOUBLE PRECISION; _fail_delta DOUBLE PRECISION; BEGIN -- Validate outcome up-front so the caller gets a clear error on bad input. IF p_outcome NOT IN ('success', 'failure', 'neutral') THEN RAISE EXCEPTION 'pgmnemo.reinforce: unknown outcome ''%'' -- expected ''success'', ''failure'', or ''neutral''', p_outcome; END IF; IF p_lesson_ids IS NULL OR array_length(p_lesson_ids, 1) IS NULL THEN RETURN 0; END IF; -- D1: read base-rate-adjusted delta GUCs once before iterating -- pgmnemo.reinforce_success_delta default 0.02, clamp [0.001, 0.5] BEGIN _success_delta := GREATEST(0.001, LEAST(0.5, COALESCE( NULLIF(current_setting('pgmnemo.reinforce_success_delta', TRUE), '')::DOUBLE PRECISION, 0.02))); EXCEPTION WHEN OTHERS THEN _success_delta := 0.02; END; -- pgmnemo.reinforce_fail_delta default 0.12, clamp [0.001, 0.5], applied negative BEGIN _fail_delta := GREATEST(0.001, LEAST(0.5, COALESCE( NULLIF(current_setting('pgmnemo.reinforce_fail_delta', TRUE), '')::DOUBLE PRECISION, 0.12))); EXCEPTION WHEN OTHERS THEN _fail_delta := 0.12; END; FOREACH _id IN ARRAY p_lesson_ids LOOP SELECT * INTO _row FROM pgmnemo.agent_lesson WHERE id = _id FOR UPDATE; IF NOT FOUND THEN CONTINUE; -- skip missing; no RAISE (bitemporal supersession / TTL normal) END IF; CASE p_outcome WHEN 'success' THEN _new_conf := LEAST(1.0, _row.confidence + _success_delta::REAL); UPDATE pgmnemo.agent_lesson SET confidence = _new_conf, success_count = _row.success_count + 1, last_outcome = 'success', last_outcome_at = NOW() WHERE id = _id; _updated := _updated + 1; WHEN 'failure' THEN _new_conf := GREATEST(0.0, _row.confidence - _fail_delta::REAL); UPDATE pgmnemo.agent_lesson SET confidence = _new_conf, fail_count = _row.fail_count + 1, last_outcome = 'failure', last_outcome_at = NOW() WHERE id = _id; _updated := _updated + 1; WHEN 'neutral' THEN NULL; -- no-op; does not increment _updated END CASE; END LOOP; RETURN _updated; END; $func$; COMMENT ON FUNCTION pgmnemo.reinforce(BIGINT[], TEXT) IS 'Batch confidence update v0.9.3 D1. Iterates p_lesson_ids; skips missing IDs silently (no RAISE). ' 'Returns count of rows actually updated (neutral outcome does not increment count). ' 'Unknown outcome string raises RAISE EXCEPTION (caller programming error). ' 'Empty or NULL array returns 0. ' 'success: +pgmnemo.reinforce_success_delta (default 0.02, clamped to 1.0), ' 'failure: -pgmnemo.reinforce_fail_delta (default 0.12, clamped to 0.0), ' 'neutral: no-op. ' 'GUCs read once before the loop -- consistent deltas across all IDs in one batch call. ' 'Scalar form reinforce(BIGINT, TEXT) updated identically. ' 'Rationale: base-rate-adjusted defaults restore confidence discriminability. ' ' +0.02/-0.12 restore discriminability (base-rate-adjusted).';