neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
Interactive terminal coding agent in Rust — runs the DeepSeek V4 Flash model locally on macOS (Metal), with a Ratatui TUI, a full tool suite, MCP, and persistent sessions.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Plank is a fast-moving agent harness built on the ds4 C reference (ds4_agent). It was ported functionality-by-functionality (not line-by-line), with each C section becoming an idiomatic Rust module, so changes landing in ds4_agent stay easy to port over — the upstream remains the source of truth for wire formats and prompt text, while plank iterates quickly on everything around it.
Plank is an interactive coding agent with a Ratatui TUI, a plain terminal REPL, a one-shot headless mode, and a set of built-in tools (shell, file read/edit, web). The project website is plank-agent.dev.README 전문 보기 ↗Plank is a fast-moving agent harness built on the ds4 C reference (ds4_agent). It was ported functionality-by-functionality (not line-by-line), with each C section becoming an idiomatic Rust module, so changes landing in ds4_agent stay easy to port over — the upstream remains the source of truth for wire formats and prompt text, while plank iterates quickly on everything around it.
Plank is an interactive coding agent with a Ratatui TUI, a plain terminal REPL, a one-shot headless mode, and a set of built-in tools (shell, file read/edit, web). The project website is plank-agent.dev.
macOS first. DeepSeek and Qwen inference uses the original ds4 C engine with the Metal backend, linked via the
refs/ds4submodule, and needs a Mac. Gemma 4 and Flint run on plank's own Rust engine instead: Metal on a Mac, the CPU elsewhere, with no C to build. Homebrew bottles are macOS only.
Homebrew is the only distribution channel (plank is not on crates.io):
brew tap aovestdipaperino/tap
brew install plank-agent # stable channel
brew install plank-agent-beta # beta channel
Or in one step without a prior tap: brew install aovestdipaperino/tap/plank-agent. Prebuilt bottles exist for Apple Silicon and Intel Macs; on other setups Homebrew builds from source (requires Rust). Upgrade with brew upgrade plank-agent.
Note — formula naming. The Homebrew formulas are
plank-agent/plank-agent-beta, notplank, because aplankformula already exists in Homebrew and the bare name collides. The installed binary is still justplank— you runplank, only thebrew installname carries the-agentsuffix.
Releases follow a two-channel scheme where the patch number is the channel: every vX.Y.0 is a stable release, and any patch above 0 is a beta (the app's version banner shows BETA accordingly). A series opens with its stable .0 and accumulates beta work as patch bumps (v2.5.1, v2.5.2, …); promoting a beta to stable opens the next minor as an identical v2.6.0 (stable) / v2.6.1 (beta) pair. The two formulas conflict since both install a plank binary, so switch channels with brew uninstall plank-agent && brew install plank-agent-beta (or the reverse). See VERSIONING.md for the channel model and the promote-to-stable process.
Requires macOS (Apple Silicon or Intel) with the Xcode command line tools. Clone with the submodule to get the ds4 engine:
git clone --recurse-submodules https://github.com/aovestdipaperino/plank
cd plank
cargo build --release
refs/ds4 present: crates/local-inference-engine builds libds4core.a from the Metal-backend objects and links the required frameworks, enabling the ds4_engine cfg. The crate can be used on its own by other tools: its Model/Session API opens a ds4-engine model, a Gemma 4 GGUF or a Flint GGUF, chosen from the file's architecture.PLANK_NO_DS4=1): plank still builds and runs Gemma 4 and Flint for real on its native Rust engines (crates/gemma-engine, candle, no C; crates/flint-engine, NVFP4 kernels on top of it). DeepSeek and Qwen need the submodule; without any usable engine plank falls back to the echo stub (useful for development/CI).gemma4-e4b, gemma4-12b, .gemma.kv transcripts and its own <|tool_call> dialect) runs on the Rust engine; see docs/GEMMA.md. So does Flint (flint, Recursive's 40B NVFP4 mixture-of-experts, .flint.kv transcripts and its <tool_call> dialect); see docs/FLINT.md. DeepSeek V4 Flash, V4.1 Flash and Qwen3.8-Flash-Next are all compiled in and told apart from the GGUF's own general.architecture; each has its own tool-call dialect, artifact set and transcript extension (.ds4.kv / .ds41.kv / .qwn.kv). Qwen was retired once, when upstream deleted its Metal kernels; upstream has since merged it properly and publishes the weights, so it is back unconditionally, addressed as the qwen engine with --model qwen.You will also need a GGUF model file for real inference; use --model <name|path> to pick one, either a catalog engine name (ds4vision, ds41, qwen, gemma4-e4b, gemma4-12b, flint) or a path of your own. See the download_model.sh script in refs/ds4.
plank # interactive REPL
plank --help # full option list
Run with a prompt argument for one-shot headless mode.
Gemma 4 is the light option: plank --model gemma4-e4b downloads about 5 GB, and gemma4-12b about 7 GB, sized for a laptop. plank --model flint downloads about 23 GB and wants a Mac with room for it. When the selected engine is missing and plank runs on a terminal without --model, it opens an engine menu instead, with every engine this machine cannot run dimmed and explained (RAM, free disk, build support); the pick downloads in the background behind a wait screen. --pick-engine opens the menu on demand and /engines restarts into it mid-session. With HF_API_KEY set, Hugging Face downloads authenticate and fetch four ranges in parallel.
The default engine needs the DeepSeek V4 Flash GGUF — the official (non-preview) -0731 build of 2026-07-31. You can point plank at any copy with -m <path>; by name it resolves to ~/.plank/ds4vision.gguf and, when nothing is there, the menu (or --model ds4vision) fetches the quantized model (~87 GB) from Hugging Face, with live progress:
Details worth knowing:
.part file next to the destination; if it's interrupted (Ctrl-C, network drop), the next launch detects the partial file and resumes from where it stopped instead of starting over.q or Ctrl-C abort the download from anywhere, so a rally can't trap you.engines.json catalog carries its own monotonic version; plank checks the catalog at most once a day and offers an upgrade only when an installed engine's version is behind, so a symlinked or manually placed GGUF is left alone rather than re-downloaded.Without a model (or on non-macOS platforms) plank still runs against a built-in echo stub — useful for developing the UI and tools, not for real inference.
Speculative decoding — multi-token prediction, --mtp — is on by default, with a different mechanism per model family. DeepSeek uses its auxiliary DSpark draft checkpoint for V4 Flash: it reads hidden states from the main model, proposes up to five tokens ahead, and the main model verifies them and commits only the prefix it agrees with, so one verification pass can advance the stream by several tokens. V4.1 Flash ships no drafter, so it decodes target-only. --mtp-off turns it off for target-only decode.
On DeepSeek the support model (~5.6 GB) does not need a flag of its own. It resolves to ~/.plank/<engine>.mtp.gguf alongside the main model (~/.plank/ds4vision.mtp.gguf for the default engine) and, when missing, is offered for download through the same resumable, playable path as the main model. --mtp-model <path> overrides it.
plank --temp 0
--mtp-off — disable speculative decoding (target-only decode).--mtp-confidence F — pruning threshold, 0..1. 0 forces fixed five-token blocks (diagnostics). The default is the engine's own and depends on the backend.--mtp-strict — load the drafter but keep target-only decode, for comparisons and correctness checks./mtp on|off — the same switch mid-session, between turns.Verification is argmax, so proposals are only used at --temp 0; sampled decoding ignores them. Whether it actually pays depends on the engine build, the quant, and the machine — on an M5 Max it was a 0.71× slowdown until upstream pipelined the Metal verifier, after which the same measurement read 1.19×. Plank's exit message reports, per model, how long the session spent prefilling and generating with the average rate for each (and how long it spent in tools), which is the quickest way to check on your own hardware.
plank tracks ds4_agent for the core agent loop but moves faster on the user-facing side. A few of the things that exist only in plank:
📄 3 · +128 -41 — files touched, then lines added in green and deleted in red) on the first row, so the location holds still, and everything volatile on the second — engine origin, reasoning level (colored by how hard the model is thinking, with a braille stand-in for the expert routing that re-rolls every token), context gauge, and the name of the tool currently running. The C reference is a plain line REPL. Resumed sessions replay through the same renderer, so history comes back as markdown with thinking dimmed, not flat text./toks live speed chart — two braille line charts side by side, generation speed and prefill speed, each a time series sampled once a second, drawn in the theme green. Open it mid-turn and it redraws on every status tick, so the line grows while the model types. /exit also works mid-turn now: it asks [y/N] first, then interrupts the turn and leaves once it stops./context breakdown, ⧗ N jobs opens the live job table, the chart glyph opens /toks, and the brain flips thinking display for the session. All of them work while the model is generating./btw side questions — ask something mid-task; the answer runs on a fork of the session, interleaved with the main generation, so it streams into a split panel while the main task keeps going. Nothing is written to the conversation, and neither side re-prefills./checkpoint//rollback and /resume snapshot the live engine KV alongside the transcript, so returning to a conversation skips re-prefilling it.edit (or an overwriting write) renders as a change card with an Update(path) header, an added/removed summary, and red/green @@ hunks; a brand-new file streams its content dimmed as it is written.agent sub-agent tool — the model delegates a bounded task to a fresh scoped sub-agent and gets back only its conclusion, keeping the main transcript clean; bounded so a sub-agent can't itself delegate.See docs/FEATURES.md for the complete list.
Assistant replies render as markdown in the TUI, with tree-sitter syntax highlighting for fenced code blocks:
The /context command visualizes context-window usage by category:
/toks charts the generation speed and the prefill speed side by side as they happen, one sample per second of decoding; typed during a turn, the panel redraws as the model types:
/btw answers a side question beside the running task rather than pausing it. The aside runs on a fork of the session, interleaved with the main generation, so both advance at once — here the model keeps counting on the left while /btw what is the capital of Italy is answered on the right, with nothing written to the conversation:
One Metal command queue means this is time-slicing, not parallelism — nothing finishes sooner overall. What changes is that the main task no longer stops. The aside takes the larger share of the thread while it runs, since it is the one you are waiting on.
Long turns end with a native macOS notification — your prompt as the headline and the tail of the answer as the body, wearing your terminal's icon and plank's logo. ui.notifications picks when they fire: always, unfocused (only while the terminal isn't focused), or never:
ui.showThinking controls whether the model's reasoning is rendered in the scrollback. It is off by default: the thinking is usually noise once you trust the answer, and hiding it keeps the transcript readable.
Hiding it does not have to mean losing it. Start plank with --debug and, while showThinking is off, it mirrors its whole raw model stream to tdk, a text-mode viewer that renders it in its own window, so the reasoning is one glance away instead of gone:
Install it and leave it running; a plank --debug finds it on its own:
brew install aovestdipaperino/tap/tdk
tdk
cargo install tdk works too. It listens on port 7878, and each plank session gets its own window titled plank:<session-name>, matching the session name plank shows above the prompt. Sessions are reconnectable: the window and its scrollback survive plank exiting, so restarting plank appends the new run below a -- reconnected -- rule instead of losing the old one.
The console is entirely optional and plank never depends on it. Without --debug plank does not even look for one: no probe, no connection, nothing sent. With it, if nothing is listening, plank connects to nothing, says nothing, and behaves exactly as it always has. If you close the console mid-turn, the mirror is dropped and the turn carries on. Turning showThinking back on disconnects it, since the reasoning is back in the scrollback where you can already see it.
The switch can be flipped inside a session too. /debug on overrides a launch without --debug: if a console is up, plank connects to it there and then and replays the session so far before the live stream continues. /debug off drops every console connection for the rest of the session, and a bare /debug reports the current state.
Start the console late and it catches up. When a console connects to a session that is already under way, plank replays the assistant passes so far into the window, between a plank backfill: N earlier passes rule and a live from here rule, then the live stream continues below. Sub-agents get the same treatment: a sub-agent still running gets its own window filled with its passes so far, and the last few finished sub-agents (the same eight /repro keeps as sidecars) each get their plank:<session>:subagent-<n> window reopened and replayed once. Nothing is replayed twice: a showThinking flip or a console restart only sends what that console has not already shown.
What arrives there is the whole stream, not just the hidden part: thinking, answer, and tool calls, rendered by the same renderer plank uses for its own output. That is deliberate. The console shares plank's streaming renderer as the trace-stream crate rather than reimplementing it, so the two cannot drift, and reasoning arrives in the context of the answer it produced rather than as disembodied fragments.
Preferences you'd otherwise retype every launch live in settings.json, hierarchical like the MCP configs: ~/.plank/settings.json applies globally, ./.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. Edit it in-session with /config (an interactive TUI form, or /config <section>.<key> <value> from the prompt, e.g. /config ui.showThinking false); changes write ./.plank/settings.json and apply immediately. In the interactive form, Ctrl-S saves and closes; Esc cancels and discards.
A few keys cannot take effect until you restart, because what they configure is built once at startup: everything under engine (the model is already loaded), safety.sandbox and safety.btwSuspend, and the keys that shape the system prompt — tools.recall, tools.fanout, tools.runCode and git.signCommits — since the prompt is built once per session and KV-cached, and rewriting it mid-session would throw that cache away. Everything else is live.
{
"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": false, "notifications": "always", "notifyAfterSecs": 10,
"screensaver": "1m", "screensaverFace": "matrix" },
"safety": { "sandbox": true, "btwSuspend": true },
"mcp": { "timeoutSecs": 30 },
"ask": { "maxOptions": 7 },
"agents": { "autoRoute": true, "maxParallel": 32 },
"git": { "signCommits": true },
"worktree": { "sparsePaths": ["src", "docs"],
"symlinkDirectories": ["target"], "isolateAgents": false }
}
| Group | Key | Default | What it does |
|---|---|---|---|
engine | model | the catalog default (ds4vision) | An engine name or a path (~ expanded). Same as -m/--model. |
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 the model emits inside its thinking block. Set false for strict refs/ds4 parity. | |
ui | 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. Off keeps the UI uncluttered; the tools still run. | |
showToolResults | false | Echo tool result text into the scrollback. Off keeps the UI clean; the model still receives the results. | |
showThinking | false | Render the model's thinking (dimmed) in the scrollback. Off hides it from the display; the model still produces it, and plank mirrors the stream to a if one is running. |
Precedence runs left to right, each layer overriding the one before:
built-in defaults → ~/.plank/settings.json → ./.plank/settings.json → environment → command-line flags
Because a settings file can move you off Metal or shrink the context — and both are invisible once the UI is up, showing only as "plank got slow" — plank prints one line at startup naming what is in force:
plank: settings in effect (/path/to/.plank/settings.json): threads=3, backend=cpu, ctx=65536
It lists only settings actually in effect: a value a command-line flag overrode is not mentioned, and with no settings file (or one that changes nothing) there is no line at all.
Two things the file deliberately does not do:
./.plank/settings.json sits inside your working tree and is easy to commit by accident, so there is no API-key setting — keep it on --api-key or the provider's environment variable.--prompt, --ui, --ui-remote, --trace, --chdir, --seed, --worktree, and serve describe one invocation rather than a preference, so they have no settings key.A broken settings file never stops plank from starting: malformed JSON, a wrongly-typed value, an unknown key, or an unrecognised backend name each fall back to that key's default. (The same unrecognised name passed to --backend is still an error — a flag is an explicit instruction, a config file is a preference.) One limitation: settings are read from the directory plank launches in, so project-scoped settings do not follow --chdir.
Plank can load external tools from stdio and Streamable HTTP MCP servers. Configs are hierarchical like Claude Code's user and project scopes: ~/.plank/.mcp.json applies globally, and ./.mcp.json in the working directory (or the file given with --mcp-config) overrides same-named servers and adds new ones. Both use the standard mcpServers format — a command entry is spawned as a stdio subprocess, a url entry is reached over Streamable HTTP (optional headers carry e.g. an Authorization token):
{
"mcpServers": {
"demo": {
"command": "some-mcp-server",
"args": ["--flag"],
"env": {"KEY": "value"},
"primaryTools": ["tool_a"]
},
"remote": {
"type": "http",
"url": "http://127.0.0.1:6510/mcp",
"headers": {"Authorization": "Bearer <token>"}
}
}
}
Tools are exposed to the model as mcp__<server>__<tool>. The optional primaryTools list controls prompt size: listed tools get their full schema in the system prompt, the rest appear in a compact directory and are described on demand via the built-in mcp_describe tool. Omit the key to make every tool primary.
The v2 beta channel extends plank past a single local process. All of it is off by default; a plain plank still runs the local Metal engine exactly as before.
Serve and connect — plank serve hosts the local ds4 engine over HTTP+SSE so another machine can use it; plank --remote <url> points a thin client at that host (drive from a laptop, infer on the Metal box). The transport is synchronous, adds no async runtime, and streams tokens as they generate. Token auth via --remote-token / $PLANK_REMOTE_TOKEN; keep it behind an SSH tunnel or a TLS reverse proxy. A server bound off loopback without a --token refuses to start unless you pass plank serve --insecure (distinct from the client's --insecure, which allows plaintext http:// to a non-loopback host); request bodies are capped at 16 MiB and idle connections time out after 30 s.
Hosted providers — behind the same Engine trait, --provider openai --model <name> targets any OpenAI-compatible endpoint (--base-url, --api-key / $OPENAI_API_KEY; covers vLLM, Ollama, OpenRouter, Together) and --provider anthropic targets the Anthropic Messages API ($ANTHROPIC_API_KEY). Native provider tool calls are synthesized back into plank's DSML tool syntax, so tools dispatch identically regardless of backend, and multi-turn tool-call ids are threaded through. Anthropic prompt caching (cache_control) is on by default (--provider-cache).
Shared engine — plank serve --shared-engine loads the weights once and serves many concurrent sessions from a single cooperative GPU thread (round-robin at token granularity; the one Metal queue means time-sliced, not parallel). A freshly attached session restores the warm system-prompt prefix instead of cold-prefilling it. --max-sessions and --kv-budget-bytes cap admission, --session-ctx-size sizes each session's context, and --idle-reclaim-secs snapshots idle sessions to disk and restores them on demand; /info reports live-session and KV accounting. Sessions are keyed per client (the client sends X-Plank-Client-Id), so many turns from one client share one session, and a session idle for 30 minutes is swept; older clients without the header are keyed per turn as before.
Remote control — /remote-control (alias /rc) inside a running TUI session opens a loopback WebSocket and prints a one-click link: http://127.0.0.1:PORT/?t=TOKEN, plus an ssh -L hint for reaching it from another machine. Opening that link auto-connects a self-contained web client and claims control, with nothing to paste and no button to press. Bare toggles; and are explicit and case-insensitive. Off tells connected clients, shuts the listener down, and kills the token, so a stale link is refused; the next mints a new port and token. The terminal client attaches to the same server. One controller at a time, many mirrors, with a reconnect grace window.
plank can drive a hosted model instead of the local one. The provider sits behind the same Engine trait as the Metal backend, so tools, sessions, /btw, compaction, and the rest of the agent loop behave identically — native provider tool calls are translated back into plank's own tool protocol on the way through.
Pick a provider with --provider and name the model with --model. The API key is read from the provider's environment variable, so you normally do not pass it on the command line:
# OpenAI
export OPENAI_API_KEY=sk-...
plank --provider openai --model gpt-4o
# Anthropic
export ANTHROPIC_API_KEY=sk-ant-...
plank --provider anthropic --model claude-sonnet-4-5
Both providers work with a one-shot prompt too: plank --provider anthropic --model <name> -p "...".
Flags
| Flag | Meaning |
|---|---|
--provider openai|anthropic | Selects the provider family. openai speaks the OpenAI-compatible Chat Completions API; anthropic speaks the Anthropic Messages API. |
--model NAME | The provider's model name (not a local GGUF path). Required with --provider. |
--api-key KEY | The key, if you would rather not use the environment variable. Prefer the env var — a key on the command line lands in your shell history. |
--base-url URL | Overrides the endpoint. Defaults to https://api.openai.com/v1 and https://api.anthropic.com/v1. |
--provider-cache on|off | Anthropic prompt caching over the stable prefix (tools + system). On by default; ignored for --provider openai. |
Key resolution — --api-key wins if given, otherwise $OPENAI_API_KEY (openai) or $ANTHROPIC_API_KEY (anthropic). With neither set, startup fails with a clear message rather than a confusing API error.
OpenAI-compatible gateways — --provider openai plus --base-url reaches anything that speaks the OpenAI Chat Completions shape: vLLM, Ollama, OpenRouter, Together, LM Studio, and similar. For example, a local Ollama:
plank --provider openai --model llama3.3 \
--base-url http://localhost:11434/v1 --api-key ollama
What stays the same — every plank tool (read/edit/bash/glob/search/…), the MCP tools, @ completion, sessions and /resume, /btw, and compaction all work unchanged against a provider. The one difference is the system prompt: a provider gets plank's own prompt with native tool definitions, never the byte-parity DeepSeek prompt (which is meant only for the local model it was trained on).
Two hosted models on one key — a sub-agent definition can name a different model at the same endpoint, authenticated with the same variable:
provider: openai # any OpenAI-compatible endpoint
model: qwen3-coder-next # the only difference from the parent
base-url: https://api.regolo.ai/v1
api-key-env: REGOLO_API_KEY # the same variable the parent uses
Only the variable's name lives in the file, never the key, so definitions stay committable. /usage then reports one row per model rather than one total, which is how you confirm the sidechain really reached the second model — self-reported identity is weak evidence, billing is not. multi-provider-tests/remote-remote/ is a runnable session that exercises exactly this, and needs no local model, so it starts instantly on any machine.
Notes — --provider cannot be combined with --remote or the local backend selectors (--metal/--cuda/--cpu); it is the engine for that run. /usage reports billed token counts for the session, including Anthropic cache read/write and hit rate. The key is never written to settings.json — it stays on the environment or --api-key by design.
A profile launches plank as a different agent: its own system prompt, its own builtin tools, its own settings, logo, name and accent colour, and optionally its own MCP servers. Start one from an installed name, a local folder, or a folder of a GitHub repository:
plank --profile aovestdipaperino/plank-profiles:HAL
The theming is the part you see first. HAL's manifest names a PNG logo, a display name and a #d0021b accent, and the TUI draws all three: the logo as the banner art, HAL v0.3.1 above the plank version, and red in place of plank's green on the rules around the prompt:
The first launch from a folder or a repository asks before installing it into
~/.plank/profiles/; after that the same command launches the installed copy,
and offers an update when the source's version goes up. /install-profile
installs from inside a session, /edit-profile edits the running profile and
restarts into the change, and a session resumes only under the profile that
started it.
A profile is a plugin folder whose plugin.json has a profile block:
{
"name": "research",
"version": "0.1.0",
"profile": {
"displayName": "Scholar",
"systemPrompt": "prompt.md",
"tools": { "builtin": ["read", "more", "glob", "search", "google_search", "visit_page"] },
"folderContext": false,
"agentsMd": false
}
}
HAL, a mail assistant over one Outlook mailbox that can read, flag, draft and
file mail but cannot send or delete it, lives in
aovestdipaperino/plank-profiles.
The Profiles chapter of the user guide (also at
plank-agent.dev/guide/14-profiles)
covers launching, installing, updating and writing one; the full manifest
reference is in docs/PROFILES.md.
plank can steer the model along a named direction: a per-layer vector that nudges its activations towards one kind of answer, terser, more cautious, less likely to refuse. Every direction you have lives in ~/.plank/models/vectors.json, filed under the model it was built for, and you pick one by name:
plank --dir-steering succinct --dir-steering-ffn 0.8
/steer lists the model's directions, changes the scale mid-session, or switches to another direction and resumes where you were. pt vectorize builds a direction from two prompt sets and stores it by name, pt install <repo>:<path> merges someone else's vectors file into yours, and a profile can pair a direction with the engine it recommends. Steering works on the native DeepSeek and Qwen engine. The file format, the flags and how the cached prompt stays unsteered are in the Steering section of the configuration guide.
Waiting on a long generation is the one moment a coding agent has nothing for you to do. So there are five games — and one thing to just watch — behind undocumented-in-/help slash commands, and they are meant to be used during a turn: type one while the model is streaming and it opens as a translucent layer over the live output, which keeps scrolling underneath.
That is a real turn underneath — the model is 1m 49s into writing a poem at 20.8 t/s, still streaming, and the wall, ball and paddle are on top of it. The dim line near the top is the resume notice: this game had been left open in an earlier turn and came back where it was.
| Command | |
|---|---|
/pelota | pong against a five-level CPU |
/breakout | knock the wall down, five walls deep |
/invaders | hold the line against the marching fleet |
/centipede | shoot it apart before it walks into you |
/frogger | cross the road, then ride the river home |
/minions | nothing to play: two of them, giggling by a lake |
And one that is not a game at all:
| Command | |
|---|---|
/matrix | glyphs falling down a black screen, for when you would rather just watch something |
/matrix keeps no state — there is nothing in rain to come back to, so closing it and reopening it deals a fresh downpour and new has nothing to undo. ↑/↓ and the wheel change how fast it falls; c cycles the alphabet through half-width katakana, binary, and punctuation-heavy ASCII. The katakana are the whole point, but they are also the one thing a terminal font can fail to draw: if you get a screen of boxes, press c.
Two arguments, combinable — /breakout new sound:
new (or reset) — deal a fresh game. Without it a command resumes the game you left: each one keeps its own slot, so you can close pelota, run three turns, open breakout, and come back to pelota exactly where it was. A finished game is not kept, so the next command deals a new one.sound — blips on. Off by default; b toggles it in-game.Controls — arrows (or hjkl/wasd), space to serve or fire, p to pause, t to switch between the translucent and opaque layer, b for sound, Esc/q to leave. The mouse works everywhere: the wheel and trackpad steer, click and drag place the paddle or the ship, and clicking fires in the two shooters. While a game is up, the first Ctrl-C closes it and a second one interrupts the model, so you are never locked out of stopping a turn.
Pelota has one extra move worth knowing: hold Shift while steering and the paddle shrinks to a third of its length, but a hit that lands leaves at triple speed — usually past the CPU. The boost lasts exactly one crossing.
After a minute of an idle prompt (ui.screensaver) the screen is taken by an ambient screen — the matrix rain, the perspective starfield, or two minions on the shore of a night lake — whichever ui.screensaverFace names, or a fresh draw each time under random. None is an easter egg: ui.easterEggs does not gate them, they never come up mid-turn or over a dialog, and the next key, click or paste puts the UI back (consumed, not typed).
The minions are also a command, /minions, which is gated like the games. They walk the shore, blink, elbow each other and fall about laughing, reflected in the water underneath. ↑/↓ and the wheel set the pace, r strips the night back to just the two of them, and t lays them over live model output like any other easter egg.
The whole animation is 218 bytes of the binary. The art is six poses of ASCII paint-by-numbers in src/resources/minions.txt — 1 440 cells, one byte each, readable and diffable — and that file never ships. build.rs packs it at build time with a three-op coder (same cell as the pose before / same ink again / something already drawn up to 256 cells back, everything else a literal) and writes the result into OUT_DIR, so 1 440 bytes of art become 218: about a seventh, or 85% saved. A const assertion fails the build if a change to the art or the coder ever gives most of that back. Nothing else about the scene is stored at all: the walk, the bob, the nudge, the laughter, the stars, the ripples and the reflection are generated from one seeded generator and a clock.
Three honest notes about what the terminal can and cannot do:
█ ▓ ▒ ░ cover a known fraction of a cell, so the coverage ramp is the alpha channel and fading is walking down it, which is what rounds their shoulders, fades the scene up when it opens and sinks their reflection into the water; and glyphs that are shapes rather than fills — a goggle rim, an eye — fade toward the night by colour instead, because a rim at quarter coverage is not a fainter rim, it is a different character.BEL has no pitch and no length, so the only thing distinguishing one cue from another is how many — one blip for a hit, two for a life lost, three for a level. Terminals set to a visual bell will flash instead, which is why it is off unless you ask.None of these appear in /help or the completion popup. That is the point.
They live behind ui.easterEggs, on by default. Setting it to false does more than hide them — it stops them being commands at all, so /pelota goes to the model as an ordinary prompt exactly like any other unrecognized slash command. That is the honest behaviour for a shared or managed install that wants no games in it, and the startup line names the setting when it is off, so a settings.json cannot quietly remove them without saying so.
Leave the TUI idle and it puts something on the screen. By default that is the matrix rain:
There are three faces, and ui.screensaverFace picks which one you get:
| Value | |
|---|---|
matrix | the falling glyphs above — the default |
starfield | a perspective starfield rushing outward past the edges |
minions | two minions on the shore of a night lake |
random | a fresh draw each time it opens |
ui.screensaver says when: 1m (the default), 2m, 5m, or never to switch it off. Both are editable live with /config, which cycles the values rather than making you type them.
Any key or mouse event dismisses it, and the keystroke that wakes the screen is swallowed rather than typed into your prompt. It never comes up mid-turn or over a dialog — an idle timer that interrupted a running generation, or covered a question waiting on an answer, would be a bug rather than a feature.
Unlike the games, the screensaver is not behind ui.easterEggs. Turning the arcade off in a shared or managed install stops /pelota and /matrix being commands at all, but it does not take the idle screen with it: one is a game you invoke, the other is what an unattended terminal shows, and they are not the same decision. Set ui.screensaver to never if you want no idle screen either.
/buddy on stands a small animated character on the rule above the prompt while plank waits for you. It does nothing, which is the point, and it steps away whenever a turn runs.
Buddies are the folders of plank-buddies; the first is minions. You have one at a time: /buddy install lists them and the one you pick replaces yours, /buddy install <name> skips the menu, and /buddy off sends it away. Its clips are decoded once with ffmpeg, so the first showing after an install takes a few seconds. It needs a terminal with kitty graphics: kitty, Ghostty or Warp, outside tmux.
Each module in src/ maps to one functional section of the original ds4_agent.c:
engine.rs / ds4engine.rs / ffi.rs — inference engine abstraction and native ds4 bindingssession.rs, compact.rs, sysprompt.rs — conversation state, compaction, system prompttools/ — built-in agent tools (bash, edit, files, web) and the MCP clientworktree.rs, tools/worktree.rs — git-worktree isolation and the EnterWorktree/ExitWorktree toolsui.rs, render.rs, statusbar.rs, editor.rs, viz.rs — terminal UIarcade.rs, arcade/ — the easter-egg games, the matrix rain, the starfield and the minions (see above)config.rs, settings.rs, trace.rs, interrupt.rs, status.rs — configuration, persistent settings, tracing, signal handlingcargo update reports one dependency as behind the latest release, and it
stays that way on purpose:
generic-array v0.14.7 (available: v0.14.9)
It is not ours to move. crypto-common 0.1 requires generic-array at
exactly =0.14.7, and that pin is deliberate: generic-array types appear
in crypto-common's public API, so the exact version is part of the contract
between every crate compiled against it. In this tree that means digest,
cipher, aead and universal-hash, and under those aes, aes-gcm,
cbc, ctr, ecb, hmac, sha1, md-5, pbkdf2 and polyval — the
whole RustCrypto 0.10-era stack.
So there is no local fix worth having. Patching it would mean forking the
trait crate that all of those compile against and carrying that fork
indefinitely, to gain a patch release. What actually resolves it is those
crates reaching digest 0.11, which drops the 0.10 stack out of the graph;
plank's own sha2 and blake2 are already on 0.11.
Two neighbouring pins are worth knowing about for the same reason, though neither produces a warning any more:
liteparse is held at =2.14 rather than tracking its latest. The
crate shipped 27 releases in three months, and the fixtures in
src/doc/mod.rs are coupled to its markdown emitter. Bump it deliberately
and re-run cargo test --lib doc:: before trusting the result.tree-sitter-sequel, requires cc = "~1.2.1", and because cargo unifies
cc across the whole graph that one optional grammar held every build
script in the tree to cc 1.2.x. Plank lists ratatui-markdown's grammar
features explicitly, minus highlight-lang-sql, so the crate leaves the
graph entirely. Restore the feature if that pin is ever relaxed upstream.~/.plank/agents/*.md./.plank/agentsprovider:model:provider: localEnterWorktree/ExitWorktree move the session into an isolated second checkout (.plank/worktrees/<name>, on branch worktree-<name>), so a large or speculative change never touches the tree you're working in; --worktree NAME / --worktree-pr N start a whole session in one, and isolation: worktree gives each sub-agent its own so a fan-out can't overwrite itself. Removal is fail-closed: a worktree holding uncommitted files or unpushed commits is not deleted without an explicit discard, and neither is one whose state git couldn't be asked about. WorktreeCreate/WorktreeRemove hooks replace git entirely for a non-git VCS.EnterPlanMode holds the model read-only (research only) until it proposes a plan you approve with ExitPlanMode, before any edits land.@ file completion, glob, and a model-visible task list that survives compaction./ command menu — typing / raises a drop-up above the prompt listing every command with its argument hint and a one-line explanation beside it, filtered as you keep typing. Skills and prompt templates appear alongside the built-ins, tagged as such, so what a project adds is as discoverable as what ships.Ctrl-C copies the selection (and still clears the line when nothing is selected), Ctrl-X cuts, Ctrl-V pastes, Ctrl-Shift-A selects everything. It all works mid-turn too, on the prompt that stays live while the model generates.settings.json for durable preferences.ask tool — when a turn is genuinely ambiguous the model can pose a multiple-choice question instead of guessing; you pick in a panel (or numbered list in the REPL), and it degrades cleanly when there's no user to ask./install-claude-plugin — fetches and installs a Claude Code plugin from a GitHub repo, an owner/repo shorthand, a browser-copied /tree///blob/ URL, a marketplace, a .tar.gz, or a local directory. It rewrites ${CLAUDE_PLUGIN_ROOT} to the real install path and unwraps Claude Code's nested hook config, since plank's hook runner and hook reader expect neither as-is; .mcp.json and settings.json need no such translation and merge in the same way any other plugin's do.'<prompt>' finished and the tail of the answer; interrupted for aborted turns), configurable to fire always, only while unfocused, or never; the terminal title tracks the task (🪵 plank - fix the bug…).notifications | always | When desktop notifications fire: always, unfocused (only while the terminal window isn't focused), or never. |
notifyAfterSecs | 10 | Minimum turn duration before a turn-end notification; awaiting-input notifications ignore it. |
crtOff | true | CRT power-off animation on clean TUI exit. |
builtinEditor | true | Ctrl-G opens the built-in editor (a fork of Microsoft Edit, in-process). false shells out to $EDITOR as before. |
screensaver | 1m | Idle time before the screensaver takes the screen: 1m, 2m, 5m, or never. Any key or mouse event dismisses it; it never comes up mid-turn or over a dialog. |
screensaverFace | matrix | Which screen it puts up: matrix (the rain), starfield, minions, or random for a fresh draw each time. |
safety | sandbox | on (macOS) | Default for the write sandbox: bash runs under Seatbelt, and write/edit refuse paths outside the workspace. Same as --sandbox/--no-sandbox. |
btwSuspend | true | Default for /btw mid-generation suspend. Same as --btw-suspend/--disable-btw-suspend. |
mcp | timeoutSecs | 30 | How long an MCP server has to answer before it's considered dead. Raise it for a slow-starting server, since a server that misses the deadline is dropped along with all of its tools. |
ask | maxOptions | 7 | Most options the ask tool may offer in one question (minimum is fixed at 2). |
agents | autoRoute | true | Whether the model may select a sub-agent definition on its own initiative. |
maxParallel | 32 | How many sub-agents may run concurrently (clamped to 32). |
git | 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). false drops the instruction and leaves commit messages to your repository's own conventions. |
worktree | sparsePaths | [] | Cone-mode sparse-checkout paths for a new worktree. Empty checks out everything; set it when a second full checkout of the repo is painful. |
symlinkDirectories | [] | Directories symlinked from the main checkout rather than duplicated, e.g. target or node_modules. A name that could climb out of the worktree is ignored. |
isolateAgents | false | Give every sub-agent its own throwaway worktree. Off because a checkout per agent costs disk and time and the work must then be merged back; use isolation: worktree on the definitions that need it instead. |
/rc/rc on/rc off/rcplank remote <ws-url>The page mirrors the session: assistant replies render as markdown, the header carries the same directory, branch, engine origin and reasoning level the TUI footer shows, and the end of a turn raises a browser notification, a banner, and a tab-title flash — the local desktop notification only reaches whoever is at the machine plank is running on. send lights up when there is something to send, stop only while a turn is running; ↑/↓ walk prompt history. A /clear clears the page too.
Remote control needs the full-screen TUI — /rc in a piped or headless session declines rather than starting a server nothing can drive. The token rides in the URL, so it lands in browser history and any Referer: treat the link as a convenience for a loopback-only listener, not a secret, and reach it from elsewhere through the SSH tunnel rather than by widening the bind.