From a6fb63222106a1ad5ae0381ce2ad06e0b3ec96ab Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 28 Jun 2026 22:50:55 +0000 Subject: [PATCH] =?UTF-8?q?feat(learning):=20raise=20writer=20lessons=20ca?= =?UTF-8?q?p=2015=20=E2=86=92=2060=20(config.WRITER=5FLESSONS=5FLIMIT)=20?= =?UTF-8?q?=E2=80=94=20closes=20#157?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit עכשיו שהלקחים מנוכי-כפילות (סינתזה #158) ומאושרי-יו"ר, התקרה 15 חתכה בשקט הנחיות-סגנון מובחנות ו-vetted (רישוי/סגנון 29>15). מעלים ל-60 (env-tunable WRITER_LESSONS_LIMIT) — מכסה כל shard נוכחי עם מרווח; הסינתזה שומרת על אי-רדודנטיות בצמיחה. ה-WARN על חיתוך נשאר רשת-הביטחון (חוקה §6) ומפנה לסינתזה/העלאת-התקרה. block_writer צורך דרך config. Invariants: INV-LRN1/G10 (עדיין רק approved זורם; superseded מוחרג), G2. closes #157 בפועל. Co-Authored-By: Claude Opus 4.8 (1M context) --- mcp-server/src/legal_mcp/config.py | 7 +++++++ .../src/legal_mcp/services/block_writer.py | 3 ++- mcp-server/src/legal_mcp/services/db.py | 20 +++++++++---------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/mcp-server/src/legal_mcp/config.py b/mcp-server/src/legal_mcp/config.py index 4ba868c..63147bc 100644 --- a/mcp-server/src/legal_mcp/config.py +++ b/mcp-server/src/legal_mcp/config.py @@ -293,6 +293,13 @@ LESSON_SYNTH_DRIFT_FLOOR = float(os.environ.get("LESSON_SYNTH_DRIFT_FLOOR", "0.8 # quality backstop below this. LESSON_SYNTH_CLUSTER_THRESHOLD = float(os.environ.get("LESSON_SYNTH_CLUSTER_THRESHOLD", "0.68")) +# How many CHAIR-APPROVED decision_lessons flow to the writer per practice_area +# (get_recent_decision_lessons). Raised 15 → 60 (#158/#157): the lessons are now both +# deduplicated (synthesis merges true near-duplicates) AND chair-approved, so the old +# cap silently truncated distinct, vetted style guidance. 60 covers every current shard +# with headroom; synthesis keeps the set non-redundant as it grows. env-tunable. +WRITER_LESSONS_LIMIT = int(os.environ.get("WRITER_LESSONS_LIMIT", "60")) + # Mistral OCR (fallback for scanned PDFs — replaces Google Cloud Vision) MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "") diff --git a/mcp-server/src/legal_mcp/services/block_writer.py b/mcp-server/src/legal_mcp/services/block_writer.py index 31edd77..672ff22 100644 --- a/mcp-server/src/legal_mcp/services/block_writer.py +++ b/mcp-server/src/legal_mcp/services/block_writer.py @@ -1028,7 +1028,8 @@ async def _build_style_context(practice_area: str = "") -> str: except Exception as e: logger.warning("methodology overrides not loaded: %s", e) try: - lessons = await db.get_recent_decision_lessons(limit=15, practice_area=practice_area) + lessons = await db.get_recent_decision_lessons( + limit=config.WRITER_LESSONS_LIMIT, practice_area=practice_area) if lessons: learned.append("\n**לקחים מהחלטות קודמות (decision_lessons):**") for ls in lessons: diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index 3fe8afa..e845f81 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -3147,19 +3147,18 @@ async def get_style_distance_history() -> list[dict]: ] -async def get_recent_decision_lessons(limit: int = 15, practice_area: str = "") -> list[dict]: +async def get_recent_decision_lessons(limit: int = 60, practice_area: str = "") -> list[dict]: """Per-decision learnings the chair/curator attached in /training (decision_lessons), so the writer consumes them too (T15). Prefers style/structure/lexicon, recent first. - Gate (INV-LRN1/G10): returns only CHAIR-APPROVED lessons (review_status='approved'). - Panel-written proposals stay out of the writer's context until the chair approves - them in /training — the chair's review is the gate, not the panel's 2/2 vote. + Gate (INV-LRN1/G10): returns only CHAIR-APPROVED lessons (review_status='approved'); + superseded sources (merged by synthesis, #158) are excluded automatically. - No silent cap (חוקה §6): when more approved lessons match than `limit`, the - overflow is dropped by ORDER BY created_at DESC — log it so coverage loss is - observable, not invisible. The real fix is lesson synthesis (TaskMaster #158): - consolidate overlapping lessons into fewer, richer ones so the set fits without - truncation. Until then this WARN is the audit trail. + Cap (#157/#158): callers pass config.WRITER_LESSONS_LIMIT (60). Raised from 15 once + lessons became deduplicated (synthesis) AND chair-approved — the old cap silently + truncated distinct, vetted style guidance. If even 60 is exceeded the overflow is + dropped (ORDER BY created_at DESC) and logged (no silent cap, חוקה §6); that WARN is + the signal to run synthesis (lesson_synthesize_pending) or raise WRITER_LESSONS_LIMIT. """ pool = await get_pool() async with pool.acquire() as conn: @@ -3185,7 +3184,8 @@ async def get_recent_decision_lessons(limit: int = 15, practice_area: str = "") if total > limit: logger.warning( "get_recent_decision_lessons: capped %d→%d approved lessons " - "(practice_area=%r) — %d not reaching the writer. See TaskMaster #158 (synthesis).", + "(practice_area=%r) — %d not reaching the writer. Run synthesis " + "(lesson_synthesize_pending) or raise WRITER_LESSONS_LIMIT.", total, limit, practice_area or "*", total - limit, ) return [dict(r) for r in rows]