Releases: ljchang/mecha
Releases · ljchang/mecha
Release list
v0.1.0 — first public release
First public release. Everything is new, so the whole feature surface is listed
under Added; later releases will record only what changed.
Added
The loop and the library
- Two crates —
mecha-coreis a plain Rust library that knows nothing about
any CLI or application;mechais a thin binary over it. ImplementToolto
add a native tool,Providerto add a backend,Approverto control what
needs permission. A provider-agnostic message vocabulary means a transcript
recorded against one backend can be replayed against another. - The agent loop — ask the model, run the tools it asked for, feed the
results back, repeat until it stops calling tools. The loop never learns which
provider is behind it or where a tool came from; both are trait objects. - An Anthropic provider over raw HTTP, with adaptive thinking, thinking
blocks echoed across tool turns, andstop_reason: "refusal"recognised as
the HTTP 200 it arrives as. - An OpenAI-compatible provider covering llama-server, vLLM and Ollama, with
streamed tool-call reassembly across arbitrary chunk boundaries, parallel
calls interleaved by index, and tool calls that survive the
finish_reason: "stop"llama-server reports alongside them. - Prompt caching on Anthropic — a fixed breakpoint covering tools and system
prompt plus a second moving breakpoint on the last message block, so an
append-only transcript reads from cache instead of being re-sent uncached
every turn. - Classified failures, with transient ones retried — rate limits (honouring
Retry-Afterup to a cap), overload, server and transport errors back off and
retry; auth, billing, invalid-request and context-overflow never do. A retry
covers the send only, so it can never duplicate work already shown or acted
on.[providers.X] fallbacksthen tries other configured providers on
exhaustion, turn-local, each answering under its own model name. - A sampler you can pin —
temperatureandseedon the OpenAI-compatible
provider, refused at startup on Anthropic rather than silently dropped, and
both recorded in the session so a transcript says whether its run was
repeatable. - Budgets —
max_turns,max_output_tokensandmax_cost_usd. All three
end a run the same way: one final turn with the tools removed, so there is an
answer rather than silence, andstop_causesays which ceiling fired. Cost
prices cache reads and writes separately from fresh input, and reportsnull
rather than a misleading zero where a provider has no prices configured. - Per-run context —
RunContextcarries the path jail, the approver, the
budget, a cancellation token and a steering queue, so one agent with one
provider connection can serve concurrent runs jailed to different directories
under different permissions. - Cancellation that stops a run at the next safe point and keeps the partial
answer, the partial assistant turn and the tokens already spent. Tools are
never interrupted mid-call. - Steering — text queued mid-run is folded into the message carrying the
tool results, so the model reads the results and the new instruction as one
user turn and keeps working, without being stopped and restarted. - Subagents — an agent wrapped as a tool, given a rebuilt registry as an
allowlist rather than an inheritance, with optional per-profile model,
provider, turn limit and system prompt. A child's output is untrusted by
default;trusted_outputoverrides that as a deliberate risk decision. - Layered TOML configuration — built-in defaults,
~/.mecha/config.toml, a
project-local./mecha.toml, environment variables, then CLI flags, each
level overriding only the fields it names. A global-only load exists for runs
that no working directory should shape.
Interfaces
mecha run— one task, one answer, with--jsonfor machine-readable
output,--resumeto continue a recorded session, and exit codes that
distinguish success, error, refusal and turn exhaustion.mecha chat— a readline REPL with slash commands and input history saved
after every accepted line, so a killed process keeps it.mecha tui— full-screen, with the input line live while the agent works:
Enter starts a run when idle and steers one already going. Streaming output,
scrollback that re-arms follow mode, session persistence and--resume.- TUI slash commands —
/help /tools /model /provider /mode /mcp /usage /todo /triggers /clear /session /exit, with modal pickers, name completion,
and mid-session switching of model, provider, permission mode and individual
MCP servers. A switch appends a configuration record, so a replay diffs
against what actually ran. - TUI keys —
?for a full key reference,^Oto reveal tool output and
reasoning retroactively,^Gto compose in$EDITOR,!commandto run a
shell command locally with no model and no taint,@pathcompletion against
the workspace, and Shift+Enter for a newline where the terminal supports the
kitty keyboard protocol. - TUI rendering — a context fuel gauge that colours at 75% and 90%, a live
todo pane, subagent work rendered nested under the call that spawned it (still
correct when delegations run in parallel), atomic frame presentation, a tab
title that says whether a run is in flight, and cached history cells so
drawing no longer costs O(transcript) per streamed token. mecha batch— the same agent over a JSONL file of prompts at bounded
concurrency, results streamed to the output file as they finish and keyed by
id, so a killed run leaves everything completed so far on disk. Every item
gets its own conversation.mecha tools— the tool surface with no provider configured, including
each tool's declared capabilities, the active sandbox, which MCP servers are
unconfined, and--schemafor exactly what the model sees.mecha sessions list | show | path | statsand
mecha config show | path | init— inspect saved transcripts, roll up
tokens, turns and cost by provider and model over a window of days, and see
what settings are in effect.
Tools
- Six built-in tools —
fs_read,fs_write,fs_edit,fs_list,shell
andhttp_fetch— plustodo, a task list the model rewrites as it goes,
kept outside the message history so it survives compaction intact. ask_user, registered only by front-ends that own a human, so the model
can stop and ask instead of guessing at an under-specified task. Declining is
a legitimate answer and returns a tool result, not a failed run.web_searchbehind aSearchBackendtrait with SearXNG, Exa and Tavily
tried in order and falling through on failure, and adepthargument
selecting a cheap round trip or a deep one.- An approval gate with
ask,allowandread-onlypermission modes,
plus a planning phase that does not offer writing tools at all rather than
offering them and refusing the call — enforced on both the advertised list and
the dispatch path, and inherited by subagents. - A per-turn tool output budget divided across a turn's concurrent calls, so
one runaway tool cannot starve its siblings. What gets cut is written to a
spill file, and the marker names the path and the line the elision starts on,
so recovering the rest is one read. - An MCP stdio client that surfaces remote tools as the same
Tooltrait,
namespaced<server>__<tool>so two servers can both expose asearch. It
followsnextCursorpagination, accepts JSON-RPC ids in either numeric or
string spelling, and routes a server's stderr through tracing instead of the
terminal.
Security
- The path jail — every model-supplied path is canonicalized and proven to
sit inside the workspace before anything touches disk;.., symlinks and
absolute paths outside the root are refused. - The lethal-trifecta interlock — tools declare
private_data,
untrusted_input,external_sendanddestructive; the loop tracks which
have entered the conversation and refuses any sending tool once both private
data and untrusted content are present. It sits ahead of the approver, because
a human clicking yes is what an injection is trying to engineer.
trifecta = "ask" | "allow"changes the policy deliberately and visibly. - Taint is a property of the conversation, not of one run, and it is
recorded in the session file — so a new turn does not reset it, resuming does
not launder it, and compaction does not summarise it away. A new conversation
(a batch item, a subagent, an eval case, a trigger fire) starts clean. block_sends_after_private— an opt-in second control aimed at ordinary
privacy leaks rather than injection: any outbound tool is refused once private
data is in context.- SSRF protections on
http_fetch— hostnames are resolved and loopback,
private, link-local (including the cloud metadata endpoint) and CGNAT
addresses refused; the connection is pinned to the addresses that passed, so a
short-TTL DNS answer cannot swap them afterwards; redirects are not followed;
allowed_domainsandblocked_domainsnarrow it further. - A sandbox for
shell—[sandbox] kind = "bwrap" | "docker" | "none". A
confined command gets the workspace, a read-only system, no home directory, no
environment beyond a named allowlist and by default no network. A configured
sandbox that does not work stops the run at startup rather than degrading to
unconfined execution. - MCP servers get the same treatment — the child environment is an allowlist
(PATH,HOME,LANG,LC_ALL,TZ, plus whateverenv_passthroughnames
andenvsets) rather than an inheritance, per-serversandbox = true
confines the process, and per-servernetworkoverrides the global switch.
[mcp.capabilities]can distrust a server...