Skip to main content

CLI

mecha [GLOBAL OPTIONS] <COMMAND> [ARGS]

mecha --help lists the commands; mecha <command> --help prints the full flag set for one. mecha --version prints the build.

Global options

These are declared once and accepted by every subcommand, including the ones that never build an agent (where they are simply ignored).

FlagDescription
-p, --provider <PROVIDER>Provider to use, by config key. Defaults to the config's default_provider.
-m, --model <MODEL>Model id, overriding the provider's default.
-e, --effort <EFFORT>Reasoning depth: low, medium, high, xhigh, max.
-s, --system <SYSTEM>System prompt. Use @path to read it from a file.
-w, --workspace <WORKSPACE>Directory the agent may read and write. Defaults to the working directory.
-y, --yesApprove every tool call without asking. Required for unattended runs that need to write or execute.
--read-onlyRefuse anything that is not read-only. Conflicts with --yes.
--max-turns <N>Stop after this many model turns.
--max-output-tokens <N>Stop once the run has generated this many output tokens.
--max-cost <USD>Stop once the run has cost this much. Needs prices configured on the provider.
--tool <NAME>Only expose these tools. Repeatable; names are matched exactly.
--no-mcpSkip MCP servers entirely.
--no-mcp-server <NAME>Skip these MCP servers by name. Repeatable; for turning one off while the rest stay.
--no-thinkingTurn off reasoning. Cheaper and faster, but noticeably worse on multi-step work.
--no-learned-rulesDo not inject learned rules from ~/.mecha/learning into the system prompt.
--no-hooksDo not run configured [[hook]] commands. Config is still validated.
--no-outboxDo not route any tools through the outbox; configured [outbox] tools execute directly.
--no-fallbackNever fall back to another provider. A transient failure that survives its retries fails the run.
--compact-at <N>Summarise older turns once the prompt passes this many tokens.
-v, --verbosePrint tool calls, results, and token usage as they happen.
-h, --helpPrint help.
-V, --versionPrint the version.

MECHA_LOG controls internal tracing, which goes to stderr and is independent of --verbose:

MECHA_LOG=debug mecha run "what changed today?"
MECHA_LOG=mecha_core::mcp=trace mecha tools # one module only

The default filter is warn.

run

Run one task and print the answer.

mecha run [OPTIONS] [PROMPT]
FlagDescription
[PROMPT]The task. Omit it, or pass -, to read from stdin.
--jsonEmit a single JSON object instead of prose. Implies --quiet.
--quietPrint only the answer — no tool narration.
--no-streamWait for the whole answer instead of streaming it.
--resume <ID>Continue a saved session by id or unique prefix.
--no-sessionDo not write a transcript.

Exit codes: 0 success, 1 error, 2 the model refused, 3 it ran out of turns.

Approval prompts are only offered when stdin is a terminal and --json was not passed; otherwise the configured permission_mode decides.

mecha run "summarize what changed in this repo today"

# Unattended, bounded, machine-readable.
mecha run --json --yes --max-cost 0.25 \
-w /srv/reports "regenerate the weekly summary in reports/weekly.md"

# Piped in, continuing an earlier session.
git log --oneline -20 | mecha run - --resume 01hq9

chat

Interactive session in the terminal, with history and slash commands.

mecha chat [OPTIONS]
FlagDescription
--resume <ID>Continue a saved session by id or unique prefix.
--no-sessionDo not write a transcript.

Slash commands: /tools, /model, /usage, /clear, /session, /help, /exit (also /quit, /q). /clear starts a new conversation, dropping its taint along with its messages. Ctrl-D exits.

mecha chat -p local -m qwen3-14b

tui

Full-screen session. The input line stays live while the agent works, so a message typed mid-run steers it instead of waiting for it.

mecha tui [OPTIONS]
FlagDescription
--resume <ID>Continue a saved session by id or unique prefix.
--no-sessionDo not write a transcript.

Slash commands:

CommandDescription
/helpThe list.
/toolsTools this agent can call.
/triggersScheduled prompts: see, edit, run, cancel.
/model [id]Show or switch the model.
/provider [name]Show or switch the provider.
/mode [ask|allow|read-only]Show or switch the permission mode.
/mcp [on|off]List MCP servers, or turn them all off and on.
/mcp <server> [on|off]Turn one server off and on.
/usageTokens used this session.
/clearStart a new conversation, dropping its taint.
/sessionWhere the transcript is being written.
/todoShow or hide the live task pane.
/exitQuit.

