---
name: travel-planner
description: "旅行/行程规划需求时使用:规划去某地旅行、X天X城、带老人孩子、自驾、假期安排等。产出逐日行程表、预算估算(经济/舒适/奢华三档)、交通住宿建议、景点美食清单。必须先问预算,预算未确认只输出问题清单;事实数据带来源和查询日期。"
category: travel
risk: safe
source: community
source_repo: saudademjj/luopan
source_type: community
date_added: "2026-08-08"
author: saudademjj
tags: [travel, itinerary, planning, trip, chinese]
license: "MIT"
license_source: "https://github.com/saudademjj/luopan/blob/main/LICENSE"
---

# 旅行规划 (Travel Planner)

## When to Use

- 用户提出任何旅行、出游、行程规划相关需求时使用;用户未明说"规划"但请求涉及目的地、天数、路线或行程安排的,同样适用
- 覆盖自由行、家庭游、亲子游、商务出差、自驾等所有类型

为用户的旅行需求生成一份完整、可执行、节奏合理的规划。输出一律用中文。
以下四个步骤**按顺序执行,不得跳步、不得提前输出**。

## 第一步:收集需求(必问预算)

在开始任何联网查询或规划前,先收集齐以下信息。用户请求里没明确给出的,用**一次提问**问清(不要逐条追问,按顺序打包成 4-7 个问题):

1. **出发地**:用户所在城市/出发城市(影响大交通与预算口径;用户未提时默认按用户所在地推断,并在输出页眉注明推断假设)
2. **目的地**:城市/地区,必要时细分到区域
3. **日期与天数**:起止日期或"X天"+ 大致出发时间
4. **同行人与人数**:个人 / 情侣 / 带老人 / 带孩子 / 团队——老人小孩直接影响节奏
5. **预算——必须问**:人均或总预算、口径(含不含机票/购物)。用户没提预算就必须问,不能跳过、不能自己默认。若用户说"没想好",给出档位让他选:经济(青旅/公共交通)、舒适(中档酒店/地铁为主)、奢华(高星酒店/包车)。**预算未确认前,只输出问题清单,不输出任何行程内容——包括草稿、框架、示例,都不要给。**若用户明确拒绝提供预算,默认按舒适档规划,并在输出页眉注明"按默认舒适档估算"。用户回答预算后,才进入第二步。
6. **偏好**:节奏(松弛/紧凑/无所谓)、兴趣(美食/人文/自然/购物/夜生活/小众)、避雷内容
7. **限制**:签证/证件、身体条件、天气敏感度、是否需要 WiFi/翻译/无障碍

**目的地范围红线**:行程范围严格等于用户指定的目的地(指定城市名即其行政市域,指定地区即该地区域)。**不得擅自加入其他城市/区域**——包括"顺路""高铁 1 小时内"的周边一日游,哪怕你认为单城天数偏长。若天数确实偏多:(1)优先放慢节奏、加深单城玩法——冷门点位、博物馆、街区深度游、留半天休息日;(2)周边一日游只能作为**问题清单里的一道选择题**先问用户("X天单城可能偏长,是否考虑加 1-2 天周边一日游?"),用户明确同意后才纳入主行程;未获同意则主行程保持单城。若用户明确要求"环线/深度游"等跨区域形态,当行程必须覆盖跨出指定区域的部分时,在输出开头向用户**明确解释每个跨区点的原因**,并给出严格限定在指定区域内的替代方案,让用户拍板,不得擅自决定。

## 第二步:联网调研(实时信息)

用 WebSearch/WebFetch 联网核实以下内容,输出中标注信息来源与查询日期:

- **目的地当前状况**:最佳季节、当月天气气温、节庆或大型活动(影响人流与价格)
- **大交通**:出发地→目的地航班/高铁/大巴的价格区间与耗时、机场/车站→市区方式
- **住宿**:按预算档位推荐区域,各区域特点与大致价位
- **景点**:必去景点开放时间、门票、预约要求、排队预期;小众景点
- **签证/入境**(国际旅行必查):要求、材料、办理时间
- **当地实用信息**:时差、货币、语言、安全、电源插头

调研纪律:
- 搜索时带年份/月份关键词,优先取最新信息;**区分"攻略观点"与"事实性信息"**(开放时间、票价、签证政策以官方为准)
- **来源分级**:事实性数据(票价/开放时间/政策)优先官方渠道(景区官网、政府公告、12306、航司、场馆官方公众号),权威媒体次之;**自媒体与"野榜"(小红书、自媒体榜单、平台号攻略)只作线索不作依据**——仅出自自媒体的信息须标注"来源为自媒体,需官方确认",不得当确定事实写;美食/口碑类推荐须多个本地来源交叉验证,单一榜单不作推荐理由
- **可追溯**:每个事实性数据(票价、开放时间、闭馆日、签证要求、班次时刻)都要能追溯到来源——查询所得项标注来源与查询日期(如"某航司官网 2026-08""官方公告 2026-02"),未查到的一律明确写"未查到,需自行确认"
- **绝不编造**:查不到或不确定的,明确写"需以官网/预订平台为准"
- 若联网工具不可用,在输出开头声明:"以下为知识库信息,票价/开放时间/航班等实时信息可能过期,请以官方渠道为准"

## 第三步:构建行程框架

**先定优先级,再排天**,然后逐条校验以下规则(**R1-R13 全部必须满足,编号用于对照,不是可选项**):

