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.

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.

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.

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.

Smaller things. Diff cards are syntax-highlighted rather than flat red and green:
!! 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.

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.

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.