The status line shows context use as a fraction of the window when [providers.X] context_window is configured. Steering is a property of this front-end: it needs one owner of stdin, which a readline REPL cannot be while a run is streaming.

mecha tui -w ~/Github/mecha

batch

Run the same agent over a JSONL file of prompts, with bounded concurrency.

mecha batch [OPTIONS] <INPUT>
FlagDescription
<INPUT>JSONL input. Each line is {"id": "...", "prompt": "...", "meta": {...}}, or a bare string used as both id and prompt. - reads stdin.
-o, --out <OUT>Where to write results, one JSON object per line. Defaults to stdout.
-c, --concurrency <N>How many items to run at once. Default 4.
--limit <N>Stop after this many items. Useful for a smoke test over a big file.

Results stream to the output file as they finish, keyed by id, so a killed run still leaves everything completed so far on disk. Each item gets a fresh conversation, and therefore fresh taint.

# items.jsonl
# {"id": "q1", "prompt": "who did I meet with last week?", "meta": {"gold": "..."}}

mecha batch items.jsonl --concurrency 8 --out results.jsonl --yes
mecha batch items.jsonl --limit 3 -c 1 -v # smoke test first

eval

Score a model on a case set, grading the tool-call trace first and the text second.

mecha eval [OPTIONS] [CASES]
FlagDescription
[CASES]JSONL case file. Default eval/cases.jsonl.
--fixture <PATH>Workspace the agent reads during the run. Defaults to a workspace directory beside the case file.
-o, --out <OUT>Write the full scorecard and per-case detail here as JSON.
-c, --concurrency <N>How many cases to run at once. Default 4.
-k, --runs <K>Run every case K times and report pass^k beside pass@k. Default 1.
--tag <TAG>Run only cases carrying this tag. Repeatable.
--failuresShow every failed check, not just the count.
--judge-model <MODEL>Model that grades expect.judge rubrics. Defaults to the model under test.
--judge-provider <PROVIDER>Provider entry the judge model runs on. Defaults to the one under test.
--keep-workspacesKeep the staged workspaces of sandboxed cases instead of deleting them.
--mcpConnect MCP servers during the eval. Off by default for reproducibility.
--mcp-file <PATH>Connect exactly the servers named in this TOML file, instead of the machine's config.
--no-ask-userWithhold ask_user, which is otherwise part of the tool surface.
--ab-rulesRun the set twice — rules-free, then with this machine's learned rules — and report the per-case flips.
--compare <FILES>...Compare previously written scorecards side by side instead of running.

mecha eval exits non-zero when anything fails, so it works as a regression gate. It forces MCP off, hooks off, learned rules off, the outbox off and fallback off, so a scorecard grades the model it names rather than this machine's local setup.

--runs k matters more than it looks: reliability decays much faster than mean success, and the gap between pass^k and pass@k is the model's unreliability. A pinned seed at --concurrency 1 replays token-for-token, making the k samples one sample counted k times; the harness warns when it detects that.

--mcp-file resolves relative paths in a server's command/args against the file's own directory, and a server that fails to connect is fatal here.

