fix: task intent classifier, Hermes brain, rolling run log
- Intent classifier: task phrases now checked before query to prevent
"add task X" mis-routing; "job item"/"job ticket"/"work order" added
to TASK_PHRASES; "please add + project keyword" fallback added;
substring match bug fixed ("in" inside "incident" triggered query)
- brain.py: routes planning fallback to Hermes cloud (claude-sonnet-4-6)
via HERMES_URL/HERMES_API_KEY env vars; falls back to local Ollama
if Hermes is unavailable
- main.py: rolling 50-run log written to logs/jon-snow/runs.jsonl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -9,15 +9,21 @@ litellm.set_verbose = False
|
||||
FAST_MODEL = os.getenv("FAST_MODEL", "ollama/gemma4")
|
||||
SMART_MODEL = os.getenv("SMART_MODEL", "ollama/gemma4")
|
||||
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://172.27.40.20:11434")
|
||||
|
||||
|
||||
def _extra_kwargs(model: str) -> dict:
|
||||
if model.startswith("ollama/"):
|
||||
return {"api_base": OLLAMA_BASE_URL}
|
||||
return {}
|
||||
HERMES_URL = os.getenv("HERMES_URL", "")
|
||||
HERMES_API_KEY = os.getenv("HERMES_API_KEY", "none")
|
||||
|
||||
|
||||
async def stream_completion(messages: list[dict], use_smart: bool = False):
|
||||
if HERMES_URL:
|
||||
logger.info("Brain: routing to Hermes cloud (claude-sonnet-4-6)")
|
||||
return await litellm.acompletion(
|
||||
model="openai/hermes-agent",
|
||||
messages=messages,
|
||||
stream=True,
|
||||
api_base=HERMES_URL,
|
||||
api_key=HERMES_API_KEY,
|
||||
)
|
||||
|
||||
model = SMART_MODEL if use_smart else FAST_MODEL
|
||||
logger.info(f"Brain: model={model} smart={use_smart}")
|
||||
try:
|
||||
@@ -25,7 +31,7 @@ async def stream_completion(messages: list[dict], use_smart: bool = False):
|
||||
model=model,
|
||||
messages=messages,
|
||||
stream=True,
|
||||
**_extra_kwargs(model),
|
||||
api_base=OLLAMA_BASE_URL if model.startswith("ollama/") else None,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Brain error ({model}): {e}")
|
||||
@@ -35,6 +41,6 @@ async def stream_completion(messages: list[dict], use_smart: bool = False):
|
||||
model=FAST_MODEL,
|
||||
messages=messages,
|
||||
stream=True,
|
||||
**_extra_kwargs(FAST_MODEL),
|
||||
api_base=OLLAMA_BASE_URL if FAST_MODEL.startswith("ollama/") else None,
|
||||
)
|
||||
raise
|
||||
|
||||
+22
-7
@@ -10,6 +10,7 @@ TASK_PHRASES = {
|
||||
"create task", "add task", "add issue", "create issue", "log task",
|
||||
"log this", "new task", "new issue", "add to plane", "add to backlog",
|
||||
"schedule", "remind", "track", "todo", "to do",
|
||||
"job item", "job ticket", "work order",
|
||||
}
|
||||
|
||||
# "plan" removed from TASK_PHRASES — it substring-matches "plane" (the tool name)
|
||||
@@ -83,14 +84,21 @@ def classify_intent(message: str) -> str:
|
||||
if any(v in msg for v in EXECUTE_VERBS) and any(s in msg for s in STACK_NAMES):
|
||||
return "execute"
|
||||
|
||||
# Explicit task phrases — check before query to prevent "add task X" → query mis-route
|
||||
if any(p in msg for p in TASK_PHRASES):
|
||||
return "task"
|
||||
if words & TASK_WORDS:
|
||||
return "task"
|
||||
|
||||
# Plane query — list/show projects or issues (check before task to avoid "plan" ⊂ "plane" bug)
|
||||
if ("project" in msg or "projects" in msg) and any(p in msg for p in QUERY_PHRASES):
|
||||
return "query"
|
||||
if "plane" in words and any(p in msg for p in QUERY_PHRASES):
|
||||
return "query"
|
||||
# Issue list query — "show issues in X", "list tasks for X", "work items for X"
|
||||
# Use word-boundary check (words set) to avoid "in" matching inside "incident" etc.
|
||||
has_issue_word = bool(words & ISSUE_KEYWORDS) or any(p in msg for p in ISSUE_PHRASES)
|
||||
if has_issue_word and any(p in msg for p in {"show", "list", "get", "what", "give", "in", "for"}):
|
||||
if has_issue_word and words & {"show", "list", "get", "what", "give", "in", "for"}:
|
||||
return "query"
|
||||
|
||||
# Agent name + status word → status
|
||||
@@ -98,15 +106,12 @@ def classify_intent(message: str) -> str:
|
||||
if any(p in msg for p in STATUS_PHRASES) or words & {"status", "check", "output", "run"}:
|
||||
return "status"
|
||||
|
||||
# Explicit task phrases → task
|
||||
if any(p in msg for p in TASK_PHRASES):
|
||||
return "task"
|
||||
# Word-boundary match for single-word task terms (avoids "plan" matching "plane")
|
||||
if words & TASK_WORDS:
|
||||
return "task"
|
||||
# Natural task creation: "add X to [project]"
|
||||
if msg.startswith("add ") and " to " in msg:
|
||||
return "task"
|
||||
# "please add" / "add" anywhere + known project keyword → task
|
||||
if "add" in words and any(kw in msg for kw in PROJECT_KEYWORDS):
|
||||
return "task"
|
||||
|
||||
# Generic status signal words
|
||||
if any(p in msg for p in STATUS_PHRASES) and words & AGENT_NAMES:
|
||||
@@ -195,6 +200,7 @@ def is_issue_query(message: str) -> bool:
|
||||
|
||||
def extract_task_title(message: str) -> str:
|
||||
"""Extract clean task title from natural phrasing like 'add buy milk to General'."""
|
||||
import re as _re
|
||||
msg = message.strip()
|
||||
lower = msg.lower()
|
||||
if lower.startswith("add ") and " to " in lower:
|
||||
@@ -204,6 +210,15 @@ def extract_task_title(message: str) -> str:
|
||||
for prefix in ("create task:", "add task:", "log task:", "new task:", "log this:"):
|
||||
if lower.startswith(prefix):
|
||||
return msg[len(prefix):].strip()
|
||||
# "add [who] a job item [description]" — extract description after "job item"
|
||||
m = _re.search(r'\bjob item\b\s*(.*)', lower)
|
||||
if m:
|
||||
remainder = msg[m.start(1):].strip()
|
||||
for filler in ("that we need to ", "that is ", "which is ", "to "):
|
||||
if remainder.lower().startswith(filler):
|
||||
remainder = remainder[len(filler):]
|
||||
if remainder.strip():
|
||||
return remainder.strip()
|
||||
return msg
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +107,15 @@ def _write_status(intent: str, summary: str, status: str = "success") -> None:
|
||||
}
|
||||
(log_dir / "last-run.json").write_text(json.dumps(payload, indent=2))
|
||||
|
||||
# Rolling 50-run history
|
||||
runs_file = log_dir / "runs.jsonl"
|
||||
try:
|
||||
existing = runs_file.read_text().splitlines() if runs_file.exists() else []
|
||||
existing.append(json.dumps(payload))
|
||||
runs_file.write_text("\n".join(existing[-50:]) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
out_dir = SITES_DIR / "jon-snow"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
(out_dir / "last-output.md").write_text(
|
||||
|
||||
Reference in New Issue
Block a user