plank β€Ί What's new

What's new

This site went up in late July 2026, around v2.5. Plank has not held still since. Below are the changes worth knowing about if you have been away, newest first. The full changelog has every last fix; this page has the ones you will actually notice.

Just landed

v7.0.8 beta: sub-agents fan out wider. Against an online provider, plank now runs up to 32 sub-agents at once by default, where it used to stop at 4. agents.maxParallel accepts up to 32; lower it if your provider starts rate limiting a wide fan-out. A local model still runs its sub-agents one at a time.

v7.0.7 beta: repro dumps show the prefills. A /repro dump now lists every prefill the engine did, from the warm-up before the first turn onward, with its size and how much of the context was reused. Micro-compaction only fires when it gives back enough of the transcript for where it sits, which cuts down the repeated rebuilds it used to cause. A dump also carries the build's commit.

v7.0.6 beta: a stuck turn stops itself. A turn that kept re-issuing the same refused tool call, slipping one real call in between each try, could run on far longer than it should. The loop guard now ends the turn once a call has been refused several times over, whether or not those tries are back to back.

v7.0.5 beta: a buddy, hints, and conversations that survive the window's edge. /buddy on stands a small animated character on the rule above the prompt while plank is idle, in kitty, Ghostty and Warp; /buddy install swaps it for another from the plank-buddies repository. /hints on asks the model for a short aside on the idea behind its work now and then. A conversation that fills the window still compacts, a tool call the window cuts off is retried rather than reported as broken, and a message too big for the window is refused instead of jamming the session. DeepSeek and Qwen now edit with exact old text, as the C reference does.

The minions buddy: a minion in a maid's outfit, feather duster in hand, standing on plank's green prompt rule above the session name and fidgeting through its clips

v7.0.4 beta: profile sessions start warm. A session started with --profile restored its cached prompt at launch and then re-prefilled all of it on the first prompt, over one trailing newline. The first prompt now reuses what startup restored, so the first answer no longer waits on a 10k-token prefill.

v7.0.3 beta: long provider sessions compact instead of failing. On a self-hosted model with a small window, plank could undercount the prompt, ask for more output than was left, and lose the turn to a maximum context length error. It now counts the prompt the way the server does, retries without the output cap when the server objects, and compacts when the conversation itself no longer fits.

v7.0.2 beta: self-hosted models size themselves. Point --provider openai at vLLM, llama.cpp, LM Studio, Ollama or OpenRouter and plank asks the server for the model's context window, and its output cap where there is one, so the context gauge tells the truth and a 32K vLLM model no longer refuses every request. The same call checks your key and the model name before the first turn, and a typo comes back with the models the server does offer. Steering directions now have names too: --dir-steering NAME, /steer to list and switch, and a profile can bring one along with its recommended model.

v7.0.0 is out, and plank no longer needs a 96 GB Mac. Gemma 4 runs as the agent model on an engine plank carries itself, written in Rust with no C underneath: Metal on a Mac, the CPU anywhere else. plank --model gemma4-e4b is a 5 GB download, gemma4-12b a 7 GB one, and tools, thinking, sessions, sub-agents and memory all behave as they do on DeepSeek. With no -c, the context window sizes itself from your RAM, up to the full 128K tokens on a big machine. Gemma sessions are kept apart from DeepSeek ones, so switching back and forth loses neither.

Picking an engine is a menu now. When the engine plank would run is not on disk, it lists every engine instead of offering a single download, with the ones your machine cannot run dimmed and the reason beside each: not enough memory, not enough free disk, not in this build. The one you pick downloads in the background while a wait screen shows how far along it is, and /engines brings the menu back from inside a session. With HF_API_KEY set, downloads authenticate to Hugging Face and pull four ranges at once.

Edits stop failing on copied line numbers. A small model that has just read a file tends to paste read's line numbers into its edit, which can never match. plank retries without them, still insisting on a unique match, and tells the model it did. The debug console, tdk 0.7.0, renders Gemma's tool calls as banners too.

v6.2.2 beta: profiles speak in their own verbs. The word in the status bar that says what the agent is doing can now come from the profile, replacing plank's vocabulary with verbs or mixing more in with additionalVerbs. The published profiles use it: d3v1l is Scheming and Smiting, EAP is Musing and Versifying, HAL is Computing and Actuating, and each keeps a few rare surprises of its own, like HAL Singing Daisy.

v6.2.0 is out. Its centrepiece is that a tool call can now hand you a real editor and wait: a WASM component takes over the screen from inside the dispatch, modally, the way the ask panel does, and the call reports what happened in its own result instead of leaving a notice for later. Everything below follows from that.

edit_csv opens a spreadsheet you actually edit. The model names a file, you get a real table with column separators, and the tool learns what changed from the counts rather than from a description. Deleting a row asks first.

Grids put an MCP server's data in that editor. A profile can route a server's frame resources to a component: plank strips them out before the model sees them, stages them, opens the editor, and writes what you saved back to the server, keeping each grid's shape and name across the round trip.

The spinner verb animates by what plank is doing. It used to sweep the same way whatever was happening. Now prefill sweeps fast and left-to-right, generation and thinking keep the familiar slower right-to-left sweep, and while a tool runs the verb stops travelling and pulses as a whole, because a tool dispatch produces nothing token by token and nothing should look like it is moving along the text.

The window title says READY with a blinking block, at the Commodore 64's own cursor rate. The log icon is gone, and both the idle and the loading title carry the running profile's name, so a profile no longer loads as itself and then goes ready as Plank.

Memory is filed per profile now, a profile's shimmer sweeps toward its own colours instead of plank's green, a profile's bundled component is offered for trust once at launch, and report panels finally scroll with the wheel and the arrows rather than only PageUp and PageDown.

Everything the 6.1.x betas carried is in this release too, including the GPU hand-off through pipes and suspend_model. The beta channel restarts at 6.2.1 with the same code.

Models are named engines now. Plank used to know its three model sets by hard-coded file names. It now reads an engines.json catalog, where each engine is a main model plus the drafter and the vision encoder that go with it. Three ship today: ds4vision (DeepSeek V4 Flash with vision, the default), ds41 (DeepSeek V4.1 Flash) and qwen (Qwen3.8-Flash-Next). The catalog is built into the binary, refreshes from the repository once a day, and each engine upgrades on its own schedule, so a new Qwen build never re-downloads DeepSeek.

