Skip to content

Changelog

The latest releases. Older ones are in the full changelog.

0.43.0

mini-tui is now a team of bots that organize work as a kanban, with workers that carry the cards out, WhatsApp groups synced with the web app, a real browser through cu, and a closure check that makes "done" mean "the evidence is on the card". This release covers everything since 0.42.0, with a new README and a new landing page.

Measured

  • Closure check on, against pi: 112.9 s vs 145.6 s, 30/30 PASS on both sides. Ten tasks, three interleaved repetitions (pi and mini alternate per task, so API drift hits both alike), MiniMax-M3.1-Flash-Preview on both sides, mini on the deployed binary and config with Laya as the judge. That is 0.78x of pi's time as the sum of per-task medians, and mini is at least as fast on 9 of 10 tasks. Ten tasks, three repetitions, one model, one machine; earlier rounds were a tie and the report keeps them.
  • Where it came from. Baseline with the model judge: 7 of 27 runs hit the 900 s timeout, because the judge rejected finished work up to 158 times (#300 caps rejections at 3). Laya had always answered "evidence truncated" and sent every closure to the slow model judge (#302 gives it a compact window). Its latency grows steeply with input, about 5 s per closure at 17k characters (#303 shrinks the window, and a closure now costs about 0.1 to 0.5 s).

Added

  • Bots and a board. Windows hold bots and each bot holds the delegates it started: a nested sidebar, a team map drawn as a live graph, and a read-only view of other bots' cards through a shared graph layer. The durable task graph keeps leases, so a card in flight is not lost when a lane stops.
  • WhatsApp groups, synced both ways (WA-2 to WA-7, v4 to v6). One group per bot, group photos from the bot avatars, a no-echo mirror between the group and the web app, bounded media with local voice transcription, a ๐Ÿ‘€ acknowledgement, typing state, semantic reactions, /newsession and /rename, and pairing with a phone code or a QR.
  • cu in the agent. The computer_use tool drives a reusable pool of cu daemons and posts screenshots to the chat.
  • Closure oracle. MINI_AGENT_GOAL_ORACLE=laya with MINI_AGENT_GOAL_ORACLE_URL points the closure check at a self-hosted classifier. The model judge stays the default and the fallback.
  • Web app. A silver-metal palette, an Apple-style 18 px shape language, a floating prompt pill, compact tool cards, generative UI in the chat, voice-note recorder and player, inline images and attachments, AI session titles, a model router with three tiers, usage metering per model, and a PWA with cookie login.

Fixed

  • Around fifty fixes from QA rounds E1 to E51, among them: a stale save no longer overwrites a newer one, a stopped consumer tears its turn down, a bodiless upload is no longer read as an empty one, a remote folder listing is bounded in work as well as in size, and the web server compares origins against headers an attacker cannot choose.

Security

  • Per-IP rate limits on the expensive routes, loopback-only CORS with no wildcard on private reads, attachment sniffing before storage, and a host guard for the web server.

0.41.0

Quality is perfect on the pi benchmark โ€” 10/10 PASS on both sides, on every one of the four complete runs (40 of 40 checks per side) โ€” and mini's speed is a tie with pi: a median of 0.98x over four full repetitions of the corpus. An earlier note in this file claimed a 0.69x speed win; that was one run inside the variance and the repetitions did not support it, so it is corrected here. Jev arrives as two optional toggles, and a truncated turn can no longer be read as a run's final answer.

Measured

  • Round 6: three fresh repetitions of the whole pi benchmark. The 0.69x of round 5 did not reproduce, and the honest result is a tie, not a win (#118). Same corpus and model on both sides (minimax/MiniMax-M3), sequential, no orchestration. The four complete runs read 0.69x, 1.13x, 0.98x and 0.93x (mini 204.5 / 254.5 / 284.8 / 246.7 s against pi's 295.3 / 225.1 / 291.0 / 265.7 s): a median of 0.98x over all four, 0.98x over the three new ones, and a range that straddles 1x. Quality is the solid part: 10/10 PASS on both sides on every run, 40/40 per side, no failing verify.sh in 80 checks.
    • Where the speed actually comes from, measured over the 40 runs: 78โ€“81 % of a run's wall time is model time and the harness itself is ~1โ€“2 s of a ~250 s run (about 1 %). The remaining variable is how many turns the model takes and how long it reasons on each one โ€” e.g. t10_crash_report ranged 12.0โ€“94.2 s across four runs at constant quality. There is no harness lever left to close a 0.98x; chasing the single 0.69x run would be optimising a report number, not the software.
    • Per task, medians over the four runs, the winners are t1_wordfreq 0.65x, t5_script_csv 0.70x, t6_readme_summary 0.72x, t8_js_bug 0.68x and t4_refactor 0.87x; the three worst are t10_crash_report 1.64x, t9_pkg_resize 1.58x and t7_regex_cli 1.50x โ€” all variance in how the model spends its turns, not a defect in either harness.
    • What the round-5 run did establish, and what survives: capping the per-turn output at 4096 removed the pathological long reasoning turns without losing correctness. It stays as the default. It does not, by itself, make mini beat pi in wall time.
    • Round 5 as first recorded: mini 204.5 s against pi's 295.3 s (0.69x), 8 of 10 tasks, 10/10 quality on both sides. It is kept in the table above for honesty, labelled as the single run it was, and superseded by the four-run median. Full detail, including the variance analysis and the note that overhead_ms is not a valid "harness costs nothing" field, is in docs/pi-vs-mini-benchmark.md ยง9.

Changed

  • The per-turn output budget is now capped at 4096 (it was an unstated 8192), and a turn that hits the cap is a retryable format error instead of an answer. Round 5 of the pi benchmark showed the front was never the number of turns but the length of the worst one: over the round-4 corpus the longest single turn of a run was a median 30 % of that run's wall time, and those turns are almost entirely reasoning โ€” the worst turn wrote 19 427 chars of <think> to emit a 193-char tool call, while the longest non-reasoning payload across 73 turns was 1 241 chars. Measured over the whole corpus, back to back per task: 4096 is 0.73x the wall time of 8192 on 7 of 10 tasks, truncates zero turns, and halves both the median longest turn (8.0 s โ†’ 3.5 s) and the median generated text (2 513 โ†’ 1 266 chars). 2048 is not faster enough to pay (0.69x) and did truncate a turn; 3072 lost a task outright. Override with MSWEA_MAX_TOKENS or model.max_tokens (an explicit model_kwargs.max_tokens still wins).
    • A truncated turn is never read as the final answer (#116). It has no tool_calls and minimax puts its reasoning in content, so the tool_calls.is_empty() arm was reading that reasoning as the run's result and submitting a run that had done nothing โ€” measured, that is how a run lost a task it would otherwise have passed. It now falls through to parse_toolcall_actions, whose "no tool call" branch is already a format error the loop retries, so the model finishes the thought it was cut off in and only a turn that emitted a real tool call is spent.
  • The verifier phase now runs in the agent, not only in the terminal UI (#115 shipped it in src/ui/App.tsx, so a run started by the web app, by mini-tui -p or by a subagent โ€” the surfaces the pi benchmark measures โ€” never got a verdict). agent-rs/src/jev.rs runs it once the loop has ended and the code is on disk: the run's own model proposes candidate defects in git diff HEAD plus the untracked files, and Jev answers the three typed questions per candidate. info.jev_verifier in traj.json carries the structured report (status, reason, model, latency, candidates, findings with their probabilities and the branch they took); the transcript gets one notice in the same wording the TUI prints. MINI_AGENT_JEV=1 turns it on, auto (default) runs it only when the run left a diff, off never does. Same thresholds as src/jev/verifier.ts, same "never block the run" contract, and every missing piece (no diff, no reader, no key, reader failure, unparseable output, HTTP error) degrades with a stated reason.
    • The reader is the run's own client (Model::reader_endpoint), so a second /connect provider is not needed. Measured: an unused-import diff came back as one candidate judged by jev-1.13.0 in 274 ms, and Jev scored it P(real)=0.46 / P(serious)=0.03 โ€” correctly not a defect. It is not a good reader, though: on a hand-written safe_div with a bare except, MiniMax-M3 proposed no candidate at all, and the phase reported reader proposed no candidates. That is the honest limit of this configuration: the reader is the run's model because asking for a second credential was the worse trade.

Added

  • Jev (TypeSafe System One) as two independent, optional toggles (#115, #116).

    • Toggle 1 โ€” jev: the System One decision service and its API key. The key goes into a small vault (~/.config/mini-tui/vault.json, 0600) as jev-api-key, editable in /settings โ†’ jev (masked, never rendered in full) or with mini-tui settings jev-key <key>. Endpoint is https://api.typesafe.ai (POST /v1/systemone, Bearer), NOT OpenRouter, which stays a plain chat provider. Keys resolve vault โ†’ $TYPESAFE_API_KEY โ†’ ~/.config/typesafe/config.json โ†’ brain.json, in one function every surface shares, so the panel, the CLI and a run can never disagree about which key is in force.
    • Toggle 2 โ€” jev-verifier: an optional post-code phase. After a turn finishes, a cheap LLM (the smallest model of an existing /connect provider โ€” no second credential) reads the diff and proposes candidate defects; Jev answers three typed Noul questions per candidate (is it real / is it serious / is it safe to fix unattended) and fixed thresholds in THRESHOLDS branch the result into auto-fix / flag / ignore. The report lands as a transcript notice; it never blocks the prompt or the run, and any missing piece (no diff, no reader, no key, HTTP error) degrades with a stated reason instead of an invented verdict.
    • mini-tui jev check (and c in the panel) probes GET /v1/models with the key in force and names the concrete model that answers.

    Both toggles ship off and neither reads the other's state: Jev can be on with the phase off, the phase on with Jev off.

0.40.0

The web UI gets its agents panel (live graph, tabs, session persistence), the agent drops its default budgets, and the Rust agent closes most of the speed gap with the pi harness.

Added

  • Agents panel in the web UI (#102, #103, #104, #105, #106, #107). A real-time graph of the session's agents (@xyflow/svelte over SSE), tabs for Agents/Graph/Messages/Definitions, session tasks per window in the sidebar, sessions that survive a server restart, no more redirect when clicking stopped sessions, x-accel-buffering: no on the agent stream, and structured debug logging across the orchestration path.
  • Shell-free read tool in agent-rs (#111, pi-speed item 4). Reads a file directly - no /bin/sh spawn - paginated by offset/limit with a 2000-line / 50KB budget and a continuation note; bash stays the only mutating path. Gated by MINITUI_AGENT_TOOLS=1.
  • Prompt-cache warmer (#111, pi-speed item 5). Refreshes at max(1, min(0.9 x TTL, TTL - 10s)) when MSWEA_CACHE_TTL is set, only when pi's economics say it pays (>= $0.05 expected saving from the model's own price row).
  • Per-phase step timings (#111, pi-speed item 6). PI_TIMING-style timings recorded in extra.
  • skills/villa-release skill.

Changed

  • Unlimited by default (#108). No more 200-step / $2 subagent / $3 parent caps; spawn-requested budgets are ignored and auto-extend is generous.
  • agent-rs speed, items 1-3 (#109). Retry clock, overlapped tool actions, no per-step clone.

Fixed

  • Stuck subagents on SIGTERM (#110). Children are stopped and recorded on SIGTERM, and dead pids are reported as dead instead of blocking the parent.

0.39.0

Claude Haiku 5.5 is in the curated model picker, and agent dispatch can end the parent's turn on the wave itself (--join), with each child confined to its lane.

Added

  • cliproxy/claude-haiku-5-5. The cli-proxy gateway advertises Anthropic's new Claude Haiku 5.5, and mini-tui lists it in the /model catalog next to cliproxy/claude-opus-5-5 and cliproxy/claude-sonnet-5-5. It runs over the same Claude subscription credential and gets the 1M-token context window (automatic compaction), in both the Python agent and agent-rs.
  • agent dispatch --join (#89). The parent's turn ends on the dispatch; when the last child finishes the hub runs --verify once and answers with every child's result, so the parent spends one step after the wave instead of 41โ€“103 s re-checking it.
  • Enforced lanes (#89). A plan task's files (and optional lane) are the only paths its child can write: the bash tool runs under bubblewrap with the work tree read-only outside the lane (unchanged behaviour where bwrap is missing).

Changed

  • Dispatch shard sizing and wave admission (#89). Shards are sized in lines, the wave warns when the heaviest is over 1.5x the lightest and starts it first; a wave is admitted as a whole on memory, and its children are no longer re-gated one by one on CPU load.
  • Child task header (#89) states ## Where (the work tree) and ## Scope (the gate is the proof; no test-harness side quests).

Fixed

  • Headless agents wrote task cards into other sessions (#89). The session-tasks rule told every agent to run mini-tui tasks set; a headless run could not find its own session and overwrote a live one. The rule now renders only when MSWEA_CONTROL_FILE is set, and never for lane children.

0.38.2

Fixed

Patch release. 0.38.1 fixed five ways agent contract-check could report a pass over nothing (defects 1โ€“5). Probing the fixed checker, an orchestrator found two more in the same arm; those fixes (PR #84) landed on main after the 0.38.1 tag and ship here (defects 6โ€“7).

  • contract-check ignored --prompt-file, and dropped the positional handshake whenever --repo was given (both found by an orchestrator probing the checker, both verified fixed in the browser). --prompt-file is parsed into req["text_file"] while the arm only read req["handshake"], so the documented way to check an arbitrary answer silently examined a stored child's handshake instead โ€” which is how a control naming a file that did not exist came back 4/4 pass. And the positional form contract-check [repo] [root] [handshake...] read the handshake from index 2 whether or not --repo had consumed the repo slot. All three invocation forms now examine the text they were handed.

0.38.1

Fixed

  • agent contract-check no longer reports a pass over nothing. Five separate ways to get a green check that had examined nothing were found by running real orchestrations in the browser; every one of them is a mechanical check parsing free text with a rule free text does not obey.
    • A vacuous pass: a child that ended with good prose but no surface: tag claimed no files, so the corpus was empty, every clause was trivially satisfied and the check printed 3/3 pass โ€” indistinguishable from "the contract holds". There is now a fourth check, coverage, which fails when no file was claimed and no clause names the repo, and says so in the message.
    • A false missing file: splitting a prose surface: line on commas invented a file called which registerRoutes (backend/routes.go. A claim has to be a real path, so path_tokens() takes every path-shaped token and cuts the :line before the extension (else handler.go:18 reads as extension go:18 and is thrown away).
    • Prose parsed as identifiers: a contract clause is a sentence, and There and Neither are Capitalised and 5+ characters, so they passed the snake_case/CamelCase filter โ€” the check then demanded that the source literally contain the word "There". is_identifier() now requires what a field name has and a sentence word does not: a _ with a letter, or an uppercase followed by a lowercase inside the token (groupMenuEnabled), or a dotted code path, minus a stoplist of the words that start a clause. A clause naming no field is skipped, not failed.
    • --repo was ignored: --repo is parsed into repos by the shared flag parser while the contract-check arm read repo from the positional, so the documented agent contract-check --repo backend asked about the repo named "" and printed 4/4 pass over zero clauses. repos now wins when the flag is present; the positional remains the fallback for contract-check <repo> [root].
    • The wrong repo: the hub took the last segment of a child's --cwd as its repo, so a child in backend/.worktrees/be-x was checked against a repo called be-x โ€” one nothing mentions, so again nothing was checked. Child now carries the repos repos_of() resolved at spawn, and a child handed two slices is checked against both repos' clauses.

Measured

Same 6-file two-repo task, orchestrator exploring once, in the web UI:

before after
discovery tokens per subagent ~1,962 ~0
subagent steps 40 28

Fewer steps for more work: the children stopped re-discovering the code their parent had already read. The saving is paid for up front by the orchestrator, so it amortises with more children or a bigger repo โ€” on a 6-file repo the orchestrator's own exploration still costs about as much as a child's would have.

The fifth defect was found by an orchestrator, not by a human: it received 4/4 pass, copied the tree to /tmp, deleted the wire field, re-ran the check, got 4/4 pass again with the field gone, and reported its own integration check as toothless. Nothing prompted it to do that.

0.38.0

Added

  • The shared context (ContextStore) โ€” the exploration is paid for once (F7 of the execution order โ€” Fase 7 of docs/orchestration-plan.md): a real orchestration measured on 2026-10-07 (6 subagents over 2 repos) spent 122,039 of 184,342 tokens (66%) re-discovering what the orchestrator already knew, re-read 28 files, and left 3 integration failures to be found later by the parent reading diffs. agent-rs now carries that knowledge in one place: agent state put <key> writes markdown documents into the run tree's context/ (findings, contracts, decisions, surface.<repo>), and every child gets them handed over by default (MINI_AGENT_CONTEXT=0 opts out), scoped to the repos it works in โ€” a backend child reads surface.backend.md and the ## backend sections, and is never billed for the frontend's. No cooperation from the parent required: repos come from the new --repo flag or, failing that, from the child's own --cwd.
  • agent surface <symbol>: who calls it, which type carries it and what contract it obeys, answered in one call from the indexed documents instead of grepping the tree again; agent surface ls lists what is indexed.
  • The handshake back to the orchestrator: when a child's turn ends the hub parses its final answer into ## surface, ## contract and ## surprise and stores it as <child>.md for the next child to read. surprise is what makes this work โ€” it is the one place a subagent can report that the brief is wrong without stopping, so an integration mismatch is raised by the child that found it instead of being rediscovered later. In a live run this is how a subagent reported that the backend emitted group_menu_enabled while the frontend expected groupMenuEnabled with no mapping layer: the field would have been false forever.
  • agent contract-check --repo R, run automatically when a child's turn ends: every file it claims to touch exists and is under the repo, every field the contract assigns to that repo appears where it should, and a no <invariant> clause is checked literally. No model in the loop, so a claim that does not hold fails instead of reaching the parent as a summary.
  • The RLM harness seeds the same store: rlm.rs points it at its context directory on startup and exposes the documents as {{context.<key>}} variables, so a scripted orchestration sees what a chat one sees.
  • The orchestrator is told to use it by default in the system prompt itself (not in a skill): agent/src/minisweagent/config/mini.yaml carries an <orchestration_rule> that names the four documents, agent surface and agent contract-check.
  • Measured in the web UI against the same task without the store (2 repos, orchestrator + 2 children): child discovery tokens ~3,197 โ†’ ~1,456 (-54%), orchestrator tokens ~26,699 โ†’ ~12,093 (-55%), and 1 integration bug caught by a subagent that would otherwise have been merged. Duplicate reads do not drop โ€” a child still reads the file it edits; what the store removes is the orientation work around it.

Fixed

  • is_discovery counted orientation as discovery. cd, pushd and export inside a compound command made a step look exploratory when it was neither. Re-validated against the villa journals it had been derived from: 120,718 vs 122,039 reported discovery tokens (1.1% off, in the honest direction).
  • The hub put mini-agent-rs on a child's PATH but not mini-tui, which the system prompt names. The model in a measured run spent ~20 tool calls hunting for it and for bun before doing the work it was asked for. It is now resolved the way the runner resolves it (MINI_TUI_BIN, then ~/.local/bin/mini-tui) and put, with ~/.bun/bin, beside the agent.
  • agent contract-check run by hand was vacuous: it looked for <repo>.md instead of the surface.<repo>.md the store actually holds, found no handshake and reported "3/3 pass".
  • A copied template became a false claim. The handshake block ended with a bracketed example and a subagent returned it verbatim; the hub stored it and the contract check failed on a file named <file. The check was right, the input was garbage: the block now shows a filled-in example and a template line is dropped on the way in instead of stored.
  • A large store refused every spawn. Past the --context-file cap a spawn failed with a message blaming a flag the parent never passed. The store now degrades to a trimmed head and says how much was dropped; only an explicit --context-file is refused.
  • Integration tests inherited the ambient environment (MINI_AGENT_SOCKET, MINI_AGENT_BIN, MINI_AGENT_CONFIG_DIR), so the children they spawned talked to a live session's hub. 5 of 5 agent plan tests failed on main for that reason alone.

0.37.0

Added

  • The RLM harness (mini-agent-rs rlm), experimental (F6 of the execution order โ€” Fase 6 of docs/orchestration-plan.md): a Recursive Language Model execution framework in pure Rust inside agent-rs/, behind its own opt-in subcommand (nothing else in the binary couples to it): a persistent REPL where long-horizon context lives in program variables (let / set, {{var}} interpolation) and sub-agents are function calls (def / call) that run their own conversation with the model and the bash tool until they answer โ€” recursively, up to --max-depth. ask is one model turn, run is shell, and the state (variables and functions, rlm.json) auto-persists across restarts (save / load). Scripts run non-interactively (exit 1 on the first error), -e runs single statements, and scripted deterministic models drive agent-rs/tests/rlm.rs (shipped as-is from the prototype; not executed in this change). No Python and no new dependencies.