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-Previewon 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,
/newsessionand/rename, and pairing with a phone code or a QR. - cu in the agent. The
computer_usetool drives a reusable pool of cu daemons and posts screenshots to the chat. - Closure oracle.
MINI_AGENT_GOAL_ORACLE=layawithMINI_AGENT_GOAL_ORACLE_URLpoints 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 failingverify.shin 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_reportranged 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_wordfreq0.65x,t5_script_csv0.70x,t6_readme_summary0.72x,t8_js_bug0.68x andt4_refactor0.87x; the three worst aret10_crash_report1.64x,t9_pkg_resize1.58x andt7_regex_cli1.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_msis not a valid "harness costs nothing" field, is indocs/pi-vs-mini-benchmark.mdยง9.
- 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.
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 withMSWEA_MAX_TOKENSormodel.max_tokens(an explicitmodel_kwargs.max_tokensstill wins).- A truncated turn is never read as the final answer (#116). It has no
tool_callsand minimax puts its reasoning incontent, so thetool_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 toparse_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.
- A truncated turn is never read as the final answer (#116). It has no
- 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, bymini-tui -por by a subagent โ the surfaces the pi benchmark measures โ never got a verdict).agent-rs/src/jev.rsruns it once the loop has ended and the code is on disk: the run's own model proposes candidate defects ingit diff HEADplus the untracked files, and Jev answers the three typed questions per candidate.info.jev_verifierintraj.jsoncarries 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=1turns it on,auto(default) runs it only when the run left a diff,offnever does. Same thresholds assrc/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/connectprovider is not needed. Measured: an unused-import diff came back as one candidate judged byjev-1.13.0in 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-writtensafe_divwith a bareexcept, MiniMax-M3 proposed no candidate at all, and the phase reportedreader 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.
- The reader is the run's own client (
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) asjev-api-key, editable in/settingsโ jev (masked, never rendered in full) or withmini-tui settings jev-key <key>. Endpoint ishttps://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/connectprovider โ 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 inTHRESHOLDSbranch the result intoauto-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(andcin the panel) probesGET /v1/modelswith 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.
- Toggle 1 โ
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: noon the agent stream, and structured debug logging across the orchestration path. - Shell-free
readtool in agent-rs (#111, pi-speed item 4). Reads a file directly - no/bin/shspawn - paginated byoffset/limitwith a 2000-line / 50KB budget and a continuation note;bashstays the only mutating path. Gated byMINITUI_AGENT_TOOLS=1. - Prompt-cache warmer (#111, pi-speed item 5). Refreshes at
max(1, min(0.9 x TTL, TTL - 10s))whenMSWEA_CACHE_TTLis 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-releaseskill.
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/modelcatalog next tocliproxy/claude-opus-5-5andcliproxy/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 andagent-rs.agent dispatch --join(#89). The parent's turn ends on the dispatch; when the last child finishes the hub runs--verifyonce 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 optionallane) 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 wherebwrapis 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 whenMSWEA_CONTROL_FILEis 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-checkignored--prompt-file, and dropped the positional handshake whenever--repowas given (both found by an orchestrator probing the checker, both verified fixed in the browser).--prompt-fileis parsed intoreq["text_file"]while the arm only readreq["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 back4/4 pass. And the positional formcontract-check [repo] [root] [handshake...]read the handshake from index 2 whether or not--repohad consumed the repo slot. All three invocation forms now examine the text they were handed.
0.38.1
Fixed
agent contract-checkno 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 printed3/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 calledwhich registerRoutes (backend/routes.go. A claim has to be a real path, sopath_tokens()takes every path-shaped token and cuts the:linebefore the extension (elsehandler.go:18reads as extensiongo:18and is thrown away). - Prose parsed as identifiers: a contract clause is a sentence, and
ThereandNeitherare 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. --repowas ignored:--repois parsed intoreposby the shared flag parser while thecontract-checkarm readrepofrom the positional, so the documentedagent contract-check --repo backendasked about the repo named""and printed4/4 passover zero clauses.reposnow wins when the flag is present; the positional remains the fallback forcontract-check <repo> [root].- The wrong repo: the hub took the last segment of a child's
--cwdas its repo, so a child inbackend/.worktrees/be-xwas checked against a repo calledbe-xโ one nothing mentions, so again nothing was checked.Childnow carries the reposrepos_of()resolved at spawn, and a child handed two slices is checked against both repos' clauses.
- A vacuous pass: a child that ended with good prose but no
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 ofdocs/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-rsnow carries that knowledge in one place:agent state put <key>writes markdown documents into the run tree'scontext/(findings,contracts,decisions,surface.<repo>), and every child gets them handed over by default (MINI_AGENT_CONTEXT=0opts out), scoped to the repos it works in โ a backend child readssurface.backend.mdand the## backendsections, and is never billed for the frontend's. No cooperation from the parent required: repos come from the new--repoflag 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 lslists what is indexed.- The handshake back to the orchestrator: when a child's turn ends the hub parses its final
answer into
## surface,## contractand## surpriseand stores it as<child>.mdfor the next child to read.surpriseis 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 emittedgroup_menu_enabledwhile the frontend expectedgroupMenuEnabledwith no mapping layer: the field would have beenfalseforever. 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 ano <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.rspoints 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.yamlcarries an<orchestration_rule>that names the four documents,agent surfaceandagent 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_discoverycounted orientation as discovery.cd,pushdandexportinside 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-rson a child's PATH but notmini-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-checkrun by hand was vacuous: it looked for<repo>.mdinstead of thesurface.<repo>.mdthe 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-filecap aspawnfailed 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-fileis 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 5agent plantests failed onmainfor that reason alone.
0.37.0
Added
- The RLM harness (
mini-agent-rs rlm), experimental (F6 of the execution order โ Fase 6 ofdocs/orchestration-plan.md): a Recursive Language Model execution framework in pure Rust insideagent-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.askis one model turn,runis 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),-eruns single statements, and scripteddeterministicmodels driveagent-rs/tests/rlm.rs(shipped as-is from the prototype; not executed in this change). No Python and no new dependencies.