jarvvski/jjfx/jjfx-dispatch
Claude Code and agent skill by jarvvski/jjfx
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Dispatch work to background agents with jjfx. Use when the user wants to schedule or hand tickets to a Worker pool, run several agents in parallel on separate tasks, or check on and steer agents already running.
Install
npx vskill@latest install jarvvski/jjfx/jjfx-dispatchSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
jjfx dispatch
jjfx runs background agents as Workers. A Worker is one reusable slot backed by its own jj workspace (a colocated Git worktree) running a single coding agent — Claude, Codex, or opencode. A Ticket is the unit of work, and Dispatch assigns tickets to idle Workers. You drive all of it from the jjfx CLI.
jjfx help <command> is the command surface: it prints a command's usage, arguments and flags, derived from the same definitions the parser validates against, so the two cannot drift. Take the flag surface from there — jjfx help dispatch, jjfx help pool list, jjfx help send — rather than transcribing it here. This skill carries the judgement a help page cannot: when to dispatch, what a flag means for how you drive the pool, and how to read the state back.
That surface is only as good as the binary answering it, so check which one you are driving. command -v jjfx names the binary and jjfx version the build; a stale jjfx earlier on PATH answers jjfx help <command> with an older flag set (and refuses a command it does not have), which makes the promise above false against it. Pin the binary you mean - run it by its path, or install the version you are driving - and re-check jjfx version after any upgrade.
Every step ends on a checkable condition, and every condition is read from a command's --json output rather than from its prose — so the same command decides the same way on every run.
`PLAYBOOK.md`, beside this file, is the working guide: the loop as it actually runs, the traps a real drive hits and the workaround for each, and what jjfx should be doing for you rather than asking you to remember. Read it before the first dispatch.
Before dispatching
Settle four things first, so a dispatch does not fail on setup:
- Repository —
jjfx rootprints the repo jjfx creates Workers beside. Run from inside it. - Colocation —
jjfx colocation status. Colocated Workers carry a working.git, whichgh stackand other Git tools need. If it reports not colocated, runjjfx colocation enable. - Runtime —
jjfx pool profile <runtime> [--provider P --model M]sets the agent every Worker runs.claude,codex, andopencodeare supported; provider and model are optional where the runtime picks them itself. Naming a runtime provisions the empty Pool document when the Pool does not exist yet, so this is safe beforejjfx pool <N>- either order works, and neither one creates capacity. - Lifecycle hooks —
jjfx hooks status. The hooks are how each runtime reports needs-input, quota-blocked and subagent progress; without them a Worker waiting on you still readsbusy, which is exactly the state you cannot tell from a working one. Claude Code and Codex integrate through hook entries in their own config, but opencode integrates through a plugin jjfx writes to~/.config/opencode/plugins/jjfx-lifecycle.ts, so an opencode runtime with no plugin installed reports nothing at all andjjfx hooks statusnames the gap (opencode: missing: lifecycle plugin).jjfx hooks installwrites them into each agent's own config, so run it when the user asks and otherwise report which integrations are missing.
Done when jjfx root resolves, colocation is enabled, the profile names the runtime the user wants, and you have reported the hook state.
Steps
1. Give the pool capacity
jjfx pool <N> is a safe shrink: it removes only idle Workers. jjfx status is an alias for jjfx pool list.
Capacity is how many Runs may be in flight at once, not how many Tickets the pool may ever run: a Worker whose Run finishes is returned to the pool automatically, so a pool of N serves a backlog larger than N without you reclaiming anything between rounds.
Done when the pool holds the capacity you want and at least one Worker reads "status": "idle".
2. Decide the tickets
Name tickets directly, or let jjfx discover them:
- Direct —
jjfx dispatch <TICKET>...takes the ids you supply. - Discovered —
jjfx dispatch --all [--label <label>]takes every ready ticket from the configured source.
The source is [tickets] source in jjfx's user config, ${XDG_CONFIG_HOME:-~/.config}/jjfx/config.toml:
source = "linear"(the default) queries Linear for tickets intickets.linear.status(defaultTodo) carrying the--labellabel (defaultready-for-agent). Discovery runs a short-lived read-only query through the pool runtime, so the runtime must be able to reach Linear.source = "local"reads the repository's own markdown tickets instead:tickets.local.root(default.scratch) holding<feature>/issues/*.mdwhoseStatus:istickets.local.ready_status(defaultready-for-agent). No tracker and no runtime reachability, so this is the deterministic choice when the repository keeps its work in.scratch/.
Done when you hold the exact ticket ids to dispatch.
3. Dispatch
Dispatch runs in the background by default, and jjfx help dispatch lists every flag. The one flag whose meaning a help page cannot carry is --force, in the open-Workspace rule below.
Read what a Dispatch did from its --json: one object per Ticket in the order you gave them, including the Tickets it did not dispatch. outcome is dispatched, failed, waiting or orchestrating; worker names the Worker that took it (null when none did); phase, pid and detail carry the failure phase, the background process id, and the prose detail. A dispatch --all that finds nothing carries the reason as a skipped entry with a null ticket and the label and root it searched, so an empty batch is never a bare [] you cannot explain. The objects go to stdout and the human prose to stderr, so jjfx dispatch --json | jq reads a batch result without parsing sentences.
jjfx will not dispatch into a Workspace you have open in a terminal. A Worker whose tab is open is not idle capacity, so jjfx skips it (implicit selection) or refuses it (an explicit Worker). When jjfx cannot see the terminal at all - a headless or off-kitty run - it cannot tell which Workspaces are open and fails closed, printing a message that names --force. Add --force to dispatch anyway. An agent running inside the terminal jjfx drives (or with terminal.listen_on configured) sees the open tabs, so it needs no flag and will correctly leave your open Workspaces alone.
A ticket that has sub-issues is a Parent: jjfx orchestrates the sub-issues as a Dispatch Group in dependency order and reports each one's progress, rather than handing the Parent itself to one Worker.
Before launching, jjfx fetches each Ticket's title and description through the configured source and inlines them in the Worker's prompt, so a Worker can implement without tracker tools; if the source cannot be read, Dispatch fails before anything is reserved.
Done when every Ticket you named has an object in the JSON: dispatched or orchestrating naming what took it, or waiting / failed naming why. A ticket with no free Worker reports the capacity gap rather than waiting silently, and a [spend] cap_usd or [dispatch] max_concurrent can hold one back deliberately - its object reads "outcome": "waiting", and jjfx status --json carries the held set in its pool-level waiting array. A finished Worker is returned to the pool on the next Dispatch, so a waiting Ticket is one a Run is genuinely running on, not one a done Worker is sitting on: run the next round once the Runs finish.
Pace the backlog from the repository, not the user's config. To bound how much you fan out, set [dispatch] max_concurrent in the repository config, <repo>/.jjfx/config.toml - never in ${XDG_CONFIG_HOME:-~/.config}/jjfx/config.toml, which would change every repository the user drives:
The repository ceiling may only tighten the user-global one, never widen it: with both set the smaller binds, and with no user-global cap a repository ceiling is the only limit. jjfx status --json reports the resolved limit as governor.limit, so the binding ceiling is visible where you read the pool. This is the deliberate way to hold work back - the same waiting outcome a full Pool gives, but by your choice rather than the pool's size.
4. Watch
The output is an object: workers is one entry per Worker across every pool, each naming its pool; waiting lists the Tickets a capped Dispatch held back, each with the pool that held it (empty when nothing was held). Poll it and read each Worker's status; done and failed are the only terminal values, and done means its Run finished with its changes sitting in its workspace. exit_code and error say why a Worker failed, and stuck is true when the Run has made no progress inside the watchdog window. A Repository with several pools reports a per-pool subtotal under pools; filter one lane with jq '.workers[] | select(.pool == "bugs")'.
jjfx logs <worker> follows that Worker's current Run until it ends, printing each activity line as it appears; it is a reading aid, not a state source — read state from jjfx status --json. Where the activity lives depends on how the Run was delivered, and the banner says which: a Run with a client process writes a structured log, log_file names that file, and logs follows it (Following opencode log for <worker>); an opencode Run delivered through its session — the common case when the daemon is reachable — writes no process log at all, so logs reads the session's messages instead (Following opencode session <id> for <worker>) and log_file stays empty. When the daemon cannot be reached for a session-delivered Run, logs says there is no process log and names the session rather than following an empty file.
Done when every dispatched Worker reads done or failed.
5. Steer
send obeys the same rule as Dispatch: it refuses a Worker whose Workspace is open, and fails closed when jjfx cannot see the terminal (add --force). review is exempt - it is how you tell a waiting Worker to address its pull request - so it works even while its Workspace is open. review reads the PR's unresolved review threads through gh, sends each pass's findings to the Worker, and stops on [review] max_passes or on a stalled round; only a pass that finds nothing is success, and any other stop escalates and exits non-zero.
Done when the follow-up Run finishes and the Worker reads idle or done again.
6. Land
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/jjfx-dispatch/SKILL.md
Popularity
- Repository stars
- 71
- 7-day trend score
- 0
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Extension points
hookPre/post lifecycle hooks
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/jarvvski/jjfx/jjfx-dispatch)