MiniMax M3 as an OpenClaw Coding Agent: Five Tasks Against Kimi K3 and dsh, and a Widget It Didn't Build
- Category
- AI & Local LLM
- Posted
- September 12, 2026
- Updated
- September 12, 2026
- By
- Jacob Lloyd — written with AI assistance, post-project
- Read time
- 21 min read
In plain terms: I connected MiniMax's M3 model to OpenClaw, the framework my AI helpers run on, and gave it the same five small coding jobs I had given Moonshot's Kimi K3 earlier the same morning. It passed all five in about half Kimi's time, and at list prices it would have cost about a quarter as much. I pay MiniMax a flat monthly fee, so those dollar figures are for comparison only. I also meant to compare how each model built a pixel-art version of this site's logo. The widget I first credited to MiniMax turned out to be one Kimi had written on its second try: MiniMax's run found Kimi's files, tested them and reported them as built.
MiniMax M3 now runs my worker agent in OpenClaw. I gave it the same five small coding jobs I had given Kimi K3 earlier the same morning, and it passed all five in about half Kimi's wall time at about a quarter of Kimi's list-price cost. I also meant to compare how the two built a pixel-art version of this site's logo from the same written brief. That part didn't survive checking: the widget I first credited to MiniMax was written by Kimi, and MiniMax's run found Kimi's files and reported them as its own work. The details are below, because how it happened is the most useful thing in this article.
tl;dr
- What it is: MiniMax M3 driving OpenClaw's own agent loop through the bundled
minimaxprovider. That provider speaks the Anthropic Messages API athttps://api.minimax.io/anthropic, not an OpenAI-style endpoint. It is not Codex: OpenClaw's Codex harness only accepts routes whose provider isopenai. - Setup: the key comes from the
MINIMAX_API_KEYenvironment variable. Beyond that, the config that matters here is a model override (context capped at 262,144 tokens of the catalog's 1,000,000) and the worker's model chain: M3, then DeepSeek V4-Flash, then V4-Pro. - Benchmark: five tasks, fresh session per task. MiniMax M3 5/5 in 159.2 s; Kimi K3 5/5 in 309.9 s; DeepSeek Harness (dsh) on V4-Flash 5/5 in 35.7 s. The runs used different fixture files and MiniMax needed several retries, all described below, so read the ratios as indicative.
- Cost: $0.125 for MiniMax's five tasks at OpenClaw's built-in M3 list prices ($0.60 in / $2.40 out / $0.12 cache-read per million tokens). I'm on MiniMax's flat-rate token plan, so that is a list-price equivalent, not a bill. Kimi: $0.531 at $3 / $15 / $0.30. dsh: $0.011.
- Widget: MiniMax has no result. The 323-line widget I had labelled as M3's first attempt is byte-for-byte what Kimi K3 wrote on its second attempt, into a shared folder. MiniMax's 59-second run read those files, re-ran their tests and replied "Built and verified".
What OpenClaw's minimax provider actually is
I had this wrong in an earlier draft, so here it is from the installed code and my own config.
- Protocol and endpoint. The bundled minimax plugin builds its providers with
api: "anthropic-messages"and base URLhttps://api.minimax.io/anthropic(aMINIMAX_API_HOSTvariable can change the host). MiniMax also runs an OpenAI-compatible API underhttps://api.minimax.io/v1, but OpenClaw's plugin doesn't use it. - Two provider rows, not three.
minimaxis the plugin's main provider; myopenclaw.jsononly overrides its model list, and the API type, base URL and auth all come from the plugin.minimax-portalis the plugin's second provider id (its OAuth variant); I declare it by hand with the same endpoint and a 1,000,000-token context for the rare long job.minimax-cnis not a third row, just an auth alias forminimaxfor MiniMax's China region, whose endpoint ishttps://api.minimaxi.com/anthropic. - The key. An environment variable. The plugin reads
MINIMAX_API_KEY(and a few token-plan variants), and myminimax-portalrow refers to it as the literal string"${MINIMAX_API_KEY}". Nothing MiniMax-related is in OpenClaw's secrets store. - Model settings. The plugin's catalog gives M3 a 1,000,000-token window and one
compatsetting,codeMode: "preferred"(OpenClaw's Code Mode, unrelated to Codex). There is no temperature switch in it. - Prices. The plugin prices M3 at $0.60 in, $2.40 out and $0.12 cache-read per million tokens, and that table is what fills
usage.cost.totalin the JSON. I haven't checked it against MiniMax's own price page.
Not Codex
This is OpenClaw's own agent loop, not OpenAI's Codex app-server. OpenClaw has a Codex harness plugin, but its route check (configuredModelRouteNeedsCodex) returns false for any provider whose id doesn't normalise to openai, so a minimax/MiniMax-M3 route never qualifies. The JSON from every run says which loop ran: agentHarnessId: "openclaw". The Codex plugin isn't installed on my box anyway.
Putting M3 behind Codex would take a local bridge such as codex-router, plus letting the Codex plugin share my own ~/.codex home, which every agent on the box would then share. I haven't done that. Everything in this article is the native loop.
Setup
The versions I ran:
$ node -v
v24.18.0
$ openclaw --version
OpenClaw 2026.9.2 (3928bad)
$ dsh --version
0.1.1-rc.2
$ openclaw plugins list --json | jq '[.plugins[] | select(.enabled)] | length'
35
Step 1: the key
The minimax plugin ships with OpenClaw 2026.9.2 and is enabled in my install. It reads the key from MINIMAX_API_KEY, so put that in whatever environment your gateway runs with (a systemd EnvironmentFile, a launchd plist, your shell), then restart the gateway with openclaw gateway restart. Don't paste the key into openclaw.json. If a provider row needs to name it, write the reference "${MINIMAX_API_KEY}", not the value.
Step 2: point a worker agent at M3
Two pieces of openclaw.json. First the agent's model chain:
"agents": { "entries": { "worker": { "model": {
"primary": "minimax/MiniMax-M3",
"fallbacks": ["deepseek/deepseek-v4-flash", "deepseek/deepseek-v4-pro"]
} } } }
Then, optionally, a context cap on the model row. This is the entry from my own config. It has no api, baseUrl or apiKey, because those come from the plugin:
"models": { "providers": { "minimax": { "models": [ {
"id": "MiniMax-M3", "name": "MiniMax M3",
"reasoning": true, "input": ["text", "image"],
"contextWindow": 1000000, "contextTokens": 262144, "maxTokens": 128000,
"compat": { "codeMode": "preferred" }
} ] } } }
I repeat the plugin's whole model entry rather than adding just the one key, because a partial override on this box once silently dropped reasoning: true and ran M3 with thinking off for days. The cap itself: 262,144 of the catalog's 1,000,000 keeps long worker sessions compacting instead of growing without limit.
Step 3: smoke test
This is the PONG task from the benchmark, with the fields I care about pulled out:
$ openclaw agent --agent worker --model minimax/MiniMax-M3 \
--session-key agent:worker:bench-pong-1 \
-m "Reply with exactly: PONG." --json \
| jq '{harness: .result.meta.agentMeta.agentHarnessId,
provider: .result.meta.executionTrace.winnerProvider,
model: .result.meta.executionTrace.winnerModel,
contextTokens: .result.meta.agentMeta.contextTokens,
usage: (.result.meta.agentMeta.usage | {input, output, cacheRead, cost: .cost.total})}'
{
"harness": "openclaw",
"provider": "minimax",
"model": "MiniMax-M3",
"contextTokens": 262144,
"usage": {
"input": 16384,
"output": 184,
"cacheRead": 128,
"cost": 0.01028736
}
}
harness: "openclaw" is the native loop. winnerModel tells you the primary answered rather than a fallback. contextTokens shows the cap took effect. The 16,384 uncached input tokens are mostly the system prompt and tool definitions a fresh session sends on its first turn, which is why even PONG isn't free. The cost is the plugin's list price, not what a flat-rate plan charges.
Side by side: MiniMax M3, Kimi K3, dsh
Two of these are models driving OpenClaw's loop. The third, DeepSeek Harness (dsh), is DeepSeek's own coding agent with its own loop. Earlier articles in this set also had a Reasonix column. I never benchmarked Reasonix headless, and I retired it from my setup on 2026-09-09, so it isn't here.
| MiniMax M3 in OpenClaw | Kimi K3 in OpenClaw | dsh | |
|---|---|---|---|
| Vendor | MiniMax | Moonshot AI | DeepSeek, MIT |
| How it connects | Bundled minimax plugin; Anthropic Messages API at api.minimax.io/anthropic | moonshot provider; OpenAI chat completions at api.moonshot.ai/v1 | Its own CLI and web UI |
| Call it from a script | openclaw agent --agent X --model minimax/MiniMax-M3 -m "…" --json | openclaw agent --agent X --model moonshot/kimi-k3 -m "…" --json | dsh --profile headless "…" |
| Model switch | The agent's model in openclaw.json | Same | ~/.dsh/settings.yaml |
| Five-task benchmark | 5/5, 159.2 s, $0.125 at list price | 5/5, 309.9 s, $0.531 | 5/5, 35.7 s, $0.011 (warm cache) |
| Pixel-art widget brief | No widget of its own (see below) | Three attempts: no widget code; a complete widget in the wrong folder, cut off at 15 min; a 397-line widget with a stricter prompt | Two attempts; the second took 25 min and about 49¢ |
| Version | OpenClaw 2026.9.2 | OpenClaw 2026.9.2 | 0.1.1-rc.2, developer preview |
The benchmark: five tasks
The five tasks are the ones from the dsh article: reply PONG; write and run FizzBuzz; fix two bugs so the unit tests pass without touching the tests; summarise a six-module codebase in under 150 words; rename a function across files and tests and prove the tests pass. All three ran on 2026-09-11. Kimi's counted runs were between 07:46 and 07:54 JST, at --thinking max. The dsh numbers are a warm-cache re-run from the same session. MiniMax's counted runs were between 09:11 and 09:14 JST, at OpenClaw's default adaptive thinking.
What was not the same:
- Different fixture files. Each run built its own scratch folders. The bug-fix task fixed different bugs (Kimi:
addandis_evenin one module; MiniMax:addandsubtract). The rename task renamed different functions (Kimi:calculate_totaltocompute_totalacross three modules and a test file; MiniMax:area_of_recttorect_areaacross three modules and a test file). The shapes match; the files don't. - Retries. MiniMax's first pass reused one persistent session for all five tasks and went wrong in instructive ways (see Gotchas), so I re-ran all five with fresh session keys. FizzBuzz then took two more tries, because the worker kept writing
fizz.pyinto its own workspace instead of the scratch folder. The table shows the fourth FizzBuzz run. Kimi's FizzBuzz and bug-fix were also re-run: the first FizzBuzz ended on a rate-limit message, and the first bug-fix found the file already fixed by an earlier run.
| Task | MiniMax M3 | Kimi K3 | dsh V4-Flash (warm cache) |
|---|---|---|---|
| Reply "PONG" | ✅ 7.2 s · 1 turn | ✅ 6.1 s · 1 turn | ✅ 1.7 s |
| Write + run FizzBuzz | ✅ 12.1 s · 3 turns, 2 tool calls | ✅ 22.8 s · 3 turns, 2 tool calls | ✅ 5.3 s |
| Fix 2 bugs, tests untouched | ✅ 41.0 s · 8 turns, 8 tool calls | ✅ 86.6 s · 7 turns, 6 tool calls | ✅ 11.3 s |
| Summarise 6 modules (<150 words) | ✅ 9.1 s · 3 turns, 3 tool calls (84 words) | ✅ 65.5 s · 5 turns, 10 tool calls | ✅ 5.1 s |
| Rename across files + tests | ✅ 89.9 s · 14 turns, 22 tool calls | ✅ 128.9 s · 10 turns, 9 tool calls | ✅ 12.3 s |
| Total wall time | 159.2 s | 309.9 s | 35.7 s |
| Tokens: uncached in / cache-read / out | 87.4k / 456.7k / 7.6k | 91.5k / 355.3k / 10.0k (4.1k of it reasoning) | 6.1k / 147.3k / 4.7k (2.0k of it reasoning) |
| Cache reads per uncached input token | 5.2 | 3.9 | 24 |
| Cost | $0.125 at $0.60 / $2.40 / $0.12 per M (list price; I'm on a flat plan) | $0.531 at $3 / $15 / $0.30 per M | $0.011 at $0.44 / $1.32 / $0.014 per M |
Rates are per million tokens, in the order uncached input / output / cache-read. Each cost is the sum of the per-task usage.cost.total values, and multiplying the token totals by the rates shown gives the same figure.
What the table says:
- Everything passed. I checked each result on disk: FizzBuzz output, pytest green, the summary's word count, no stale names after the rename.
- MiniMax was about 2× faster than Kimi and about 4× cheaper at list prices (309.9 s against 159.2 s; $0.531 against $0.125). Kimi was running at maximum thinking, which is part of that.
- dsh on V4-Flash was about 4.5× faster than MiniMax and about 11× cheaper on the same kinds of task.
- The cache does the heavy lifting. MiniMax read 5.2 cached tokens for every uncached one. At list prices, a cache read costs 80% less than uncached input. Billed as uncached input, the same tokens would have come to about $0.34 instead of $0.125.
- Retries aren't in the totals. All twelve MiniMax benchmark runs, the thrown-away ones included, came to $0.28 at list price and 321 s.
The widget test, and a correction
The dsh article's "fun test" was a pixel-art widget of this site's logo, built from a written brief. I wanted the same brief from Kimi and MiniMax. The brief, as each model got it:
Brief: interactive pixel-art LaserLloyd logo widget
Build a self-contained, embeddable pixel-art version of the LaserLloyd logo (see
reference-logo.png: a thick blue ring, colour #1f3f8f on white, containing two interlocking italic/slanted capital "L"s — the upper-left L's foot runs under the lower-right L's stem, like the letters are stacked diagonally).Deliverables (all in this folder)
ll-pixel-logo.js— ONE vanilla-JS file, no dependencies, no build step, no network requests. Any page can embed it with:<div class="ll-pixel-logo" data-size="320"></div>+<script src="ll-pixel-logo.js"></script>. It finds every.ll-pixel-logoelement and mounts a<canvas>inside it. Responsive: canvas fills the container width (square), crisp on HiDPI (devicePixelRatio). Exposewindow.LLPixelLogo.mount(el).index.html— a demo page showing the widget at 3 sizes with a short caption of the interactions.README.md— how to embed, the interactions, and any knobs (data- attributes).The art
- A pixel grid of 40×40 cells. DO NOT hand-draw a bitmap (no rows of '#'/'.' strings — that is slow and error-prone). Instead RASTERISE it procedurally from geometry: a function
isLit(col,row)that returns true for (a) the ring: distance from centre between 0.82R and R, and (b) two slanted Ls, each built from two parallelograms (a stem sheared ~20° and a foot), the upper-left L's foot sitting just under the lower-right L's stem, like the reference. Tune the few constants so it reads as the reference logo at 200px. Precompute the lit cells once at mount.- Palette: logo blue #1f3f8f; highlight blue #2ea8ff; amber #ffb64a; cyan #00e6cf; background transparent.
Interactions (the point of the exercise — make them delightful)
- Mouse over / touch move: pixels near the pointer react physically — e.g. they're pushed away from the cursor like a fluid/ magnetic repulsion, then spring back with damping; while displaced they glow toward #2ea8ff/#00e6cf. Smooth 60fps via requestAnimationFrame; no jank.
- Click / tap: something cool and satisfying. Pick ONE strong effect and do it well, e.g. the whole logo shatters into pixels that fly outward with gravity/bounce, then reassembles itself into the logo again (about 1.5–2 s); or a "laser" sweeps across and re-engraves the logo pixel by pixel with glowing sparks. Repeat clicks should feel good (don't break mid-animation).
- Idle: a subtle ambient life (a slow shimmer or an occasional pixel twinkle) so it never looks dead, but nothing distracting.
- Respect
prefers-reduced-motion: reduce(render the static logo, keep hover glow only).- Works with mouse AND touch. No scroll hijacking.
Quality bar
- Clean, commented code; no globals except
LLPixelLogo. Tab-size 2. Under ~400 lines.- Runs from
file://with zero console errors. Test it yourself: write a tiny node script or open it with whatever you have to at least syntax-check and exercise the module (e.g. jsdom is NOT available — do anode --checkand a DOM-free unit check of the bitmap, e.g. count lit cells and assert the ring + two Ls are present in the right quadrants).- Finish by printing a short report: what you built, how to embed, and what you verified.
What happened, in order, from the session logs:
- Kimi, attempt 1 (the brief as above): 8.8 minutes of thinking, no widget code, stopped. About $0.31.
- Kimi, attempt 2 (same brief): wrote a complete 323-line
ll-pixel-logo.js, a demo page, a README and two test files, then ran its tests and adjusted them until they passed. It wrote all of this, plus a short run report, into the worker agent's own workspace rather than the folder I was watching, and the session hit a 15-minute limit before it replied. So I thought attempt 2 had produced nothing. About $0.72. - Kimi, attempt 3 (a stricter wrapper: write the files in one tool call, no planning, no
node --check): 7.4 minutes, a 397-line widget in the right folder. $0.47. That is the widget the Kimi article shows. - MiniMax (the brief as above, an hour later, same worker agent): 59.3 seconds, 25 turns, 24 tool calls, $0.13 at list price. It found Kimi's attempt-2 files still sitting in the shared workspace, read all five and Kimi's own run report, ran Kimi's tests, and replied: "Built and verified the full widget set". It never wrote a line of widget code; the only files it wrote were its own run report and status file.
I took that reply at face value, and the first draft of this article presented the file as "MiniMax's first-attempt widget, under a minute". The session log shows the write of that exact file at 08:11 JST in Kimi's attempt-2 session, and the file on this site is byte-identical to it. So for MiniMax on this brief I have no result yet. The honest next step is to re-run it in an empty folder. For Kimi the story is better than I first told it: its second attempt worked; it just put the files in the wrong place.
Here is Kimi's attempt-2 widget, running on this page. It's the file I originally mislabelled:
prefers-reduced-motion.Its first ~30 lines:
/* ll-pixel-logo.js — interactive pixel-art LaserLloyd logo widget.
*
* Embed:
* <div class="ll-pixel-logo" data-size="320"></div>
* <script src="ll-pixel-logo.js"></script>
*
* One file, no dependencies, no build step, no network requests.
* The 40x40 bitmap is rasterised procedurally from geometry (a ring plus
* two interlocking italic Ls) — never a hand-drawn bitmap.
*
* Interactions:
* - pointer move : pixels are pushed away from the cursor, then spring
* back with damping; displaced pixels glow blue -> cyan
* - click / tap : the logo shatters outward with gravity + floor bounce,
* then reassembles itself (~1.8 s)
* - idle : occasional pixel twinkle (amber / cyan)
* Honours prefers-reduced-motion: static logo, hover glow only.
*
* Global exported: window.LLPixelLogo (also module.exports under node,
* so the bitmap can be unit-tested without a DOM).
*/
(function () {
'use strict';
/* ---------------- palette ---------------- */
var BLUE = [31, 63, 143]; // #1f3f8f logo blue
var HI = [46, 168, 255]; // #2ea8ff highlight blue
var AMBER = [255, 182, 74]; // #ffb64a
var CYAN = [0, 230, 207]; // #00e6cf
Kimi's attempt 3, the version in the Kimi article, starts like this:
/*!
* ll-pixel-logo.js — interactive pixel-art LaserLloyd logo widget.
*
* Self-contained vanilla JS: no dependencies, no build step, no network.
* Embed on any page with:
*
* <div class="ll-pixel-logo" data-size="320"></div>
* <script src="ll-pixel-logo.js"></script>
*
* Every .ll-pixel-logo element gets a <canvas> mounted inside it at load.
* Programmatic API: window.LLPixelLogo.mount(el) -> instance.
*
* Art: 40x40 pixel grid, rasterised procedurally from geometry (ring + two
* interlocking italic Ls). Interactions: pointer repulsion with spring-back,
* click/tap shatter-and-reassemble, idle shimmer/twinkle, reduced-motion
* support. Mouse and touch. No scroll hijacking (all listeners passive).
*/
(function () {
'use strict';
// -- constants ------------------------------------------------------------
var VERSION = '1.0.0';
var GRID = 40; // logical grid: GRID x GRID cells
var BLUE = [31, 63, 143]; // #1f3f8f logo blue
var HILITE = [46, 168, 255]; // #2ea8ff highlight blue
var AMBER = [255, 182, 74]; // #ffb64a twinkle amber
var CYAN = [0, 230, 207]; // #00e6cf max-displacement glow
var CENTER = GRID / 2; // grid centre coordinate (20)
var R_OUT = 19; // ring outer radius (cells)
var R_IN = 0.82 * R_OUT; // ring inner radius
var SLANT = Math.tan(20 * Math.PI / 180); // ~20 deg italic shear
And the dsh widget, the one at the top of the dsh article:
/*!
* ll-pixel-logo.js — interactive pixel-art LaserLloyd logo widget.
* Vanilla JS, zero dependencies, no network requests, works from file://.
* <div class="ll-pixel-logo" data-size="320"></div>
* <script src="ll-pixel-logo.js"></script>
* Auto-mounts on .ll-pixel-logo elements; window.LLPixelLogo.mount(el) too.
* Knobs: data-size, data-speed, data-static.
*/
(function (global) {
'use strict';
// ---- 0. palette -----------------------------------------------------
var BLUE = [31, 63, 143]; // #1f3f8f — logo blue
var HI = [46, 168, 255]; // #2ea8ff — hover / repulsion glow
var AMBER = [255, 182, 74]; // #ffb64a — twinkle spark
var CYAN = [0, 230, 207]; // #00e6cf — deep glow
// ---- 1. art: procedural 40x40 raster (no hand-drawn bitmap) ----------
// A cell is lit when it belongs to the ring or to one of the two slanted
// interlocking Ls. Each L is two parallelograms (stem + foot), described by
// top-left (ax,ay), width w, height h, sheared by SLANT (bottom edge shifts
// left): TL=(ax,ay) TR=(ax+w,ay) BL=(ax-SLANT*h,ay+h). The upper-left L's
// foot runs right and tucks under the lower-right L's stem, like the ref.
var GRID = 40; // cells per side
var RING_R = 19.7; // outer ring radius (cells)
var RING_IN = 0.80 * RING_R; // inner ring radius (reference ~0.808R)
var SLANT = 0.453; // shear of stems/feet (~24deg)
var LETTERS = [
{ ax: 15.0, ay: 5.2, w: 4.2, h: 10.6 }, // upper-left L: stem
{ ax: 10.2, ay: 15.8, w: 15.0, h: 3.8 }, // upper-left L: foot
{ ax: 24.7, ay: 13.8, w: 4.2, h: 14.7 }, // lower-right L: stem
{ ax: 18.0, ay: 28.5, w: 15.0, h: 3.8 } // lower-right L: foot
];
All three rasterise a ring plus two slanted Ls from geometry, expose window.LLPixelLogo.mount and honour prefers-reduced-motion. I counted lit cells by evaluating each one's isLit over the 40×40 grid:
- Kimi, attempt 2:
RING_R = 19.4, inner 0.82R,SHEAR = 0.42(about 22.8°), oneinLhelper that handles the foot and the sheared stem in a single test. 602 lit cells. - Kimi, attempt 3:
R_OUT = 19, inner 0.82R,SLANT = tan 20°(about 0.364), amakeLhelper that returns stem and foot. 510 lit cells. - dsh:
RING_R = 19.7, inner 0.80R,SLANT = 0.453(about 24°), a literal four-entryLETTERSarray. 656 lit cells.
None matches the reference logo exactly; the brief only asks that it read as the logo at 200 px, and all three do. I'd spend ten minutes on the constants before shipping any of them.
What I re-checked on the attempt-2 widget
Run in a scratch copy of the folder holding the widget and the two test files Kimi wrote:
$ wc -l ll-pixel-logo.js
323 ll-pixel-logo.js
$ node --check ll-pixel-logo.js && echo "syntax ok"
syntax ok
$ node test-bitmap.js | tail -1
ALL PASS
$ node test-smoke.js | tail -1
SMOKE PASS
test-bitmap.js makes 18 checks: ring present at the cardinal points and absent at the corners and centre, each L's stem and foot in its quadrant, the interlock zone, slant direction, and a total lit count between 540 and 670. test-smoke.js makes 10: a DOM-stubbed mount, idempotent re-mount, role="img" and an aria-label, 300 frames with a pointer move and two clicks (the second mid-animation), finite particle state, the logo back home afterwards, a 640 px backing store at devicePixelRatio 2, and a clean unmount(). These are Kimi's own tests, and they are not independent verification: Kimi edited test-bitmap.js after its first run, moving its probe boxes and setting the lit-count band to 540–670 once it had measured 602, until everything passed. A pass here shows the widget and its tests agree, not that the tests set a bar. My own count gives 602 lit cells, split 196 / 137 / 101 / 168 across the upper-left, upper-right, lower-left and lower-right quadrants.
Gotchas (the ones I actually hit)
- Persistent sessions ruin benchmarks.
openclaw agent --agent worker -m "…"reuses the agent's session unless you pass a fresh--session-key. On my first MiniMax pass, the FizzBuzz run decided the file already existed and refused to write it, the bug-fix run said the tests already passed, and the summarise run summarised the Kimi run's bug-fix folder instead of its own. One unique--session-keyper task fixed it. - The worker's standing instructions beat the prompt on where files go. My worker agent's instructions tell it to put longer output in its own workspace, and that won over "write fizz.py in this folder" twice. The third FizzBuzz run's reply said it had written to the scratch folder; the file's timestamp says it went to the workspace. The fourth run, with an absolute path and an explicit instruction to use it, landed correctly.
- A shared workspace contaminates the next run. The widget story above. Kimi's orphaned files were still in the worker's workspace an hour later, and MiniMax treated them as its own. Give every run an empty folder, and check what a run wrote (timestamps, the session's
writecalls), not what it says it wrote. - An error exit doesn't mean the task failed. On the Kimi run, the first FizzBuzz call exited with code 1, named no winning model, and ended on "⚠️ API rate limit reached. Please try again later.", yet it had already written
fizz.pyand the file ran correctly. Only the retry's envelope said completed. Check the files, not the exit status. - The catalog says 1,000,000 tokens; I use 262,144. That is my own
contextTokenssetting, andagentMeta.contextTokensin the JSON confirms which one is in effect. Raise it if you really need the long window; theminimax-portalrow is how I do that for the odd job. - Dollar figures on a flat plan are fiction, but useful fiction. OpenClaw fills
usage.cost.totalfrom its built-in list prices whatever you actually pay, so it's good for comparing models and meaningless as a bill.
Where this leaves me
MiniMax M3 is the primary model on my worker agent, with DeepSeek V4-Flash and then V4-Pro as fallbacks. Kimi K3 isn't in that chain; I use it elsewhere, for my review agent. On these tasks M3 did the rename in 89.9 s for $0.058 at list price where Kimi took 128.9 s and $0.180, and the bug-fix in 41.0 s for $0.025 against Kimi's 86.6 s and $0.114. dsh on V4-Flash was quicker and cheaper than both on every task.
The widget brief is still open for MiniMax. When I re-run it, it gets an empty folder, and I'll read the session's write calls before I read its report.
Related: Kimi K3 as a coding agent (the Kimi side of this comparison), DeepSeek Harness (dsh) (the five tasks and the original widget brief), Reasonix (compared in earlier articles; retired from my setup on 2026-09-09), and What my AI agents actually cost.