plank --model qwen          # an engine by name, with everything it needs
plank --model:ds41          # the same, but the word must be an engine
plank --model ~/x.gguf      # still works: a file loads as itself

--qwen is gone in favour of --model qwen. You can describe your own models in ~/.plank/engines.local.json, pointing each role at a file you already have; plank never downloads or upgrades those. The installation chapter shows the format.

Your model files get new names, and that is one way. The first launch renames ~/.plank/ds4flash.gguf and its siblings to ds4vision.gguf, ds4vision.mtp.gguf and ds4vision.vision.gguf, and the V4.1 files to ds41.*. Nothing is downloaded again. An older plank pointed at the same ~/.plank will not find its model any more and will offer to fetch it, so upgrade every copy you run together. A settings file or command line that still names ~/.plank/ds4flash.gguf keeps working and prints a one-line note naming the engine to use instead.

A bare --model /some/file.gguf now loads just that file, with no drafter or encoder picked up beside it: pass --mtp-model for speculation, or describe the file as a local engine. Two exceptions keep existing setups whole. A path that is the same file as an engine's main model, including through a symlink or a hard link, selects that engine, and a .ggd weight delta built on an engine's main model inherits that engine's drafter and encoder, though the delta itself is never upgraded.

A local engine can also say where to fetch a file it does not have yet. Give a role a url beside its path, including a Hugging Face file page, and plank offers to download it into that path the first time it is missing. The engine stays yours: plank never checks it for updates.

Commands that need the GPU get it. A local model holds the Mac's GPU for as long as plank runs, so a tool that wants it for itself used to fail or thrash. mex generating an illustration with its diffusion model is the first such tool. When a command cannot get the GPU, plank saves its cache, unloads the model, runs the command again, reloads the model and restores the cache, and the conversation carries on without reading the whole transcript back in. It works for commands the model runs and for the ones you run yourself with ! and !!.

A tool asks in one of two ways. It can exit with status 75 and print a line starting with GPU not available, or it can write that line to the file named by PLANK_GPU_YIELD_FILE, which plank sets for every command it runs. The file is the one that survives a pipeline such as mex post.md 2>&1 | tail, which hides the exit status, so it arrived in the 6.1.2 beta after 6.1.0 was seen missing exactly that case. mex 0.2.4 uses both, and stops a post conversion before anything is uploaded.

The model can also ask up front. A bash call with suspend_model="true" unloads the model before the command runs and reloads it afterwards, so a command known to need the GPU does not have to fail first. The tools chapter covers the limits: at most two hand-offs per turn, background jobs are left alone, and pressing Esc never starts one.

A profile can recommend a model. recommendedModel in a profile's manifest names the engine it works best with. plank uses it ahead of your engine.model setting, but only when that engine's files are already on disk, and never over a --model you typed. HAL recommends qwen. The profiles chapter has the details.

Smaller things. /stats now labels its rows ↑ input/prefill and ↓ output/generated, with the same arrows as the status bar.

v6.0: plank can be a different agent

v6.0.0 moved the major number for one reason: profiles.

Plank can be a different agent. A profile gives it its own system prompt, its own set of tools, its own settings, and its own logo, name and colour, so the same binary can start as a coding assistant, a mail assistant or a budget analyst. You start one by name, from a folder, or straight from a folder of a GitHub repository:

plank --profile aovestdipaperino/plank-profiles:HAL

The first launch asks before installing it; after that the same command starts the installed copy, and when the profile's author raises its version, plank offers the update before it starts. /edit-profile opens the running profile's files with a note beside every setting saying where it comes from, and after a save it offers to restart into the change without losing the conversation. A profile also decides whether it wants to know about the folder you launched it from, so a mail assistant started in a code checkout is not told the checkout's git status or offered an AGENTS.md.

The first published profile is HAL, a mail assistant over one Outlook mailbox. It reads, flags, drafts and files mail, and it cannot send or delete any: the permission it signs in with does not include sending, and its mail server has no delete tool. The Profiles chapter of the guide covers installing, updating and writing your own.

plank launched with --profile hal: HAL's pixel-art logo as the banner art beside HAL v0.3.1, plank v6.0.1 BETA and context 1.0M tokens, with the prompt framed by red rules instead of plank's green

Two smaller changes came along. --plugin-dir is gone: put a plugin you are working on in ./.plank/plugins/, and launch a profile folder with --profile <folder>. And an MCP server that stops answering is now restarted on the next call instead of being written off for the rest of the session.

The rest of this section is what landed in 5.3.

Plank guesses your next prompt. After an answer, a short background pass works out the most likely thing you are about to type and offers it as grey ghost text on the empty prompt. Tab or Right arrow drops it into the line so you can edit it, Enter sends it as it stands, and typing anything else makes it disappear. It rides the conversation already sitting in the KV cache, so it costs a suffix rather than a fresh read of everything, and it skips itself entirely on the one occasion it would be expensive: a session whose cache would have to be rebuilt from scratch. Turn it off with "suggestions": {"enabled": false}.

A suggestion never runs anything. A line starting with / or ! is thrown away rather than offered, because Enter over a placed suggestion sends it immediately, and one keystroke away from /clear is not a place to be relaxed.

Typed decisions, read straight out of the logits. Plank can now ask the model a multiple-choice question and get the answer back without generating a single token. The question is appended to the conversation already in the cache, one forward pass runs, and the probabilities of the answer letters are read directly from the output layer. No sampling loop, no JSON to parse, and a real number for how sure the model was rather than a guess dressed as one.

The first thing using it is a gate in front of the memory pass. Most turns hold nothing worth remembering, and until now plank spent a full generation discovering that. Now it asks one yes/no question first and only runs the expensive pass when the answer is a confident yes. It is off while its threshold is still being calibrated: "memory": {"gate": true} turns it on, memory.gatePercent sets the bar. If the model is unsure, or cannot answer at all, the pass runs anyway. A gate that is not certain is never allowed to be the reason a memory is lost.

Short turns no longer trigger the memory pass. A four-second exchange is rarely worth a snapshot, a prefill, a generation and a restore, so memory.minTurnSeconds (30 by default) skips it. The turn's span is not thrown away, just deferred: the next turn that clears the floor reads the short ones too.

