triflux 의 Claude Code mods: HUD band 와 서브에이전트 effort 정책

<img alt="triflux" src="docs/assets/logo-dark.svg" width="200">
<h3 align="center">CLI-first multi-model orchestration for Claude Code, Codex, and Antigravity</h3>
<a href="https://www.npmjs.com/package/triflux"><img src="https://img.shields.io/npm/v/triflux?style=flat-square&color=FFAF00&label=npm" alt="npm version"></a> <a href="https://www.npmjs.com/package/triflux"><img src="https://img.shields.io/npm/dm/triflux?style=flat-square&color=F5C242" alt="npm downloads"></a> <a href="https://github.com/tellang/triflux/stargazers"><img src="https://img.shields.io/github/stars/tellang/triflux?style=flat-square&color=FFAF00" alt="GitHub stars"></a> <img src="https://img.shields.io/badge/node-%3E%3D22-374151?style=flat-square" alt="Node >= 22"> <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-374151?style=flat-square" alt="License: MIT"></a>
triflux is a Claude Code plugin and npm CLI that routes coding work across Claude, Codex, and Antigravity. You describe the task once with /tfx-auto; triflux picks the CLI lane (Codex by default), runs it through managed routes instead of ad-hoc shell commands, and can fan the work out to parallel workers, live Claude↔Codex sessions, or remote hosts. Parallel code changes use separate worktrees and one session per worktree. The tfx shell CLI covers setup, diagnostics, and team orchestration.
npm install -g triflux # postinstall runs the setup script
tfx doctor # check CLIs, tmux, MCP, profiles, skills
npm 12 and newer block install scripts by default. Allow the setup script:
npm i -g triflux --allow-scripts=triflux
Setup registers the triflux marketplace and prints a mods installation hint. To install usage bands and subagent effort enforcement (Claude Code 2.1.287 or newer):
tfx setup --mods
Or install the Claude Code plugin from this repository's marketplace:
claude plugin marketplace add tellang/triflux
claude plugin install triflux@triflux
tfx doctor --fix repairs common drift; tfx doctor --json is for automation.
/tfx-auto "fix the failing auth tests"
/tfx-auto "review this change for trust-boundary issues" --mode consensus
/tfx-auto "finish the migration and verify it" --mode deep --retry ralph --max-iterations 10
Skills you invoke directly:
| Skill | Use |
|---|---|
/tfx-auto | Front door for implementing, fixing, reviewing, and parallel work. Behavior is set by flags (below). |
/tfx-live | Live Claude↔Codex sessions: start/ask/wait/stop, peer relay, list-sessions. |
/tfx-lead | Lead role for running several Claude/Codex sessions: roles and models, briefs, cross review, merge and release coordination. |
/tfx-remote | Remote Claude Code sessions over SSH: start, list, reattach, send, readiness probe, monitor, stop. |
/tfx-setup | Setup: file sync, HUD, Codex and Antigravity profiles, MCP. tfx setup --mods installs mods. |
/tfx-doctor | Diagnose and repair. |
/tfx-ship | triflux release flow (maintainers). |
/tfx-wt | Windows Terminal tabs and panes. tfx setup installs it on Windows only. |
Internal lanes (marked internal: true; other skills and the router call them, but you can name them explicitly):
| Skill | Use |
|---|---|
tfx-harness | Meta routing: returns which skill or path fits a request, without running it. |
tfx-review | Code review verdict (--quick for a lighter pass). |
tfx-research | Web research with cross-checked sources (--quick, --auto, --depth). |
Use superpowers writing-plans for implementation plans, host deep-interview for requirements, and Claude Code’s built-in /goal for goals. Use /tfx-auto --mode deep for multi-model planning and execution. Manage Codex profiles directly in ~/.codex/<profile>.config.toml.
/tfx-auto flags| Flag | Values | Effect |
|---|---|---|
--mode | quick (default), deep, consensus, live | deep = plan → execute → verify loop; consensus = multi-CLI agreement; live = hand off to tfx-live peer |
--shape | consensus, debate, panel | Output shape for --mode consensus |
--cli | auto, codex, antigravity, claude | Force a CLI lane |
--cli-set | triad, no-antigravity, custom | Consensus participants |
--parallel | 1, N | N = local workers (tfx multi) |
--retry | 0, 1 (default), ralph, auto-escalate | ralph = retry state machine with stuck detection; auto-escalate = move up the model chain |
--max-iterations | N | Cap for ralph / auto-escalate (0 = unlimited) |
--rounds | N (default 4) | Round trips for --mode live |
--skill | <name> | Prepend skills/<name>/SKILL.md to the Codex/Antigravity prompt |
--no-native-bridge-ui | Hide headless workers from the claude agents panel |
The full contract, including --lead, --options, --experts, and conflict rules, lives in skills/tfx-auto/SKILL.md.
Codex runs through named profiles; model IDs live in ~/.codex/<profile>.config.toml, and the role → profile map lives in scripts/lib/agent-route-policy.mjs. Claude is called by alias, so the newest model in each tier is picked up automatically.
| Profile / alias | Lanes | |
|---|---|---|
gpt6_astra_xhigh | Architecture, planning, critique, debugging, security review, deep execution | |
gpt6_astra_max, gpt6_astra_ultra | Hardest single tasks (`TFX_CODEX_PROFILE=max\ | ultra); max is the first auto-escalate` step |
gpt61_sol_high / gpt61_sol_med | Default implementation, review, verification, tests, docs / cleanup | |
gpt6_luna_high / gpt6_luna_low | Build fixes, writing / latency-first lookups | |
Claude fable | Final --retry auto-escalate step | |
Claude opus / sonnet / haiku | Meta routing and planning gates / Claude-native QA and verification / fast exploration |
Routing policy: .claude/rules/tfx-routing.md · escalation chain: .claude/rules/tfx-escalation-chain.md.
| Command | Use |
|---|---|
tfx setup / tfx doctor | Sync files, HUD, MCP, profiles / diagnose and repair (--fix, --json) |
tfx multi | Local multi-CLI team in tmux |
tfx mcp | Managed MCP registry: list, sync, add, remove |
bash ~/.claude/scripts/tfx-route.sh code-reviewer "<instruction>" | Send review to Codex (codex exec review via policy) |
tfx list, tfx update, tfx version | Installed skills, update, version |
tfx-live | Live session bridge (same as the /tfx-live skill) |
tfx <command> --help prints the exact arguments.
Live sessions. tfx-live drives Claude Code and Codex TUI sessions. Claude daemon targets (--short/--session-id) try UDS first and fall back to tmux when --session is also given; Codex ask queues the message through the app-server thread/queue/add API (shown in the TUI with a [from <sender>] first line) and falls back to tmux with a reported reason; UDS stays available with --transport uds --thread <id|auto>. peer relays between two sessions for --rounds. triflux's Codex hook records running Codex sessions under ~/.local/state/triflux/codex-sessions/, so tfx-live list-sessions --cli codex|claude also finds sessions you started yourself in tmux.
Long jobs. scripts/tfx-route.sh --async <agent> "<prompt>" returns a job id at once, which gets past Claude Code's 600-second Bash limit. Follow up with --job-status, --job-wait, and --job-result.
Headless workers. Workers from tfx-auto and tfx multi appear in the claude agents panel unless you pass --no-native-bridge-ui. Press Enter on a row to open the tmux pane the worker runs in.
Retry and escalation. --retry ralph loops until done or stuck (three identical failures). --retry auto-escalate steps from Codex gpt6_astra_max to Claude fable; override the chain in .triflux/config/escalation-chain.json.
Machine profile. Setup records which CLIs this machine may use and its timeout policy in ~/.config/triflux/machine-profile.env. TFX_DISABLE_CODEX=1 or TFX_DISABLE_ANTIGRAVITY=1 removes a CLI from routing; if no allowed CLI is available the route fails instead of silently falling back. Details: .claude/rules/tfx-machine-profile.md.
Remote hosts. /tfx-remote reads hosts from ~/.config/triflux/hosts.json (Windows: %APPDATA%\triflux\hosts.json). Start a session with remote-spawn.mjs --host <host> --prompt "<request>". Options: skills/tfx-remote/SKILL.md.
graph TD
User([Claude Code prompt / shell]) --> Skills["/tfx-auto · /tfx-live · /tfx-remote"]
User --> Lead["/tfx-lead"]
User --> CLI[tfx CLI]
Lead -->|"briefs, cross review, merge"| Live
Skills --> Route[tfx-route.sh]
Skills --> Live[tfx-live]
CLI --> Team["tfx multi"]
Route --> Codex[Codex CLI]
Route --> Agy[Antigravity agy]
Route --> Claude[Claude Code]
Team -->|headless workers| Route
Team --> Index[("results index: tfx-headless/*.results.json")]
Team --> Rows["claude agents rows"]
Rows -->|Enter| Room["worker tmux room"]
Live -->|"codex queue, tmux fallback"| CodexTUI[Codex TUI sessions]
Live -->|"UDS or tmux"| ClaudeTUI[Claude Code sessions]
Route --> HUD[HUD]
Package layout and execution paths: ARCHITECTURE.md. Documentation map: docs/README.md.
| Platform | Multiplexer | Notes |
|---|---|---|
| macOS | tmux | Default path. Install coreutils (gtimeout) if you keep a positive hard ceiling. |
| Linux | tmux | Supported. |
| Windows | psmux + Windows Terminal | See below. |
Windows. psmux (a tmux fork) runs PowerShell by default. Callers do not run wt.exe or raw psmux kill-session directly; tabs and panes go through the tfx-wt skill (set up only on Windows) and hub/team/wt-manager.mjs. Agent rules: .claude/rules/tfx-psmux.md.
| Layer | Protection |
|---|---|
| Managed routes | Codex and Antigravity are called through tfx-route.sh, headless workers, or tfx, never through a bare codex exec or agy. This is a rule for callers; no hook enforces it. |
| MCP registry | Replaces stale or unsupported MCP entries with managed ones. |
| Consensus | Deep and consensus runs report degraded or disputed results instead of hiding them. |
Node 22 or newer. See CONTRIBUTING.md for tests, lint, package boundaries, mirror and release checks, and state snapshots. Releases are automated: merging a version-bump commit to main runs release.yml after CI passes; it tags, creates the GitHub release, and publishes to npm through OIDC Trusted Publishing. Decisions are recorded in docs/adr/.
<sub>MIT License · Made by <a href="https://github.com/tellang">tellang</a></sub>
hooks/register.ts 10 lines1import type { Register } from 'claude-code'
2
3import { registerAgentEffort } from './agent-effort.ts'
4import { registerHudBand } from './hud-band.tsx'
5
6export const register: Register = on => {
7 registerHudBand(on)
8 registerAgentEffort(on)
9}
10hooks/agent-effort.ts 61 lines1import type { On } from 'claude-code'
2
3type Effort = 'low' | 'medium' | 'high'
4
5// effort 없이 부른 서브에이전트는 부모 effort 를 물려받는다(2.1.292 실측). 탐색은 낮게, 판정은 높게 고정한다.
6const EFFORT_BY_AGENT: Record<string, Effort> = {
7 Explore: 'low',
8 'oh-my-claudecode:explore': 'low',
9 'general-purpose': 'medium',
10 'oh-my-claudecode:executor': 'medium',
11 'oh-my-claudecode:test-engineer': 'medium',
12 'oh-my-claudecode:writer': 'medium',
13 Plan: 'high',
14 'oh-my-claudecode:architect': 'high',
15 'oh-my-claudecode:analyst': 'high',
16 'oh-my-claudecode:critic': 'high',
17 'oh-my-claudecode:planner': 'high',
18 'oh-my-claudecode:code-reviewer': 'high',
19 'oh-my-claudecode:security-reviewer': 'high',
20 'oh-my-claudecode:verifier': 'high',
21 'oh-my-claudecode:debugger': 'high',
22 'oh-my-claudecode:tracer': 'high',
23}
24
25export function registerAgentEffort(on: On) {
26 const explicitCalls = new Set<string>()
27 const effortByAgentId = new Map<string, Effort>()
28
29 on('tool.call', { tool: 'Agent' }, async ($, e, next) => {
30 if (!e.effort) return next(e)
31 explicitCalls.add(e.tool_use_id)
32 try {
33 return await next(e)
34 } finally {
35 explicitCalls.delete(e.tool_use_id)
36 }
37 })
38
39 on('agent.spawn', async ($, e, next) => {
40 const spawned = await next(e)
41 const effort = EFFORT_BY_AGENT[e.subagentType]
42 // 호출자가 Agent 도구에 effort 를 직접 적었으면 그 값을 따른다.
43 if (spawned.agentId && effort && !e.fork && !explicitCalls.has(e.tool_use_id))
44 effortByAgentId.set(spawned.agentId, effort)
45 return spawned
46 })
47
48 // /clear 는 session.start 없이 새 세션으로 넘어가므로 여기서 비운다.
49 on('session.end', ($, e, next) => {
50 explicitCalls.clear()
51 effortByAgentId.clear()
52 return next(e)
53 })
54
55 on('turn.step', async function* ($, e, next) {
56 const effort = e.agentId ? effortByAgentId.get(e.agentId) : undefined
57 // effort 가 없는 모델(Haiku 등)은 건드리지 않는다.
58 return yield* next(effort && e.effort !== undefined ? { ...e, effort } : e)
59 })
60}
61hooks/hud-band.tsx 76 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, On } from 'claude-code'
3
4import type { UsageSnapshot, UsageWindow } from '../types'
5
6const usage = atom({ plugin: 'triflux-mods', key: 'usage' } as const, null)
7
8const WINDOW_LABEL: Record<string, string> = { five_hour: '5h', seven_day: '1w', spend_limit: '$' }
9
10// 선택 기능이라 실패를 삼킨다. hook 오류가 쌓이면 mod 전체가 꺼진다.
11async function refreshUsage($: EngineInterface) {
12 try {
13 const { rateLimits, context, cost } = await $.session.usage()
14 const snapshot: UsageSnapshot = {
15 windows: rateLimits.map(({ kind, percentUsed, resetsAt }) => ({ kind, percentUsed, resetsAt })),
16 contextPercent: context.percent ?? null,
17 costUsd: cost?.usd ?? null,
18 }
19 await update($, usage, () => snapshot)
20 // band 가 실제로 그려질 때만 statusLine HUD 가 Claude 행을 뺀다(hud/hud-qos-status.mjs).
21 const home = await $.env.get('HOME')
22 if (home && snapshot.windows.length > 0)
23 await $.fs.write(`${home}/.claude/cache/triflux/claude-band/${await $.session.id()}`, String(await $.clock.now()))
24 } catch {}
25}
26
27function formatRemaining(resetsAt: string | undefined, now: number) {
28 if (!resetsAt) return ''
29 const minutes = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60000))
30 const days = Math.floor(minutes / 1440)
31 const hours = Math.floor((minutes % 1440) / 60)
32 return days > 0 ? `${days}d${String(hours).padStart(2, '0')}h` : `${hours}h${String(minutes % 60).padStart(2, '0')}m`
33}
34
35function percentColor(percent: number) {
36 return percent >= 80 ? 'error' : percent >= 50 ? 'warning' : undefined
37}
38
39export function registerHudBand(on: On) {
40 on('session.start', async ($, e, next) => {
41 await refreshUsage($)
42 return next(e)
43 })
44
45 // 응답이 끝날 때마다 rate limit 과 비용이 바뀐다.
46 on('turn.complete', async ($, e, next) => {
47 const result = await next(e)
48 await refreshUsage($)
49 return result
50 })
51
52 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
53 const snapshot = await read($, usage)
54 if (e.props.hasSurvey || !snapshot || snapshot.windows.length === 0) return next(e)
55
56 const { Box, Text } = $.ui.resolve(e)
57 const now = await $.clock.now()
58 const windowText = (w: UsageWindow) =>
59 `${WINDOW_LABEL[w.kind] ?? w.kind} ${Math.round(w.percentUsed)}% ${formatRemaining(w.resetsAt, now)}`.trimEnd()
60
61 return (
62 <Box>
63 <Text dimColor>c </Text>
64 {snapshot.windows.map((w, i) => (
65 <Text color={percentColor(w.percentUsed)} dimColor={!percentColor(w.percentUsed)}>
66 {i > 0 ? ' · ' : ''}
67 {windowText(w)}
68 </Text>
69 ))}
70 {snapshot.contextPercent !== null && <Text dimColor> · ctx {snapshot.contextPercent}%</Text>}
71 {snapshot.costUsd !== null && <Text dimColor> · ${snapshot.costUsd.toFixed(2)}</Text>}
72 </Box>
73 )
74 })
75}
76types/index.d.ts 14 lines1export type UsageWindow = { kind: string; percentUsed: number; resetsAt?: string }
2
3export type UsageSnapshot = {
4 windows: UsageWindow[]
5 contextPercent: number | null
6 costUsd: number | null
7}
8
9declare module 'claude-code' {
10 interface PluginState {
11 'triflux-mods': { usage: UsageSnapshot | null }
12 }
13}
14