priority-triage Reference
The Loom workflow that powers the Morning Ritual feature. Source: optimalOS/workflows/priority-triage.ts.
Trigger
Manual only. No cron schedule attached.
Two trigger surfaces:
- HTTP:
POST /api/triage/rerun(optimalOS/src/routes/triage.ts:16-60). - Loom UI: the manual trigger on the workflow's strand row.
The Rerank button on the TODAY widget calls the HTTP endpoint.
Logical steps
The workflow currently runs as a single Loom step run with internal phasing. The 9 logical phases below are the conceptual steps the code executes. Per-step Loom UI visibility is on the roadmap.
| # | Phase | What it does | Failure handling |
|---|---|---|---|
| 1 | snapshot_inputs | Query open unlocked tasks, unconsumed briefs, unapplied overrides, locked task ids. Hash for replay. | If no open tasks, marks run succeeded with summary "No open unlocked tasks to score." |
| 2 | assemble_prompt | Build the system prompt + user content (CLAUDE.md, memories, tasks, briefs, overrides, caps). Estimate tokens, check vs 950 k hard limit per chunk. | If estimated tokens exceed limit, throws an Error with the chunk number; workflow fails. |
| 3 | ship_to_ollama | POST to triage.ollama_url + "/v1/chat/completions" with stream=false, response_format={type:"json_object"}, temperature=0.2, 10-minute timeout. Bearer token added if configured. | OllamaError with stage network / http. No retry. |
| 4 | parse_response | JSON.parse model output, zod-validate against OllamaResponseSchema. | OllamaError("parse") or OllamaError("schema"). |
| 5 | enforce_distribution | Server-side: recompute scores from model's I/U/C/E values, snap each task's lane to the score-derived threshold, auto-demote excess past lane caps cascading (lowest unlocked first). Locked tasks exempt. | No rejection. Populates distribution_warning if demotions occurred. |
| 6 | write_back | Insert score_history rows (one per scored task). Update tasks denorm columns (current_score, current_lane, current_rank, last_scored_at). All in one transaction. Locked tasks' denorm columns are not updated. | Any insert/update failure rolls back the transaction. |
| 7 | mark_briefs_consumed | UPDATE briefs SET consumed_in_run_id = ? for every brief included in the prompt. | Idempotent. |
| 8 | apply_override_acks | UPDATE score_overrides SET applied_to_run_id = ? for every unapplied override included in the prompt. | Idempotent. |
| 9 | emit_summary | UPDATE triage_runs SET run_summary, clash_summary, distribution_warning. Broadcast triage_run_completed (or triage_run_failed) on the state hub. | Best-effort; top-level catch ensures the failure path still emits an event. |
Code references: optimalOS/workflows/priority-triage.ts:74-244.
Inputs
The workflow snapshots three live data sources at the start of every run:
| Source | Filter |
|---|---|
tasks | status NOT IN ('done','completed') AND score_locked = 0. |
briefs | consumed_in_run_id IS NULL. |
score_overrides | applied_to_run_id IS NULL. |
| Locked tasks | score_locked = 1 — included in the prompt as "user locked this at LANE" so the model sees the constraint, but their lane is preserved verbatim. |
Outputs
Per-task in score_history (one row per scored task per run):
{
task_id, run_id,
impact, urgency, confidence, effort, // 1-5 each
score, // 0-1 (server-recomputed, authoritative)
rank, // dense, 1-based, per-lane
lane, // now | next | later | backlog
reasoning, // model's text rationale
clash_resolution, // optional, populated when lane was snapped
sources_used, // JSON array of brief ids the model cited
scored_at
}Per-run in triage_runs:
{
id, started_at, completed_at, status,
trigger, // manual | api | scheduled
model, ollama_url,
prompt_tokens, completion_tokens,
task_count, brief_count,
distribution_warning, // populated when caps overflowed
clash_summary, // populated when lanes were snapped
run_summary, // model's overall summary
error // populated on failure
}Batching
Default triage.batch_size = 25. Tasks split into chunks ≤ batch size; each chunk gets its own Ollama call. Briefs and overrides are sent with every chunk so the model sees identical context. Server-side, raw scores from all chunks are merged, deduplicated by task_id, normalized globally, then capped.
Set batch_size = 0 to disable batching. See Configuration.
Failure modes (summary)
The run is rejected only on:
- JSON parse failure
- zod schema validation failure
- empty
scores[]array
Distribution overflow never rejects — it auto-demotes. DB lock contention is not retried; the transaction either commits or rolls back atomically. Token-budget overrun fails immediately; there is no automatic 30-day-trim retry.
WebSocket events
triage_run_completed and triage_run_failed are broadcast through the state hub from optimalOS/src/routes/triage.ts:39-53. The Command Center bridges them to a window-level CustomEvent("triage:updated", ...) that the TODAY widget listens for.
Step-level events not shipped
The charter calls for a triage_run_step event for each phase. These are not implemented yet; only the run-level events fire.