The prompt stays green while plank works in the background. Prompt prediction and the memory pass are not turns you asked for, and typing during one stops it and starts your turn straight away, so the cursor no longer turns red as though you had to wait. Ctrl-D on an empty prompt during a memory pass quits, too, instead of being quietly swallowed.

/stats: a year of your work, in one panel. A heatmap of the last 53 weeks, four green tones from your quietest day to your busiest, with the figures that go with it underneath: favourite model, total tokens, sessions, longest span, days started, longest and current streak, most active day.

The /stats panel: a 53-week heatmap in four green tones with Mon, Wed, Fri and Sun down the left and month names across the top, a Less-to-More legend under it, then All time Β· Last 7 days Β· Last 30 days, and eight figures reading favorite model DeepSeek V4 Flash, total tokens 11.1m, sessions 718, longest span 14h 15m, days started 50/62, longest streak 16 days, most active day Sep 5, current streak 16 days

It reads the same per-session cache /insights already fills, so it costs nothing after the first run and never calls the model. The heatmap always shows the full year; the figures under it take a range, so /stats 7 and /stats 30 narrow them and re-issuing bare /stats cycles the three. Esc closes it.

Two of the figures are named for exactly what they count rather than what you might wish they counted. Days started counts days a session began, so a session opened on Monday and resumed through Thursday lights one square and not four. Longest span is wall-clock between a session's first and last message, which is not time spent working: resume something a month later and the span is a month.

5.1.10: a quiet rule. The figures a turn floats on the rule under the prompt are cleared the moment the turn ends, and the row is repainted whole so no notch is left where they were. The memory pass keeps its own figures off that rule unless you start plank with --show-memory-stats.

5.1.9: notes are taken at idle, and the status bar holds still. The memory extraction pass no longer holds your prompt. A turn end only snapshots the new part of the conversation into a queue; the reading happens at the next idle moment, with a ✍️ per queued span in the footer and one dim memory completed in 12s line at the end. Type while it runs and it stops, goes back on the queue and your turn starts at once; the retry resumes from the prefill it already did. The figures that used to churn in the status bar (prefill and generation rates, the MTP per-step numbers) now float at the right end of the rule under the prompt, like the session name above it, and the loop-guard and micro-compaction icons and the idle word are gone.

5.1.8: a lighter system-prompt reminder. Every 50K tokens plank reminds the model of its system prompt so a long session does not drift. Until now that meant re-sending the whole tools prompt, thousands of tokens prefilled and then kept in context for the rest of the session. The reminder is now the short form by default: the tool-call syntax, the list of tools, and a line saying the original prompt still applies. "context": {"shortReminder": false} brings the full reminder back.

5.1.7: the extraction pass is on by default. 5.1.6 shipped it off, so you had to opt in. Now a fresh install keeps its own memory from the first session. The price is the same as before: a pause after each tool-free answer while the pass takes a KV snapshot, prefills the new part of the conversation and generates its verdicts. memory.extractEveryNTurns makes it rarer, and "memory": {"autoExtract": false} in settings.json turns it back off.

5.1.6: memory that maintains itself. Until now the two MEMORY.md files only ever changed when you typed /remember or opened /memory. The model now has a remember tool and a forget tool of its own, and an extraction pass (memory.autoExtract, off in 5.1.6 and on from 5.1.7; it costs a generation after each answer) that reads the new part of a conversation and proposes adds, updates and deletions, which plank applies itself. Entries carry a type tag, each type has its own budget for what reaches the prompt, and eviction goes by how often an entry has actually mattered rather than by age, so the oldest fact about you is no longer the first one to fall off the end. Every automatic change is written to ~/.plank/memory-log.jsonl, /memory log reads it back, and /forget <pattern> removes entries after showing you what matched. Nothing rewrites the cached prompt mid-session: model writes land on disk and join the context at the next session start, which is what keeps the whole feature cheap. The design, and what took four rounds of review to get right, is in docs/MEMORY.md.

5.1.5: cargo, npm and go work under the sandbox again. Model-run shell commands are sandboxed to the project and the temp dirs, which is the right default right up until a build has to fetch a dependency β€” because every package manager writes a machine-wide cache outside the project by design. Until now that write was denied with a bare Operation not permitted, which reads like a broken filesystem rather than a sandbox saying no, and it meant cargo build worked only as long as every crate happened to be cached already. The caches are writable now: ~/.cargo/registry, ~/.cargo/git, the rustup download dirs, ~/.npm/_cacache, the Go module cache, ~/.cache, ~/Library/Caches. What is still withheld is anything on your PATH β€” ~/.cargo/bin, ~/.local/bin, /usr/local/bin β€” because a binary installed there is one you later run, so those get the same one-question prompt ~/.plank has always had. cargo install asks instead of failing. This is also why the grant names ~/.cargo/registry rather than ~/.cargo: the parent carries bin with it.

5.1.5: /init is not a loop. Its phases re-read and re-survey the same tree on purpose β€” the interview, the codebase survey and the write all revisit it β€” which is precisely the pattern the loop guards exist to stop. So a setup run you asked for could be refused halfway through for doing exactly what its own prompt told it to. /init now runs with the guards down for that one turn and no longer; a /loopguard you typed yourself still outranks it, in either direction.

5.1.5: a headless run says how long it took. plank -p ... ends with total time: 8.4s. It goes to stderr, so anything piping the reply sees exactly what it saw before, and --ui chart and --ui quiet keep printing their one deliberate thing and nothing else.

5.1.4: --non-interactive is now --ui, and two of its modes print almost nothing. The flag that picked a front end only ever had two answers, on or off, so it becomes one flag with four: --ui tui is the default you already have, --ui console is exactly what --non-interactive was, and the two new ones are for runs you are not reading. --ui chart swallows the turn's output and paints the /toks throughput chart in its place, live, filling in as the model generates, which is what you want when the question is how fast the engine is rather than what it said. --ui quiet prints one line and no more: Prompting. when the turn starts, Started working... when the first token is imminent, done. at the end. Both need a -p prompt. --non-interactive is gone rather than kept as an alias, so a script that used it wants --ui console.

