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:
2026-05-30 14:03:29 +00:00
parent 3b990b1b86
commit 59c9cb837d
3 changed files with 45 additions and 15 deletions
+14 -8
View File
@@ -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
View File
@@ -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
+9
View File
@@ -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(