# Triage Orchestrator

> The pipeline driver for the Hermes Multi-Agent Workflow. Triggered by new `intake` tasks on the triage board. Dedups, scores, fans out research, routes, proposes at the human gate, and on approval lets the engine spawn the fulfillment chain. It is DELIBERATELY THIN: it calls engine/* for every deterministic step and only supplies judgment (scoring, classification, proposal prose).

- Skill: `tonbistudio/triage-orchestrator` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tonbistudio/triage-orchestrator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tonbistudio/triage-orchestrator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tonbistudio (https://skillmd.com/u/tonbistudio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tonbistudio/triage-orchestrator

---


# Triage orchestrator (thin driver)

> **Design contract:** fat engine, thin skill. Anything deterministic —
> dedup lookup, applying the score threshold, route resolution, building research
> fan-out, building the prep/fulfillment chains, choosing workspaces — is a call
> into `engine/`. You (the model) only do what needs judgment. Do NOT re-derive
> the pipeline shape in prose here; it lives in `triage.yaml`. Read
> `docs/01-architecture.md` and `docs/05-pipeline-stages.md`.

All commands below run from the repo root with `triage.yaml` present.
`TRIAGE_CONFIG`, `TRIAGE_VAULT_DIR`, and `HERMES_KANBAN_DB` are honored.

## Trigger

A new `intake` task assigned to you appears on the triage board. Its body is a
path to a scout report.

## Procedure

### 1. Parse intake
Read the report file. Parse it into candidates (`engine/intake_parser.py` shape).

### 2. Dedup (deterministic — call the engine)
For each candidate, ask the engine for similar existing items:
```
python -c "from engine.config import TriageConfig; from engine.engine import TriageEngine; \
import json,sys; e=TriageEngine(TriageConfig.load()); \
print(json.dumps([m.__dict__ for m in e.dedup(sys.argv[1])]))" "<candidate title + claim>"
```
- `duplicate` → append the new source to the existing item, stop. Don't re-research.
- `possible` → note it, continue, re-check after research.
- `new` → create a vault item (`ItemVault.create_item`) with `status: triage`.

### 3. Score (judgment + engine validation)
This is YOUR judgment. Get the rubric prompt from the engine
(`TriageEngine.rubric_prompt()`), score each dimension honestly, then hand your
breakdown back to `TriageEngine.score(breakdown)` to apply the maxes + threshold.
Write `score` / `score_breakdown` to the item file regardless of outcome.
- Below threshold → shelve automatically. **Do not bother the human.**
- At/above → continue.

(For a deterministic/offline pass you may instead call
`TriageEngine.score_heuristic(candidate)` — see engine/scoring.py.)

### 4. Research fan-out (engine builds the cards)
Create one triage root task, then create the research lane cards from
`TriageEngine.research_specs(slug, triage_id)` — they run in parallel, all
parented to the triage task. Create a single `route` card parented to ALL lanes
so the kernel fires it the instant the last lane finishes (fan-in). Assign the
`route` card back to yourself.

### 5. Route (deterministic — call the engine)
When the route card fires, read the classifier value the classifier lane emitted
(`route.classifier` in triage.yaml). Resolve the path:
`TriageEngine.route(classification)` → a path name. Write `path: <name>` on the
item. If the path is `auto` (e.g. `shelve`), close out — no proposal.

### 6. Prep + propose (engine builds prep; you write the proposal)
Spawn the path's prep chain from `TriageEngine.prep_specs(slug, path)`. When prep
finishes, draft the proposal using the path's proposal template
(`paths/proposals/<path>.md`), set item `status: awaiting_approval`, and **send it
to the human** — you MUST actually deliver it:
```
hermes send --to telegram --file <proposal.md>
```
Setting status is NOT delivery. (See docs/06 + the runbook.) Then move on to
other items while waiting — the gate is non-blocking.

### 7. Gate (human replies; you shell to the handler)
Map the human's reply verb (see `gate:` in triage.yaml — NO leading slash) to:
```
python proposal_actions.py approve     <slug>
python proposal_actions.py shelve      <slug> --reason "..."
python proposal_actions.py shelve-all  [--except <slug>]
python proposal_actions.py modify      <slug> --change "..."
```
On `approve`, the handler reads `paths.<path>.fulfill` from triage.yaml and
spawns the post-gate chain in a shared persistent workspace. You do nothing else.

### 8. Deliver
When the final fulfillment stage completes, DM the deliverable to the human
(`hermes send --to telegram --file <deliverable>`).

## Rules

- Narrate one line per decision to Telegram so the human has a pulse.
- Never auto-approve. The gate is real.
- Only YOU write vault item files and create child tasks. Workers don't fan out.
- Be honest in scoring/classification — gaming them wastes the human's one tap
  and produces low-value output.
- If you hit a missing tool or ambiguous state, block the task with a reason
  rather than guessing.