5.1.3: a long read-only investigation is no longer mistaken for a stall. One rung of the loop guard watched a whole turn and ended it after 32 KB of generated output with no file changed. It was built for a real failure β€” a turn whose every individual pass looks reasonable and which has still written nothing fifty minutes later β€” but what it actually measures is output volume, and a perfectly ordinary turn trips it too. Ask plank why a test is flaky, or to explain how a subsystem fits together, and it will read, search, build and reason its way to a real answer without touching a file. That turn was stopped with a notice telling you to narrow a request that was never too wide. The rung is now off unless you ask for it, with "tools": {"noProgressGuard": true}. Every other guard, which watches for genuine repetition rather than for silence, is unchanged and still armed by default.

5.1.2: sentences no longer finish themselves when a tool returns. The TUI renders streaming markdown on a bounded cadence, so highlighting a long code block does not cost a re-render per token. The tail that cadence deferred was only committed when the next token arrived β€” and a model that stops writing to open a tool call sends no next token. So the last few words of a sentence sat off screen for as long as the tool ran, and the sentence appeared to complete itself once the result came back. The deferred tail now goes out on the draw clock instead, in the main transcript, the /btw panel and every sub-agent view.

5.1.0: plank runs DeepSeek V4.1 Flash. It is a family of its own rather than a V4 revision, with its own weights, tokenizer, tool-call spelling and .ds41.kv transcripts that never mix with V4's. Point -m at a V4.1 GGUF and the family, the dialect and the paths all follow from the GGUF's own architecture field; V4 stays the model plank ships and offers to download. V4.1 also has a real reasoning dial, 0 to 100, so there low, medium and max are names for the 25, 75 and 100 the model is actually told, /think 40 works, and the footer shows the number rather than the label.

Two models that needed a flag now just work. A model too large to sit in RAM turns on SSD streaming by itself, printing the arithmetic it used, instead of refusing to open and telling you to go find --ssd-streaming. A draft checkpoint that does not match the model you loaded no longer stops the open either: plank retries without the drafter it chose for you and decodes target-only, and a companion you named with --mtp-model is still never dropped. While a streaming model is loaded the footer carries a πŸ’Ύ, blinking while a pass runs.

Qwen3.8-Flash-Next is retired. Upstream deleted its Metal kernels, so the family, the --qwen flag, its artifact set and its dialect are gone. Existing .qwn.kv files are left strictly alone: a retired model name matches no live family, so nothing lists them, sweeps them or hands them to a DeepSeek engine. They are still on disk, and no longer loadable.

plank gets out of the way when the Mac runs out of memory. Under memory pressure it releases the KV cache and the engine session at a turn boundary, says so, and returns to the same continuation once pressure clears, rather than thrashing or being killed. ⏸ paused: memory on the footer marks the wait so it does not read as a hang.

/toks charts prefill speed as well, beside generation speed on the same rows, because the question a slow pass raises is whether it is prefill-bound or decode-bound.

/toks panel: two side-by-side braille line charts in the theme green. Generation speed on the left reads now 29.5, avg 32.9, min 26.9, max 45.9 tok/s; prefill speed on the right reads now 232.9, avg 440.0, min 42.8, max 2112.0 tok/s

Smaller things. Diff cards are syntax-highlighted rather than flat red and green:

An edit's diff card: added lines on green and the removed line on red, with Rust keywords, strings and function names highlighted inside both, line numbers down the left and a changed-word highlight on && !force !! output opens in its own scrollable panel instead of scrolling away inside the model's. Sub-agents you did not name are alpha, bravo and charlie instead of four identical sub-agent labels. Clicking the footer's brain hides or shows thinking for the session without writing a setting. /hooks off and /skills off are session master switches. And an interrupted model download resumes where it stopped instead of starting over, which used to cost 88 GiB.

One settings bug worth naming. /config tools.<key> reported "saved" and wrote nothing: the whole tools section was missing from the save path, so nine settings including bashNotify quietly reset at every launch. All nine persist now. The toggles that are deliberately session-only, the footer brain and /loopguard, still cannot reach settings.json; /mc still can, because that one is a real preference.

5.0.7 (beta) stops the parent from wearing the sub-agent's numbers. While a sub-agent is working, the main transcript's progress line says Waiting… (for sub-agent <name> to complete), and the live verb, clock, tokens and tokens per second move to that agent's own view β€” ← then Enter from the roster. Until now the main transcript showed whatever pass the engine was running, which made the parent look busy generating when it was only waiting.

5.0.6 (beta) puts micro-compaction on the footer. A wastebasket with a green or red light shows whether it is on, and double-clicking it flips the setting for the session; /mc [on|off] does the same by typing, and both work mid-turn. The loop guards learned two new shapes: reasoning that is drafting the answer rather than deciding what to do β€” a numbered list of findings, or the implementation as fenced code β€” is stopped and told to write it as the answer, and a cycle whose only variation is the list number is caught even though it is not byte-exact. A model that fails to open now says why, naming a dangling symlink's missing target, a truncated install caught against the manifest, or an absent companion sidecar. And every repro dump carries a table of its generation passes, which is how both guard changes were found.

5.0.5 (beta) charts the generation speed. /toks draws tokens per second as a braille line in the theme green, one sample per second of decoding, in the same dismissable panel as /usage. Type it during a turn and it redraws on every status tick, so you can watch the rate move while the model types.

/exit works mid-turn too now: it asks [y/N], then interrupts the turn and leaves once it stops. The loop guard's reasoning budget also scales with the context window instead of stopping at a fixed 16 KiB, after it was caught cutting off a long design that was not looping.

5.0.4 (beta) moves the routing glyph into the window title, and a /init you type mid-session keeps the conversation; only the startup AGENTS.md offer clears afterwards.

5.0.3 (beta) lets a shell job wake the model. Turn on tools.bashNotify and a bash job the model leaves running no longer has to be polled with bash_status: the model ends its turn, you get the prompt back, and when the job exits plank appends a notification with the job's final output and starts a turn to report it. Once per job, never over a line you are still typing. The status bar shows β§— N jobs while anything runs; click it, or type /jobs, for a live job panel that also works mid-turn. The same release fixes a cache regression where every tool call under speculative decoding quietly re-read the whole conversation.