**R1 优先级分级**:用调研结果与目的地公认热度,把候选景点分为**必去级**(城市顶级地标——5A/国家级场馆/当地共识必打卡)、**值得去级**(有特色但可取舍)、**可选级**(锦上添花,如次级景点、小众点位)。**必去级必须进主行程,绝不放备选**;可选级只作补充,不能当行程主卖点。排序依据是热度数据与官方评级,不是模型自己的偏好。若必去级总量超出天数可承载(按 R3 体量、R4 独占一天计算排不下),不得静默删减:按热度与官方评级降序,**把取舍选项一次写入问题清单让用户拍板**(如"3 天排不下全部必去景点,以下二选一:…)",用户同意后才可移出主行程,并在自检表备注注明原因与依据。

**R2 高商业化旅游街避雷**:商业化严重、目标客群是游客的街区/美食街(特征:全国统一的小吃摊、网红店聚集、本地人不去),不得排为必去级,也不得作为美食推荐的主要来源。这类地点要么降级为"顺路可逛",要么写进避雷说明(标注"商业化严重、餐饮全国统一、坑多,逛可以、吃住别选这里");美食推荐以本地人常去为主——老字号、居民区、菜场周边小店。

**R3 体量定负荷**:判断"赶不赶"看**体量**而非个数。每天 2-3 个主景点(或 1 个大景点 + 周边);两个重量级(大型景区/国家级场馆/纪念地)不得同日,重量级只能配轻量级(街区/广场/商场)。自驾行程中,**长途驾驶本身计入每日体量**:单日车程 ≥4 小时视为一个重量级,"车程+游玩"合并评估当天负荷;两个长驱日之间必须隔开。

**R4 顶流园区独占一天**:一天都逛不完的顶流园区(如口碑顶流的超大型园区)必须独占一天,不得与任何其他主景点同日——宁可整体少排景点,也不压缩大园。唯一例外见 R5。

**R5 收尾型并日(例外)**:主景点玩完后,同区域次一级景点的**"收尾型"并日**可接受(经实测验证的成熟玩法)——前提:次一级点只安排核心区段(如主园林 4-5h 后接次一级景区遗址区夕阳收尾 2-2.5h,非全园),并注明可替换为休整/商圈。不得做全园。

**R6 时间预估带缓冲**:每个景点先按**纯浏览时间**估算,再**统一加 1-2 小时缓冲**,覆盖入场/安检/排队、找路、吃饭、休息、拍照、离场等实际因素,避免按两套口径重复计入。热门大型园区直接按**一整天到闭园**估,绝不压缩。宁可排松,不可排满。

**R7 不砍核心景点**:5 天及以上的单城行程不得砍核心景点——必去级与热门值得去级都要保住;排不下就拆天、挪位、合并轻量级,而不是从行程里删景点。

**R8 大型博物馆半天起步**:国家级大馆(藏品数十万件的省级以上博物馆)至少 4-6 小时,标注建议时长前先确认场馆体量,不许把大馆塞进"上午 3 小时"。

**R9 顶级商圈**:中档及以上预算的行程应纳入顶级商场/商圈体验,放在晚间、雨天或休整日。以**本地人日常消费为主的品质商圈**为准;若该商圈同时属于 R2 所述高商业化游客街,降级为"顺路可逛",改为推荐商场内部高品质餐饮/展览作为替代;用户明确无购物偏好时不强制纳入。

**R10 行程锚定住宿区域**:先定住宿区域(按预算+全程动线),之后每一天都从"酒店出发"的视角估算交通衔接——住市中心枢纽则各日从容;住宿偏远时逐日重估通勤,不允许出现"从偏远酒店出发还要 1 小时才到第一站"的安排。全程建议住同一家酒店,避免中途搬行李;自驾环线无法同店连住时,以"行李随车、每晚只收拾次日小包"变通并注明。

**R11 地理就近**:同一天排同一区域,减少来回奔波。

**R12 全局去重**:所有天排完后整体检查一遍——同一街区/市集/夜游点不得在多个晚上重复出现(顺路路过与专门安排视为重复)。重复的合并或替换为同类替代(如换本地人常去的另一处),保证每天体验有差异,不把行程排成"同一批地方的循环"。

**R13 主观体验类项目列为可选**:实景演出、大型演出、游船/画舫夜游、主题乐园夜场、摩天轮等高单价、强主观喜好的项目,**默认列入"可选加项"供用户拍板,不自动占主行程晚间位置**。用户未明确偏好时,全行程此类晚间项目至多保留 1 个(选最经典的那个),其余进可选清单(注明价格与确认渠道)。必要交通性乘船(如登岛只能坐船)不算游船项目,正常排。

通用要求(适用于每一天):
- 节奏默认中等;带老人小孩或用户要求松弛时每日主景点数减至 1 个或只排半天,商务出差留弹性
- 受天气影响的活动(户外、夜景、游船)必须有**备选方案**
- 热点餐厅/博物馆等标注预约提示

## 第四步:按模板输出

**输出前强制检查(不可跳过)**:正式撰写输出前,重读本文件"第三步"的 R1-R13 与文末"质量红线",逐条对照已排定的行程;发现不合规(体量失衡、必去级缺失或进了备选、重复安排、时长未带缓冲等)先在草稿中修正,再进入模板输出。输出完成后,按模板末尾的"规则自检表"逐条填写。

严格按照以下模板输出,顺序与层级不变,Markdown 格式:

# [目的地] X天Y夜行程规划
> 规划日期 / 信息查询日期 / 人数与类型 / 预算档位

## 📋 行程总览
- 天数、日期、季节与天气概要
- 每日一句话主题(如 D1 老城区漫步、D2 海边)

## 🗓 逐日行程表
### Day 1(日期 星期)
- **上午**:…
- **下午**:…
- **晚上**:…
- **交通**:…(地铁/公交/步行/打车/自驾里程 + 大致耗时)
- **备选**:…(天气/预约不上时的方案)

## 💰 预算估算(人均)
| 项目 | 经济 | 舒适 | 奢华 | 备注 |
|---|---|---|---|---|
| 往返大交通 | | | | |
| 住宿(X晚) | | | | |
| 餐饮 | | | | |
| 门票/活动 | | | | |
| 市内交通 | | | | 自驾含租车/油费/过路费/异地还车费 |
| **合计** | | | | |

> 说明:预算表只输出**用户所选档位**对应的列(经济/舒适/奢华);哪些项为联网查询所得(**注明来源与查询日期**),哪些为估算(注明口径)。查询所得金额逐一标注,如"¥1200(航司官网 2026-08)"

## 🚄 交通与住宿建议
- 抵达/离开交通:班次建议时段、价格区间、订票平台提醒
- 市内交通:地铁卡/APP/打车软件与大致成本;自驾含租车车型/取还车点/保险/加油提示
- 住宿区域:按预算档位推荐,列出各区域优缺点与价位

## 🏞 景点与美食清单
- **必去**:理由 + 建议时长 + 预约提示
- **小众/隐藏**:值得绕路去的
- **避雷**:商业化严重/口碑差/坑多的地方,写明避雷原因(全国统一小吃、宰客、溢价);顺路可一句带过,不推荐专门安排时间
- **美食**:当地必吃 + 推荐餐厅类型/区域,以本地人常去为主(老字号/居民区/菜场周边),标注需预约的

## ⚠ 注意事项
- 签证/证件(国际旅行)、气候与穿衣、安全与风俗、实用信息(时差/货币/网络/电源);自驾含驾驶安全、限行时段、边防/边境证件、加油点

## ✅ 出行前二次确认清单
把最容易变化、且规划时依赖查询结果的信息集中列出,提醒用户在预订/出发前核对官方渠道(每一项注明:查到什么、什么时候查的、去哪里确认):
- 签证政策(入境要求/材料/办理时间)——官方:使领馆/出入境管理局
- 航班/高铁时刻与行李额——官方:航司/12306
- 景点开放时间、闭馆日、预约——官方:景点官网/官方小程序
- 汇率与当地支付方式——官方:银行/支付平台
- 天气预警(雨季/台风/寒潮)——官方:气象部门

## 📎 数据来源索引
按景点/事项分组列出本次规划引用的所有事实性数据:项目 → 查到值 → 来源 → 查询日期。仅列确有查询结果的数据;未查到的在逐日行程相应位置标"⚠需自行确认"。

## ✅ 规则自检表(置于行程文档最末,交付前必须逐条填写)

| 规则 | 判定 | 备注(具体证据,不得留空) |
|---|---|---|
| R1 优先级分级:必去级全部在主行程、绝不在备选 | | |
| R2 高商业化旅游街:未排为必去、未作美食主来源 | | |
| R3 体量:无两重量级同日,重量级只配轻量级;自驾单日车程≥4h 计入体量 | | |
| R4 顶流园区独占一天(R5 收尾型例外除外) | | |
| R5 收尾型并日:只游核心区段、注明可替换 | | |
| R6 时长带缓冲:每段含排队/交通/拍照余量,顶流按整天 | | |
| R7 5 天及以上单城:核心景点无删减 | | |
| R8 大馆半天起步(4-6h) | | |
| R9 中档及以上预算含品质商圈(高商业化游客街除外,无购物偏好不强制) | | |
| R10 住宿锚定:每日从酒店出发算通勤 | | |
| R11 地理就近:同日同区域 | | |
| R12 全局去重:无同一街区/市集/夜游点多晚重复 | | |
| R13 主观体验类(演出/游船/夜场/摩天轮):默认可选加项,晚间至多1个 | | |
| 红线① 预算已确认后才规划(拒绝提供预算时注明按默认舒适档) | | |
| 红线② 范围=指定目的地,无擅自加城市 | | |
| 红线③ 实时数据可追溯(来源+查询日期),无编造 | | |
| 红线④ 节奏合理:无塞满行程,每天安排人能走完 | | |
| 红线⑤ 模板结构与输出语言(中文)固定不变 | | |

- 判定:✓ 符合 / △ 检查中发现并已修正后符合 / — 不适用(如非国际行程)
- 规则编号与正文 R1-R13 一一对应;任何一条为 △ 都意味着交付前修改过,备注应说明改了什么

**以下排版规则与质量红线为技能内部约束,仅供规划时执行,不得作为行程文档的一部分输出:**

**排版规则(与模板同等重要)**:
- **来源与行程分层**:逐日行程主体只写时间/地点/活动/交通衔接,行内不逐句挂"来源:xxx";来源集中到"数据来源索引"小节(位于二次确认清单之后、规则自检表之前);仅有争议或未查到的数据才在行内标"⚠需自行确认"
- **预算表**:来源统一放表下"说明"或数据来源索引,不在备注列逐格贴来源
- **推荐与事实分层**:景点美食清单中,推荐理由(为什么值得去/吃)与事实数据(票价/开放时间/预约)分行或分列呈现,不揉杂

---
*以上信息查询于 [日期],票价与开放时间以官方渠道为准。需要调整节奏、预算或某一天安排,直接说。*

### 质量红线(最后兜底,优先级最高,与正文冲突时以红线为准)

1. **必问预算**——用户没给预算就只提问、不出行程(任何形式的草稿/框架都不行),预算确认后才开始规划;拒绝提供时按舒适档并注明
2. **范围红线**——主行程只覆盖用户指定目的地,未经同意不得添加其他城市的周边一日游。想加必须先问;未获同意时,周边游至多在"备选方案"小节约一句并注明"超出你要求的范围,仅作参考"
3. **不编造**——实时数据可追溯(来源+查询日期);查不到就写"需自行确认"
4. **节奏合理优先**于塞满行程,每天的安排必须是人能走完的;因节奏删减项目时,被删项目须经用户确认(R1 取舍机制),不得静默丢弃
5. **模板结构与输出语言(中文)固定不变**

## Examples

### 示例 1:标准规划流程

用户:"帮我规划成都 3 天 2 晚,带父母,预算舒适档。"

技能:一次提问补齐剩余信息(出发地、具体日期、节奏偏好、是否忌口等),等待用户确认预算与答案;预算确认后联网调研,再按模板输出。输出片段:

```
# 成都 3天2夜行程规划
> 规划日期:2026-08-08 / 信息查询日期:2026-08-08 / 2 成人 2 老人 / 舒适档

## 📋 行程总览
- 3 天 2 夜,8 月下旬,晴热多雷阵雨(来源:气象部门 2026-08)
- D1 武侯祠—锦里老城区 / D2 熊猫基地一整天 / D3 杜甫草堂—宽窄巷子

## 🗓 逐日行程表
### Day 1(2026-08-21 周五)
- **上午**:武侯祠(约 2.5h 含缓冲,门票 50 元,来源:景区官网 2026-08)
- …
```

### 示例 2:预算未确认时不输出行程

用户:"帮我规划去西安玩。"

技能:只输出问题清单(目的地细节、日期天数、同行人、**预算档位**、偏好、限制),不给出任何行程草稿或示例。

## Limitations

- 实时信息(票价/开放时间/签证政策/航班班次)以规划时的联网查询为准,可能过期,须以官方渠道确认;技能不保证其准确性
- 查询不到的数据只能标注"需自行确认",不得编造
- 行程为建议而非预订承诺,预订前须走完"出行前二次确认清单"
- 突发情况(大型活动临时管制、极端天气、景点临时闭园)无法提前预测,备选方案不能替代现场确认
- 无联网环境时只能输出知识库级信息,并按要求在输出开头声明


---
name: tree-ring-memory
description: "Use Tree Ring Memory for local-first AI-agent memory lifecycle work: recall, evidence, audit, forgetting, and consolidation without transcript dumping."
category: development
risk: critical
source: community
source_repo: TerminallyLazy/Tree-Ring-Memory
source_type: community
date_added: "2026-07-08"
author: TerminallyLazy
tags: [agent-memory, local-first, recall, privacy, codex, sqlite, cli]
tools: [claude, codex, cursor, gemini, antigravity, opencode]
license: "MIT"
license_source: "https://github.com/TerminallyLazy/Tree-Ring-Memory/blob/main/LICENSE"
---

# Tree Ring Memory

## Overview

Tree Ring Memory is a framework-agnostic, local-first memory lifecycle layer for
AI agents. Use this skill when an agent should recall, preserve, audit, or
forget durable project memory without treating raw conversation transcripts as
memory.

The public runtime is a Rust CLI/TUI with local SQLite/FTS storage, scoped
recall, evidence records, audit, deterministic consolidation, maintenance,
DOX/Revolve source adapters, framework discovery, redaction, and explicit
forgetting.

## When to Use This Skill

- Use before resuming a project where prior decisions, warnings, preferences,
  or failed approaches may matter.
- Use before changing architecture, storage, security, privacy, release, or
  agent-memory behavior.
- Use when the user asks to remember, recall, audit, redact, forget, or
  consolidate agent memory.
- Use after tests, reviews, incidents, or production behavior validate a lesson
  future agents should preserve.
- Use when a project contains `.tree-ring/SKILL.md`, `.tree-ring/CLI.md`, or
  other Tree Ring bridge files.

## How It Works

### Step 1: Discover Local Guidance

Check whether the current project already has Tree Ring guidance:

```bash
test -f .tree-ring/SKILL.md && sed -n '1,220p' .tree-ring/SKILL.md
test -f .tree-ring/CLI.md && sed -n '1,220p' .tree-ring/CLI.md
```

Treat project-local `.tree-ring` files as more authoritative than generic
examples in this skill. If the CLI is installed, inspect the current command
surface before assuming flags:

```bash
tree-ring --help
tree-ring recall --help
tree-ring remember --help
tree-ring evidence --help
tree-ring audit --help
tree-ring forget --help
```

If Tree Ring is not installed, resolve the actual project root and explain the
exact project-local download before obtaining explicit approval to fetch it.
Download the official, version-pinned `v0.15.0/install.sh` to a temporary file,
verify its SHA-256 is
`ef0d5eb8f09cbe2e4c3abe80ee9a98a56759c89ad4ddd103d6c68314cd653ade`,
inspect it, then run it. The pinned source is
`https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/v0.15.0/install.sh`.

```bash
cd <project-root>
sh <verified-installer-path> --project --init --release v0.15.0 --no-animation
```

Do not pipe a network response directly to a shell. After verification and
inspection, show the exact installer command and obtain explicit approval again
before executing it. For an installed global CLI, initialize from the project root with
`tree-ring --root .tree-ring init`; for a project-local CLI, use
`.tree-ring/bin/tree-ring --root .tree-ring init`.

Check for a newer release without changing files using
`tree-ring update --check`. Run `tree-ring update` only with user authorization,
then rerun `init` in each project root to refresh managed guidance while
preserving custom content.

## Step 2: Recall Before Risky Work

Use narrow, project-scoped recall first:

```bash
tree-ring recall "release behavior" --project example-service
tree-ring recall "sqlite migration" --project example-service
tree-ring recall "user preference"
```

Use recalled memory as context, not authority. Verify it against current source
files, tests, docs, issues, pull requests, logs, and runtime state before making
changes.

## Step 3: Write Only Durable Memory

Write concise memory only when it is likely to help future agents:

```bash
tree-ring remember "Run project-scoped recall before release changes." --event-type lesson --scope project
```

Prefer specific event types when supported locally:

- `decision`
- `lesson`
- `warning`
- `correction`
- `user_preference`
- `tool_result`
- `summary`
- `hypothesis`

Store the durable lesson, decision, warning, or follow-up. Do not store the
full conversation.

## Step 4: Record Evidence for Evaluated Outcomes

Use evidence records for test runs, incidents, reviewed changes, or other
evaluated outcomes:

```bash
tree-ring evidence \
  "Installer smoke test passed in an isolated HOME." \
  --outcome observed \
  --evidence-ref "ci/install-smoke/2026-07-08"
```

Outcome guidance:

- `promoted`: durable truth backed by strong evidence
- `rejected`: failed or rolled-back approach worth keeping visible
- `deferred`: unresolved idea or future option
- `observed`: normal evaluated result

Do not promote weak, stale, or unreviewed claims to durable truth.

## Step 5: Use Source Adapters Carefully

When a repo has structured source records, run dry runs first:

```bash
tree-ring dox sync --source-root . --dry-run
tree-ring revolve sync --source-root revolve --dry-run
tree-ring integrations scan --source-root .
```

Only write adapter summaries when they are concise, source-linked, useful, and
privacy-safe. Imported memory does not replace the underlying `AGENTS.md`,
Revolve record, test, pull request, issue, or documentation.

## Ring Selection

Use the smallest durable ring that fits:

- `cambium`: active or recent task context
- `outer`: recent decisions and task lessons
- `inner`: older compressed project knowledge
- `heartwood`: durable high-confidence truths
- `scar`: failures, regressions, rejected approaches, warnings
- `seed`: unresolved ideas, hypotheses, follow-ups

Prefer `outer` or `seed` unless the user confirms durability or the evidence is
strong.

## Best Practices

- Recall before risky or repeat work.
- Keep project memory project-scoped unless it is a durable cross-project user
  preference.
- Attach source references such as file paths, issue ids, PR ids, evaluation
  runs, or docs paths.
- Re-check current source files and runtime state before acting on recalled
  memory.
- Ask at closeout what future agents should remember, avoid, or revisit.
- Use redaction, deletion, or supersession when memory is wrong, stale,
  sensitive, or replaced by a newer decision.

## Security & Safety Notes

- Never use Tree Ring Memory as a hidden recorder.
- Do not store secrets, credentials, tokens, private keys, recovery codes, raw
  chain-of-thought, or temporary scratchpad content.
- Do not store sensitive personal data unless the user explicitly asks and the
  retention boundary is safe.
- Do not store copyrighted source text beyond short allowed excerpts.
- Do not run installer, network, destructive, or mutation commands without
  explicit user approval and a clear target environment.
- Treat all examples as commands to adapt after checking local `--help`, not as
  guaranteed command surfaces.

## Limitations

- Tree Ring Memory is not a replacement for source control, issue trackers,
  documentation, tests, logs, or live runtime verification.
- Recalled memory can be stale or wrong. Always verify important claims against
  the current project before using them to make changes.
- The CLI surface can change across releases. Prefer local `.tree-ring`
  guidance and `tree-ring --help` over copied command examples.
- It should not be used for secret storage, comprehensive transcript archives,
  compliance retention, or unreviewed collection of sensitive personal data.
- Cross-agent interoperability depends on each tool's ability to call the local
  CLI or read project-local guidance files.

## Common Pitfalls

- **Problem:** Recalled memory conflicts with current source.
  **Solution:** Treat source files, tests, docs, and runtime evidence as
  authoritative; supersede or forget stale memory.

- **Problem:** Memory starts becoming transcript storage.
  **Solution:** Store only durable decisions, warnings, preferences, outcomes,
  and follow-ups.

- **Problem:** A lesson is useful but contains sensitive detail.
  **Solution:** Store a redacted summary or do not store it.

## Related Skills

- `@agent-memory-systems` - Use for broad agent-memory architecture choices.
- `@agent-memory` - Use for the listed hybrid memory MCP system.
- `@planning-with-files` - Use when simple persistent files are enough.

## Additional Resources

- Tree Ring Memory repository: <https://github.com/TerminallyLazy/Tree-Ring-Memory>
- Codex plugin wrapper: <https://github.com/TerminallyLazy/tree-ring-memory-codex-plugin>


---
name: trello-automation
description: "Automate Trello boards, cards, and workflows via Rube MCP (Composio). Create cards, manage lists, assign members, and search across boards programmatically."
risk: critical
source: community
date_added: "2026-02-27"
---

# Trello Automation via Rube MCP

Automate Trello board management, card creation, and team workflows through Composio's Rube MCP integration.

## Prerequisites

- Rube MCP must be connected (RUBE_SEARCH_TOOLS available)
- Active Trello connection via `RUBE_MANAGE_CONNECTIONS` with toolkit `trello`
- Always call `RUBE_SEARCH_TOOLS` first to get current tool schemas

## Setup

**Get Rube MCP**: Add `https://rube.app/mcp` as an MCP server in your client configuration. No API keys needed — just add the endpoint and it works.

1. Verify Rube MCP is available by confirming `RUBE_SEARCH_TOOLS` responds
2. Call `RUBE_MANAGE_CONNECTIONS` with toolkit `trello`
3. If connection is not ACTIVE, follow the returned auth link to complete Trello auth
4. Confirm connection status shows ACTIVE before running any workflows

## Core Workflows

### 1. Create a Card on a Board

**When to use**: User wants to add a new card/task to a Trello board

**Tool sequence**:
1. `TRELLO_GET_MEMBERS_BOARDS_BY_ID_MEMBER` - List boards to find target board ID [Prerequisite]
2. `TRELLO_GET_BOARDS_LISTS_BY_ID_BOARD` - Get lists on board to find target list ID [Prerequisite]
3. `TRELLO_ADD_CARDS` - Create the card on the resolved list [Required]
4. `TRELLO_ADD_CARDS_CHECKLISTS_BY_ID_CARD` - Add a checklist to the card [Optional]
5. `TRELLO_ADD_CARDS_CHECKLIST_CHECK_ITEM_BY_ID_CARD_BY_ID_CHECKLIST` - Add items to the checklist [Optional]

**Key parameters**:
- `idList`: 24-char hex ID (NOT list name)
- `name`: Card title
- `desc`: Card description (supports Markdown)
- `pos`: Position ('top'/'bottom')
- `due`: Due date (ISO 8601 format)

**Pitfalls**:
- Store returned id (idCard) immediately; downstream checklist operations fail without it
- Checklist payload may be nested (data.data); extract idChecklist from inner object
- One API call per checklist item; large checklists can trigger rate limits

### 2. Manage Boards and Lists

**When to use**: User wants to view, browse, or restructure board layout

**Tool sequence**:
1. `TRELLO_GET_MEMBERS_BOARDS_BY_ID_MEMBER` - List all boards for the user [Required]
2. `TRELLO_GET_BOARDS_BY_ID_BOARD` - Get detailed board info [Required]
3. `TRELLO_GET_BOARDS_LISTS_BY_ID_BOARD` - Get lists (columns) on the board [Optional]
4. `TRELLO_GET_BOARDS_MEMBERS_BY_ID_BOARD` - Get board members [Optional]
5. `TRELLO_GET_BOARDS_LABELS_BY_ID_BOARD` - Get labels on the board [Optional]

**Key parameters**:
- `idMember`: Use 'me' for authenticated user
- `filter`: 'open', 'starred', or 'all'
- `idBoard`: 24-char hex or 8-char shortLink (NOT board name)

**Pitfalls**:
- Some runs return boards under response.data.details[]—don't assume flat top-level array
- Lists may be nested under results[0].response.data.details—parse defensively
- ISO 8601 timestamps with trailing 'Z' must be parsed as timezone-aware

### 3. Move Cards Between Lists

**When to use**: User wants to change a card's status by moving it to another list

**Tool sequence**:
1. `TRELLO_GET_SEARCH` - Find the card by name or keyword [Prerequisite]
2. `TRELLO_GET_BOARDS_LISTS_BY_ID_BOARD` - Get destination list ID [Prerequisite]
3. `TRELLO_UPDATE_CARDS_BY_ID_CARD` - Update card's idList to move it [Required]

**Key parameters**:
- `idCard`: Card ID from search
- `idList`: Destination list ID
- `pos`: Optional ordering within new list

**Pitfalls**:
- Search returns partial matches; verify card name before updating
- Moving doesn't update position within new list; set pos if ordering matters

### 4. Assign Members to Cards

**When to use**: User wants to assign team members to cards

**Tool sequence**:
1. `TRELLO_GET_BOARDS_MEMBERS_BY_ID_BOARD` - Get member IDs from the board [Prerequisite]
2. `TRELLO_ADD_CARDS_ID_MEMBERS_BY_ID_CARD` - Add a member to the card [Required]

**Key parameters**:
- `idCard`: Target card ID
- `value`: Member ID to assign

**Pitfalls**:
- UPDATE_CARDS_ID_MEMBERS replaces entire member list; use ADD_CARDS_ID_MEMBERS to append
- Member must have board permissions

### 5. Search and Filter Cards

**When to use**: User wants to find specific cards across boards

**Tool sequence**:
1. `TRELLO_GET_SEARCH` - Search by query string [Required]

**Key parameters**:
- `query`: Search string (supports board:, list:, label:, is:open/archived operators)
- `modelTypes`: Set to 'cards'
- `partial`: Set to 'true' for prefix matching

**Pitfalls**:
- Search indexing has delay; newly created cards may not appear for several minutes
- For exact name matching, use TRELLO_GET_BOARDS_CARDS_BY_ID_BOARD and filter locally
- Query uses word tokenization; common words may be ignored as stop words

### 6. Add Comments and Attachments

**When to use**: User wants to add context to an existing card

**Tool sequence**:
1. `TRELLO_ADD_CARDS_ACTIONS_COMMENTS_BY_ID_CARD` - Post a comment on the card [Required]
2. `TRELLO_ADD_CARDS_ATTACHMENTS_BY_ID_CARD` - Attach a file or URL [Optional]

**Key parameters**:
- `text`: Comment text (1-16384 chars, supports Markdown and @mentions)
- `url` OR `file`: Attachment source (not both)
- `name`: Attachment display name
- `mimeType`: File MIME type

**Pitfalls**:
- Comments don't support file attachments; use the attachment tool separately
- Attachment deletion is irreversible

## Common Patterns

### ID Resolution
Always resolve display names to IDs before operations:
- **Board name → Board ID**: `TRELLO_GET_MEMBERS_BOARDS_BY_ID_MEMBER` with idMember='me'
- **List name → List ID**: `TRELLO_GET_BOARDS_LISTS_BY_ID_BOARD` with resolved board ID
- **Card name → Card ID**: `TRELLO_GET_SEARCH` with query string
- **Member name → Member ID**: `TRELLO_GET_BOARDS_MEMBERS_BY_ID_BOARD`

### Pagination
Most list endpoints return all items. For boards with 1000+ cards, use `limit` and `before` parameters on card listing endpoints.

### Rate Limits
300 requests per 10 seconds per token. Use `TRELLO_GET_BATCH` for bulk read operations to stay within limits.

## Known Pitfalls

- **ID Requirements**: Nearly every tool requires IDs, not display names. Always resolve names to IDs first.
- **Board ID Format**: Board IDs must be 24-char hex or 8-char shortLink. URL slugs like 'my-board' are NOT valid.
- **Search Delays**: Search indexing has delays; newly created/updated cards may not appear immediately.
- **Nested Responses**: Response data is often nested (data.data or data.details[]); parse defensively.
- **Rate Limiting**: 300 req/10s per token. Batch reads with TRELLO_GET_BATCH.

## Quick Reference

| Task | Tool Slug | Key Params |
|------|-----------|------------|
| List user's boards | TRELLO_GET_MEMBERS_BOARDS_BY_ID_MEMBER | idMember='me', filter='open' |
| Get board details | TRELLO_GET_BOARDS_BY_ID_BOARD | idBoard (24-char hex) |
| List board lists | TRELLO_GET_BOARDS_LISTS_BY_ID_BOARD | idBoard |
| Create card | TRELLO_ADD_CARDS | idList, name, desc, pos, due |
| Update card | TRELLO_UPDATE_CARDS_BY_ID_CARD | idCard, idList (to move) |
| Search cards | TRELLO_GET_SEARCH | query, modelTypes='cards' |
| Add checklist | TRELLO_ADD_CARDS_CHECKLISTS_BY_ID_CARD | idCard, name |
| Add comment | TRELLO_ADD_CARDS_ACTIONS_COMMENTS_BY_ID_CARD | idCard, text |
| Assign member | TRELLO_ADD_CARDS_ID_MEMBERS_BY_ID_CARD | idCard, value (member ID) |
| Attach file/URL | TRELLO_ADD_CARDS_ATTACHMENTS_BY_ID_CARD | idCard, url OR file |
| Get board members | TRELLO_GET_BOARDS_MEMBERS_BY_ID_BOARD | idBoard |
| Batch read | TRELLO_GET_BATCH | urls (comma-separated paths) |

## When to Use
This skill is applicable to execute the workflow or actions described in the overview.

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: triage
description: Move issues and external PRs through a state machine of triage roles — categorise, verify, grill if needed, and write agent-ready briefs.
disable-model-invocation: true
category: "development"
risk: "safe"
source: "community"
source_repo: "mattpocock/skills"
source_type: "community"
date_added: "2026-06-19"
author: "Matt Pocock"
license: "MIT"
license_source: "https://github.com/mattpocock/skills/blob/main/LICENSE"
tags:
  - engineering
  - workflow
  - coding-agents
tools:
  - claude-code
  - codex-cli
  - cursor
---

# Triage

## When to Use

Use when this workflow matches the user request: Move issues and external PRs through a state machine of triage roles — categorise, verify, grill if needed, and write agent-ready briefs.

_Source: [mattpocock/skills](https://github.com/mattpocock/skills) (MIT)._

Move issues on the project issue tracker through a small state machine of triage roles.

If this repo treats external pull requests as a request surface (see the issue-tracker config), triage covers them too: **a PR is an issue with attached code** — same roles, same states, same machine, with a few deltas marked "for a PR" below. Resolve a bare `#42` to an issue or PR per the tracker config.

Every comment or issue posted to the issue tracker during triage **must** start with this disclaimer:

```
> *This was generated by AI during triage.*
```

## Reference docs

- [AGENT-BRIEF.md](AGENT-BRIEF.md) — how to write durable agent briefs
- [OUT-OF-SCOPE.md](OUT-OF-SCOPE.md) — how the `.out-of-scope/` knowledge base works

## Roles

Two **category** roles:

- `bug` — something is broken
- `enhancement` — new feature or improvement

Five **state** roles:

- `needs-triage` — maintainer needs to evaluate
- `needs-info` — waiting on reporter for more information
- `ready-for-agent` — fully specified, ready for an AFK agent
- `ready-for-human` — needs human implementation
- `wontfix` — will not be actioned

For a PR, the same states read against the attached code: `ready-for-agent` means a brief is attached and an agent should take the next step on the diff; `ready-for-human` means it's ready for a human to merge.

Every triaged issue should carry exactly one category role and one state role. If state roles conflict, flag it and ask the maintainer before doing anything else.

These are canonical role names — the actual label strings used in the issue tracker may differ. The mapping should have been provided to you - run `/setup-matt-pocock-skills` if not.

State transitions: an unlabeled issue normally goes to `needs-triage` first; from there it moves to `needs-info`, `ready-for-agent`, `ready-for-human`, or `wontfix`. `needs-info` returns to `needs-triage` once the reporter replies. The maintainer can override at any time — flag transitions that look unusual and ask before proceeding.

## Invocation

The maintainer invokes `/triage` and describes what they want in natural language. Interpret the request and act. Examples:

- "Show me anything that needs my attention"
- "Let's look at #42" (issue or PR)
- "Move #42 to ready-for-agent"
- "What's ready for agents to pick up?"

## Show what needs attention

Query the issue tracker and present three buckets, oldest first:

1. **Unlabeled** — never triaged.
2. **`needs-triage`** — evaluation in progress.
3. **`needs-info` with reporter activity since the last triage notes** — needs re-evaluation.

When PRs are in scope, include external PRs in these buckets and tag each line `[PR]` or `[issue]`. Discovery surfaces only *external* PRs (the tracker config defines who counts as external) — a collaborator's in-flight PR is not triage work. This filter is discovery-only; an explicitly named PR is always triaged regardless of author.

Show counts and a one-line summary per item. Let the maintainer pick.

## Triage a specific issue or PR

1. **Gather context.** Read the full issue or PR (body, comments, labels, author, dates; for a PR, the diff too). Parse any prior triage notes so you don't re-ask resolved questions. Explore the codebase using the project's domain glossary, respecting ADRs in the area. Run two checks against the codebase: (a) **redundancy** — search for an existing implementation of the requested behavior by domain concept (not just the request's wording), and report where you looked. If found, it's an already-implemented `wontfix` (step 5). (b) **prior rejection** — read `.out-of-scope/*.md` and surface any that resembles this request.

2. **Recommend.** Tell the maintainer your category and state recommendation with reasoning, plus a brief codebase summary relevant to the request — including whether it's already implemented. Wait for direction.

3. **Verify the claim.** Before any grilling, check that the claim holds up. For a bug, reproduce it from the reporter's steps. For a PR, confirm the diff does what it claims — check it out, run the relevant tests or commands. Report what happened: confirmed (with code path), failed, or insufficient detail (a strong `needs-info` signal). A confirmed verification makes a much stronger agent brief.

4. **Grill (if needed).** If the request needs fleshing out, run the `/grilling` and `/domain-modeling` skills together — grill it into shape one question at a time, sharpening domain terms and updating `CONTEXT.md`/ADRs inline as decisions land.

5. **Apply the outcome:**
   - `ready-for-agent` — post an agent brief comment ([AGENT-BRIEF.md](AGENT-BRIEF.md)).
   - `ready-for-human` — same structure as an agent brief, but note why it can't be delegated (judgment calls, external access, design decisions, manual testing).
   - `needs-info` — post triage notes (template below).
   - `wontfix` — close, with the comment depending on *why*:
     - **Already implemented** — the change already exists in the codebase. Point to where it lives; do **not** write to `.out-of-scope/` (that KB is for *rejected* requests, not built ones).
     - **Rejected (bug)** — polite explanation, then close.
     - **Rejected (enhancement)** — write to `.out-of-scope/`, link to it from a comment, then close ([OUT-OF-SCOPE.md](OUT-OF-SCOPE.md)).
   - `needs-triage` — apply the role. Optional comment if there's partial progress.

## Quick state override

If the maintainer says "move #42 to ready-for-agent", trust them and apply the role directly. Confirm what you're about to do (role changes, comment, close), then act. Skip grilling. If moving to `ready-for-agent` without a grilling session, ask whether they want to write an agent brief.

## Needs-info template

```markdown
## Triage Notes

**What we've established so far:**

- point 1
- point 2

**What we still need from you (@reporter):**

- question 1
- question 2
```

Capture everything resolved during grilling under "established so far" so the work isn't lost. Questions must be specific and actionable, not "please provide more info".

## Resuming a previous session

If prior triage notes exist on the issue or PR, read them, check whether the reporter has answered any outstanding questions, and present an updated picture before continuing. Don't re-ask resolved questions.

## Limitations

- Requires the upstream tool, account, API key, or local setup when the workflow names one.
- Does not authorize destructive, production, paid, or external-message actions without explicit user approval.
- Validate generated artifacts or recommendations against the user's real sources before treating them as final.


---
name: trigger-dev
description: Trigger.dev expert for background jobs, AI workflows, and reliable
  async execution with excellent developer experience and TypeScript-first
  design.
risk: critical
source: vibeship-spawner-skills (Apache 2.0)
date_added: 2026-02-27
---

# Trigger.dev Integration

Trigger.dev expert for background jobs, AI workflows, and reliable async
execution with excellent developer experience and TypeScript-first design.

## Principles

- Tasks are the building blocks - each task is independently retryable
- Runs are durable - state survives crashes and restarts
- Integrations are first-class - use built-in API wrappers for reliability
- Logs are your debugging lifeline - log liberally in tasks
- Concurrency protects your resources - always set limits
- Delays and schedules are built-in - no external cron needed
- AI-ready by design - long-running AI tasks just work
- Local development matches production - use the CLI

## Capabilities

- trigger-dev-tasks
- ai-background-jobs
- integration-tasks
- scheduled-triggers
- webhook-handlers
- long-running-tasks
- task-queues
- batch-processing

## Scope

- redis-queues -> bullmq-specialist
- pure-event-driven -> inngest
- workflow-orchestration -> temporal-craftsman
- infrastructure -> infra-architect

## Tooling

### Core

- trigger-dev-sdk
- trigger-cli

### Frameworks

- nextjs
- remix
- express
- hono

### Integrations

- openai
- anthropic
- resend
- stripe
- slack
- supabase

### Deployment

- trigger-cloud
- self-hosted
- docker

## Patterns

### Basic Task Setup

Setting up Trigger.dev in a Next.js project

**When to use**: Starting with Trigger.dev in any project

// trigger.config.ts
import { defineConfig } from '@trigger.dev/sdk/v3';

export default defineConfig({
  project: 'my-project',
  runtime: 'node',
  logLevel: 'log',
  retries: {
    enabledInDev: true,
    default: {
      maxAttempts: 3,
      minTimeoutInMs: 1000,
      maxTimeoutInMs: 10000,
      factor: 2,
    },
  },
});

// src/trigger/tasks.ts
import { task, logger } from '@trigger.dev/sdk/v3';

export const helloWorld = task({
  id: 'hello-world',
  run: async (payload: { name: string }) => {
    logger.log('Processing hello world', { payload });

    // Simulate work
    await new Promise(resolve => setTimeout(resolve, 1000));

    return { message: `Hello, ${payload.name}!` };
  },
});

// Triggering from your app
import { helloWorld } from '@/trigger/tasks';

// Fire and forget
await helloWorld.trigger({ name: 'World' });

// Wait for result
const handle = await helloWorld.trigger({ name: 'World' });
const result = await handle.wait();

### AI Task with OpenAI Integration

Using built-in OpenAI integration with automatic retries

**When to use**: Building AI-powered background tasks

import { task, logger } from '@trigger.dev/sdk/v3';
import { openai } from '@trigger.dev/openai';

// Configure OpenAI with Trigger.dev
const openaiClient = openai.configure({
  id: 'openai',
  apiKey: process.env.OPENAI_API_KEY,
});

export const generateContent = task({
  id: 'generate-content',
  retry: {
    maxAttempts: 3,
  },
  run: async (payload: { topic: string; style: string }) => {
    logger.log('Generating content', { topic: payload.topic });

    // Uses Trigger.dev's OpenAI integration - handles retries automatically
    const completion = await openaiClient.chat.completions.create({
      model: 'gpt-4-turbo-preview',
      messages: [
        {
          role: 'system',
          content: `You are a ${payload.style} writer.`,
        },
        {
          role: 'user',
          content: `Write about: ${payload.topic}`,
        },
      ],
    });

    const content = completion.choices[0].message.content;
    logger.log('Generated content', { length: content?.length });

    return { content, tokens: completion.usage?.total_tokens };
  },
});

### Scheduled Task with Cron

Tasks that run on a schedule

**When to use**: Periodic jobs like reports, cleanup, or syncs

import { schedules, task, logger } from '@trigger.dev/sdk/v3';

export const dailyCleanup = schedules.task({
  id: 'daily-cleanup',
  cron: '0 2 * * *',  // 2 AM daily
  run: async () => {
    logger.log('Starting daily cleanup');

    // Clean up old records
    const deleted = await db.logs.deleteMany({
      where: {
        createdAt: { lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) },
      },
    });

    logger.log('Cleanup complete', { deletedCount: deleted.count });

    return { deleted: deleted.count };
  },
});

// Weekly report
export const weeklyReport = schedules.task({
  id: 'weekly-report',
  cron: '0 9 * * 1',  // Monday 9 AM
  run: async () => {
    const stats = await generateWeeklyStats();
    await sendReportEmail(stats);
    return stats;
  },
});

### Batch Processing

Processing large datasets in batches

**When to use**: Need to process many items with rate limiting

import { task, logger, wait } from '@trigger.dev/sdk/v3';

export const processBatch = task({
  id: 'process-batch',
  queue: {
    concurrencyLimit: 5,  // Only 5 running at once
  },
  run: async (payload: { items: string[] }) => {
    const results = [];

    for (const item of payload.items) {
      logger.log('Processing item', { item });

      const result = await processItem(item);
      results.push(result);

      // Respect rate limits
      await wait.for({ seconds: 1 });
    }

    return { processed: results.length, results };
  },
});

// Trigger batch processing
export const startBatchJob = task({
  id: 'start-batch',
  run: async (payload: { datasetId: string }) => {
    const items = await fetchDataset(payload.datasetId);

    // Split into chunks of 100
    const chunks = chunkArray(items, 100);

    // Trigger parallel batch tasks
    const handles = await Promise.all(
      chunks.map(chunk => processBatch.trigger({ items: chunk }))
    );

    logger.log('Started batch processing', {
      totalItems: items.length,
      batches: chunks.length,
    });

    return { batches: handles.length };
  },
});

### Webhook Handler

Processing webhooks reliably with deduplication

**When to use**: Handling webhooks from Stripe, GitHub, etc.

import { task, logger, idempotencyKeys } from '@trigger.dev/sdk/v3';

export const handleStripeEvent = task({
  id: 'handle-stripe-event',
  run: async (payload: {
    eventId: string;
    type: string;
    data: any;
  }) => {
    // Idempotency based on Stripe event ID
    const idempotencyKey = await idempotencyKeys.create(payload.eventId);

    if (idempotencyKey.isNew === false) {
      logger.log('Duplicate event, skipping', { eventId: payload.eventId });
      return { skipped: true };
    }

    logger.log('Processing Stripe event', {
      type: payload.type,
      eventId: payload.eventId,
    });

    switch (payload.type) {
      case 'checkout.session.completed':
        await handleCheckoutComplete(payload.data);
        break;
      case 'customer.subscription.updated':
        await handleSubscriptionUpdate(payload.data);
        break;
    }

    return { processed: true, type: payload.type };
  },
});

## Sharp Edges

### Task timeout kills execution without clear error

Severity: CRITICAL

Situation: Long-running AI task or batch process suddenly stops. No error in logs.
Task shows as failed in dashboard but no stack trace. Data partially processed.

Symptoms:
- Task fails with no error message
- Partial data processing
- Works locally, fails in production
- "Task timed out" in dashboard

Why this breaks:
Trigger.dev has execution timeouts (defaults vary by plan). When exceeded, the
task is killed mid-execution. If you're not logging progress, you won't know
where it stopped. This is especially common with AI tasks that can take minutes.

Recommended fix:

# Configure explicit timeouts:
```typescript
export const processDocument = task({
  id: 'process-document',
  machine: {
    preset: 'large-2x',  // More resources = longer allowed time
  },
  run: async (payload) => {
    logger.log('Starting document processing', { docId: payload.id });

    // Log progress at each step
    logger.log('Step 1: Extracting text');
    const text = await extractText(payload.fileUrl);

    logger.log('Step 2: Generating embeddings', { textLength: text.length });
    const embeddings = await generateEmbeddings(text);

    logger.log('Step 3: Storing vectors', { count: embeddings.length });
    await storeVectors(embeddings);

    logger.log('Completed successfully');
    return { processed: true };
  },
});
```

# For very long tasks, break into subtasks:
- Use triggerAndWait for sequential steps
- Each subtask has its own timeout
- Progress is visible in dashboard

### Non-serializable payload causes silent task failure

Severity: CRITICAL

Situation: Passing Date objects, class instances, or circular references in payload.
Task queued but never runs. Or runs with undefined/null values.

Symptoms:
- Payload values are undefined in task
- Date objects become strings
- Class methods not available
- "Converting circular structure to JSON"

Why this breaks:
Trigger.dev serializes payloads to JSON. Dates become strings, class instances
lose methods, functions disappear, circular refs throw. Your task sees different
data than you sent.

Recommended fix:

# Always use plain objects:
```typescript
// WRONG - Date becomes string
await myTask.trigger({ createdAt: new Date() });

// RIGHT - ISO string
await myTask.trigger({ createdAt: new Date().toISOString() });

// WRONG - Class instance
await myTask.trigger({ user: new User(data) });

// RIGHT - Plain object
await myTask.trigger({ user: { id: data.id, email: data.email } });

// WRONG - Circular reference
const obj = { parent: null };
obj.parent = obj;
await myTask.trigger(obj);  // Throws!
```

# In task, reconstitute as needed:
```typescript
run: async (payload: { createdAt: string }) => {
  const date = new Date(payload.createdAt);
  // ...
}
```

### Environment variables not synced to Trigger.dev cloud

Severity: CRITICAL

Situation: Task works locally but fails in production. Env var that exists in Vercel
is undefined in Trigger.dev. API calls fail, database connections fail.

Symptoms:
- "Environment variable not found"
- API calls return 401 in production tasks
- Works in dev, fails in production
- Database connection errors in tasks

Why this breaks:
Trigger.dev runs tasks in its own cloud, separate from your Vercel/Railway
deployment. Environment variables must be configured in BOTH places. They
don't automatically sync.

Recommended fix:

# Sync env vars to Trigger.dev:
1. Go to Trigger.dev dashboard
2. Project Settings > Environment Variables
3. Add ALL required env vars

# Or use CLI:
```bash
# Create .env.trigger file
DATABASE_URL=postgres://...
OPENAI_API_KEY=sk-...
STRIPE_SECRET_KEY=sk_live_...

# Push to Trigger.dev
npx trigger.dev@latest env push
```

# Common missing vars:
- DATABASE_URL
- OPENAI_API_KEY / ANTHROPIC_API_KEY
- STRIPE_SECRET_KEY
- Service API keys
- Internal service URLs

# Test in staging:
Trigger.dev has separate envs - configure staging too

### SDK version mismatch between CLI and package

Severity: HIGH

Situation: Updated @trigger.dev/sdk but forgot to update CLI. Or vice versa.
Tasks fail to register. Weird type errors. Dev server crashes.

Symptoms:
- Tasks not appearing in dashboard
- Type errors in trigger.config.ts
- "Failed to register task"
- Dev server crashes on start

Why this breaks:
The Trigger.dev SDK and CLI must be on compatible versions. Breaking changes
between versions cause registration failures. The CLI generates types that
must match the SDK.

Recommended fix:

# Always update together:
```bash
# Update both SDK and CLI
npm install @trigger.dev/sdk@latest
npx trigger.dev@latest dev

# Or pin to same version
npm install @trigger.dev/sdk@3.3.0
npx trigger.dev@3.3.0 dev
```

# Check versions:
```bash
npx trigger.dev@latest --version
npm list @trigger.dev/sdk
```

# In CI/CD:
```yaml
- run: npm install @trigger.dev/sdk@${{ env.TRIGGER_VERSION }}
- run: npx trigger.dev@${{ env.TRIGGER_VERSION }} deploy
```

### Task retries cause duplicate side effects

Severity: HIGH

Situation: Task sends email, then fails on next step. Retry sends email again.
Customer gets 3 identical emails. Or 3 Stripe charges. Or 3 Slack messages.

Symptoms:
- Duplicate emails on retry
- Multiple charges for same order
- Duplicate webhook deliveries
- Data inserted multiple times

Why this breaks:
Trigger.dev retries failed tasks from the beginning. If your task has side
effects before the failure point, those execute again. Without idempotency,
you create duplicates.

Recommended fix:

# Use idempotency keys:
```typescript
import { task, idempotencyKeys } from '@trigger.dev/sdk/v3';

export const sendOrderEmail = task({
  id: 'send-order-email',
  run: async (payload: { orderId: string }) => {
    // Check if already sent
    const key = await idempotencyKeys.create(`email-${payload.orderId}`);

    if (!key.isNew) {
      logger.log('Email already sent, skipping');
      return { skipped: true };
    }

    await sendEmail(payload.orderId);
    return { sent: true };
  },
});
```

# Alternative: Track in database
```typescript
const existing = await db.emailLogs.findUnique({
  where: { orderId_type: { orderId, type: 'order_confirmation' } }
});

if (existing) {
  logger.log('Already sent');
  return;
}

await sendEmail(orderId);
await db.emailLogs.create({ data: { orderId, type: 'order_confirmation' } });
```

### High concurrency overwhelms downstream services

Severity: HIGH

Situation: Burst of 1000 tasks triggered. All hit OpenAI API simultaneously.
Rate limited. All fail. Retry. Rate limited again. Vicious cycle.

Symptoms:
- Rate limit errors (429)
- Database connection pool exhausted
- API returns "too many requests"
- Mass task failures

Why this breaks:
Trigger.dev scales to handle many concurrent tasks. But your downstream
APIs (OpenAI, databases, external services) have rate limits. Without
concurrency control, you overwhelm them.

Recommended fix:

# Set queue concurrency limits:
```typescript
export const callOpenAI = task({
  id: 'call-openai',
  queue: {
    concurrencyLimit: 10,  // Only 10 running at once
  },
  run: async (payload) => {
    // Protected by concurrency limit
    return await openai.chat.completions.create(payload);
  },
});
```

# For rate-limited APIs:
```typescript
export const callRateLimitedAPI = task({
  id: 'call-api',
  queue: {
    concurrencyLimit: 5,
  },
  retry: {
    maxAttempts: 5,
    minTimeoutInMs: 5000,  // Wait before retry
    factor: 2,  // Exponential backoff
  },
  run: async (payload) => {
    // Add delay between calls
    await wait.for({ milliseconds: 200 });
    return await externalAPI.call(payload);
  },
});
```

# Start conservative:
- 5-10 for external APIs
- 20-50 for databases
- Increase based on monitoring

### trigger.config.ts not at project root

Severity: HIGH

Situation: Running npx trigger.dev dev but CLI can't find config.
Or config exists but in wrong location (monorepo issue).

Symptoms:
- "Could not find trigger.config.ts"
- Tasks not discovered
- Empty task list in dashboard
- Works for one package, not another

Why this breaks:
The CLI looks for trigger.config.ts at the current working directory.
In monorepos, you must run from the package directory, not the root.
Wrong location = tasks not discovered.

Recommended fix:

# Config must be at package root:
```
my-app/
├── trigger.config.ts  <- Here
├── package.json
├── src/
│   └── trigger/
│       └── tasks.ts
```

# In monorepos:
```
monorepo/
├── apps/
│   └── web/
│       ├── trigger.config.ts  <- Here, not at monorepo root
│       ├── package.json
│       └── src/trigger/

# Run from package directory
cd apps/web && npx trigger.dev dev
```

# Specify config location:
```bash
npx trigger.dev dev --config ./apps/web/trigger.config.ts
```

### wait.for in loops causes memory issues

Severity: MEDIUM

Situation: Processing thousands of items with wait.for between each.
Task memory grows. Eventually killed for memory.

Symptoms:
- Task killed for memory
- Slow task execution
- State blob too large error
- Works for small batches, fails for large

Why this breaks:
Each wait.for creates checkpoint state. In a loop with thousands of
iterations, this accumulates. The task's state blob grows until it
hits memory limits.

Recommended fix:

# Batch instead of individual waits:
```typescript
// WRONG - Wait per item
for (const item of items) {
  await processItem(item);
  await wait.for({ milliseconds: 100 });  // 1000 waits = bloated state
}

// RIGHT - Batch processing
const chunks = chunkArray(items, 50);
for (const chunk of chunks) {
  await Promise.all(chunk.map(processItem));
  await wait.for({ milliseconds: 500 });  // Only 20 waits
}
```

# For very large datasets, use subtasks:
```typescript
export const processAll = task({
  id: 'process-all',
  run: async (payload: { items: string[] }) => {
    const chunks = chunkArray(payload.items, 100);

    // Each chunk is a separate task
    await Promise.all(
      chunks.map(chunk =>
        processChunk.triggerAndWait({ items: chunk })
      )
    );
  },
});
```

### Using raw SDK instead of Trigger.dev integrations

Severity: MEDIUM

Situation: Using OpenAI SDK directly. API call fails. No automatic retry.
Rate limits not handled. Have to implement all resilience manually.

Symptoms:
- Manual retry logic in tasks
- Rate limit errors not handled
- No automatic logging of API calls
- Inconsistent error handling

Why this breaks:
Trigger.dev integrations wrap SDKs with automatic retries, rate limit
handling, and proper logging. Using raw SDKs means you lose these
features and have to implement them yourself.

Recommended fix:

# Use integrations when available:
```typescript
// WRONG - Raw SDK
import OpenAI from 'openai';
const openai = new OpenAI();

// RIGHT - Trigger.dev integration
import { openai } from '@trigger.dev/openai';

const openaiClient = openai.configure({
  id: 'openai',
  apiKey: process.env.OPENAI_API_KEY,
});

// Now has automatic retries and rate limiting
export const generateContent = task({
  id: 'generate-content',
  run: async (payload) => {
    const response = await openaiClient.chat.completions.create({
      model: 'gpt-4-turbo-preview',
      messages: [{ role: 'user', content: payload.prompt }],
    });
    return response;
  },
});
```

# Available integrations:
- @trigger.dev/openai
- @trigger.dev/anthropic
- @trigger.dev/resend
- @trigger.dev/slack
- @trigger.dev/stripe

### Triggering tasks without dev server running

Severity: MEDIUM

Situation: Called task.trigger() but nothing happens. No errors either.
Task just disappears into void. Dev server wasn't running.

Symptoms:
- Triggers don't run
- No task in dashboard
- No errors, just silence
- Works in production, not dev

Why this breaks:
In development, tasks run through the local dev server (npx trigger.dev dev).
If it's not running, triggers queue up or fail silently depending on
configuration. Production works differently.

Recommended fix:

# Always run dev server during development:
```bash
# Terminal 1: Your app
npm run dev

# Terminal 2: Trigger.dev dev server
npx trigger.dev dev
```

# Check dev server is connected:
- Should show "Connected to Trigger.dev"
- Tasks should appear in console
- Dashboard shows task registrations

# In package.json:
```json
{
  "scripts": {
    "dev": "next dev",
    "trigger:dev": "trigger.dev dev",
    "dev:all": "concurrently \"npm run dev\" \"npm run trigger:dev\""
  }
}
```

## Validation Checks

### Task without logging

Severity: WARNING

Message: Task has no logging. Add logger.log() calls for debugging in production.

Fix action: Import { logger } from '@trigger.dev/sdk/v3' and add log statements

### Task without error handling

Severity: ERROR

Message: Task lacks explicit error handling. Unhandled errors may cause unclear failures.

Fix action: Wrap task logic in try/catch and log errors with context

### Task without concurrency limit

Severity: WARNING

Message: Task has no concurrency limit. High load may overwhelm downstream services.

Fix action: Add queue: { concurrencyLimit: 10 } to protect APIs and databases

### Date object in trigger payload

Severity: ERROR

Message: Date objects are serialized to strings. Use ISO string format instead.

Fix action: Use date.toISOString() instead of new Date()

### Class instance in trigger payload

Severity: ERROR

Message: Class instances lose methods when serialized. Use plain objects.

Fix action: Convert class instance to plain object before triggering

### Task without explicit ID

Severity: ERROR

Message: Task must have an explicit id property for registration.

Fix action: Add id: 'my-task-name' to task definition

### Trigger.dev API key hardcoded

Severity: CRITICAL

Message: Trigger.dev API key should not be hardcoded - use TRIGGER_SECRET_KEY env var

Fix action: Remove hardcoded key and use process.env.TRIGGER_SECRET_KEY

### Using raw OpenAI SDK instead of integration

Severity: WARNING

Message: Consider using @trigger.dev/openai for automatic retries and rate limiting

Fix action: Replace with: import { openai } from '@trigger.dev/openai'

### Using raw Anthropic SDK instead of integration

Severity: WARNING

Message: Consider using @trigger.dev/anthropic for automatic retries and rate limiting

Fix action: Replace with: import { anthropic } from '@trigger.dev/anthropic'

### wait.for inside loop

Severity: WARNING

Message: wait.for in loops creates many checkpoints. Consider batching instead.

Fix action: Batch items and use fewer waits, or split into subtasks

## Collaboration

### Delegation Triggers

- redis|bullmq|traditional queue -> bullmq-specialist (Need Redis-backed queues instead of managed service)
- vercel|deployment|serverless -> vercel-deployment (Trigger.dev needs deployment config)
- database|postgres|supabase -> supabase-backend (Tasks need database access)
- openai|anthropic|ai model|llm -> llm-architect (Tasks need AI model integration)
- event-driven|event sourcing|fan out -> inngest (Need pure event-driven model)

### AI Background Processing

Skills: trigger-dev, llm-architect, nextjs-app-router, supabase-backend

Workflow:

```
1. User triggers via UI (nextjs-app-router)
2. Task queued (trigger-dev)
3. AI processing (llm-architect)
4. Results stored (supabase-backend)
```

### Webhook Processing Pipeline

Skills: trigger-dev, stripe-integration, email-systems, supabase-backend

Workflow:

```
1. Webhook received (stripe-integration)
2. Task triggered (trigger-dev)
3. Database updated (supabase-backend)
4. Notification sent (email-systems)
```

### Batch Data Processing

Skills: trigger-dev, supabase-backend, backend

Workflow:

```
1. Batch job triggered (backend)
2. Data chunked and processed (trigger-dev)
3. Results aggregated (supabase-backend)
```

### Scheduled Reports

Skills: trigger-dev, supabase-backend, email-systems

Workflow:

```
1. Cron triggers task (trigger-dev)
2. Data aggregated (supabase-backend)
3. Report generated and sent (email-systems)
```

## Related Skills

Works well with: `nextjs-app-router`, `vercel-deployment`, `ai-agents-architect`, `llm-architect`, `email-systems`, `stripe-integration`

## When to Use
- User mentions or implies: trigger.dev
- User mentions or implies: trigger dev
- User mentions or implies: background task
- User mentions or implies: ai background job
- User mentions or implies: long running task
- User mentions or implies: integration task
- User mentions or implies: scheduled task

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: trl-training
description: Train and fine-tune transformer language models using TRL (Transformers Reinforcement Learning). Supports SFT, DPO, GRPO, KTO, RLOO and Reward Model training via CLI commands.
risk: critical
source: https://github.com/huggingface/skills/tree/main/skills/trl-training
source_repo: huggingface/skills
source_type: official
date_added: 2026-07-01
license: Apache-2.0
license_source: https://github.com/huggingface/skills/blob/main/LICENSE
---

# TRL Training Skill
## When to Use

Use this skill when you need train and fine-tune transformer language models using TRL (Transformers Reinforcement Learning). Supports SFT, DPO, GRPO, KTO, RLOO and Reward Model training via CLI commands.

You are an expert at using the TRL (Transformers Reinforcement Learning) library to train and fine-tune large language models.

## Overview

TRL provides CLI commands for post-training foundation models using state-of-the-art techniques:

- **SFT** (Supervised Fine-Tuning): Fine-tune models on instruction-following or conversational datasets
- **DPO** (Direct Preference Optimization): Align models using preference data
- **GRPO** (Group Relative Policy Optimization): Train models by ranking multiple sampled outputs relative to each other and optimizing based on their comparative rewards.
- **RLOO** (Reinforce Leave One Out): Online RL training with generation-based rewards
- **Reward Model Training**: Train reward models for RLHF

TRL is built on top of Hugging Face Transformers and Accelerate, providing seamless integration with the Hugging Face ecosystem.

## Core Commands

### trl sft - Supervised Fine-Tuning

Fine-tune language models on instruction-following or conversational datasets.

**Full training:**

```bash
trl sft \
  --model_name_or_path Qwen/Qwen2-0.5B \
  --dataset_name trl-lib/Capybara \
  --learning_rate 2.0e-5 \
  --num_train_epochs 1 \
  --packing \
  --per_device_train_batch_size 2 \
  --gradient_accumulation_steps 8 \
  --eos_token '<|im_end|>' \
  --eval_strategy steps \
  --eval_steps 100 \
  --output_dir Qwen2-0.5B-SFT \
  --push_to_hub
```

**Train with LoRA adapters:**

```bash
trl sft \
  --model_name_or_path Qwen/Qwen2-0.5B \
  --dataset_name trl-lib/Capybara \
  --learning_rate 2.0e-4 \
  --num_train_epochs 1 \
  --packing \
  --per_device_train_batch_size 2 \
  --gradient_accumulation_steps 8 \
  --eos_token '<|im_end|>' \
  --eval_strategy steps \
  --eval_steps 100 \
  --use_peft \
  --lora_r 32 \
  --lora_alpha 16 \
  --output_dir Qwen2-0.5B-SFT \
  --push_to_hub
```

### trl dpo - Direct Preference Optimization

Align models using preference data (chosen/rejected pairs).

**Full training:**

```bash
trl dpo \
  --dataset_name trl-lib/ultrafeedback_binarized \
  --model_name_or_path Qwen/Qwen2-0.5B-Instruct \
  --learning_rate 5.0e-7 \
  --num_train_epochs 1 \
  --per_device_train_batch_size 2 \
  --max_steps 1000 \
  --gradient_accumulation_steps 8 \
  --eval_strategy steps \
  --eval_steps 50 \
  --output_dir Qwen2-0.5B-DPO \
  --no_remove_unused_columns
```

**Train with LoRA adapters:**

```bash
trl dpo \
  --dataset_name trl-lib/ultrafeedback_binarized \
  --model_name_or_path Qwen/Qwen2-0.5B-Instruct \
  --learning_rate 5.0e-6 \
  --num_train_epochs 1 \
  --per_device_train_batch_size 2 \
  --max_steps 1000 \
  --gradient_accumulation_steps 8 \
  --eval_strategy steps \
  --eval_steps 50 \
  --output_dir Qwen2-0.5B-DPO \
  --no_remove_unused_columns \
  --use_peft \
  --lora_r 32 \
  --lora_alpha 16
```

### trl grpo - Group Relative Policy Optimization

Train models using reward functions or LLM-as-a-judge for evaluating generations and providing rewards.

**Basic usage:**

```bash
trl grpo \
  --model_name_or_path Qwen/Qwen2.5-0.5B \
  --dataset_name trl-lib/gsm8k \
  --reward_funcs accuracy_reward \
  --output_dir Qwen2-0.5B-GRPO \
  --push_to_hub
```

### trl rloo - Reinforce Leave One Out

Online RL training where the model generates text and receives rewards based on custom criteria.

**Basic usage:**

```bash
trl rloo \
  --model_name_or_path Qwen/Qwen2.5-0.5B \
  --dataset_name trl-lib/tldr \
  --reward_model_name_or_path sentiment-analysis:nlptown/bert-base-multilingual-uncased-sentiment \
  --output_dir Qwen2-0.5B-RLOO \
  --push_to_hub
```

### trl reward - Reward Model Training

Train a reward model to score text quality for RLHF.

**Full training:**

```bash
trl reward \
  --model_name_or_path Qwen/Qwen2-0.5B-Instruct \
  --dataset_name trl-lib/ultrafeedback_binarized \
  --output_dir Qwen2-0.5B-Reward \
  --per_device_train_batch_size 8 \
  --num_train_epochs 1 \
  --learning_rate 1.0e-5 \
  --eval_strategy steps \
  --eval_steps 50 \
  --max_length 2048
```

**Train with LoRA adapters:**

```bash
trl reward \
  --model_name_or_path Qwen/Qwen2-0.5B-Instruct \
  --dataset_name trl-lib/ultrafeedback_binarized \
  --output_dir Qwen2-0.5B-Reward-LoRA \
  --per_device_train_batch_size 8 \
  --num_train_epochs 1 \
  --learning_rate 1.0e-4 \
  --eval_strategy steps \
  --eval_steps 50 \
  --max_length 2048 \
  --use_peft \
  --lora_task_type SEQ_CLS \
  --lora_r 32 \
  --lora_alpha 16
```

## Configuration Files

TRL supports YAML configuration files for reproducible training. All CLI arguments can be specified in a config file.

**Example config (sft_config.yaml):**

```yaml
model_name_or_path: Qwen/Qwen2.5-0.5B
dataset_name: trl-lib/Capybara
learning_rate: 2.0e-5
num_train_epochs: 1
per_device_train_batch_size: 8
gradient_accumulation_steps: 2
output_dir: ./sft_output
use_peft: true
lora_r: 16
lora_alpha: 16
report_to: trackio
```

**Launch with config:**

```bash
trl sft --config sft_config.yaml
```

**Override config values:**

```bash
trl sft --config sft_config.yaml --learning_rate 1.0e-5
```

## Distributed Training

TRL integrates with Accelerate for multi-GPU and multi-node training.

**Multi-GPU training:**

```bash
trl sft \
  --config sft_config.yaml \
  --num_processes 4
```

**Use predefined Accelerate configs:**

TRL provides predefined configs: `single_gpu`, `multi_gpu`, `fsdp1`, `fsdp2`, `zero1`, `zero2`, `zero3`

```bash
trl sft \
  --config sft_config.yaml \
  --accelerate_config zero2
```

**Custom Accelerate config:**

```bash
# Generate custom config
accelerate config

# Use custom config
trl sft --config sft_config.yaml --config_file ~/.cache/huggingface/accelerate/default_config.yaml
```

**Fully Sharded Data Parallel (FSDP):**

```bash
trl sft --config sft_config.yaml --accelerate_config fsdp2
```

**DeepSpeed ZeRO:**

```bash
trl sft --config sft_config.yaml --accelerate_config zero3
```

## Troubleshooting

### CUDA Out of Memory

- Reduce `--per_device_train_batch_size` and increase `--gradient_accumulation_steps`
- Enable `--use_peft` for LoRA training
- Use `--gradient_checkpointing` to save memory
- Try smaller model or longer sequence truncation

### Dataset Loading Issues

- Verify dataset exists: check Hugging Face Hub or local path
- Check dataset format matches expected columns
- Use `--dataset_config` for multi-config datasets
- Inspect dataset: `from datasets import load_dataset; ds = load_dataset(name)`

### Model Loading Issues

- Verify model exists on Hugging Face Hub
- Check if gated model requires authentication: `hf auth login`
- For local models, provide absolute path
- Ensure sufficient disk space and memory

### Slow Training

- Enable dataset `--packing` for short sequences
- Use larger `--per_device_train_batch_size` if memory allows
- Enable `--tf32` for faster computation on Ampere GPUs
- Use `--bf16` on supported hardware
- Consider multi-GPU training with `--num_processes`

### Generation Issues (GRPO/RLOO)

- Check prompt format in dataset
- Adjust `--temperature` and `--top_p` for generation
- Verify the reward function (for GRPO/RLOO)

## Additional Resources

- **Documentation**: https://huggingface.co/docs/trl
- **GitHub**: https://github.com/huggingface/trl
- **Examples**: https://github.com/huggingface/trl/tree/main/examples

## Best Practices

1. **Start with SFT**: Always fine-tune base models with SFT before preference alignment
2. **Use LoRA for efficiency**: Enable `--use_peft` for faster training and lower memory
3. **Monitor training**: Use `--report_to trackio` (or `--report_to wandb` or `--report_to tensorboard`) for tracking
4. **Save checkpoints**: TRL automatically saves checkpoints in `--output_dir`
5. **Test on small datasets first**: Verify pipeline works before full training
6. **Use configuration files**: Create YAML configs for reproducibility
7. **Leverage Accelerate**: Use multi-GPU training for faster iteration

When helping users with TRL:
- Always check which training method is appropriate for their use case
- Verify dataset format matches the expected schema
- Recommend starting with smaller models for testing
- Suggest LoRA for resource-constrained environments
- Point to specific documentation sections for advanced features

## Limitations

- Use this skill only when the task clearly matches its upstream product or API scope.
- Verify commands, API behavior, pricing, quotas, credentials, and deployment effects against current official documentation before making changes.
- Do not treat generated examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.


---
name: trpc-fullstack
description: "Build end-to-end type-safe APIs with tRPC — routers, procedures, middleware, subscriptions, and Next.js/React integration patterns."
category: framework
risk: none
source: community
date_added: "2026-03-17"
author: suhaibjanjua
tags: [typescript, trpc, api, fullstack, nextjs, react, type-safety]
tools: [claude, cursor, gemini]
---

# tRPC Full-Stack

## Overview

tRPC lets you build fully type-safe APIs without writing a schema or code-generation step. Your TypeScript types flow from the server router directly to the client — so every API call is autocompleted, validated at compile time, and refactoring-safe. Use this skill when building TypeScript monorepos, Next.js apps, or any project where the server and client share a codebase.

## When to Use This Skill

- Use when building a TypeScript full-stack app (Next.js, Remix, Express + React) where the client and server share a single repo
- Use when you want end-to-end type safety on API calls without REST/GraphQL schema overhead
- Use when adding real-time features (subscriptions) to an existing tRPC setup
- Use when designing multi-step middleware (auth, rate limiting, tenant scoping) on tRPC procedures
- Use when migrating an existing REST/GraphQL API to tRPC incrementally

## Core Concepts

### Routers and Procedures

A **router** groups related **procedures** (think: endpoints). Procedures are typed functions — `query` for reads, `mutation` for writes, `subscription` for real-time streams.

### Input Validation with Zod

All procedure inputs are validated with Zod schemas. The validated, typed input is available in the procedure handler — no manual parsing.

### Context

`context` is shared state passed to every procedure — auth session, database client, request headers, etc. It is built once per request in a context factory. **Important:** Next.js App Router and Pages Router require separate context factories because App Router handlers receive a fetch `Request`, not a Node.js `NextApiRequest`.

### Middleware

Middleware chains run before a procedure. Use them for authentication, logging, and request enrichment. They can extend the context for downstream procedures.

---

## How It Works

### Step 1: Install and Initialize

```bash
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod
```

Create the tRPC instance and reusable builders:

```typescript
// src/server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { type Context } from './context';
import { ZodError } from 'zod';

const t = initTRPC.context<Context>().create({
  errorFormatter({ shape, error }) {
    return {
      ...shape,
      data: {
        ...shape.data,
        zodError:
          error.cause instanceof ZodError ? error.cause.flatten() : null,
      },
    };
  },
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
```

### Step 2: Define Two Context Factories

Next.js App Router handlers receive a fetch `Request` (not a Node.js `NextApiRequest`), so the context
must be built differently depending on the call site. Define one factory per surface:

```typescript
// src/server/context.ts
import { type FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { auth } from '@/server/auth'; // Next-Auth v5 / your auth helper
import { db } from './db';

/**
 * Context for the HTTP handler (App Router Route Handler).
 * `opts.req` is the fetch Request — auth is resolved server-side via `auth()`.
 */
export async function createTRPCContext(opts: FetchCreateContextFnOptions) {
  const session = await auth(); // server-side auth — no req/res needed
  return { session, db, headers: opts.req.headers };
}

/**
 * Context for direct server-side callers (Server Components, RSC, cron jobs).
 * No HTTP request is involved, so we call auth() directly from the server.
 */
export async function createServerContext() {
  const session = await auth();
  return { session, db };
}

export type Context = Awaited<ReturnType<typeof createTRPCContext>>;
```

### Step 3: Build an Auth Middleware and Protected Procedure

```typescript
// src/server/trpc.ts (continued)
const enforceAuth = middleware(({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({
    ctx: {
      // Narrows type: session is non-null from here
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});

export const protectedProcedure = t.procedure.use(enforceAuth);
```

### Step 4: Create Routers

```typescript
// src/server/routers/post.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
import { TRPCError } from '@trpc/server';

export const postRouter = router({
  list: publicProcedure
    .input(
      z.object({
        limit: z.number().min(1).max(100).default(20),
        cursor: z.string().optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const posts = await ctx.db.post.findMany({
        take: input.limit + 1,
        cursor: input.cursor ? { id: input.cursor } : undefined,
        orderBy: { createdAt: 'desc' },
      });
      const nextCursor =
        posts.length > input.limit ? posts.pop()!.id : undefined;
      return { posts, nextCursor };
    }),

  byId: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const post = await ctx.db.post.findUnique({ where: { id: input.id } });
      if (!post) throw new TRPCError({ code: 'NOT_FOUND' });
      return post;
    }),

  create: protectedProcedure
    .input(
      z.object({
        title: z.string().min(1).max(200),
        body: z.string().min(1),
      })
    )
    .mutation(async ({ ctx, input }) => {
      return ctx.db.post.create({
        data: { ...input, authorId: ctx.session.user.id },
      });
    }),

  delete: protectedProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const post = await ctx.db.post.findUnique({ where: { id: input.id } });
      if (!post) throw new TRPCError({ code: 'NOT_FOUND' });
      if (post.authorId !== ctx.session.user.id)
        throw new TRPCError({ code: 'FORBIDDEN' });
      return ctx.db.post.delete({ where: { id: input.id } });
    }),
});
```

### Step 5: Compose the Root Router and Export Types

```typescript
// src/server/root.ts
import { router } from './trpc';
import { postRouter } from './routers/post';
import { userRouter } from './routers/user';

export const appRouter = router({
  post: postRouter,
  user: userRouter,
});

// Export the type for the client — never import the appRouter itself on the client
export type AppRouter = typeof appRouter;
```

### Step 6: Mount the API Handler (Next.js App Router)

The App Router handler must use `fetchRequestHandler` and the **fetch-based** context factory.
`createTRPCContext` receives `FetchCreateContextFnOptions` (with a fetch `Request`), not
a Pages Router `req/res` pair.

```typescript
// src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { type FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/root';
import { createTRPCContext } from '@/server/context';

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    // opts is FetchCreateContextFnOptions — req is the fetch Request
    createContext: (opts: FetchCreateContextFnOptions) => createTRPCContext(opts),
  });

export { handler as GET, handler as POST };
```

### Step 7: Set Up the Client (React Query)

```typescript
// src/utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@/server/root';

export const trpc = createTRPCReact<AppRouter>();
```

```typescript
// src/app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { useState } from 'react';
import { trpc } from '@/utils/trpc';

export function TRPCProvider({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: '/api/trpc',
          headers: () => ({ 'x-trpc-source': 'react' }),
        }),
      ],
    })
  );

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </trpc.Provider>
  );
}
```

---

## Examples

### Example 1: Fetching Data in a Component

```typescript
// components/PostList.tsx
'use client';
import { trpc } from '@/utils/trpc';

export function PostList() {
  const { data, isLoading, error } = trpc.post.list.useQuery({ limit: 10 });

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data?.posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
```

### Example 2: Mutation with Cache Invalidation

```typescript
'use client';
import { trpc } from '@/utils/trpc';

export function CreatePost() {
  const utils = trpc.useUtils();

  const createPost = trpc.post.create.useMutation({
    onSuccess: () => {
      // Invalidate and refetch the post list
      utils.post.list.invalidate();
    },
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    const data = new FormData(form);
    createPost.mutate({
      title: data.get('title') as string,
      body: data.get('body') as string,
    });
    form.reset();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Title" required />
      <textarea name="body" placeholder="Body" required />
      <button type="submit" disabled={createPost.isPending}>
        {createPost.isPending ? 'Creating…' : 'Create Post'}
      </button>
      {createPost.error && <p>{createPost.error.message}</p>}
    </form>
  );
}
```

### Example 3: Server-Side Caller (Server Components / SSR)

Use `createServerContext` — the dedicated server-side factory — so that `auth()` is called
correctly without needing a synthetic or empty request object:

```typescript
// app/posts/page.tsx (Next.js Server Component)
import { appRouter } from '@/server/root';
import { createCallerFactory } from '@trpc/server';
import { createServerContext } from '@/server/context';

const createCaller = createCallerFactory(appRouter);

export default async function PostsPage() {
  // Uses createServerContext — calls auth() server-side, no req/res cast needed
  const caller = createCaller(await createServerContext());
  const { posts } = await caller.post.list({ limit: 20 });

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
```

### Example 4: Real-Time Subscriptions (WebSocket)

```typescript
// server/routers/notifications.ts
import { observable } from '@trpc/server/observable';
import { EventEmitter } from 'events';

const ee = new EventEmitter();

export const notificationRouter = router({
  onNew: protectedProcedure.subscription(({ ctx }) => {
    return observable<{ message: string; at: Date }>((emit) => {
      const onNotification = (data: { message: string }) => {
        emit.next({ message: data.message, at: new Date() });
      };

      const channel = `user:${ctx.session.user.id}`;
      ee.on(channel, onNotification);
      return () => ee.off(channel, onNotification);
    });
  }),
});
```

```typescript
// Client usage — requires wsLink in the client config
trpc.notification.onNew.useSubscription(undefined, {
  onData(data) {
    toast(data.message);
  },
});
```

---

## Best Practices

- ✅ **Export only `AppRouter` type** from server code — never import `appRouter` on the client
- ✅ **Use separate context factories** — `createTRPCContext` for the HTTP handler, `createServerContext` for Server Components and callers
- ✅ **Validate all inputs with Zod** — never trust raw `input` without a schema
- ✅ **Split routers by domain** (posts, users, billing) and merge in `root.ts`
- ✅ **Extend context in middleware** rather than querying the DB multiple times per request
- ✅ **Use `utils.invalidate()`** after mutations to keep the cache fresh
- ❌ **Don't cast context with `as any`** to silence type errors — the mismatch will surface as a runtime failure when auth or session lookups return undefined
- ❌ **Don't use `createContext({} as any)`** in Server Components — use `createServerContext()` which calls `auth()` directly
- ❌ **Don't put business logic in the route handler** — keep it in the procedure or a service layer
- ❌ **Don't share the tRPC client instance globally** — create it per-provider to avoid stale closures

---

## Security & Safety Notes

- Always enforce authorization in `protectedProcedure` — never rely on client-side checks alone
- Validate all input shapes with Zod, including pagination cursors and IDs, to prevent injection via malformed inputs
- Avoid exposing internal error details to clients — use `TRPCError` with a public-safe `message` and keep stack traces server-side only
- Rate-limit public procedures using middleware to prevent abuse

---

## Common Pitfalls

- **Problem:** Auth session is `null` in protected procedures even when the user is logged in
  **Solution:** Ensure `createTRPCContext` uses the correct server-side auth call (e.g. `auth()` from Next-Auth v5) and is not receiving a Pages Router `req/res` cast via `as any` in an App Router handler

- **Problem:** Server Component caller fails for auth-dependent queries
  **Solution:** Use `createServerContext()` (the dedicated server-side factory) instead of passing an empty or synthetic object to `createContext`

- **Problem:** "Type error: AppRouter is not assignable to AnyRouter"
  **Solution:** Import `AppRouter` as a `type` import (`import type { AppRouter }`) on the client, not the full module

- **Problem:** Mutations not reflecting in the UI after success
  **Solution:** Call `utils.<router>.<procedure>.invalidate()` in `onSuccess` to trigger a refetch via React Query

- **Problem:** "Cannot find module '@trpc/server/adapters/next'" with App Router
  **Solution:** Use `@trpc/server/adapters/fetch` and `fetchRequestHandler` for the App Router; the `nextjs` adapter is for Pages Router only

- **Problem:** Subscriptions not connecting
  **Solution:** Subscriptions require `splitLink` — route subscriptions to `wsLink` and queries/mutations to `httpBatchLink`

---

## Related Skills

- `@typescript-expert` — Deep TypeScript patterns used inside tRPC routers and generic utilities
- `@react-patterns` — React hooks patterns that pair with `trpc.*.useQuery` and `useMutation`
- `@test-driven-development` — Write procedure unit tests using `createCallerFactory` without an HTTP server
- `@security-auditor` — Review tRPC middleware chains for auth bypass and input validation gaps

## Additional Resources

- [tRPC Official Docs](https://trpc.io/docs)
- [create-t3-app](https://create.t3.gg) — Production Next.js starter with tRPC wired in
- [tRPC GitHub](https://github.com/trpc/trpc)
- [TanStack Query Docs](https://tanstack.com/query/latest)

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: trust-calibrator
description: "One sentence - what this skill does and when to invoke it"
risk: safe
source: community
date_added: "2026-04-04"
---
You are a **Social Psychologist specializing in trust formation and credibility research**. Your task is to diagnose the specific trust barriers a target audience holds toward a brand, offer, or category and prescribe the exact signals needed to build credibility.

## When to Use
- Use when messaging needs the right level of certainty, proof, and claim strength for a skeptical audience.
- Use when overclaiming, underselling, or weak credibility signals are hurting conversion.

## CONTEXT GATHERING

Before calibrating trust, establish:

1. **The Target Human** - psychographic profile and skepticism level.
2. **The Objective** - what trust must unlock.
3. **The Output** - trust audit and trust-building prescription.
4. **Constraints** - category risk, history, and ethics.

If the trust problem is unclear, ask before proceeding.

## PSYCHOLOGICAL FRAMEWORK: CREDIBILITY LADDER

### Mechanism
Trust forms when the audience believes the source can deliver, will act in their interest, and will not violate expectations. Different categories require different mixes of ability, benevolence, integrity, similarity, and transparency. Calibrate each stage instead of treating trust as a single trait (Mayer trust model; Hovland source credibility; Rowley et al., 2015; Nagy et al., 2022; Bagozzi et al., 2021).

### Execution Steps

**Step 1 - Identify the trust barrier**
Name what is missing: competence, intent, proof, familiarity, or legitimacy.
*Research basis: trust formation is multi-dimensional and category-specific (Rowley et al., 2015).*

**Step 2 - Diagnose the category baseline**
Determine whether the category is naturally trusted, distrusted, or polarized.
*Research basis: category skepticism changes how much evidence is required before action (Nagy et al., 2022; Nguyen-Viet & Nguyen, 2024).*

**Step 3 - Select the trust signal**
Choose proof, transparency, credentials, endorsements, or process visibility.
*Research basis: different trust signals solve different credibility gaps (Hovland; Bagozzi et al., 2021).*

**Step 4 - Sequence the signal**
Place the signal before the highest-risk decision.
*Research basis: trust grows when the audience receives the right signal at the right point in the funnel (Rowley et al., 2015).*

**Step 5 - Check for trust repair risk**
Ensure the signal cannot be interpreted as overclaiming or manipulation.
*Research basis: skepticism and backlash intensify when messages feel defensive or exaggerated (Nguyen-Viet & Nguyen, 2024).*

## DECISION MATRIX

### Variable: trust barrier
- If competence is the barrier -> show expertise, process, and results.
- If benevolence is the barrier -> show care, support, and customer interest.
- If integrity is the barrier -> show transparency, consistency, and honesty.
- If legitimacy is the barrier -> show compliance, certification, and institutional backing.

### Variable: audience familiarity
- If unfamiliar -> use simple, low-pressure trust signals.
- If somewhat familiar -> add proof and comparisons.
- If already familiar -> reduce clutter and let evidence speak.

### Variable: category skepticism
- If high -> use more explicit proof and less flourish.
- If medium -> blend proof with narrative.
- If low -> keep trust signals minimal and clean.

## FAILURE MODES - DO NOT DO THESE

**Failure Mode 1**
- Agents typically: assume one testimonial fixes trust.
- Why it fails psychologically: trust problems are usually structural, not cosmetic.
- Instead: match the signal to the actual barrier.

**Failure Mode 2**
- Agents typically: overdo transparency in a way that feels defensive.
- Why it fails psychologically: defensive language can increase suspicion.
- Instead: be clear, calm, and bounded.

**Failure Mode 3**
- Agents typically: use trust signals out of sequence.
- Why it fails psychologically: trust must be present at the decision point.
- Instead: place signals where the risk is felt.

## ETHICAL GUARDRAILS

This skill must:
- Build trust with real evidence.
- Avoid fake intimacy and fake authority.
- Respect uncertainty when the evidence is incomplete.

The line between persuasion and manipulation is giving a person the signals they need to make an informed choice versus manufacturing a trust persona that is not real. Never cross it.

## SKILL CHAINING

Before invoking this skill, the agent should have completed:
- [ ] `@customer-psychographic-profiler`
- [ ] `@awareness-stage-mapper`

This skill's output feeds into:
- [ ] `@social-proof-architect`
- [ ] `@copywriting-psychologist`
- [ ] `@pitch-psychologist`
- [ ] `@sequence-psychologist`

## OUTPUT QUALITY CHECK

Before finalizing output, the agent asks:
- [ ] Did I identify the actual trust barrier?
- [ ] Did I choose the right trust signal?
- [ ] Did I place it at the right decision point?
- [ ] Did I avoid defensive over-explaining?
- [ ] Does the output feel credible, calm, and real?

## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.


---
name: tune-monitor
description: Analyze a Monte Carlo monitor and recommend config changes to reduce alert noise. Supports metric, custom SQL, validation, and table monitors. Fetches the report, identifies patterns, and suggests tuning.
risk: critical
source: https://github.com/monte-carlo-data/mc-agent-toolkit/tree/main/skills/tune-monitor
source_repo: monte-carlo-data/mc-agent-toolkit
source_type: community
date_added: 2026-07-01
license: Apache-2.0
license_source: https://github.com/monte-carlo-data/mc-agent-toolkit/blob/main/LICENSE
---

# Tune Monitor: Noise Reduction Analysis
## When to Use

Use this skill when you need analyze a Monte Carlo monitor and recommend config changes to reduce alert noise. Supports metric, custom SQL, validation, and table monitors. Fetches the report, identifies patterns, and suggests tuning.

You are a Monte Carlo monitor tuning agent. Your job is to fetch a monitor's report, dump it to
a file for reference, analyze the alert patterns, and recommend concrete configuration changes to
reduce noise without sacrificing real signal.

> **Monte Carlo tool routing (required):** Always call Monte Carlo MCP tools through this plugin's
> bundled server, whose fully-qualified tool names are
> `mcp__plugin_mc-agent-toolkit_monte-carlo-mcp__<tool>` (e.g.
> `mcp__plugin_mc-agent-toolkit_monte-carlo-mcp__get_alerts`). Bare tool names used in this skill
> (`get_alerts`, `search`, `get_table`, …) refer to that bundled server. If the session also has a
> separately-configured `monte-carlo-mcp` server, do **not** route to it — it may point at a
> different endpoint or credentials.

**Arguments:** $ARGUMENTS

Reference files live next to this skill file. **Use the Read tool** (not MCP resources) to access
them:

- Metric monitor tuning: `references/metric-monitor.md` (relative to this file)
- Custom SQL monitor tuning: `references/custom-sql-monitor.md` (relative to this file)
- Validation monitor tuning: `references/validation-monitor.md` (relative to this file)
- Table monitor tuning: `references/table-monitor.md` (relative to this file)

---

## Prerequisites

- **Required:** Monte Carlo MCP server (`monte-carlo-mcp`) must be configured and authenticated

---

## Available MCP tools

| Tool | Purpose |
|---|---|
| `get_monitor_report` | Fetch a monitor's alert history, incident details, and troubleshooting summaries |
| `get_monitors` | Fetch monitor configuration (type, thresholds, schedule, segments) |
| `create_or_update_metric_monitor` | Update a metric monitor in place (pass `monitor_uuid`; used in Phase 5) |
| `create_or_update_sql_monitor` | Update a custom SQL monitor in place (pass `monitor_uuid`; used in Phase 5) |
| `create_or_update_validation_monitor` | Update a validation monitor in place (pass `monitor_uuid`; used in Phase 5) |
| `create_or_update_table_monitor_asset_rule` | Tune freshness / volume change / unchanged size for a single table; pick the per-metric variant via `rule_type` (`last_updated_on` / `total_row_count` / `total_row_count_last_changed_on`). One call per `(table, metric)` pair (used in Phase 5). |

All three `create_or_update_*_monitor` tools follow a **two-call preview-then-confirm pattern**: the first call (with the default `dry_run=True`) returns the rendered MaC YAML for review in `result.yaml`; the second call (`dry_run=False`) deploys the change live and returns a deep link in `result.instructions`. **Always pass `monitor_uuid=<uuid>`** on both calls so the tool updates the existing monitor in place rather than creating a new one.

---

## Phase 0: Validate Input

Extract the monitor UUID from `$ARGUMENTS`. It must be a valid UUID (format:
`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`).

If no UUID is provided or it doesn't look like a UUID, stop and tell the user:

> Please provide a monitor UUID. Example: `/tune-monitor 94c2dd3a-ef49-40f8-b1c1-741ba057cabf`

---

## Phase 1: Fetch Monitor Report

Call `get_monitor_report` with:
- `monitor_uuid`: the UUID from `$ARGUMENTS`
- `max_incidents`: 50

If the tool returns an error or empty result, tell the user the monitor was not found and stop.

Also fetch the monitor's full config via `get_monitors` with:
- `monitor_ids`: [`{monitor_uuid}`]
- `include_fields`: [`config`]

Run both calls in parallel.

---

## Phase 1.5: Determine Monitor Type and Load Reference

From the `get_monitors` config response, determine the monitor type:

| Config indicator | Type | Reference file |
|---|---|---|
| Monitor type is a metric monitor variant (e.g., metric, field health) | Metric | `references/metric-monitor.md` |
| Monitor type is a custom SQL rule / custom monitor | Custom SQL | `references/custom-sql-monitor.md` |
| Monitor type is a validation rule / validation monitor | Validation | `references/validation-monitor.md` |
| Monitor type is a table monitor (freshness, volume, schema across tables) | Table | `references/table-monitor.md` |

**Read** the appropriate reference file using the Read tool with the path relative to this skill
file. The reference contains type-specific config fields to extract, recommendation guidance, and
apply-changes instructions.

If the monitor type is not metric, custom SQL, validation, or table, stop and tell the user:

> This skill supports tuning metric, custom SQL, validation, and table monitors. This monitor
> is a {type} monitor, which is not supported.

---

## Phase 2: Analyze the Report

Analyze the monitor report and config together. Focus on:

### 2a. Alert volume & frequency
- How many incidents in the last 30 days? Last 7 days?
- What is the firing cadence — multiple times per day? Daily? Sporadic?
- Are incidents clustered in time (bursts) or spread evenly?

### 2b. Anomaly patterns
- Which segments (field values) are firing most? Are they the same segments repeatedly?
- Are anomalies consistently marginal (just above threshold) or severe?
- Are any anomalies from sparse/bursty event types that naturally spike?
- Are anomalies caused by known operational events (deployments, batch jobs, bulk user actions)?
- For validation monitors: how many invalid rows per incident? Is the count stable or growing?
- For table monitors: which (table, metric) pairs are firing most? Are they the same repeatedly?

### 2c. Current configuration
Extract the current configuration. The specific fields to look for are documented in the per-type
reference loaded in Phase 1.5. At minimum, extract:
- Monitor type and what it measures
- Schedule interval
- Audiences / notification channels
- Whether the monitor uses ML thresholds or explicit thresholds

### 2d. Troubleshooting analysis (if available)
Look at any troubleshooting TL;DRs in the report. Note:
- Are most anomalies assessed as "likely normal data variation"?
- Are there recurring root causes?
- Is there a blind spot (e.g., no upstream metadata)?

---

## Phase 3: Generate Recommendations

Based on the analysis, produce a prioritized list of recommendations. For each recommendation:
- State the **problem** it solves
- Give the **specific config change** (use exact field names from the MC config schema)
- Explain the **trade-off** (what signal might be lost)

### General recommendations (all monitor types)

#### Sensitivity tuning (ML thresholds only)
This applies to any monitor that uses ML thresholds — both metric monitors and custom SQL monitors.
Skip this section for validation monitors (they don't use ML thresholds), for table monitors
(they have their own per-metric sensitivity — see the table monitor reference), and for monitors
with explicit thresholds (for custom SQL monitors, see threshold adjustment in the per-type
reference instead).

- If anomalies are consistently marginal (observed value just barely above threshold) AND assessed
  as normal variation → recommend lowering sensitivity one step:
  - If current sensitivity is `HIGH` → recommend `"sensitivity": "medium"`
  - If current sensitivity is `MEDIUM` or `AUTO` → recommend `"sensitivity": "low"`
- If current sensitivity is already `LOW` and still noisy → note this isn't a sensitivity issue

#### Schedule / interval
- If the monitor fires multiple times per day but anomalies always resolve within hours → recommend
  increasing schedule interval (e.g., from 720 min to 1440 min) to reduce duplicate alerts
- If anomalies are caused by data arriving late → recommend increasing `collection_lag`

#### Snooze / training period
- If the monitor was recently created (<30 days) and is still learning patterns → recommend
  waiting for the model to stabilize before tuning

#### Audience / notification routing
- If the monitor has no audiences configured and is generating noise → recommend adding audiences
  only for high-severity anomalies, or removing notifications entirely for known-noisy monitors

### Type-specific recommendations

For type-specific recommendations (WHERE conditions, segment exclusion, aggregation changes,
threshold adjustment, SQL modifications, alert condition modifications, per-table-metric
sensitivity tuning), follow the guidance in the per-type reference loaded in Phase 1.5.

---

## Phase 4: Present the Report

Output a structured analysis. **This is the primary output — include it in full.**

```markdown
## Monitor Tune Report: {monitor_uuid}

**Monitor:** {display_name or mac_name}
**Type:** {monitor type — metric, custom SQL, validation, or table}
**Table:** {table}
**What it monitors:** {metric and segments, SQL query summary, validation conditions, or table/metric coverage}
**Current sensitivity:** {sensitivity or "AUTO (default)" or "N/A (explicit thresholds)"}
**Schedule:** every {interval_minutes / 60}h

### Alert Summary (last 30 days)
- Total alerts: {count}
- Firing frequency: {e.g., "~twice daily", "daily", "sporadic"}
- Most noisy segments: {top 2-3 segment values by alert count, or N/A for custom SQL/validation}
- Most noisy (table, metric) pairs: {for table monitors: top pairs by anomaly count}

### Root Cause Pattern
{1-3 sentence summary of what the alerts represent — operational events, bursty data, model
miscalibration, genuine issues, etc.}

### Recommendations

#### 1. {Highest-impact change} [RECOMMENDED]
**Problem:** ...
**Change:**
```yaml
{specific config field}: {new value}
```
**Trade-off:** ...

#### 2. {Second change} [OPTIONAL]
...

#### 3. {Third change} [OPTIONAL]
...

### What NOT to change
{Any configurations that look correct and should be left alone — avoid over-tuning.}

### If these changes are made
{Predict the expected outcome: estimated alert reduction, what genuine anomalies would still fire.}
```

**Next step:** "Want me to apply any of these changes to the monitor config, or explore the alert
history further?"

---

## Phase 5: Apply Changes (if user requests)

To apply changes, follow the apply-changes instructions in the per-type reference loaded in
Phase 1.5. Each reference specifies the correct tool and constraints for that monitor type.

General rules for all types:
1. **Always preview first** — show the user what will change before applying.
2. **Get explicit confirmation** before applying any change.
3. **Validate the preview YAML against the schema** — before presenting the preview YAML to the user, fetch the published MaC JSON Schema from `https://clidocs.getmontecarlo.com/mac/schema.json` (WebFetch) and check the preview YAML against it. If any field in the YAML does not appear in the schema for the given monitor type, flag it and correct it. Note: the schema validates field names, types, and enum values only — cross-field semantic constraints are enforced by the backend at apply time, not by the schema.
4. **MaC-managed monitors** — if `get_monitors` returns a `mac_name` or the user mentions the monitor is managed via a MaC YAML file, note this before applying: changes made via the API will be overwritten the next time `montecarlo monitors apply` runs. Offer to hand off to `/manage-mac` (edit workflow) instead so the YAML file stays the source of truth.

---

## Guidelines

- **Be specific.** Generic advice like "reduce sensitivity" is less useful than exact config changes.
- **Prefer surgical changes.** A targeted WHERE condition beats a blunt sensitivity reduction.
- **Preserve signal.** Always explain what genuine anomalies would still be caught after tuning.
- **Cite evidence.** Reference specific incident dates, segment values, and counts from the report.
- **Degrade gracefully.** If troubleshooting runs are missing, note the limited context and
  reason from alert patterns alone.
- **Add `$schema` when saving YAML to a file.** If the user asks to save the MaC YAML to a file, add `# yaml-language-server: $schema=https://clidocs.getmontecarlo.com/mac/schema.json` as the first line of that file.

## Limitations

- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.