---
name: coo-classify
description: Programmatic classifier for Inbox REQs. Returns {complexity, safety_class, route, lane, confidence}. Called by coo-inbox-poll.
agent: hermes-coo
type: programmatic
context_files:
  - HQ-Vault/_System/COO/safety-rails.md
---

# coo-classify skill

## Purpose

Pure function: REQ-body-text → classification record. Stateless. Deterministic where possible, LLM-assisted where heuristics fail. Called by `coo-inbox-poll` for every unresolved REQ.

## Input

```yaml
req_id: REQ-2026-05-26-1130-fix-scraper
from: rocky
lane: rr
type: task
what: Fix Gumtree scraper extracting 0 contacts
why: Hunt pipeline dead at source
deadline: this-week
context: scripts/hunt/scrapers/gumtree-workers.js
```

## Output

```json
{
  "complexity": "medium",
  "safety_class": "ok",
  "route": "kanban:rr",
  "lane": "rr",
  "confidence": 0.85,
  "rationale": "Code change in scripts/hunt — RR lane mutating, heavy enough for kanban dispatch to claude-code-rr or hermes-rr"
}
```

## Classification rules — applied in order

### 1. Safety class check (per `safety-rails.md`)

For each of the 10 blocked classes, check REQ body for keywords/intent:

| Class | Triggers |
|---|---|
| 1. Deletion | "delete", "remove", "rm -rf", "drop table", "purge" |
| 2. Key/token rotation | "rotate key", "new pat", "regenerate token", "secret rotation" |
| 3. Schema change | "create table", "alter table", "add column", "drop field", "migration" |
| 4. Code merge to main | "merge pr", "push to main", "force push" |
| 5. Off-team messaging | "email liam", "send to robert", "message client", "send to TCE" |
| 6. SMS | any mention of SMS |
| 7. Spending money | "buy", "subscribe", "upgrade plan", "charge", "purchase" |
| 8. System file mod | `/etc/`, nginx, systemd, "edit cron file" |
| 9. Cross-lane write | "write to LFCS", "update RR brain" (when called from wrong lane) |
| 10. Spawn profile | "create hermes profile", "new hermes agent" |

→ If matched: `safety_class: blocked`, `route: escalate`, exit.

### 2. Greylist check

Greylist triggers (per `safety-rails.md`):
- "modify cron", "add cron", "change cron schedule"
- "rotate api key" (any)
- "touch rateright code", "modify the-50-dollar-app", "rateright-v2 change"
- "mutate supabase", "update database", "insert into", "update row"
- "re-enable PM2" (if service stopped >7d)

→ If matched: `safety_class: greylist`, `route: greylist-confirm` (suspend + Telegram-ask Rocky).

### 3. Whitelist confirmation (default for safe actions)

If neither blocklist nor greylist matched: `safety_class: ok`. Proceed.

### 4. Lane assignment

Match keywords from `coo-inbox-poll/SKILL.md` § "Classification cheatsheet":

| Keywords | Lane |
|---|---|
| hunt, scraper, worker, contractor, supabase memscjot..., scraped_leads, RateRight, $50 hire, hostel | rr |
| Airtable LFCS, bid, RFI, drawing, BoQ, pricing, Liam, TCE, MWS, job number 2xxx, R0220 | lfcs |
| site induction, ITP, RAMS, SWMS, white card | siteops |
| gym, energy, schedule, Window, weekend | personal |
| vault, sync, cron, hermes config, MCP, profile | system |

→ If REQ `lane:` field set explicitly: use that. Else infer.

### 5. Complexity assessment

Heuristic:

- **light** — single tool call, read-only query, append-to-log, status check. <5 min, <3 tools.
- **medium** — multi-step task with state changes, 5-30 min, 3-15 tools.
- **heavy** — code refactor, multi-file edit, design review, novel investigation. >30 min, or requires sustained reasoning context.

Match against keywords:

| Keywords | Complexity |
|---|---|
| "status", "check", "show me", "what's", "list", "count" | light |
| "fix" + small target, "draft", "update register", "send draft to drafts" | medium |
| "refactor", "rebuild", "design", "investigate", "audit", "migrate" | heavy |

### 6. Route assignment

| Complexity | Lane | Safety | Route |
|---|---|---|---|
| light | * | ok | `direct` |
| medium | rr | ok | `kanban:rr` |
| medium | lfcs | ok | `kanban:lfcs` |
| medium | siteops | ok | `kanban:siteops` |
| medium | personal | ok | `direct` |
| medium | system | ok | `direct` |
| heavy | rr (code) | ok | `cc-cli:claude-code-rr` |
| heavy | lfcs (code) | ok | `cc-cli:claude-code-opsman` |
| heavy | system | ok | `cc-cli:claude-code-coo` (only via Inbox; never on autopilot) |
| * | * | greylist | `greylist-confirm` |
| * | * | blocked | `escalate` |

### 7. Confidence score

`0.95+` — exact keyword match + explicit lane field set + complexity unambiguous.
`0.75-0.95` — clear lane, clear complexity, no edge cases.
`0.50-0.75` — ambiguous lane OR ambiguous complexity. Add `rationale` explaining.
`<0.50` — too unclear to route → `route: escalate`, surface to Telegram for Rocky.

## Edge cases

- **REQ touches multiple lanes** (e.g. "RR Supabase query for LFCS bid analysis") → assign to primary lane based on which has the action verb; flag in rationale.
- **REQ missing `lane:` field** → infer from keywords, set confidence ≤0.75.
- **REQ contains a question and a task** → split into two REQs internally, classify each, return both.
- **REQ context references blacklisted paths** (e.g. `vault/`, LFCS job folders from non-LFCS-lane REQ) → block.

## Failure modes

- LLM-assist call fails → fall back to keyword-only heuristic at confidence ≤0.6, set `rationale: "classifier-llm-down, keyword-only fallback"`.
- Multiple classes match → use stricter (blocked > greylist > ok).
- Lane field set but conflicts with body content → trust the body, lower confidence, flag in rationale.

## What good looks like

`coo-inbox-poll` calls this and gets a deterministic record every time. Same REQ → same classification (within LLM-noise). Rocky can audit by reading `rationale` if a classification feels wrong.