5.0.2 (beta) gives the write tool a live preview. A Writing <path> header, the first five lines of the file, then a … N lines counter that ticks as the file streams and settles on a β”” N lines summary when the write lands. /usage typed during a turn now opens its dismissable panel instead of spilling into the scrollback, the rotating tip sits on its own line while the agent works, and the AGENTS.md startup offer has a "Don't ask for this folder" choice that remembers the answer in ~/.plank/agentsmd-skip.

Plank runs two models now. Qwen3.8-Flash-Next joins DeepSeek V4 Flash: --qwen reads ~/.plank/qwen.gguf and the PLE sidecar it requires from ~/.plank/qwen.mtp.gguf, both expected to be symlinks pointing at whichever build you keep. The flag is only a shorthand for those two paths β€” everything that actually differs for Qwen is read from the model's own architecture, so -m some-qwen.gguf behaves the same way.

Qwen speaks a different tool-call dialect, and plank now understands it. It does not write DSML; it writes <tool_call> / <function=…> / <parameter=…> and expects JSON schemas inside <tools>. Before this every tool call it made came back as invalid DSML tool call β€” one recorded session tried four times and then told the user the harness was broken, which it was.

Speculative decoding has one name: --mtp. Multi-token prediction is one idea with two mechanisms β€” DeepSeek speculates from its DSpark draft checkpoint, Qwen from the MTP block inside its own main GGUF β€” so it is one flag and one switch now. This is a breaking change: /dspark is /mtp, --dspark* are --mtp*, and both the old --mtp PATH and --ple PATH are --mtp-model PATH. The old spellings are gone rather than deprecated, so a stale --mtp x.gguf fails loudly instead of quietly misreading the path.

The footer says which model you are talking to, (local:ds ⚑100%) or (local:qwen ⚑100%). Two local runs in the same directory used to look identical in the one place that answers that question.

Qwen upgrades on its own manifest. qwen.manifest sits beside ds4.manifest with its own staging area, and an install already on disk at the manifest's sizes is adopted rather than re-downloaded β€” nobody re-fetches 107 GB they already have.

The model can ask to compact. A compact tool lets it summarize the conversation and carry on from the summary. It is told not to reach for it unprompted: only you know whether the detail a summary drops still matters.

A reasoning loop can finally be stopped at any length. Confirming that a block of text is repeating meant re-searching the tail for several copies at once, which needed four periods of window β€” so the check that stops a pass only ever saw cycles up to a quarter of the window, while the check that warns reached half of it. A cycle in that gap warned forever and could never be stopped: one saved dump ran a 2.4 KB cycle seventeen times over 41 KB with the footer flagging a loop the whole way. The guard now holds on to the block it first matched and checks each new copy as it arrives, which needs two periods instead of four and puts no ceiling on the period. Replayed against that dump, it stops at 12.9 KB rather than running to 46.7 KB.

What you type during a turn stays where you can see it. A prompt typed while the model is working used to be announced in the scrollback and then scroll away while it waited its turn. It now sits in a small region pinned directly under the status bar until the turn takes it, and it survives a window resize. It also survives a failure: several ways out of a turn used to throw the queued text away while leaving its row on screen, so the next queued prompt adopted the stale row and the transcript no longer matched what you had read β€” and when a remote controller was attached, remotely queued lines were dropped outright.

The question panel has a way out. When the model asks you a multiple-choice question, every row used to be one of its own options; if you thought the question itself was wrong, your only exits were Esc, which tells the model to carry on using its own judgment, and Ctrl-C. There is now a Chat about this row under the options, and choosing it tells the model to stop and wait for you. plank's own confirmation prompts β€” leaving plan mode, granting a write or a web fetch β€” do not offer it, since talking it over is not an answer they can act on.

Sub-agents report while they work. A long sub-agent pass used to sit on a frozen token count and a climbing clock, which read as a hang; it publishes the same progress the main pass does now. Its pane shows diff cards and activity lines instead of raw tool banners and follows your thinking setting, and the roster highlights the live row, walks with the arrow keys, and lets a finished row leave a minute after it ends. /usage draws a session usage panel for local engines, in the shape the hosted providers use.

Looking at an image no longer sends the model hunting for a command-line flag. plank reads images through the model itself, in process β€” there is no separate viewer to run. But when the vision encoder was not loaded, the view_image tool answered with the wording plank's C reference uses, which names a ds4-agent --vision flag. plank has no such flag, so the model took the message as an instruction, tried to run it, failed, went looking through plank --help, and concluded that seeing images was broken. The refusal now says what is actually true and tells the model not to go shelling out. An encoder that fails to load is reported when plank starts, rather than several turns later as a confusing tool error, and an image observation that fails to reach the model is written to ~/.plank/errors.log instead of passing silently as text that claims a picture is attached.

4.1.2 (beta) stops the sandbox asking about reads. Any bash command that so much as mentioned ~/.plank, including cat ~/.plank/settings.json, used to raise "Allow it to write there?", because the sandbox profile is built before the command runs and the trigger was a text match. A command made only of known read-only utilities with no redirect now runs without the question. Anything that could write still asks, and the sandbox itself is unchanged.

A turn can no longer spend an hour repeating one thought. This release started from a measurement rather than a hunch. A small feature request that Claude Code finishes in a few minutes took plank two hours, and the saved transcript showed why: not a hundred tool calls, but one thinking block of 149K characters in which three paragraphs repeated 263 times until the token cap. The repetition guard that already protected /insights now watches the reasoning of every generation pass, and when the tail starts cycling the pass is stopped and the model is told to act on the plan it already has.

The same request was then rerun against the same commit after each fix, and the numbers are in FINDINGS.md: from no result in two hours, to a finished and tested change in 23 minutes, to 16 minutes. Three things did most of that work. Reasoning now defaults to low effort, which turned a stuttering list of "maybe" thoughts into numbered plans and cut a third off the run; --think gives you the old medium level back. The edit tool understands CRLF files, so a Windows-style source file no longer fails every multi-line edit and quietly converts single lines to LF. And search skips build output, so a search over . no longer returns the target/package/ copy of every file ahead of the real matches.

The system prompt has a working style. A plank-owned section after the tool schemas, outside the byte-for-byte C parity base, tells the model to batch independent tool calls in one stanza, explore briefly and then act, pick the smaller of two plausible designs rather than deliberate, edit straight from search context, stay inside the requested scope, and review diffs one file at a time. Each line was earned by a specific turn wasted in those reruns.

