feat(curator): trigger Knowledge Curator from api_mark_final, drop CEO F2
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s

The previous F2 stage in legal-ceo.md fired after the first DOCX export
— too early, since the user often iterates with עריכה-* uploads after
the first export. The true "this is dafna's chosen final" signal is the
"סמן כסופי" button in the UI, which calls api_mark_final.

This commit moves the curator wakeup from CEO's instructions to a
direct hook in api_mark_final:

- web/paperclip_client.py: add CURATOR_AGENTS dict (CMP + CMPA UUIDs)
  and wake_curator_for_final() helper. Looks up main case issue,
  creates a child issue assigned to the curator, tags plugin_state for
  case visibility, and triggers wakeup via Paperclip API.
- web/app.py: api_mark_final now calls workflow_tools.ingest_final_version
  (so case_law table finally gets populated for search_decisions) and
  pc_wake_curator_for_final. Both are best-effort — failure does not
  block marking final.
- legal-ceo.md: remove F2 stage, leave only the agents-table reference
  noting the curator runs from api_mark_final.
- hermes-curator.md: update activation description to reflect the new
  flow.

Result: curator runs only when chaim deliberately clicks "סמן כסופי",
on the actual final file, with no risk of analyzing a draft that will
later change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 14:47:03 +00:00
parent 77e5996497
commit 799b950961
4 changed files with 141 additions and 59 deletions

View File

@@ -62,6 +62,7 @@ from web.paperclip_client import (
respond_to_interaction as pc_respond_to_interaction,
restore_project as pc_restore_project,
wake_ceo_agent as pc_wake_ceo,
wake_curator_for_final as pc_wake_curator_for_final,
wake_for_precedent_extraction as pc_wake_for_precedent_extraction,
)
@@ -2347,10 +2348,43 @@ async def api_mark_final(case_number: str, filename: str):
if case_dir.exists():
commit_and_push(case_dir, f"גרסה סופית: {final_name}")
# Best-effort: ingest final into internal corpus (case_law) — feeds
# search_decisions and learning_loop. Non-fatal on failure.
ingest_status: dict = {"status": "skipped"}
try:
ingest_result = await workflow_tools.ingest_final_version(
case_number, file_path=str(final_path)
)
ingest_status = {"status": "ok", "result": (ingest_result or "")[:200]}
except Exception as e:
logger.warning("ingest_final_version failed for %s: %s", case_number, e)
ingest_status = {"status": "error", "error": str(e)}
# Best-effort: wake the Knowledge Curator (Hermes) to analyze the
# signed final and propose updates to skills/lessons. Non-fatal on
# failure so marking final never breaks for the user.
curator_status: dict = {"status": "skipped"}
try:
# Company by case-number prefix: 1xxx=CMP (licensing), 8/9xxx=CMPA (betterment)
prefix = case_number[:1]
company_id = (
PAPERCLIP_COMPANIES["licensing"] if prefix == "1"
else PAPERCLIP_COMPANIES["betterment"] if prefix in ("8", "9")
else ""
)
curator_status = await pc_wake_curator_for_final(
case_number, final_name, company_id=company_id
)
except Exception as e:
logger.warning("curator wakeup failed for %s: %s", case_number, e)
curator_status = {"status": "error", "error": str(e)}
return {
"final_filename": final_name,
"training_copy": str(training_dest),
"status": "final",
"ingest_final": ingest_status,
"curator": curator_status,
}