Skip to content

Releases: ljchang/mecha

v0.1.0 — first public release

Choose a tag to compare

@ljchang ljchang released this 05 Aug 19:26

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 cratesmecha-core is a plain Rust library that knows nothing about
    any CLI or application; mecha is a thin binary over it. Implement Tool to
    add a native tool, Provider to add a backend, Approver to 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, and stop_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-After up 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] fallbacks then tries other configured providers on
    exhaustion, turn-local, each answering under its own model name.
  • A sampler you can pintemperature and seed on 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.
  • Budgetsmax_turns, max_output_tokens and max_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, and stop_cause says which ceiling fired. Cost
    prices cache reads and writes separately from fresh input, and reports null
    rather than a misleading zero where a provider has no prices configured.
  • Per-run contextRunContext carries 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_output overrides 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 --json for machine-readable
    output, --resume to 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, ^O to reveal tool output and
    reasoning retroactively, ^G to compose in $EDITOR, !command to run a
    shell command locally with no model and no taint, @path completion 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 --schema for exactly what the model sees.
  • mecha sessions list | show | path | stats and
    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 toolsfs_read, fs_write, fs_edit, fs_list, shell
    and http_fetch — plus todo, 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_search behind a SearchBackend trait with SearXNG, Exa and Tavily
    tried in order and falling through on failure, and a depth argument
    selecting a cheap round trip or a deep one.
  • An approval gate with ask, allow and read-only permission 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 Tool trait,
    namespaced <server>__<tool> so two servers can both expose a search. It
    follows nextCursor pagination, 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_send and destructive; 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_domains and blocked_domains narrow 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 whatever env_passthrough names
    and env sets) rather than an inheritance, per-server sandbox = true
    confines the process, and per-server network overrides the global switch.
    [mcp.capabilities] can distrust a server...
Read more