/memory edits every memory file at once. It builds one buffer from your user and project MEMORY.md files, each between markers naming its scope and path, and opens it in the built-in editor. On save the buffer is split back along the markers and only the files whose text changed are written. A note typed under the last section joins the section above it, and deleting a section's markers leaves that file alone.

AGENTS.md is the only instructions file plank reads. CLAUDE.md is no longer a silent fallback. Instead, an interactive start in a project that has a CLAUDE.md and no AGENTS.md links the one to the other and says so, and a project with neither asks whether you want /init to write one. Headless runs do none of this; they also save their transcript now, so a slow run can be analysed afterwards, with --no-session to opt out.

From 4.0.0, the day before: the prompt's idle/busy cursor is drawn into the frame instead of sent as an OSC 12 escape, so it finally shows up in Warp and the other terminals that ignored the recolouring; --ctx takes 128k and 1m; and model upgrades are driven by a versioned manifest that asks before downloading, verifies as it streams, and installs at the next launch rather than under a running session.

v3.6: vision, self-compacting turns, a codebase review

v3.6.0 shipped in early September, with the beta channel on 3.6.3.

Long turns compact themselves now. Plank asked whether the context needed compacting before a turn started, and then never asked again while that turn ran β€” but a turn appends a tool result on every round, so a session that reads, edits and tests its way through a dozen rounds could sail past the soft limit halfway through and keep generating against a window that was already full. The check now runs at the top of every round, which is what the C reference plank was ported from has always done.

Plank can see. Paste a screenshot and ask what broke. Vision has been in the box for a while, but it never actually worked: four separate faults in the image path sat on top of each other, and between them they either crashed the process outright with malloc: pointer being freed was not allocated or, worse, answered confidently about an image the model had never been shown. A screenshot of a browser window came back described as a close-up photograph of a person's face, which is the sort of failure that looks like a bad model rather than a bad pointer.

All four are fixed. The engine takes ownership of the buffer it is handed and frees it itself, so it now gets a copy of its own instead of a pointer into memory Rust was also freeing. An image already in the conversation keeps the span the engine built for it rather than having one reconstructed on the next turn, which could never have worked, because the engine rewrites that span as it consumes it. The encoder's metadata is read before the call that zeroes it, so an image no longer arrives with an empty layout and gets discarded along with the message text wrapped around it. And the prefill chunk was raised past the 384 token cap on an image block, which the engine refuses to split, so anything larger than a thumbnail no longer fails the whole prefill.

What that adds up to: paste a screenshot of a stack trace, a failing UI, a diagram or a photo of a whiteboard, and the model looks at the picture. The encoder lives beside the main model at ~/.plank/ds4flash.vision.gguf, is fetched on first launch if it is missing, and runs on the same Metal device, so no pixels leave the Mac. Images are encoded within a budget of 384 visual tokens, so crop to the part that matters when you want small text read, and reach for ocr-mcp when you want a dense page transcribed rather than described.

A whole-codebase review, and the fixes that came out of it, shipped in v3.6.0. The one you will feel first is that the sandbox now means what it says: write and edit are held to the same boundary as the shell, so a path outside the working directory, the temp roots and your writablePaths is refused with Tool error: write path escapes workspace, where before the model could route around the bash sandbox simply by choosing the file tool instead. A project's .plank/sandbox.json can now only tighten the policy, so a cloned repository cannot switch your sandbox off. plank serve got the same treatment: binding off loopback with no token now refuses to start unless you pass --insecure on the server side, bodies are capped at 16 MiB, idle connections time out, and shared-engine sessions are keyed per client so many turns from one laptop reuse one warm session. Underneath, every bash job, ! command, MCP server and hook runs in its own process group and is killed as a tree, so a timed-out sleep 600; echo ok or a cmd | tee pipeline no longer outlives plank, and Ctrl-C or Esc on a running command actually reaches it and reports exit 143. And two slash commands the TUI had quietly left to the plain REPL now work in both: /rate [+|-] [note] is in the completion popup, and /config <key> <value> sets the value directly, opening the form only when you type a bare /config.

Every sub-agent gets a console window of its own. Delegating work to sub-agents made plank quieter and less legible at the same time: the parent told you it had handed off, and everything after that happened somewhere you could not see. The debug console now gives each sub-agent its own window, titled plank:<session>:subagent-<n> and numbered in the order they start.

Four tdk windows tiled: the parent session plank:sassy-washington bottom-left summarising both results, and subagent-1 and subagent-2 on the right, each streaming its own Python and its own answer

When a block of agent calls fans out, the slots run concurrently and you can read them side by side β€” above, two sub-agents working the same question in different formats, each with its own code and its own conclusion, while the parent window collects both into one answer. Each window retires when its sub-agent finishes. A single agent call gets a window too, so the serial case is no darker than the parallel one.

While fixing that, one long-standing annoyance went with it: a malformed tool call used to freeze a console window for the rest of the session, so it sat there showing nothing while plank quietly recovered and carried on. It now prints the error and keeps going. Both need tdk 0.2.1 or newer.

The footer counts what you have changed. The TUI's top row has always told you which tree you are in β€” directory and branch, held still while everything else churns. It now also tells you what you have done to it: πŸ“„ 3 Β· +128 -41, files touched, then lines added in bright green and lines deleted in bright red. Staged and unstaged work are counted together and untracked files are included, so a file you edited and then added appears once, not twice. A clean tree shows nothing at all.

/insights now recommends the plank features you are not using. The report already knew how you work; a new Features to try section turns that into two or three concrete suggestions β€” a skill for the routine you retype every week, a PostToolUse hook for the lint you keep running by hand, a subagent for the sweeps you do serially β€” each with a ready-to-run snippet rather than a description. It reads your installed skills, templates, subagents, hooks and MCP servers first, so it suggests what is missing instead of what you already have. See Slash commands.

The thinking you hid is one glance away, in a second window. With showThinking off the scrollback stays about the answer β€” but the reasoning is still worth watching while it happens, and a log file only tells you afterwards. plank mirrors its whole raw model stream to tdk, a text-mode viewer that renders it in its own window: thinking dimmed above the answer, code highlighted, tool calls as banners.

