feat: add skills, memory, delegation and self-learning (Phase 3)
Add three Hermes-inspired capabilities to Shira: - Skills system: firm-wide workflow templates (list/view/manage tools), 4 pre-seeded skills (case summary, hearing prep, direct access report, deadline tracker), auto-learning via post-conversation meta-prompt - Cross-conversation memory: per-lawyer profiles and per-case memory stored as bounded markdown files, injected into system prompts - Sub-agent delegation: spawn up to 3 child AgentRunner instances for parallel document analysis and complex multi-step tasks New files: skills.py, memory.py, delegate.py, learner.py, 4 SKILL.md Updated: agent_runner (6 new tools, depth/blocked_tools support), prompt_builder (skills/memory injection), route (memory loading, background learning), main.py (data dirs), Dockerfile (pyyaml, skills) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import logging
|
||||
|
||||
@@ -10,6 +11,9 @@ from pydantic import BaseModel
|
||||
|
||||
from api.services.prompt_builder import build_case_prompt, build_office_prompt
|
||||
from api.services.agent_runner import AgentRunner
|
||||
from api.services.skills import list_skills
|
||||
from api.services.memory import load_user_profile, load_case_memory
|
||||
from api.services.learner import extract_skills
|
||||
|
||||
logger = logging.getLogger("shira.api")
|
||||
|
||||
@@ -64,8 +68,29 @@ async def smart_assistant_chat(request: Request):
|
||||
if not message and not conversation_history:
|
||||
return {"text": "no message", "actions": [], "conversationId": conversation_id}
|
||||
|
||||
# Build system prompt
|
||||
system_prompt = build_office_prompt(context) if mode == "office" else build_case_prompt(context)
|
||||
# Extract IDs for memory loading
|
||||
case_id = (context.get("case") or {}).get("id") or (context.get("drillDown", {}).get("case", {}).get("id"))
|
||||
user_id = (context.get("currentUser") or {}).get("id")
|
||||
|
||||
# Load cross-conversation memory and skills
|
||||
user_profile = load_user_profile(user_id) if user_id else ""
|
||||
case_memory_notes = load_case_memory(case_id) if case_id else ""
|
||||
available_skills = list_skills()
|
||||
|
||||
# Build system prompt with memory and skills injected
|
||||
if mode == "office":
|
||||
system_prompt = build_office_prompt(
|
||||
context,
|
||||
user_profile=user_profile,
|
||||
available_skills=available_skills,
|
||||
)
|
||||
else:
|
||||
system_prompt = build_case_prompt(
|
||||
context,
|
||||
user_profile=user_profile,
|
||||
case_memory_notes=case_memory_notes,
|
||||
available_skills=available_skills,
|
||||
)
|
||||
|
||||
# Build messages from conversation history
|
||||
messages = []
|
||||
@@ -100,9 +125,22 @@ async def smart_assistant_chat(request: Request):
|
||||
|
||||
logger.info("[smart-assistant] response: %s...", text[:80])
|
||||
|
||||
# Trigger self-learning in the background (non-blocking)
|
||||
conversation_messages = runner.get_messages()
|
||||
if conversation_messages:
|
||||
asyncio.create_task(_learn_in_background(conversation_messages))
|
||||
|
||||
return {
|
||||
"text": text,
|
||||
"actions": [],
|
||||
"executedActions": [],
|
||||
"conversationId": conversation_id,
|
||||
}
|
||||
|
||||
|
||||
async def _learn_in_background(messages: list[dict]) -> None:
|
||||
"""Run skill extraction in background — errors are swallowed and logged."""
|
||||
try:
|
||||
await extract_skills(messages)
|
||||
except Exception as e:
|
||||
logger.error("[learner] background learning failed: %s", e)
|
||||
|
||||
Reference in New Issue
Block a user