Build your own
Write a registered agent that auto-appears in bun run munchkins --help alongside the defaults. The default bundle is just three small files using the public framework — there is no hidden surface.
Scaffold with the new-munchkin skill
If you're inside Claude Code, the new-munchkin skill is the path. It scaffolds a new agent — or revises an existing one — by walking you through a short sequential interview: purpose, distinctness from agents already in the repo, archetype (single-step vs. main+refactor vs. main+refactor+tests), kebab-case slug, and the prompt body. It introspects your repo first, so generated files use your shared presets, your CI gate commands, and the language and package manager you already use. Nothing is hardcoded against the munchkins monorepo.
Trigger it from Claude Code with /new-munchkin, or with phrases like "new munchkin", "add a default agent", "scaffold a munchkin agent", or "design an agent for this repo". To revise an existing agent, name it: "edit the X munchkin", "tweak X's prompt", "demote X to a single-step agent".
Create-mode produces the agent's <name>-agent.ts (fully wired against AgentBuilder and the discovered shared presets), its prompts/<name>.md, the side-effect import line in your bundle's entry, and an AGENTS.md row. After it's done, the agent shows up in bun run munchkins --help and runs identically to the defaults. See packages/munchkins/skills/new-munchkin/SKILL.md in this repo for the full workflow, including pre-flight, mode pick (create vs. edit), and verification.
What you're building
The rest of this page is the manual path. Use it to understand what the skill is doing, or when you want to do something the skill doesn't cover.
A custom agent is a TypeScript file that constructs an AgentBuilder, attaches steps and a deterministic gate, and calls registry.register(builder). A side-effect import from your bundle's entry pulls it into the CLI. After that:
…just works. --help lists it; --dry-run describes it; resume, daemon, and the launch-munchkin skill all treat it like a default.
File layout
The convention the default agents and the new-munchkin skill assume:
Then in src/index.ts:
The side-effect import is what causes registry.register() to fire. Without it the agent is invisible.
AgentBuilder full surface
Every method on AgentBuilder is part of the public API. Construct one, chain whatever you need, register the result.
Example: a single-step agent
OptionSchema
Every CLI flag has a schema. Most agents only ever declare one flag, userMessage, indirectly via withUserMessageFromOption.
The CLI auto-converts camelCase option names to kebab-case flags: userMessage becomes --user-message. string[] flags become repeatable variadic args (--target a b c). Booleans are presence-only (no value). String defaults are wired into commander's option default; required strings use requiredOption.
Prompt full surface
A Prompt is a system prompt + a user prompt fragment list. The system can be one or more files; the user prompt can mix literal text and option-driven fragments.
Path-vs-literal resolution
Both system paths and option-driven user-message fragments resolve identically:
- Absolute paths are read from disk.
- Relative paths are joined to
repoRoot(resolved at the call site). - For option-driven fragments, if the value resolves to an existing file path, the file's contents are used; otherwise the value itself is the prompt.
That last rule is what lets --user-message="Fix add() in src/math.ts" work alongside --user-message=./scratch/bug.md with no schema change.
The __MUNCHKINS_OPT_<name> env channel
Options reach the agent via environment variables prefixed with __MUNCHKINS_OPT_. The CLI sets them right before calling builder.run(); Prompt.resolve() reads them at prompt-construction time. The exported constant is OPTION_ENV_PREFIX. You only need to know about this if you're wrapping the framework — most authors never touch the env channel directly.
Registration
Call it once per agent at module top level. The registry rejects duplicate names. The registry export is a singleton — every package importing @serranolabs.io/munchkins-core shares it, which is why a side-effect import is enough to wire your agent into --help.
If you're testing or composing agents and need to overwrite an existing registration, registry.replace(builder) swaps without throwing. Use sparingly.
Sandboxes
The default sandbox is gitWorktreeSandbox(). It cuts a fresh .worktrees/<agent>-<ts>-<uuid> checkout from repoRoot, exposes the resulting cwd to every step, and on success removes the directory and deletes the branch. On failure, it preserves both for inspection.
Three environment variables are injected into every agent step:
WORKTREE— absolute path to the worktree.BRANCH— current branch name (the agent renames it toagent/<slug>-<short-id>before steps start).REPO_ROOT— absolute path to the repo root the worktree was cut from.
Use these in your prompts to point the model at the right place: Commit on $BRANCH with a message that names ….
For advanced cases, SandboxFactory is the interface to implement:
A rehydrate implementation is required for munchkins resume to work against your sandbox. The shipped gitWorktreeSandbox() implements both.
Integration strategies
The agent's branch has to land somewhere. Strategy resolution order: operator's --integrate flag → author's .integrate(strategy) declaration → run-layer default (integrateMerge).
integrateMerge() rebases the worktree branch onto the base branch (with up to 3 merge-fixer iterations on conflicts) and fast-forwards the base branch.
integratePR() does the same rebase, then git push -u <remote> <branch> and opens a PR via gh (GitHub) or glab (GitLab). Provider defaults to "auto", which calls detectProvider(repoRoot, remote) — that returns "gitlab" if the remote URL contains gitlab and "github" otherwise. Override with provider: "github" | "gitlab".
The PR's title is the summary writer's commit message; its body is the markdown changelog entry. The PR URL is returned in the IntegrationResult and printed in the PASS line.
Deterministic checks + fixer
The default bundle ships three reusable presets out of packages/munchkins/agents/_shared/presets.ts:
DEFAULT_CHECKS is exported as readonly; spread it ([...DEFAULT_CHECKS]) when handing it to addDeterministic, which expects a mutable array. Override the loop's max iterations or fixer prompt by passing your own:
defaultSummaryWriter() returns a Prompt that prepends agent-guidelines.md to summary-writer.md. Replace it with your own when you want a different output format — but bear in mind that parseSummaryWriterJson expects a specific JSON envelope. The shipped writer prompt produces it; a custom writer must too.
Composition with .thenRun()
thenRun() concatenates two builders into a new one and strips the sandbox, summary writer, and integration. The caller must reattach them. Reference example: packages/munchkins/agents/bugfix-then-refactor/bugfix-then-refactor-agent.ts:
Why strip the three concerns? Because composing two agents with two summary writers, two sandboxes, or two integration strategies is almost always a bug. The caller knows which one matters; the framework refuses to guess.
thenRun() does not mutate either input builder. The original a and b are still usable. getStepCount(), getSandbox(), getSummaryWriter(), and getIntegration() are read-only accessors useful for tests asserting non-mutation.
Scheduling
Attach .cron(spec, { userMessage, verbosity }) to your builder, then start the daemon:
The daemon collects every cronned builder in the registry, prints a startup table (next firing time per agent), and arms one timer per agent. When a tick fires, the daemon resets --verbose / --thinking, sets --user-message from the cron config, and calls builder.run(). Each builder reschedules itself after its run completes.
Overlap policy: one timer per builder. If a tick fires while the previous run is still in flight, both will execute concurrently. Keep cron specs loose enough that one tick finishes before the next.
Distributing skills with your bundle
Drop a skills/<name>/SKILL.md into your bundle and ship it. Users in any host repo install it into their .claude/skills directory:
By default this copies every skill bundled with @serranolabs.io/munchkins (or your package, if you wrap your own CLI) into .claude/skills in the current working directory. Override the destination:
The shipped skills include launch-munchkin (delegate work to a background agent from inside Claude Code) and new-munchkin (scaffold a new agent in a host repo). See packages/munchkins/skills/launch-munchkin/SKILL.md and packages/munchkins/skills/new-munchkin/SKILL.md for what each one does. After installing, the skills auto-appear in Claude Code's skill list — trigger them with phrases like "launch a refactor agent on …" or "new munchkin".
Worked example
A custom dep-bump agent that takes an inline target version, runs a single Claude step, and ships.
packages/<your-bundle>/agents/dep-bump/dep-bump-agent.ts:
packages/<your-bundle>/agents/dep-bump/prompts/dep-bump.md:
Side-effect import in packages/<your-bundle>/src/index.ts:
Invoke:
That's the whole agent. ~30 lines of TypeScript, ~12 lines of system prompt, one import line. Everything else — the sandbox, the deterministic gate with retries, the summary writer, the changelog, the resume support, the merge integration — is inherited from the framework.