Context efficiencyWhere tokens go
Parses 15 languages into skeletons: imports, type defs, function signatures with their line ranges. Adds 59 tok/turn but saves 224 tok/turn on reads, netting 165 tok/turn saved. My usage indicated reads are ~65% of all tokens, so this optimization is big.
A sandboxed Python interpreter with memory and time limits. All tools are exposed as async functions, so the model can asyncio.gather() a bunch of reads, grep the results, and only return what matters. Intermediate data never reaches your context.
The model picks weak, medium, or strong for each subagent. Haiku-tier for grep-heavy research, opus-tier for architecture. Subagents can be read-only or have full tool access.
The system prompt, tool descriptions, and examples are short. When context gets too long, maki compacts history automatically: strips images, thinking blocks, and summarizes older turns.
Datadog's MCP server exposes over 100 tools with all toolsets on, and each definition sits in your context on every request, used or not. Maki hides them behind a single tool_search tool, so the model looks up what it needs and the rest never loads.
User experienceWhat you get
Native binary. No javascript runtime, no react. Even the splash screen animation uses SIMD. Syntax highlighting runs on a background thread pool so it never blocks your input. Fits well on small laptop screens.
Philosophy: don't hide anything. Token count, cost, and model are always in the status bar. Each subagent gets its own chat window you can flip through with Ctrl-N/P. Ctrl-F for fuzzy search. /btw runs a side query without touching the current session. ! runs shell commands, !! runs them silently.
Bash commands are parsed with tree-sitter so maki knows what's actually being run. git diff && rm -rf / correctly flags both git and rm. Most agents only see git. Handles subshells, command substitution, pipes. Per-tool allow/deny rules, or --yolo to skip it all. SSRF protection on webfetch.
Sessions run in parallel: switch away and the agent keeps working, /sessions to jump back when it flashes done. Long-term memory that persists across sessions. Tell maki to remember something, sometimes it picks things up on its own. Double-Escape to rewind. Plan mode restricts the agent to read-only. MCP servers over stdio or HTTP. Skills. 26 themes. Paste images. --print for headless (output is Claude Code-compatible). maki acp runs an ACP server for editors like Zed.
Extend maki in Lua with a Neovim-style plugin API. Every built-in tool is a Lua plugin, so anything maki does out of the box, your plugins can do too: add tools, slash commands, keymaps, and UI.
index: read less, know more
Instead of reading full files, index parses with tree-sitter and returns a compact skeleton. The model sees the structure, then reads only the lines it needs.
use std::fs; use clap::Parser; use color_eyre::Result; #[derive(Parser)] struct Args { paths: Vec<PathBuf>, #[arg(short, long)] lines: bool, } fn count_words(text: &str) -> usize { text.split_whitespace().count() } fn count_lines(text: &str) -> usize { text.lines().count() } fn main() -> Result<()> { let args = Args::parse(); for path in &args.paths { let text = fs::read_to_string(path)?; let n = if args.lines { count_lines(&text) } else { count_words(&text) }; println!("{}: {n}", path.display()); } Ok(()) }
imports: [1-3] clap::Parser, color_eyre::Result, std::fs types: #[derive(Parser)] struct Args [5-9] paths: Vec<PathBuf> lines: bool fns: count_words(text: &str) -> usize [11-13] count_lines(text: &str) -> usize [15-17] main() -> Result<()> [19-29]
code_execution: think inside the sandbox
Tools are exposed as async Python functions. The model writes a script, runs it sandboxed, and only the print() output enters your context.
# find dead exports in a TS repo files = await glob(pattern='src/**/*.ts') srcs = await asyncio.gather( *[read(path=f) for f in files] ) exports = {} imports = set() for f, src in zip(files, srcs): for m in re.finditer(r'^export \w+ (\w+)', src, re.M): exports[m.group(1)] = f for m in re.finditer(r'import\s*\{([^}]+)\}', src): imports.update(n.strip() for n in m.group(1).split(',')) for name, f in exports.items(): if name not in imports: print(f'{f} {name}')
src/lib/csv.ts parseCsvLegacy src/auth/jwt.ts signV1 src/utils/phone.ts formatE164
Lua plugins: hackable all the way down
Every built-in tool, read, bash, edit, even batch, is itself a Lua plugin. Read them in ./plugins. The API mirrors Neovim (maki.fs, maki.uv, maki.keymap, maki.treesitter), so it feels familiar. Drop a file in ~/.config/maki/plugins/ to add tools, slash commands, keymaps, or UI.
maki.api.register_tool({ name = "ci_status", description = "Latest CI run for this branch", schema = { type = "object", properties = {} }, handler = function() local res, err = maki.net.request( "https://ci.internal/runs/json?branch=main") if err then return { llm_output = err, is_error = true } end local buf = maki.ui.buf() buf:lines(maki.ui.highlight(res.body, "json")) return { llm_output = res.body, body = buf } end, })
maki.api.register_command({ name = "/standup", description = "Yesterday's commits", handler = function() local buf = maki.ui.buf() local win = maki.ui.open_win(buf, { title = "standup" }) maki.fn.jobstart( "git log --since=yesterday --oneline", { on_stdout = function(_, line) buf:line(line) end, }) repeat local ev = win:recv() until not ev or ev.key == "esc" win:close() end, })
And yes... it can even run DOOM!