Three changes paired with SmartAssistant 2.10.0:
1. prompt_builder.py — every prompt section (tool_rules, writing_style,
legal_assistance, personality_case, personality_office, other_rules_case,
other_rules_office) now reads from context['assistantPrompts'][key]
with the existing string as a fallback default. Admins editing
AssistantPrompt rows in the EspoCRM UI immediately affect the next
conversation. A missing/inactive row falls back to the in-code default
so deleting everything in the UI never bricks Shira.
Also: the user-profile section now prefers context['userProfile']
(from the new UserProfile entity) over the explicit `user_profile`
parameter, completing the move from /opt/data/profiles/*.md to the CRM.
2. crm_tools.py:save_memory — stops POSTing directly to /Note with a
'🧠 category:' prefix. Routes through SmartAssistant/action/executeTool
so the PHP saveMemory handler actually creates a CaseMemory row.
Cause of yesterday's prod issue: CaseMemory table was empty even
though Shira was "saving" to it.
3. scripts/migrate_legacy_memory_notes.py — one-shot migration that
sweeps all existing '🧠 category: ...' Notes into CaseMemory rows
and soft-deletes the originals. Idempotent; safe to re-run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real-world failures from prod sessions today:
* Shira called save_rule, EspoCRM returned 404 ("Controller AssistantRule
does not exist"), the tool returned a fail() result — and Shira told the
user "✅ כלל חדש נשמר". Same hallucination pattern as the document-content
incident yesterday, just on a different surface. Added an explicit
VERIFY-TOOL-RESULT block that says: read the tool result, if it starts
with ❌ or mentions שגיאה/נכשל/failed/error, report the failure honestly.
* When the user named a specific file in a case ("הודעה לבית הדין-...docx")
and list_documents only showed the case root with empty subfolders,
Shira reported "not found" instead of recursing into מסמכים/התכתבויות/
כללי/אסמכתאות. Then "fixed" it by permanently set_case_folder_path-ing
the root to a subfolder, which would have broken access to every other
document in the case. Added a SUBFOLDER SEARCH section to TOOL_RULES.
* The user noted that Shira's writing uses em-dashes (— U+2014) as a
primary separator, which is a strong "this was written by AI" tell.
Added a WRITING STYLE section banning em-dash and en-dash, mandating
hyphen-minus only when truly needed, and warning against dash pileups
and other AI tells.
Refs Task Master none (these are prompt-only changes, no schema impact).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent bugs combined to make Shira fabricate the content of
case-46-Friedman's appeal in production (CTS case rewritten as a knee
injury, wrong dates, wrong court file number, wrong %-of-disability —
written into save_memory/Task/Meeting on prod).
* api/services/ocr.py — ocr_docx_images now reads word/document.xml first
(text content), then OCRs embedded images. Previously it only OCR'd the
word/media/* images, so a text-heavy DOCX with one signature image
returned only "[signature]" to the LLM. Verified against the same
Friedman appeal: 16633 chars / 80 paragraphs / 0 XML noise.
* mcp_server/tools/document_tools.py — _ocr_fallback now refuses to wrap
an empty/signature-only OCR result as success. If <80 useful chars or
just "[signature]", returns an explicit failure that instructs the LLM
not to describe / summarize the document.
* api/services/prompt_builder.py — TOOL_RULES adds the absolute rule
"DOCUMENT FAITHFULNESS": never quote a document not literally in the
tool result for THIS turn; never infer content from a file name; treat
dates and case numbers as especially dangerous to invent.
Refs Task Master #5, #6, #7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the Python side of the SmartAssistant 2.9 release. Each tool maps to
either a new EspoCRM action or a generic CRM endpoint.
Document creation:
- create_document: free-form RTL DOCX with markdown body; calls
SmartAssistant/action/createDocument.
Folder management (all sandboxed to the current case's root):
- write_document_to_folder: raw upload to a path inside the case folder.
- create_subfolder: mkdir with recursive parent creation.
- rename_item / move_item: rename/move files and folders.
- mark_document_for_deletion: soft-delete — moves the file to
"מסמכים למחיקה/" with a "למחיקה - " prefix. Shira is explicitly told
she has NO physical-delete permission.
CRM gaps:
- update_case_fields: allow-listed PUT on Case (judge, court, caseType,
practiceArea, opposingParty, status, priority, etc.).
- search_entities: generic GET with EspoCRM where[] params across
Case/Contact/Account/Document/Task. Wildcard "*" maps to LIKE.
- draft_email: POST Email with status="Draft", linked to the current case.
prompt_builder additions:
- FREE DOCUMENT CREATION: don't say "I can't" — call create_document.
- FOLDER MANAGEMENT: full read/write inside the case folder only.
- FILE DELETION: always mark_document_for_deletion; never claim a physical
delete.
Pairs with SmartAssistant extension release that adds FreeDocumentGenerator,
CaseFolderManager, and 6 controller actions.
Refs Task Master #4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two new tool categories — billing (10 tools) and legal_aid (6 tools)
— so Shira can drive the full Green Invoice flow: list unbilled activities,
manage charges (approve/cancel/calculate), build draft invoices from
activities, sync to Green Invoice, plus the legal-aid sub-flow (proforma,
mark-submitted, monthly 320 wrap-up, prepare-email, resubmit rejected).
Wires both registrars into agent_runner with allowed_toolsets gating, and
extends TOOL_RULES so Shira (a) shows draft totals before creating, (b)
treats send_invoice_to_green_invoice as irreversible and demands explicit
confirmation, and (c) never marks a legal-aid proforma as submitted
without an explicit request number from the user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two minimal additions for the upcoming v0.1.11 client work:
- /kb/search hits now include chunk_index, so the client can call
/source/{id}/chunks?around=N&ctx=K to fetch the matched chunk plus K
chunks before/after as context. Required by the new search-preview
layout for text sources, which until now duplicated the matched chunk
in both panes.
- /source/{id}/chunks accepts around (chunk_index) + ctx (window radius
clamped to 0..10). When around is given, returns only the windowed
range; otherwise unchanged (returns all chunks of the source).
Refs Task Master #1
EspoCRM stores dates in UTC. Shira was displaying them raw (e.g., 08:00 UTC
instead of 11:00 Israel time) and storing user-provided local times as UTC.
- Add _to_local() in prompt_builder to convert UTC→Asia/Jerusalem for all
displayed dates (hearings, meetings, calls, tasks, notes)
- Update normalize_date() in _helpers to convert Israel local→UTC before
sending to EspoCRM
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security:
- Path traversal: validate user_id, case_id, skill_name against safe regex
- Input sanitization in memory.py and skills.py file path construction
Correctness:
- ISR timezone: replace hardcoded UTC+3 with ZoneInfo("Asia/Jerusalem") for DST
- save_rule: use EspoCrmApiError.status_code instead of fragile string matching
- Background tasks: store references to prevent GC before completion
- Lock race: use dict.setdefault() for atomic lock creation
- Version: health.py now matches main.py (0.2.0)
- Skill names: normalized to lowercase for dedup consistency
Build:
- Dockerfile: install from pyproject.toml instead of hardcoded pip list
- Remove unused mcp>=1.0.0 dependency from pyproject.toml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EspoCRM may send documents as a list instead of a dict with
totalFiles. Convert list to dict format to avoid AttributeError.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>