Short-lived agents. One task. Then gone.
Ephemeral single-purpose workers for Cursor, Claude, Codex, OpenCode, and friends.
mayfly hatch examples/fix-test.json --worktree /path/to/repo
# → validate → buckets worktree → spawn harness → watch done_when / TTL → tear down → reportNot a teammate. Not a persona. A disposable specialist with a hard lifespan.
Current release: v0.1.6 · repo nixpt/mayfly
Long-lived agent sessions expand scope, linger, and multiply. mayfly is the opposite contract:
| rule | meaning |
|---|---|
| One purpose | exactly one task + one done_when |
| Hard lifespan | TTL + budget; auto-teardown |
| Vague asks die at the hatch | validate rejects open-ended tasks |
| Workers can't hatch others | no recursive spawn |
| Harness-agnostic | same schema; adapters per runner |
For durable fleet work, use horses + agent-launch.
For "fix this one thing and vanish", use mayfly.
cargo install --git https://github.com/nixpt/mayfly --tag v0.1.6
# or from a checkout:
cargo install --path .
# binary: mayflyNot yet installed on nixps (s463): nothing puts mayfly on PATH there.
Needs host harness binaries on PATH for the runners you use (cursor-agent, claude, codex, …). Optional: buckets for --worktree.
| Field | claude / ccf | codex / cxf | opencode | copilot | cursor / cece / exec |
|---|---|---|---|---|---|
model |
--model |
-m |
-m (beats MAYFLY_OPENCODE_MODEL) |
--model |
refused |
read_only: true |
--tools Read,Grep,Glob --permission-mode dontAsk --strict-mcp-config, no skip-permissions |
--sandbox read-only |
refused | --deny-tool write/shell/url + --disable-builtin-mcps |
refused |
tools |
--tools <builtins> --allowedTools <all> --permission-mode dontAsk --strict-mcp-config, no skip-permissions |
refused | refused | translated to --allow-tool rules (below) |
refused |
mcp_servers |
hatch-local --mcp-config from ~/.claude.json + --strict-mcp-config (requires tools) |
refused | refused | refused | refused |
budget.max_usd |
--max-budget-usd |
refused | refused | refused | refused |
budget.max_turns |
not enforced (no CLI turn cap); validate prints a note |
same | same | same | same |
copilot tools (MAYFLY-16). Rules are translated into Copilot permission rules, never broadened
silently: Bash → shell, Bash(cmd) / Bash(prefix:*) → shell(cmd) / shell(prefix:*);
Edit → write; Edit(//abs/file) → write(/abs/file) and Edit(/file) → write(<cwd>/file), with
--add-dir for a directory outside the project; bare Read/Grep/Glob/LS need no rule. Globs, path-scoped
reads, bare relative Edit(file) (Copilot would match that name in any directory), MCP tools and any other
tool are refused. With no Bash rule the shell is denied outright. With one, validate notes that Copilot also
auto-approves its built-in read-only commands (ls, cat, pwd, git log, gh pr list, …): writes, network and
other commands stay denied (live-verified, Copilot CLI 1.0.98).
tools and read_only are mutually exclusive. mcp_servers without tools is refused. An
mcp__<server>__* tool whose server is not listed in mcp_servers is refused. Unknown
mcp_servers names (missing from the user's Claude mcpServers) fail validate.
"Refused" means validate/hatch exit 2 before anything is created. A knob that would be silently ignored
is worse than a refusal: the caller would believe a cheap, read-only, capped run happened.
A read-only hatch can't write files, so its done_when must not require writes. Its answer is the harness's
stdout (stdout_tail in the report).
Success requires done_when to pass (and the harness to exit 0). MAYFLY_DONE printed without a passing
done_when is recorded as a failed claim, not a success. For exec, the exit code must equal expect_exit.
A failed hatch never exits 0, even when the harness did.
In a repo that has adopted .jagent/ (the fleet's v2 layout), mayfly keeps both halves with the project:
| What | Where | Committed? |
|---|---|---|
| Runners: named partial tasks of defaults (harness, model, read_only, ttl, budget, constraints, aging) | <repo>/.jagent/agents/mayfly/<name>.json |
yes (list mayfly/*.json under [commit] in .jagent/agents/.manifest) |
Hatch records (list/status/expire) |
<repo>/.jagent/local/mayfly/ |
no (gitignored .jagent/local/) |
mayfly init-runners # scaffold read.json (claude, haiku, read_only, 10m, $0.25) + README; never overwrites
mayfly runners # name harness model read_only ttl
mayfly hatch task.json --runner read # runner defaults merged under the task
mayfly validate task.json --runner readMerge rule: the task's fields win, and nested objects (budget, constraints, aging) merge key by key.
Arrays and scalars are replaced. task and done_when must come from the task; a runner that sets them is
rejected. An unknown --runner exits 2 and lists the known ones.
State dir precedence: --state-dir > MAYFLY_STATE_DIR > <main checkout>/.jagent/local/mayfly (when the
task's cwd, or the caller's for list/status, is inside a repo whose main checkout has .jagent/; linked
worktrees resolve to their main checkout) > ~/.local/state/mayfly. mayfly warns if .jagent/local/ isn't
gitignored. It never creates .jagent/ in a repo that hasn't adopted it: runner commands exit 2 there, and
state falls back to home. .jagent/state/ is reserved for squadron and never used.
# Check a task without spawning
mayfly validate examples/fix-test.json
# Hatch (dry-run prints the plan; omit --dry-run to launch)
mayfly hatch examples/fix-test.json --dry-run
# Hatch into a throwaway git worktree (public nixpt/buckets — on PATH)
mayfly hatch examples/fix-test.json --worktree /path/to/repo
mayfly hatch examples/fix-test.json --worktree . --branch mayfly/demo --keep-worktree
# Watch / force end
mayfly status <id>
mayfly expire <id>
mayfly list--worktree shells out to buckets worktree create/remove (no crates.io path-dep).
Flame/firefly are intentionally not used for hatch cwd — different layer.
{
"task": "Fix the failing test in foo::bar::test_baz",
"done_when": {
"type": "command",
"run": "cargo test -p foo bar::test_baz -- --exact",
"expect_exit": 0
},
"harness": "cursor",
"cwd": ".",
"ttl": "15m"
}See DESIGN.md for the full contract (aging ladder, adapters, fuzziness gate).
| id | adapter | notes |
|---|---|---|
cursor |
cursor-agent -p --yolo --trust |
smoke OK @ v0.1.x |
ccf |
same argv as claude |
needs fleet ccf/flownet Anthropic env — smoke OK |
cxf |
codex --profile flownet exec … |
needs FLOWNET_TOKEN_CODEX — smoke OK |
opencode |
opencode run --auto --dir … |
set MAYFLY_OPENCODE_MODEL (e.g. opencode/big-pickle) — smoke OK |
copilot |
copilot -p … -s --allow-all (or permission rules, above) |
GitHub Copilot CLI (npm i -g @github/copilot); auth via GH_TOKEN/COPILOT_GITHUB_TOKEN or copilot login; each prompt uses the account's premium requests. In a Codespace, SSH sessions lack GITHUB_TOKEN: set -a; . /workspaces/.codespaces/shared/.env; set +a; export GH_TOKEN=$GITHUB_TOKEN — live OK (4 hatches, below) |
claude |
claude -p … --dangerously-skip-permissions |
needs Anthropic or ccf env |
codex |
codex exec --sandbox workspace-write … |
raw Codex; prefer cxf on this fleet |
cece |
cece-rs -w … -p … --afk |
fleet binary; subject to provider budget |
exec |
sh -c <done_when> |
no LLM — smoke OK |
Fleet wrappers ccf / cxf are shell functions; export their env (or run under a login zsh that defines them) before mayfly hatch with harness ccf/cxf.
Optional smoke (needs jq + harness on PATH):
cargo build --release
MAYFLY_BIN=./target/release/mayfly ./scripts/smoke-harness.sh cursor
MAYFLY_BIN=./target/release/mayfly ./scripts/smoke-harness.sh cxf # FLOWNET_TOKEN_CODEX
MAYFLY_BIN=./target/release/mayfly ./scripts/smoke-harness.sh ccf # ANTHROPIC_* flownet env
MAYFLY_OPENCODE_MODEL=opencode/big-pickle \
MAYFLY_BIN=./target/release/mayfly ./scripts/smoke-harness.sh opencodeMissing harness binaries error clearly (harness '…' binary … not found on PATH) instead of hanging.
From a dispatched horse or any shell with buckets + mayfly on PATH:
mayfly hatch /path/to/task.json --worktree "$REPO"
# provisions sibling worktree via buckets, runs harness, tears down with --forcePrefer mechanical done_when + TTL ≤ 2h. Durable multi-step work stays on horses/agent-launch.
Ready-made tasks under examples/fleet/ for repetitive fleet chores (Haiku,
curated tools — no --dangerously-skip-permissions). All three verified live (2026-10-08).
| File | Role | Mode |
|---|---|---|
mailer.json |
Unread INBOX → digest file (+ drafts only when the task names recipients) | tools + mcp_servers: ["mailgate"]: accounts/list/read/search/draft + Edit(//…digest); no mail_send, no Bash |
inbox-digest.json |
Unread bridge DMs → digest file, grouped by sender, duplicates collapsed | tools: Bash(…/squad-msg --list:*) + Edit(//…digest); run with AGENT_NAME=<agent> |
pr-survey.json |
Open PRs + CI across named nixpt/* repos → report file |
tools: Bash(gh pr list/view/checks:*) + Edit(//…report) |
pr-reviewer-copilot.json |
Review one PR (edit the number) → review file with verdict, problems with file:line evidence, verified claims | copilot tools: Bash(gh pr view/diff:*) + Read/Grep/Glob + Edit(//…review) |
Writing a fleet task:
- File rules are
Edit(//abs/path):Write(...)rules never match, and/xin a rule is project-root-relative. - A
Bash(prefix:*)rule matches one simple command. Tell the agent to run each command alone (no loops, pipes,&&,;, redirects); a compound command is denied unless every part matches. done_whenmust still fail before the agent starts.hatchrefuses one that already passes (a stale output file,command: true): it would end the hatch at the first poll, before the agent did anything.
mkdir -p /build/tmp/mayfly
mayfly hatch examples/fleet/mailer.json # → /build/tmp/mayfly/mailer-digest.md
AGENT_NAME=foreman mayfly hatch examples/fleet/inbox-digest.json # → /build/tmp/mayfly/inbox-digest.md
mayfly hatch examples/fleet/pr-survey.json # → /build/tmp/mayfly/pr-survey.mdDelete the previous output first; a leftover file makes done_when pass before the agent starts.
foreman / horse → multi-step, memory, merge
mayfly → throwaway *agents* (harness + TTL)
agent-launch → resource-controlled durable dispatch
buckets → throwaway *runtimes* + *worktrees* (mayfly uses worktree)
flame / firefly → workspace OS (heat/brands/fuel) — held, not wired
Self-contained: no path deps on peer projects.
| path | role |
|---|---|
.dejavue/ |
architectural memory — dejavue context boot packet |
.jagent/ |
planning board — MAYFLY-NN tickets in .jagent/planning/ |
scripts/bump-version.sh |
conventional-commit bumps on push to main (see .github/workflows/release.yml) |
The project logo, monochrome mark, and social preview are in assets/. See assets/README.md for usage and palette details.
Licensed under either of
- Apache License, Version 2.0 (
LICENSE-APACHEor http://www.apache.org/licenses/LICENSE-2.0) - MIT license (
LICENSE-MITor http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.