8. Configuration
Precedence
Each layer overrides the one before it:
built-in defaults โ ~/.plank/settings.json โ ./.plank/settings.json โ environment โ command-line flags
The rule of thumb: settings.json holds
preferences, flags hold per-run choices.
settings.json
Hierarchical like the MCP config: ~/.plank/settings.json
applies everywhere, ./.plank/settings.json in the working
directory overrides it key by key. Everything is optional โ the file
need not exist, and any subset of keys works.
{
"engine": { "model": "~/models/ds4.gguf", "threads": 8,
"backend": "metal", "power": 80, "ctx": 262144,
"thinkingToolCalls": true },
"ui": { "respectGitignore": true, "popupRows": 15, "indexRefreshSecs": 5,
"historySize": 512, "showToolCalls": false, "showToolResults": false,
"showThinking": true, "notifications": "always", "notifyAfterSecs": 10 },
"safety": { "sandbox": true, "btwSuspend": true },
"mcp": { "timeoutSecs": 30 },
"ask": { "maxOptions": 7 },
"update": { "check": true },
"agents": { "autoRoute": true, "maxParallel": 32 },
"git": { "signCommits": true },
"worktree": { "sparsePaths": ["src", "docs"],
"symlinkDirectories": ["target"], "isolateAgents": false }
}
Edit it in-session with /config (an interactive form) or
one key at a time:
/config ui.showThinking false
Changes write ./.plank/settings.json and apply
immediately. The one-key form behaves the same in the TUI and in the
plain REPL; only a bare /config opens the interactive
form.
engine
| Key | Default | What |
|---|---|---|
model |
the catalog default, ds4vision |
an engine name from engines.json, or a model file path
(~ expanded). Same as -m. |
threads |
engine default | worker threads. Same as -t. |
backend |
platform default | metal, cuda, or cpu. Same as
--backend. |
power |
unset | GPU power cap percent. Same as --power. |
ctx |
1048576 | context window in tokens. Same as -c. |
thinkingToolCalls |
true |
dispatch tool calls emitted inside the thinking block.
false for strict ds4 parity. |
ui
| Key | Default | What |
|---|---|---|
respectGitignore |
true |
whether @ completion honours .gitignore
for untracked files |
popupRows |
15 | rows the @ completion popup offers |
indexRefreshSecs |
5 | how long the file index is trusted before a rebuild |
historySize |
512 | prompt history entries retained |
showToolCalls |
false |
show the model's ๐ ๏ธ tool-call banners |
showToolResults |
false |
echo tool result text into the scrollback |
showThinking |
true |
render thinking (dimmed) in the scrollback |
notifications |
always |
always, unfocused, or
never |
notifyAfterSecs |
10 | minimum turn duration before a completion notification |
crtOff |
true |
CRT power-off animation on clean TUI exit |
reducedMotion |
false |
collapse every animation to a static fallback |
screensaver |
1m |
idle delay before the screensaver: 1m, 2m,
5m, never |
screensaverFace |
matrix |
which screen it shows: matrix, starfield,
minions, or random for a coin flip each
time |
easterEggs |
true |
whether the arcade commands exist at all |
builtinEditor |
true |
Ctrl-G uses the built-in editor; false
shells out to $EDITOR |
None of showToolCalls, showToolResults, or
showThinking change what the model receives โ only what you
see.
safety
| Key | Default | What |
|---|---|---|
sandbox |
on (macOS) | default for the bash write sandbox. Same as --sandbox /
--no-sandbox. |
btwSuspend |
true |
default for /btw mid-generation suspend |
tools
| Key | Default | What |
|---|---|---|
tools.editUpto |
by model | whether the edit tool takes [upto] anchored old text
and the prompt teaches it. Unset, it is off for DeepSeek and Qwen (as in
ds4, whose V4 Flash checkpoint struggles with anchored edits) and on for
Gemma. Changes the system prompt, so it takes effect in a new
session. |
tools.bashNotify |
false |
wake the model when a bash job it left running finishes, instead of
having it poll with bash_status. Adds one sentence to the
system prompt, so it takes effect in a new session. See Background jobs. |
tools.loopGuards |
true |
arm the loop guards (/loopguard flips it for the
session) |
tools.noProgressGuard |
false |
also stop a turn that generates 32 KB of output without changing a
file. Requires tools.loopGuards; see The no-progress
budget. |
tools.recall, tools.fanout,
tools.runCode |
true |
offer the recall, fanout and
run_code tools to the model |
tools.remember |
true |
offer the remember and forget tools, which
write memory to disk for the next session. See Memory. |
context
| Key | Default | What |
|---|---|---|
context.microcompact |
true |
let micro-compaction clear old tool-result bodies in place to
reclaim context. /mc off is the session equivalent. |
context.shortReminder |
true |
make the system-prompt reminder plank re-injects every 50K tokens
the short form: tool-call syntax, tool names, and a pointer to the
original prompt. false re-injects the whole tools prompt as
the C reference does. |
context.resumeAcrossCompaction |
false |
when a reply runs into the context window, compact and continue it in the same message instead of ending the turn cut short. The summary reads the start of the reply and the model carries on from its last words; the reasoning budget spans the whole reply. Experimental: checked on DeepSeek V4 Flash and Gemma 4 E4B, off by default for now. |
memory
| Key | Default | What |
|---|---|---|
memory.autoExtract |
true |
queue the extraction pass at the end of turns with no tool calls; it runs at the next idle moment and a typed prompt interrupts it. It costs a generation and a KV snapshot each time, at idle. |
memory.extractEveryNTurns |
1 |
run the pass only every N eligible turns; 0 is treated
as 1 |
memory.budgets |
4096 / 4096 / 6144 /
2048 |
bytes of user, feedback,
project and reference entries rendered into
context. Hand-edit only; not exposed in /config. |
mcp, ask,
update, agents
| Key | Default | What |
|---|---|---|
mcp.timeoutSecs |
30 | how long an MCP server has to answer before it is considered dead. Raise it for a slow-starting server โ one that misses the deadline is dropped along with all its tools. |
ask.maxOptions |
7 | most options the ask tool may offer in one question
(minimum is fixed at 2) |
update.check |
true |
check GitHub Releases at startup for a newer version |
agents.autoRoute |
true |
let the model pick a subagent definition on its own |
agents.maxParallel |
32 | how many subagents may run at once (capped at 32) |
git
| Key | Default | What |
|---|---|---|
signCommits |
true |
ask the model to end each commit message it writes with a blank line
and --Co-Authored by Plank (https://plank-agent.dev). Set
it to false and commit messages follow your repository's
conventions and nothing else. |
kvcache
How long plank keeps the KV snapshots under
~/.plank/kvcache/, and how much disk they may occupy in
total. Ages are measured from a blob's last use, so a checkpoint you
keep hitting never expires.
| Key | Default | What |
|---|---|---|
ttlSessionDays |
14 | days a session's KV payload survives after its last use. Expiring one costs a re-prefill of that conversation the next time you resume it, nothing more. |
ttlTierDays |
30 | days a system-prompt or project-context checkpoint survives after its last use. These are the expensive ones to rebuild, so they get the longer window. |
maxBytes |
21474836480 (20 GB) | hard ceiling on the whole cache. Once the age-based pass is done,
anything still over budget is evicted least-recently-used first.
0 means no ceiling. |
Three things always survive both passes: anything you have pinned in
/kvcache, the chain the current launch is actually using,
and any checkpoint that something newer still builds on. A budget is a
target rather than a licence, so if everything left is protected plank
stays over budget instead of deleting something it should not.
worktree
| Key | Default | What |
|---|---|---|
sparsePaths |
[] (everything) |
cone-mode sparse-checkout paths for a new worktree. Worth setting for a repository large enough that a second full checkout is painful. |
symlinkDirectories |
[] |
directories linked from the main checkout instead of duplicated,
e.g. target or node_modules. A name that could
climb out of the worktree is ignored. |
isolateAgents |
false |
give every subagent its own throwaway worktree. Off because a
checkout per agent costs disk and time, and the work then has to be
merged back; turn it on per-definition with
isolation: worktree instead if only some need it. |
Two things the file deliberately will not do
- No secrets.
./.plank/settings.jsonsits inside your working tree and is easy to commit by accident, so there is no API-key setting. Keep keys on--api-keyor the provider's environment variable. - No per-run choices.
--prompt,--ui,--ui-remote,--trace,--chdir,--seed,--worktree, and the serve/control options describe one invocation, not a preference, so they have no settings key.
When it goes wrong
A broken settings file never stops plank from starting. Malformed
JSON, a wrongly-typed value, an unknown key, an unrecognised backend
name โ each falls back to that key's default. (The same bad name passed
to --backend is an error: a flag is an explicit
instruction, a config file is a preference.)
Because a settings file can quietly move you off Metal or shrink the context โ both of which show up only as "plank got slow" โ plank prints one startup line naming what is in force, listing only settings actually in effect. With no settings file, or one that changes nothing, there is no line at all.
One limitation: settings come from the directory plank launches in,
so project settings do not follow --chdir.
Command-line flags
plank --help prints the full list. The ones you are most
likely to want:
Model and engine
| Flag | What |
|---|---|
-m, --model NAME|PATH |
run a named engine (ds4vision, ds41,
qwen, gemma4-e4b, gemma4-12b, or
one of yours from ~/.plank/engines.local.json) with its
companions, or load a GGUF file on its own |
--model:NAME |
run a named engine, and fail rather than read NAME as a
path |
--pick-engine |
open the engine menu at launch even when the selected engine is installed |
-t, --threads N |
worker thread count |
--backend NAME |
metal, cuda, or cpu |
--metal / --cuda / --cpu |
the same, as switches |
--power N |
GPU power cap percent (1..100) |
-c, --ctx N |
context window in tokens, or with a k/m
suffix: -c 128k, -c 1m. Without it, Gemma
sizes its window from the RAM |
-n, --tokens N |
maximum tokens to generate (default 50000) |
--quality |
quality mode |
--warm-weights |
touch all weights at load |
Sampling and reasoning
| Flag | What |
|---|---|
--temp F |
temperature (0..100) |
--top-p F |
nucleus threshold (0..1) |
--min-p F |
minimum-probability threshold (0..1) |
--seed N |
RNG seed |
--think-low (default) / --think /
--think-max / --nothink |
reasoning effort |
Session and mode
| Flag | What |
|---|---|
-p, --prompt TEXT |
run one prompt and exit |
--ui MODE |
front end: tui (default), console
(headless), chart (headless, prints only the live
/toks chart) or quiet (headless, prints only a
start and a finish note); chart and quiet need
-p |
-sys, --system TEXT |
override the system prompt |
--chdir PATH |
change working directory before starting |
--worktree NAME |
start inside an isolated git worktree of this repository |
--worktree-pr N |
base that worktree on pull request N (implies
--worktree) |
--trace PATH |
append a trace log |
--prefix-file PATH |
condition the session on scripted USER: /
ASSISTANT: turns placed after the system prompt; see Conversation prefix. Wins over a
profile's prefix. |
-h, --help [topic] |
help, optionally on one topic |
Safety and extensions
| Flag | What |
|---|---|
--sandbox / --no-sandbox |
bash write sandbox (on by default on macOS) |
--disable-btw-suspend |
queue an in-pass /btw at the next boundary instead of
suspending |
--mcp-config FILE |
local MCP config (default ./.mcp.json) |
--hooks on|off |
whether plugin and settings hooks run (default on); off also injects
no SessionStart context |
--tools on|off |
whether the model gets tools (default on); off sends no tools prompt, starts no MCP servers or WASM tools, refuses any tool call and skips the session-start context |
--minimal-prompt implies both off; an
explicit flag wins whichever order you give them in.
Speculative decoding
| Flag | What |
|---|---|
--mtp |
speculative decoding (multi-token prediction), on by default |
--mtp-off |
disable speculative decoding (target-only decode) |
--mtp-model PATH |
the loaded model's companion GGUF: the DSpark drafter for DeepSeek V4 |
--mtp-confidence F |
pruning threshold, 0..1 (0 forces fixed
five-token blocks) |
--mtp-strict |
load the drafter but keep target-only decode, for comparisons |
Speculative decoding is on by default under one
name, --mtp. DeepSeek V4 Flash uses its auxiliary DSpark
draft checkpoint: it proposes up to five tokens ahead and the main model
verifies them, committing only the prefix it agrees with, so one
verification pass can advance the stream by several tokens. DeepSeek
V4.1 Flash has no drafter upstream, so plank never pairs one with it and
decodes target-only there. --mtp-off turns speculation off
explicitly.
On V4 the support model (~5.6 GB) needs no flag of its own: it is the
ds4vision engine's mtp companion, resolves to
~/.plank/ds4vision.mtp.gguf, and is offered for download
through the same resumable path as the main model, unless
--mtp-model names one. A model loaded by a bare path has no
companions, so speculation there needs --mtp-model.
A drafter plank paired for you is no longer able to stop a model from
loading. The engine refuses to open a model at all when the draft
checkpoint does not match it, so a checkpoint the default drafter does
not fit used to fail until you found --mtp-off; plank now
retries the open once without the companion it chose and reports the
original error only if that fails too. A companion you named with
--mtp-model is never dropped. When speculation turns out
not to run, the temperature you would have been sampling at is restored
rather than left pinned at 0.
Verification is argmax, so proposals are only used at
--temp 0; sampled decoding ignores them. Whether it pays
depends on the engine build, the quant and the machine: on an M5 Max it
was a 0.71ร slowdown until the Metal verifier was pipelined
upstream, after which the same measurement read 1.19ร. The peak rates in
the exit message are the way to check on your own hardware.
Advanced engine tuning
--mtp PATH, --mtp-draft N,
--mtp-margin F configure multi-token prediction with a
draft model. --ssd-streaming and its companions
(--ssd-streaming-cold,
--ssd-streaming-cache-experts,
--ssd-streaming-preload-experts) stream experts from SSD
instead of loading them resident, which is how you run a model that does
not fit. You rarely need to reach for --ssd-streaming
yourself any more: plank measures the model file at startup and turns
streaming on when it exceeds 80% of installed RAM less the engine's
context buffers, printing the calculation it used. Passing the flag
yourself skips that decision.
--simulate-used-memory <N>GB pretends memory is
already used, for testing those paths. Directional steering has a
section of its own, Steering, below.
Steering
Directional steering nudges the model's activations along a
direction, layer by layer, so it leans towards one kind of answer:
terser, more cautious, less likely to refuse. plank keeps every
direction you have in one file,
~/.plank/models/vectors.json, and you choose one by
name:
plank --dir-steering succinct
The file is a JSON array with one entry per model, each listing its
named directions. A direction's value is base64 of the raw
little-endian f32 matrix the engine loads, one unit vector
per layer:
[
{ "model": "ds4vision",
"vectors": [
{ "name": "succinct", "value": "Pq3vPLnF...", "emoji": "๐" },
{ "name": "heretic", "value": "u0Kz/D2p..." }
] }
]
An entry's model is the engine name, so a direction
built for ds4vision follows that engine whatever its file
is called. A model loaded by a path that is no engine is looked up by
its file name instead, and plank tries the engine name first and the
file name second. A direction belongs to the model it was built on: the
same name under another model is a different vector.
You rarely write the file by hand. pt vectorize, from plank-replay,
builds a direction from two prompt sets (or from the difference between
a model and a modified copy of it) and stores it under the name you give
with -n, and
pt install <repo>:<path> merges a vectors file
someone else published into yours, asking before it replaces a direction
of the same name. Both write the file in place through a temporary copy,
and keep any fields they do not recognise. One such field is yours to
add: a direction's optional emoji is how the status bar
marks it while it steers, as ๐ ffn 2, with the direction's
name standing in when it has none.
| Flag | What it does |
|---|---|
--dir-steering NAME |
steer along direction NAME, stored for this model; a
name the file does not hold stops the launch and lists the ones it
does |
--dir-steering-ffn F |
the scale on the feed-forward edit, -100..100, 1 by
default; a positive scale pushes towards the prompts the direction was
built --to |
--dir-steering-attn F |
the scale on the attention edit, -100..100, 0 by
default |
--dir-steering-from user|all |
user, the default, holds the edit off while the system
prompt and session context prefill and arms it for your first message;
all steers every token |
Holding the edit until your first message keeps the cached prompt
unsteered, so a steered run reuses the same prompt checkpoint as an
unsteered one. An attention edit cannot be held back that way, so a
non-zero --dir-steering-attn means all.
Inside a session, /steer lists the model's directions
with the loaded one starred, /steer <scale> changes
the feed-forward scale on the spot (name the direction as well when the
model has more than one), and /steer <name> [scale]
switches to another direction. When you launch without
--dir-steering, plank loads the model's first direction at
scale 0, which changes nothing until a /steer turns it up.
Switching to another direction swaps its vector into the running model,
so it applies to the tokens generated next without a reload; the
conversation so far keeps the activations the old direction produced. On
an engine that cannot swap a vector live, plank restarts into the new
direction instead and resumes the session where you were.
A profile can carry a direction too: a steering block
pairs one with the profile's recommendedModel (see Profiles), and a --dir-steering
on the command line overrides it, scales included.
Steering is a feature of the native DeepSeek and Qwen engine. The
Gemma engine, --remote hosts and --provider
models ignore it. The direction plank loads is decoded once into
~/.plank/cache/steering/, named by a hash of its contents,
so two names for the same vector share one file. The old
--dir-steering-file PATH, and a steering block
on an engine in engines.local.json, are refused with a
pointer to their replacements.
Remote, shared-engine, control, and provider flags are covered in Remote and hosted engines.
Environment variables
| Variable | What |
|---|---|
OPENAI_API_KEY |
key for --provider openai |
ANTHROPIC_API_KEY |
key for --provider anthropic |
HF_API_KEY |
Hugging Face token: model downloads authenticate with it and fetch four ranges in parallel |
PLANK_REMOTE_TOKEN |
bearer token for --remote, --control, and
plank remote |
EDITOR |
editor for Ctrl-G when ui.builtinEditor is
false |
Next: Extending plank โ
Conversation prefix
--prefix-file PATH (or a profile's prefix)
reads a file of scripted turns and places them between the system prompt
and the conversation, as if that exchange had already happened. The
model then continues in the manner the turns demonstrate, which a local
model follows more reliably than a rule stated in a prompt:
USER: Write terse answers.
This line is still part of the user turn.
ASSISTANT: Understood.
USER: Return code without an introduction.
ASSISTANT: I will return only the code.
A marker starts a turn only at the beginning of a line; the file must
start with USER:, alternate, and end with
ASSISTANT:, or plank refuses to start and says which line
is wrong. The turns are read once at launch, are never saved with a
session or compacted, and apply to whatever session the run starts or
resumes. The format is ds4's --prefix-file.