mecha eval -p local -m qwen3-moe -o results/qwen.json
mecha eval -p anthropic -o results/opus5.json
mecha eval --compare results/*.json

mecha eval --tag chaining --failures # one slice, with reasons
mecha eval -k 5 -o results/qwen-k5.json # pass^5 beside pass@5
mecha eval eval/pkg-cases.jsonl --mcp-file eval/mcp.toml

tools

List the tools an agent would see. Runs without any provider configured, which makes it a good MCP-server smoke test.

mecha tools [OPTIONS]
FlagDescription
--schemaPrint the full JSON schema for each tool, exactly as the model sees it.
--jsonEmit JSON instead of a table.

The output names the active sandbox backend, and --json includes each tool's capabilities. Subagent profiles are shown with the tools they were granted, with a warning when a profile holds all three legs of the trifecta.

mecha tools
mecha tools --json | jq '.[] | select(.capabilities.external_send)'
mecha tools --schema --no-mcp

sessions

Inspect saved transcripts. Requires a subcommand.

mecha sessions <list|show|path|stats> [OPTIONS]
SubcommandFlagDescription
list-n, --limit <N>How many to show. Default 20.
show<ID>Session id or unique prefix.
show--jsonEmit the raw JSONL records instead of formatted text.
path<ID>Print the path to a session file.
stats--days <N>Only sessions started in the last N days.
stats--jsonEmit JSON instead of a table.

stats totals token usage — and cost, where prices are configured — grouped by provider and model. Transcripts live in ~/.mecha/sessions unless MECHA_SESSION_DIR says otherwise.

mecha sessions list -n 50
mecha sessions show 01hq9 --json | jq -r 'select(.role == "user") | .content'
mecha sessions stats --days 30
cat "$(mecha sessions path 01hq9)"

replay

Re-run a recorded session against its recorded tool results and report where the model diverged.

mecha replay [OPTIONS] <SESSION>
FlagDescription
<SESSION>Session id, unique prefix, or a path to a transcript file.
--on-divergence <stop|error|live>What to do when the replay departs from the recording. Default stop.
--jsonEmit the report as JSON instead of prose.

stop ends the run at the divergence, because after one every later recorded result answers a question nobody asked. error does the same and exits non-zero on any divergence, argument spellings included — use it in CI. live abandons the recording and keeps going against the real tools.

mecha replay 01hq9
mecha replay 01hq9 --on-divergence error --json > replay.json # CI gate
mecha replay ~/.mecha/sessions/01hq9....jsonl -m qwen3-14b -p local

outbox

Review, edit, release, or reject staged outbound actions. list is the default subcommand.

mecha outbox [list|show|edit|review|send|reject] [ARGS]
SubcommandFlagDescription
listList staged items, grouped by kind.
list--kind <KIND>Only message or only publish.
list--via <VIA>Only items staged by a tool whose name contains this.
show<ID>The exact arguments a release would execute, its provenance, and the edit diff if there is one.
edit<ID>Open the item's arguments in $EDITOR. What you save is what send executes.
review[IDS]...Walk items one at a time, deciding each. Ids, or unique prefixes; several is fine.
review--allEvery pending item, subject to the filters.
review--kind <KIND>Only message or only publish.
send<ID>Execute the item's tool call, for real, and mark it sent.
send-y, --yesSkip the confirmation shown for items drafted in a tainted conversation.
reject<ID>Refuse an item. It stays on file as the record of the refusal.
reject--reason <REASON>Why — recorded on the item for the next reader.

edit rewrites the arguments only; the original draft is kept, and the pair is what mecha reflect mines into writing-domain reflections. send holds the store's lock across execution so two sends cannot double-fire.

An item's kind decides how it is reviewed, not how it was staged. A publish shows the rendered page rather than the arguments, and refuses edit — see Publishing.

mecha outbox
mecha outbox show 3f2a
mecha outbox edit 3f2a && mecha outbox send 3f2a
mecha outbox review --all --kind message
mecha outbox reject 3f2a --reason "wrong recipient"

work

What runs have generated, and removing what is past. Every producer — a trigger, a chat, a session — writes into its own directory, which is also the path jail its runs get. list is the default subcommand.

mecha work [list|path|clean] [ARGS]
SubcommandFlagDescription
listWhat each producer has generated, with entry counts, size, and the newest entry.
path<PRODUCER>Print one producer's directory, creating it if absent. For cd $(mecha work path x).
clean--keep <N>How many entries survive per producer. Defaults to [work] keep (10).
clean--producer <NAME>Only this producer.
clean--dry-runSay what would go, and remove nothing.

clean never removes anything a published bundle names as a source, and says which entries survived for that reason. The producer directory itself is never removed.

mecha work
mecha work path briefing
mecha work clean --dry-run
mecha work clean --producer briefing --keep 3

See The work directory.

frontdoor

Requests that arrived through the public surface, and the quarantine they pass through before any run with tools is told about them. list is the default subcommand.

mecha frontdoor [list|show|extract|next] [ARGS]
SubcommandFlagDescription
listWhat has arrived, and what state each request is in.
list--state <STATE>Only this state: drained, extracted, extraction_failed, …
show<SEQ>One request in full, including the prose a stranger wrote.
extractRun the quarantined extraction over everything not yet extracted.
extract--seq <SEQ>Just this one.
extract--forceRe-extract records that already have an extraction.
next--limit <N>What a triage run may be told, as JSON — extractions only, never prose. Default 5.

show is the one place the original text is printed, and a terminal is where that is safe. next is what a triage trigger pipes into a prompt; it is structurally incapable of including the words a stranger typed.

Draining is deliberately not here — mecha-factory-publish drain holds the key and speaks the protocol.

mecha frontdoor
mecha frontdoor list --state extraction_failed
mecha frontdoor show 42
mecha frontdoor extract
mecha frontdoor next --limit 3

See The front door.

trigger

Prompts that run on a schedule. list is the default subcommand.

mecha trigger [list|add|show|edit|rm|enable|disable|next|run|tick|daemon|cancel|runs] [ARGS]
SubcommandFlagDescription
listTriggers, when each next fires, and how the last run went.
add<NAME>Lowercase letters, digits, - and _. It is the filename.
add--schedule <CRON>Five-field cron, or @daily/@hourly/@weekly. Required.
add--prompt <PROMPT>What to ask. @path reads it from a file. Required.
add--description <TEXT>One line, shown under the trigger in list.
add--timezone <IANA>Defaults to [agent] timezone, and is written into the file either way.
add--timeout <DUR>Wall-clock ceiling on one run. 20m by default.
add--catch-up <SPEC>always (default), never, or a duration like 2h.
add--notify <CMD>Command run with the answer on stdin.
add--disabledCreate it switched off.
add--forceOverwrite an existing trigger of the same name.
show<NAME>The trigger's settings and its recent runs.
show--lastPrint the last run's answer, read back from its session transcript.
edit<NAME>Open the trigger's file in $EDITOR.
rm<NAME>Delete a trigger. Its ledger rows stay as the record.
enable<NAME>Let a trigger fire again.
disable<NAME>Stop it firing without deleting it or losing its history.
next[NAME], -n, --count <N>Upcoming fire times, without running anything. Default 5.
run<NAME>Run one trigger now, whatever its schedule says.
tick--dry-runSay what would fire, and fire nothing.
daemonTick once a minute until stopped.
cancel<NAME>Stop the run in flight, if there is one.
runs[NAME], -n, --count <N>The run ledger, newest first. Default 20.

The schedule is five fields — minute hour day-of-month month day-of-week. Seconds are not a field: 0 7 * * * is 7am.

tick is the primitive and daemon is a loop over it, so a crontab line or a systemd timer reaches the same answer: being due is a function of the ledger and the clock. Missed slots collapse — a machine off for a week owes one run of each trigger, not a week's worth — and --catch-up decides whether a stale slot still runs, with skips written to the ledger.

trigger run is recorded with no slot, so a test run at noon does not cancel tomorrow's 07:00. trigger cancel stops the run at its next safe point, keeping the partial answer, and works even when the run is inside the daemon's process.

Triggers are read-only unless the file says otherwise; --yes at add time is what writes allow. Outbox staging still works under read-only, because staging executes nothing. Definitions live in ~/.mecha/triggers/<name>.toml and a trigger run reads ~/.mecha/config.toml only, never a mecha.toml from the directory it starts in.

mecha trigger add briefing \
--schedule '0 7 * * 1-5' \
--prompt "Summarise anything in my inbox that needs an answer today, and what's on my calendar." \
--catch-up 3h --notify 'notify-send "mecha briefing"'

mecha trigger next # when everything fires next
mecha trigger tick --dry-run # what would fire, and why
mecha trigger run briefing # fire now, without consuming the scheduled slot
mecha trigger show briefing --last # the answer it produced
mecha trigger daemon # or point a systemd timer at `mecha trigger tick`

reflect

Mine recorded sessions for user interventions — a mid-run steer, a denied tool call, a corrective follow-up — and turn each into one reflection.

mecha reflect [OPTIONS]
FlagDescription
--sessions-dir <DIR>Directory of session transcripts. Defaults to the standard location.
--dry-runList what would be mined without calling a model or writing anything.
--limit <N>Mine at most this many sessions this run.

Reflections are appended to reflections.jsonl in the learning store, each carrying the session id that proves it and an Origin classified from the transcript's recorded taint. A session whose reflections fail is left unmined for a later run to retry rather than marked and silently lost.

mecha reflect --dry-run
mecha reflect -p local --limit 20

learn

Absorb unprocessed reflections into the consolidated learned rule set.

mecha learn [OPTIONS]
FlagDescription
--min <N>Only run when a domain has at least this many unprocessed reflections. Default 3.
--holdout <F>Hold out this fraction of unprocessed reflections from the pass. Default 0.
--proposeStage the result as a proposal instead of writing the live rules.
--dry-runShow what would run without calling a model or writing anything.

learn rewrites rules/<domain>.learned.toml within a fixed character budget; rules/<domain>.user.toml is yours and is never written by code. The store is a git repo, so git log is the learning history and git revert is the undo. Non-clean reflections are excluded structurally, before any prompt is built.

--holdout is deterministic (every k-th by id), because a measurement set that changes between runs measures nothing. --propose gates the candidate by counterfactual replay first and stages what survives for mecha proposals.

mecha learn --dry-run
mecha learn --holdout 0.25 # leave a measurement set for validate
mecha learn --propose -p local # the nightly, unattended form

validate

Probe whether the learned rules change the answers at the recorded moments the user stepped in, and append every outcome to the validation ledger.

mecha validate [OPTIONS]
FlagDescription
--unprocessed-onlyOnly validate reflections not yet consumed by a learn pass — the held-out set.
--trigger <LIST>Probe only these triggers (comma-separated: steer, denial, followup). Default is all three.
--judge-model <MODEL>Judge model id.
--judge-provider <PROVIDER>Provider entry the judge runs on. Defaults to the model under test.
--no-attributeSkip the bisection that attributes a regression to one rule. Regressions are still recorded, just unattributed.

Steer and denial probes are counterfactual replays: the recorded prefix is driven again, with and without the rules, and the verdict is structural. Followup probes are judge-graded, so n=1 means little — read the answers before believing a flip. Run validate before learn, or the rules are graded on their own training data.

mecha validate --unprocessed-only
mecha validate --trigger steer,denial --judge-provider anthropic

rules

Rule tenure: ledger tallies per rule, retirement, and staging retirements for rules the validation ledger keeps convicting. list is the default subcommand.

mecha rules [list|retire|restore|propose-retirements] [ARGS]
SubcommandFlagDescription
listEvery rule with its ledger tallies and staleness.
retire<ID>Retire a rule by id or unique prefix.
retire--reason <REASON>Recorded on the rule and shown to the learner so the lesson does not come back reworded.
restore<ID>Un-retire a rule by id or unique prefix.
propose-retirements--min-attributed <N>Attributed regressions required before a rule is proposed for retirement. Default 3.

Retirement is a flag, never a deletion: the rule stays in the file as evidence and rules restore undoes it. propose-retirements is a deterministic ledger scan with no model anywhere; what it stages goes through the same proposal gate as any other rule change.

mecha rules
mecha rules retire 7c1e --reason "measured harmful on the audit probes"
mecha rules propose-retirements --min-attributed 3

proposals

Review, accept, or reject rule changes staged by mecha learn --propose or mecha rules propose-retirements. list is the default subcommand.

mecha proposals [list|show|accept|reject] [ARGS]
SubcommandFlagDescription
listList proposals.
show<ID>The rules diff and the gate's evidence.
accept<ID>Apply a pending proposal to the live rules.
accept--forceApply even though the live rules changed since the proposal was measured.
reject<ID>Refuse a pending proposal, consuming its reflections.
reject--reason <REASON>Why — recorded on the proposal for the next reader.

accept checks that the live rules still match what the candidate was measured against; a diff on screen that is not the change being applied needs --force to say so. reject retires the reflections so a human's no is not re-argued nightly. Proposals can only ever touch rules/*.learned.toml.

mecha proposals
mecha proposals show 9a4
mecha proposals accept 9a4
mecha proposals reject 9a4 --reason "the second rule contradicts the first"

distill

Summarise closed sessions into episodes staged to a knowledge-graph MCP server.

mecha distill [OPTIONS]
FlagDescription
--sessions-dir <DIR>Directory of session transcripts. Defaults to the standard location.
--server <SERVER>The [[mcp]] server holding the knowledge graph. Default pkg.
--dry-runList what would be distilled without calling a model or writing.
--limit <N>Distill at most this many sessions this run.

Episodes are pushed through the server's kg_upsert as evidence, not belief: the graph's own extractor turns them into candidates that wait in the user's review queue. A tainted session still distills — losing the record of a real afternoon because a web page was open would gut the memory — and the taint snapshot is recorded on the episode's metadata instead. Idempotent at both ends.

mecha distill --dry-run
mecha distill -p local --limit 10 --server pkg

config

Show or create configuration. Requires a subcommand.

mecha config <show|path|init> [OPTIONS]
SubcommandFlagDescription
showPrint the merged configuration as TOML.
pathPrint the files that are being read, and whether they exist.
init--projectWrite ./mecha.toml instead of ~/.mecha/config.toml.
init--forceOverwrite an existing file.
mecha config init # ~/.mecha/config.toml
mecha config init --project # ./mecha.toml
mecha config path
mecha config show | grep -A4 '\[sandbox\]'

See the configuration reference for every key.