tdk showing a plank session: the model's thinking in dim grey above its answer in white, in a text-mode window titled plank:sneezy-einstein

Each session gets a window titled plank:<session-name>, matching the name above your prompt, and the window and its scrollback survive plank exiting β€” restart and the new run appends below a -- reconnected -- rule. It is entirely optional: with nothing listening plank connects to nothing, says nothing, and behaves exactly as it always has. brew install aovestdipaperino/tap/tdk.

plank tells you where every setting came from. Settings arrive from five layers β€” built-in defaults, plugins, ~/.plank, the project's ./.plank, and CLI flags β€” and "I set that and nothing happened" used to mean reading code. plank --dump-config now prints each effective key with the layer that won and the layers it beat, and /config --resolved does the same inside a session, including which plugin won a contested skill or agent name. See Configuration.

Loop guards for the failure local models actually have. A stale edit anchor sends a model into read/edit/read/edit, and small models often do not notice. On the third identical call the result now carries "you have called this tool with these arguments 3 times; the result has not changed". Nothing is blocked β€” a legitimate third read exists, and blocking on a guess would be worse β€” and polling an async job is exempt, because a poll looks exactly like a stuck loop. Set tools.callTimeoutSec and the model is also told when a call overran its budget, so a hung test suite stops being invisible.

Large tool output is no longer a dead end. Ask about a 5 MB build log and read used to truncate with nowhere to go. The full payload now goes to ~/.plank/spill/, the model gets a bounded preview plus a continue_offset it can page through with more, and /export still sees everything the tool returned. The same bound applies to oversized MCP results, which previously had no cap at all β€” a single chatty server could fill your context in one call.

Context reclaims itself between turns. After a dozen large reads those bodies are dead weight. plank now clears the old ones at end of turn β€” keeping the newest three, anything small, and everything belonging to the task you are on β€” with no model round-trip and no summarising. It holds off unless it would reclaim at least 4 KiB, because rewriting the transcript invalidates the KV prefix and an eager pass costs more than it saves.

/search across your own sessions. You remember fixing a Metal crash a few weeks ago but not how. /search metal finds the session, shows the matching snippet and offers /resume; --all widens beyond the current project. It is deliberately compaction-proof β€” long sessions are both the ones worth searching and the ones that get compacted, so the index keeps conversation the transcript has since dropped.

Goals that outlive the session. /goal --max 5 make the failing test pass runs a loop that adjudicates its own progress and stops on a verdict or the cap. The objective is durable state: it survives /save, /resume and /compact, so you can come back tomorrow and still see what it was pursuing and how far it got.

/rate records what worked, somewhere the model cannot see. A rating goes to a sidecar, never the transcript, context or KV β€” if the model could read your ratings it would start optimising for them and the signal would be worthless. /insights turns a week of them into satisfaction over time plus the notes on the turns that went wrong.

Three new tools, on by default. recall gives the model that same session history, so it can look up a past decision instead of guessing or interrupting you. fanout runs several independent subtasks and joins their reports in a fixed order, with optional throwaway-worktree isolation when subtasks edit files. run_code batches a few pre-decided operations β€” read, glob, edit, bash β€” into one turn instead of a round-trip each; every operation is routed through the normal tool dispatch, so the sandbox, the ~/.plank write grant and your PreToolUse hooks apply exactly as they would to a bare call. Switch any of them off with tools.recall, tools.fanout or tools.runCode.

plank signs the commits it writes. When the model creates a git commit, the message now ends with a blank line and the single line --Co-Authored by Plank (https://plank-agent.dev), so a git log months from now still says which commits came out of a session rather than out of your fingers. It is a system-prompt instruction, not a hook, which means the model can be told to leave it off for one commit and your repository conventions still come first. If you would rather it never appear, set "git": { "signCommits": false } in settings.json or run /config git.signCommits false, and the instruction is gone from the prompt entirely. See Configuration.

DSpark speculative decoding is on by default. The auxiliary draft checkpoint used to need --dspark; now it is the default and --dspark-off is how you get target-only decode. Speculation only engages at temperature 0, so a bare plank samples argmax: pass --temp if you want sampling back, and --dspark-off leaves the old 0.6 default in force. The support model is still fetched on demand the first time it is needed.

The exit summary says where the session went. It used to end with a peak prefill rate and a peak generation rate β€” one lucky pass each, which tells you nothing about the run you just had. Now every model that ran gets a line: time spent prefilling and time spent generating, each with the session's average rate, and time spent running tools.

avg deepseek-v4-flash  prefill 12.3s (1420.5 tok/s)  Β·  generation 45.2s (38.1 tok/s)  Β·  tools 8.4s

That last number is the one nobody was measuring, and a turn that felt slow is often not the model at all.

The think segment shows the router working. Two braille cells beside the reasoning level re-roll on every decoded token, standing in for the mixture-of-experts routing. Being straight about it: the real selection never leaves the GPU on the Metal path, so the pattern is derived from the token id. It is honest about sparsity, about routing changing every token, and about the same token lighting the same dots β€” and it does not know which experts. The reasoning level itself is now colored by how hard the model is thinking, red for max down to grey for off.

The decode rate stopped lying on long prompts. It was timed from the start of the generation call, so it divided tokens by decode time plus prefill plus the wait for the first token, and opened far below the real rate. It is measured from the first token out now.

Greedy chain decode on Metal. At temperature 0 a run of argmax tokens decodes with the next token id kept on-device, dropping the per-token GPU sync and logits readback. Output is bit-identical. Off on M5, where it measures slightly slower than the plain path.

The --dspark footer reads 1.5t/step, not 1.5x. It was always tokens committed per speculative step, and that is not a wall-clock speedup: on Metal it sits above 1.0 on runs that decode slower than plain decode. Calling it a multiplier was the bug.

plank can read your screenshots. Image pasting is on by default now, and paired with the ocr-mcp server the model can act on what you paste: it calls transcribe_image on the cached path and gets the text back. Screenshot a stack trace, paste it, ask what it says. Transcription runs on your own machine against a 0.9B OCR model, so no image leaves the laptop and there is no API key. Install it with brew install llama.cpp && cargo install ocr-mcp, register it in .mcp.json, and see Extending plank for the weights. Pasted images are also cached byte-for-byte now: the old downsampling was inherited from an image-upload limit plank never had, and it only threw away the pixel density an OCR tool needs.

Plugins load. A plugin is one directory bundling skills, agents, templates, hooks, an .mcp.json and a settings.json, contributed to a session as a unit. plank picks them up from ~/.plank/plugins/dev/, from ./.plank/plugins/, or from a repeatable --plugin-dir for the session you are in, and reads both its own spelling and Claude Code's. A plugin contribution is always addressable as <plugin>:<name> and keeps the bare name only when nothing else claims it, so your own skills and agents never lose theirs. Plugin settings sit below your own files, and /plugins shows what loaded, what each one contributes and every warning. There is no installer and no marketplace yet β€” you place the directory yourself. See Extending plank.

v3.3.0 is out, and the beta channel has opened on 3.3.1. The patch number is still the channel: .0 is stable, anything above it is beta.

Your session has a name from the first frame. The memorable adjective-celebrity name used to be minted when a session was first saved, so until you quit there was nothing to call the conversation you were in. It is minted at the start now, and floats at the right end of the rule above the prompt. /rename <name> changes what later saves use without touching what is already on disk, so the earlier file stays resumable under its old name. Resuming a session no longer replays plank's own scaffolding at you either β€” the agent instructions, memory, git status and date that open a session are sent to the model, not typed by you, and they stay out of the transcript you read. See Sessions.

/kvcache shows the cache as the tree it really is. Every KV snapshot on disk now carries a small metadata file recording what it is, which snapshot it was built on, the model and reasoning level behind it, its size, how many times it has been reused, and when. /kvcache draws that as a tree you can walk with the arrow keys: p pins an entry so nothing will ever sweep it, d deletes one, g sweeps now.

The cache expires on age and is capped on size. Snapshots used to be kept only for the current system prompt and project, and every sibling was deleted, so switching model or reasoning level and back paid a full system-prompt re-prefill each way. Now they expire on time since last use (14 days for a conversation, 30 for a shared checkpoint) and a 20 GB ceiling evicts the least recently used beyond that. Several system prompts coexist for as long as you are using them. See Sessions and Configuration.

/open [path] edits an existing file in plank's own editor: Ctrl-S saves, Esc discards. Bare /open reopens the last file a tool call touched, which is usually the one you wanted to look at.

One session, several engines

Cross-engine sub-agents. A subagent definition can name the engine it runs on, independently of the main agent: a provider: and model: in its frontmatter, with an optional base URL and the name of the environment variable holding the key, so the file stays committable. provider: local names the local engine specifically, so a hosted main agent can delegate to the model on your own Mac. /agent shows each definition's engine and the variable to set when it is missing.

Git worktrees. A session can move itself into an isolated worktree and back out again, so an agent can work on a copy of the repo without touching yours. Subagents can each get their own with isolation: worktree.

DSpark speculative decoding, behind --dspark. DeepSeek's auxiliary draft checkpoint (~5.6 GB on top of the model) proposes tokens the main model verifies in batches. It downloads and resumes the same way the model does.

A live agent roster under the status bar. Instead of one sub-agent's output in a hidden pane, every run gets a row: what it is doing, how long it has been at it, and what it has spent. ← on an empty prompt steps into it, Enter opens an agent's output in full, Esc comes back. A fan-out shows every agent at once, each with its own buffer, and what comes back into your transcript is the agent's answer with its thinking stripped out. See The agent roster.

Drive it from somewhere else

/remote-control, or /rc, starts and stops a remote-control server from inside a running session, and /grant approves a client that asks for control. The old --control* flags are gone.

The bundled web client is a real front-end now. It wears plank's own dark theme, streams the turn as it happens, and tells you unmistakably when the connection drops. Attached clients get the end-of-turn notification too, so you can walk away from the laptop and still be told when it is your move.

Reasoning you can dial

/think off | low | medium | max, with --think-max and friends on the command line. low is experimental and cheap; max prepends a reasoning-effort preamble and is the one worth reaching for on a hard problem. The status footer shows which level is in force as a 🧠 med segment, because it changes both cost and answers and used to be invisible.

The terminal got more useful

! and !!. A bare !command runs a shell command and hands the result to the model, which is what you almost always wanted; !!command keeps the output to yourself.

/btw <question> answers beside the running task instead of freezing it. The aside runs on a fork of the session in a split panel, so the real conversation is never touched and neither side waits for the other.

PDFs are readable. read on a .pdf converts the document to Markdown, with OCR for a scanned one.

/insights builds a personal usage report over every session you have ever saved and writes it to an HTML file: where the time went, which tools you lean on, how the model actually behaves for you.

A prompt editor on Ctrl-G, built in rather than shelling out to $EDITOR, for when the thing you are about to ask has outgrown one line.

/compact [instructions] compacts the conversation now rather than waiting for the automatic pass, and an argument steers what that one summary keeps. Compaction shows its progress in the status bar, and Ctrl-C interrupts it.

A screensaver, and an arcade. After a few idle minutes plank goes to a starfield, matrix rain, or a couple of minions, chosen at random and configurable. There are also games that run over a live turn, which is either a feature or a confession (chapter 11).

Sessions became a tree

/tree, /fork, /clone. A session is a tree of messages rather than a line. /tree draws it and numbers the fork points; /fork <n> rewinds to just before one of your prompts and keeps everything after it as a sibling branch, so you can try a different approach without losing the first; /clone freezes the current branch and continues on a copy. All of it is shaped so the cache is reused rather than rebuilt.

/export [md|html] renders the transcript to a shareable file. The HTML is standalone.

Prompt templates. Markdown files in ~/.plank/templates become commands, with {{variable}} substitution.

MCP over Streamable HTTP. An .mcp.json entry with a "url" speaks to a remote MCP server, alongside the stdio servers that were already supported.

Things that were quietly broken

A resumed session used to re-prefill its whole conversation. /new and /clear used to rebuild the system-prompt cache from scratch. A dropped network could hang a turn forever with Ctrl-C doing nothing. A compaction that produced no usable summary could destroy the transcript. Several shapes of tool call the model emits were parsed wrongly and died. All fixed, and each one has an entry in the changelog explaining what actually went wrong, if you like that sort of thing.


New here instead? Start with the user guide, or install it and get going.