Strument is an AI pair-programming tool for the terminal. It is designed for developers who want to review and guide technical and UX decisions as the model works. Working with Strument is organized into turns. Each turn starts with your instructions to the model; changes are recorded in a commit or, outside Git, an undo snapshot.
Strument began as a ground-up reimplementation of aider but has since diverged.
See doc/ for the developer overview.
config.star file replaces YAML, .env files, and a JSON model database.
Project-local config is supported as either .strument.star or .strument/config.star.
They are loaded only after you authorize them by running strument trust in the project directory.
That command prints what the config would be allowed to do — which hosts, which commands, which variables — and asks before recording anything.
Trust is recorded by content hash, following the direnv model.bash runs a command using the embedded mvdan/sh shell, a cross-platform reimplementation of Bash.SKILL.md under ~/.local/share/strument/skills/foo/ or the project's .strument/skills/foo/, and the model can ask for it by name.
Project skills also require strument trust.
A skill's allowed-tools field does not grant tool permissions.run_code tool.
The model can run short JavaScript programs for calculations, formatting, or processing several inputs at once, in an embedded interpreter with no access to the host.
Programs have no direct filesystem or network access.
They can inspect project data through the five exposed read-only search tools.
Tools that modify files or run shell commands are not exposed.
See the run_code tool./undo can restore those file changes for a whole turn even in a directory that is not a repository,
like a live configuration directory or a checkout under another SCM.
In a Git repository, a turn is one commit.
The command /squash [n] merges commits.
Files that the model's commands created, rather than its edits (a compiled binary, a scratch script), are not committed.
Strument lists those that git neither tracks nor ignores at the end of the turn, and tells the model about them once, when it thinks it is done, so it can remove any by-product it did not mean to leave.check config setting is a dictionary of named verification commands, like tests, a linter, and a build.
The model can run them by name without a permission prompt.
project_checks() detects standard checks for your project type.
check_auto lists which of the check commands Strument runs at the end of any turn that changed a file./web <url> fetches a page, converts it to Markdown, and offers it to the model.
Fetching uses either a built-in HTTPS client or an external browser command (necessary for pages that rely on JavaScript).
The model has a webfetch tool, which asks your permission before fetching from an unfamiliar origin; the built-in client does not follow a redirect to one either.
A URL fragment limits the result to that section.
If a page exceeds the size limit, the tool returns an outline instead.websearch and the model gets a websearch tool.
Use your own SearXNG instance, with your choice of engines and no API key, or a hosted backend that needs no setup: AnySearch, which works with or without a key, or Exa, which searches its own index and returns page text instead of snippets (key required).The terminal interface has stayed deliberately close to aider's, including the green/blue palette (with --dark-mode and --light-mode).
Strument diverges where its programming loop is different.
Model reasoning is marked with ‹thinking›.
Multiline blocks end with ‹/›; single-line blocks need no closing marker.
Syntax highlighting and the inverted code-block background are omitted.
> Rename defaultTimeout to pollInterval and update the callers.
‹thinking› Let me find where that constant is defined.
Searched for defaultTimeout: 3 matches in 2 files
Read internal/poll/poll.go (118 lines)
‹thinking›
The declaration is in poll.go and there is one more use there and one in
watch.go. I'll rename the declaration, then each use.
‹/›
internal/poll/poll.go
const (
- defaultTimeout = 30 * time.Second
+ pollInterval = 30 * time.Second
maxRetries = 3
)
internal/poll/poll.go
- t := time.NewTicker(defaultTimeout)
+ t := time.NewTicker(pollInterval)
internal/poll/watch.go
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+ ctx, cancel := context.WithTimeout(ctx, pollInterval)
Applied edit to internal/poll/poll.go
Applied edit to internal/poll/watch.go
Renamed the constant and updated its two uses.
Running the automatic checks.
‹check› test
$ go test ./...
passed
Commit 6c1e0a4 refactor(poll): rename defaultTimeout to pollInterval
Tokens: 12.4k sent, 1.8k received, 30 t/s. Cost: $0.03 turn, $0.03 run. 4 steps, 2 files changed.
Go 1.26 or later is required, but not a C toolchain:
go install dbohdan.com/strument/cmd/strument@latest
Strument requires a configuration file before starting in chat mode. It has no bundled model database, so you must configure the models you want to use.
Put this minimal configuration in ~/.config/strument/config.star:
openrouter = provider("openrouter", api_key=env("OPENROUTER_API_KEY"))
models = {"mimo": model(openrouter, "xiaomi/mimo-v2.5", context=1050000)}
default = "mimo"
Export the key and start Strument in your project:
cd ~/src/myproject
OPENROUTER_API_KEY=sk-or-... strument
Set context so Strument can warn you before a request exceeds the model's context window and summarize older chat history when needed.
Without it, a long session can exceed the provider's limit and have its requests rejected.
Cost fields are optional.
OpenRouter includes each request's cost in its response.
A plain OpenAI-compatible endpoint may not report costs.
For those endpoints, Strument estimates the turn's cost using input_cost and output_cost.
strument model-config <slug> fetches all of this information from a provider's catalog; see Configuration.
Strument reports costs per turn.
Each turn is limited to 25 steps by default; set max_steps to change the limit.
Try a small request with an inexpensive model and check the reported cost before attempting a larger task.
Type what you want changed. The model works until it finishes or reaches the step limit. At the limit (25 steps by default) Strument reports the number of edits and the cost so far, then asks whether to continue.
Strument prints a status line for each tool call.
Shell commands ask for permission first, which you can grant for that command or for all commands in the turn; webfetch and websearch ask too.
Reading, searching, and editing do not ask.
In a Git repository, each turn that changes a file ends in a commit.
--no-git turns the Git integration off inside a repository; outside one it is already off.
/undo works either way.
The prompt is a single line, and line breaks in it are shown as ↵.
Alt-Enter adds one; a multi-line paste arrives whole, line breaks included, and is not sent until you press Enter.
For anything longer, /editor opens your editor ($VISUAL, then $EDITOR), and Ctrl-X Ctrl-E does the same starting from what you have typed.
What you save comes back to the prompt for you to read and send; an empty file sends nothing.
/editor <command> uses that command instead, for example /editor code --wait.
While the model is responding or a tool is running, press Ctrl-C once to interrupt it.
Strument keeps the conversation and any completed work, then asks whether to continue, stop, or enter a correction.
Press Ctrl-C twice within two seconds to exit Strument.
A script can interrupt a run with SIGUSR1 instead; see script mode.
You can stop a long response and redirect the model without starting over:
‹thinking› I'll inspect the authentication package first...
Reading internal/auth/auth.go
^C
Press Ctrl-C again to exit
‹question› You stopped the model. What now?
1. Continue — Carry on from where it was cut off
2. Stop — End the turn here
Answer (1-2, or your own text): Use the existing token helper instead
‹thinking› I'll continue from the interrupted response using the existing token helper.
...
Continue lets the model resume from the partial response with its context preserved.
Typing your own answer sends it as a correction, and Stop ends the turn.
Edits made before the interruption remain undoable with /undo.
/add <file> ..., /drop, /ls | Pin files you want the model to inspect or change. Strument gives the model their names; the model reads them as needed and can find other project files itself. |
/ask <question> | Ask about the project without giving the model editing tools. /ask on its own switches to ask mode, and /code switches back. |
/yes [add <name> ... | drop <name> ... | reset] | Show or change which prompts are approved automatically. On its own it lists each approval and where it came from: --yes, the config's auto_approve, or this run. Dropping one makes Strument ask again mid-run, which is useful when a turn starts going somewhere unexpected. |
/attach <file> ... | Attach images (PNG, JPEG, GIF, WebP) to your next message, from anywhere on disk. On its own it lists what is attached; /attach drop removes attachments. Unlike pinned files, attachments go with one message only. A model that does not accept images is told an image was there and that it could not see it, rather than the request failing; declare input_modalities for one that can. |
/check [<name>] | Run a project check by name, or all checks if no name is given. Checks run in the order the config lists them and stop at the first failure. On failure or non-empty output, Strument offers to add the transcript to the chat. A successful check with no output is not offered to the chat. |
/consult <alias> <question>, /consult scope [<name>] | Ask another model without switching the active model, then optionally add its answer to the conversation, labeled with the advisor's name. /consult scope shows or sets how much the advisor sees: none, files (the pinned files, the default), or chat (the pinned files and the conversation); --consult-scope sets the starting value. The consultation is billed at the advisor's rates and appears in the cost ledger under its slug. |
/session, /session new|switch|fork|rename|delete <name> | List this project's sessions, or create, switch to, fork, rename, or delete one. See Sessions. |
/notes, /notes generate, /notes drop | Show the session notes, regenerate them from the session record, or discard them. Notes stay in memory and are not saved to disk. They carry context from one session to another; to pick up this session's conversation, use --continue. See doc/sessions.md. |
/read-only <file> ... | Pin a file the model can read but not edit, such as a spec or a header from a sibling repository. This is the way to show the model something outside the project; the search tools see only the project itself. |
/commits [on | off] | Show or change whether a turn that edits a file ends in a commit. With no argument, it shows the current setting. --no-auto-commits starts a session with commits off. With commits off, edits are still written to the working tree, and /undo and /diff still work. |
/undo | Revert the last turn. Restores files changed through Strument's file tools and removes the commit if there was one. |
/rewind [<n>] | Take the last n turns (default 1) out of the conversation, for a turn that went wrong in a way that would steer the next one. Files are not changed, and Strument names any the rewound turns edited; /undo reverts edits. The turns stay in the session record, and --continue restores the conversation without them. Turns folded into a compaction summary cannot be rewound. |
/squash [<n>] | Combine the last n turns' commits into one. |
/usage [<provider> | all] | Show token usage and cost for the last 24 hours, 7 days, and 30 days. Defaults to the current model's provider. See Usage reports. |
/diff, /tokens | Show what changed and how full the context window is. |
/context [<n>] | Show the chat history as the model receives it: compaction summaries followed by recent, unsummarized messages. With n, show only the first n summaries. |
/skill [<name>] | List the available skills, or add a skill's instructions to the chat yourself. See Skills. |
/symbol <name> [definition | reference] | Find where a name is defined or used, using the language parser rather than a text search. |
/editor [<command>] | Write your message in an editor: $VISUAL, then $EDITOR, or the command given. What you save comes back to the prompt to read and send. Ctrl-X Ctrl-E opens it with what you have typed. |
/submit <file> | Send a file's contents as your message, as if you had typed them: the trimmed contents are printed first, then sent. Paths outside the project are allowed. Files over 100 KiB are refused rather than truncated. |
/run <cmd>, /web <url> | Run a command or fetch a page and offer the output to the model. /run keeps your full environment; model-run commands receive an allowlist. /web on its own lists the origins webfetch can fetch from without asking, and /web drop and /web reset revoke those approvals. |
/env, /env add <NAME>..., /env drop <NAME>..., /env reset | Show or change, for this run, which environment variables model-run commands receive. Tab completes variable names. Persistent changes belong in env_allow. |
/model [alias], /reload | Switch models mid-session; reload the configuration without restarting (what a reload applies). |
/help lists all commands.
The model also has a run_code tool, which runs a short JavaScript program in a sandbox, in either mode.
See the run_code tool.
strument -m '<request>' runs a single turn and exits, and --dry-run shows the edits without writing them.
Without a terminal, every prompt is declined unless --yes <name> approves it; read script mode before giving an unattended run --yes bash.
A project can hold several sessions, each with its own conversation, pinned files, and undo history.
-s <name> (--session) picks one, creating it the first time, and a bare strument returns to the last one used.
A session starts with an empty conversation; -c (--continue) restores its conversation from the session record, so your work survives a crash, a closed laptop, or a restart.
Inside Strument, /session lists, switches, forks, renames, and deletes sessions, and /notes carries context from one session into another.
From the shell, strument session list, rename, and delete do the same.
doc/history.md has the details, and doc/sessions.md explains why sessions work this way.
Strument records every session outside your project as JSON Lines: each message, tool call, and result, plus a summary row with the cost of each turn.
strument history list shows a session's runs, strument history markdown renders them as a transcript, and strument history path prints the file for jq, and strument history zip <file> packs a run with its stored tool output for sharing; -b <n> picks one run.
--no-history records nothing.
The record format, stored tool output, strument history strip, and what to do if you rename a project directory are in doc/history.md.
strument usage [<provider>] reports token usage and cost per provider, across every project: usage all covers every provider, and with no argument it reports the default model's provider.
/usage [<provider>] prints the same report inside Strument, defaulting to the current model's provider.
It shows rolling windows (the last 24 hours, 7 days, and 30 days), which will not match a provider's calendar-month invoice; see doc/config.md.
The shell subcommand prints a completion script for Bash or fish.
Load the generated script in your current shell:
# Bash
source <(strument shell bash)
# fish
strument shell fish | source
The -M/--model option completes model aliases from the effective config by running strument config models, and -s/--session completes session names.
Subcommands, their flags, and enumerable option values (--yes, --mode, --consult-scope) complete too, and paths complete where a command takes one.
To load completions automatically, add the command to your shell configuration.
Strument is configured in Starlark, a small sandboxed dialect of Python.
A config file is a short program that builds model objects and assigns values to the configuration variables.
doc/config.md is the reference for the settings and every built-in function specific to Strument.
strument config edit opens your config in your editor (--project for the project's), and strument config path prints where it is.
A more complete configuration:
openrouter = provider("openrouter", api_key=env("OPENROUTER_API_KEY"))
local_llm = provider(
"openai",
name="local",
base_url="http://localhost:8000/v1",
)
def flex(m):
return m.with_extra_params(service_tier="flex")
models = {
"deepseek-flash": model(
openrouter,
"deepseek/deepseek-v4.1-flash",
display_name="DeepSeek V4.1 Flash",
context=1048576,
max_output=384000,
input_cost=0.15,
output_cost=0.6,
cache=True, # OpenRouter reports prompt caching for this model.
# reasoning="high", # Uncomment and set the effort: "low", "medium", or "high".
),
"gpt-luna": flex(
model(
openrouter,
"openai/gpt-5.6-luna",
display_name="GPT-5.6 Luna",
context=1050000,
max_output=128000,
cache=True,
reasoning="high",
),
),
"mimo": model(
openrouter,
"xiaomi/mimo-v2.5",
display_name="MiMo-V2.5",
context=1050000,
max_output=131072,
cache=True,
),
"sonnet": model(
openrouter,
"anthropic/claude-sonnet-5",
display_name="Claude Sonnet 5",
context=1000000,
max_output=128000,
input_cost=2,
output_cost=10,
cache=True, # Cache the prompt prefix (Anthropic honors this).
reasoning="medium",
side_model="mimo", # A cheaper model for commit messages and summaries.
),
"qwen": model(
local_llm,
"qwen/qwen3.6-27b",
display_name="Qwen3.6 27B",
reasoning="high",
reasoning_tag="think", # This model emits reasoning in inline tags.
),
}
models["ds"] = models["deepseek-flash"] # One model, two aliases.
default = "mimo"
cache (off by default) attaches cache-control breakpoints with a one-hour TTL to stable prompt sections.
Anthropic models reached through OpenRouter explicitly honor them.
Other providers may ignore them or implement their own prompt-caching behavior.
When a turn used the cache, the usage line breaks down the figure in parentheses: 12.4k sent (4.2k cache write, 3.2k cache hit).
Cache-write and cache-hit tokens are included in the sent total.
Writing context, max_output, and the costs by hand for every model is tedious.
Instead, strument model-config z-ai/glm-5.3 fetches them from the provider's catalog and prints a model block you can copy into your configuration.
It works before you have a config.
Settings that are your choice (reasoning, reasoning_tag, side_model) appear as commented-out placeholders.
The catalog is fetched on demand and cached.
Some settings live at the top level rather than on a model:
check names the commands that check your project.check_auto lists which checks should run automatically at the end of an editing turn.reasoning_display says how much of the model's thinking to show.check = {
"lint": ["golangci-lint", "run"],
"test": ["go", "test", "./..."],
}
check_auto = ["lint", "test"]
reasoning_display = 10 # "full" (the default), a line count, or "off".
Checks run in the order in which they are listed in check or check_auto, depending on which setting is being used.
They stop at the first failure, so put the fast checks first.
A shell command that exactly matches a configured check runs without a permission prompt. A modified command, such as one with an extra flag, still requires permission.
check = project_checks() fills the dictionary with commands detected from your project's marker files for Go, Rust, Python, Node, Deno, make/task/just, Java, .NET, PHP, Ruby, Elixir, Crystal, and Haskell.
Check detection is opt-in and includes only targets the project defines.
These are your project's own commands: npm test runs whatever your package.json says.
Hiding reasoning is not the same as disabling it.
The reasoning tokens are still generated, logged, and billed.
Set reasoning="off" on a model that supports this to disable it.
On a network that cannot reach a provider directly, a proxy on the provider() call routes requests to that provider through SOCKS5.
A top-level proxy is applied to all providers and every outbound HTTPS connection Strument makes.
proxy="direct" disables the top-level proxy for that provider.
search() calls work the same way.
A project-local config can override any of these settings, once you have run strument trust in the directory.
strument trust shows what the config grants and which skills it found, then asks; --yes skips the question for scripts, and without a terminal it refuses rather than trusting silently.
The same command trusts the project's skills.
Project skills are not loaded until you trust them.
See doc/config.md for details.
On Linux, Strument confines itself with Landlock before the session starts.
Every process it spawns inherits the Landlock sandbox, as does the bash tool.
As a result, your checks and every child process they start can write only to your project, a temporary directory, the session's state directory, and the machine's toolchain caches.
/sandbox lists the effective paths.
sandbox_write in the config adds writable paths; sandbox = "" turns the sandbox off, which is the default on non-Linux platforms.
The sandbox protects integrity, not confidentiality.
While writes are confined, reads are not.
A mistaken or injected command cannot edit your dotfiles or your other repositories, but it can read them.
The sandbox is intended to limit damage from mistakes and prompt injection during supervised use, not to contain a deliberately malicious agent over a long session.
doc/security.md describes the restrictions, exceptions, and remaining risks.
Strument is pre-1.0 and its behavior is not stable. Expect settings to change and read the commit log before upgrading.
Known limits:
SEARCH/REPLACE, fenced, whole-file) have been removed.go build ./cmd/strument # A full build with every bundled tree-sitter grammar.
task build:strument:subset # Release variant: only the grammars Strument uses.
task release # Cross-compile the subset build for every platform.
The subset build compiles in just the 35 grammars the parse layer supports,
via gotreesitter's grammar_subset build tags.
The tag list lives in script/grammar-tags.txt.
A test keeps it in sync with the supported languages.
Strument builds and tests offline, with no API keys or extra setup.
To read aider's source alongside it, run task setup:reference, which clones aider at commit 5dc9490
into a gitignored reference/ directory.
Nothing in the build needs it.
Strument is derived from aider by Paul Gauthier and the aider contributors, licensed under the Apache License 2.0, and carries the same license.
Four components are forked and vendored; three carry a NOTICE recording the changes:
internal/render/) is ported from
streaming-markdown by Damian Tarnawski (MIT)..gitignore pattern matcher (internal/gitignore/) comes from
go-git at v6.0.0-alpha.5 (Apache 2.0).internal/readline/) is a fork of
ergochat/readline v0.1.3 (MIT).
Its redraw algorithm is a non-destructive single-write repaint after
bestline by jart (2-clause BSD):
cells are overwritten in place and only the leftovers erased.Go
95.2%
Python
2.7%
Tree-sitter Query
1.1%
Strument is an AI pair-programming tool for the terminal. It is designed for developers who want to review and guide technical and UX decisions as the model works. Working with Strument is organized into turns. Each turn starts with your instructions to the model; changes are recorded in a commit or, outside Git, an undo snapshot.
Strument began as a ground-up reimplementation of aider but has since diverged.
See doc/ for the developer overview.
config.star file replaces YAML, .env files, and a JSON model database.
Project-local config is supported as either .strument.star or .strument/config.star.
They are loaded only after you authorize them by running strument trust in the project directory.
That command prints what the config would be allowed to do — which hosts, which commands, which variables — and asks before recording anything.
Trust is recorded by content hash, following the direnv model.bash runs a command using the embedded mvdan/sh shell, a cross-platform reimplementation of Bash.SKILL.md under ~/.local/share/strument/skills/foo/ or the project's .strument/skills/foo/, and the model can ask for it by name.
Project skills also require strument trust.
A skill's allowed-tools field does not grant tool permissions.run_code tool.
The model can run short JavaScript programs for calculations, formatting, or processing several inputs at once, in an embedded interpreter with no access to the host.
Programs have no direct filesystem or network access.
They can inspect project data through the five exposed read-only search tools.
Tools that modify files or run shell commands are not exposed.
See the run_code tool./undo can restore those file changes for a whole turn even in a directory that is not a repository,
like a live configuration directory or a checkout under another SCM.
In a Git repository, a turn is one commit.
The command /squash [n] merges commits.
Files that the model's commands created, rather than its edits (a compiled binary, a scratch script), are not committed.
Strument lists those that git neither tracks nor ignores at the end of the turn, and tells the model about them once, when it thinks it is done, so it can remove any by-product it did not mean to leave.check config setting is a dictionary of named verification commands, like tests, a linter, and a build.
The model can run them by name without a permission prompt.
project_checks() detects standard checks for your project type.
check_auto lists which of the check commands Strument runs at the end of any turn that changed a file./web <url> fetches a page, converts it to Markdown, and offers it to the model.
Fetching uses either a built-in HTTPS client or an external browser command (necessary for pages that rely on JavaScript).
The model has a webfetch tool, which asks your permission before fetching from an unfamiliar origin; the built-in client does not follow a redirect to one either.
A URL fragment limits the result to that section.
If a page exceeds the size limit, the tool returns an outline instead.websearch and the model gets a websearch tool.
Use your own SearXNG instance, with your choice of engines and no API key, or a hosted backend that needs no setup: AnySearch, which works with or without a key, or Exa, which searches its own index and returns page text instead of snippets (key required).The terminal interface has stayed deliberately close to aider's, including the green/blue palette (with --dark-mode and --light-mode).
Strument diverges where its programming loop is different.
Model reasoning is marked with ‹thinking›.
Multiline blocks end with ‹/›; single-line blocks need no closing marker.
Syntax highlighting and the inverted code-block background are omitted.
> Rename defaultTimeout to pollInterval and update the callers.
‹thinking› Let me find where that constant is defined.
Searched for defaultTimeout: 3 matches in 2 files
Read internal/poll/poll.go (118 lines)
‹thinking›
The declaration is in poll.go and there is one more use there and one in
watch.go. I'll rename the declaration, then each use.
‹/›
internal/poll/poll.go
const (
- defaultTimeout = 30 * time.Second
+ pollInterval = 30 * time.Second
maxRetries = 3
)
internal/poll/poll.go
- t := time.NewTicker(defaultTimeout)
+ t := time.NewTicker(pollInterval)
internal/poll/watch.go
- ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
+ ctx, cancel := context.WithTimeout(ctx, pollInterval)
Applied edit to internal/poll/poll.go
Applied edit to internal/poll/watch.go
Renamed the constant and updated its two uses.
Running the automatic checks.
‹check› test
$ go test ./...
passed
Commit 6c1e0a4 refactor(poll): rename defaultTimeout to pollInterval
Tokens: 12.4k sent, 1.8k received, 30 t/s. Cost: $0.03 turn, $0.03 run. 4 steps, 2 files changed.
Go 1.26 or later is required, but not a C toolchain:
go install dbohdan.com/strument/cmd/strument@latest
Strument requires a configuration file before starting in chat mode. It has no bundled model database, so you must configure the models you want to use.
Put this minimal configuration in ~/.config/strument/config.star:
openrouter = provider("openrouter", api_key=env("OPENROUTER_API_KEY"))
models = {"mimo": model(openrouter, "xiaomi/mimo-v2.5", context=1050000)}
default = "mimo"
Export the key and start Strument in your project:
cd ~/src/myproject
OPENROUTER_API_KEY=sk-or-... strument
Set context so Strument can warn you before a request exceeds the model's context window and summarize older chat history when needed.
Without it, a long session can exceed the provider's limit and have its requests rejected.
Cost fields are optional.
OpenRouter includes each request's cost in its response.
A plain OpenAI-compatible endpoint may not report costs.
For those endpoints, Strument estimates the turn's cost using input_cost and output_cost.
strument model-config <slug> fetches all of this information from a provider's catalog; see Configuration.
Strument reports costs per turn.
Each turn is limited to 25 steps by default; set max_steps to change the limit.
Try a small request with an inexpensive model and check the reported cost before attempting a larger task.
Type what you want changed. The model works until it finishes or reaches the step limit. At the limit (25 steps by default) Strument reports the number of edits and the cost so far, then asks whether to continue.
Strument prints a status line for each tool call.
Shell commands ask for permission first, which you can grant for that command or for all commands in the turn; webfetch and websearch ask too.
Reading, searching, and editing do not ask.
In a Git repository, each turn that changes a file ends in a commit.
--no-git turns the Git integration off inside a repository; outside one it is already off.
/undo works either way.
The prompt is a single line, and line breaks in it are shown as ↵.
Alt-Enter adds one; a multi-line paste arrives whole, line breaks included, and is not sent until you press Enter.
For anything longer, /editor opens your editor ($VISUAL, then $EDITOR), and Ctrl-X Ctrl-E does the same starting from what you have typed.
What you save comes back to the prompt for you to read and send; an empty file sends nothing.
/editor <command> uses that command instead, for example /editor code --wait.
While the model is responding or a tool is running, press Ctrl-C once to interrupt it.
Strument keeps the conversation and any completed work, then asks whether to continue, stop, or enter a correction.
Press Ctrl-C twice within two seconds to exit Strument.
A script can interrupt a run with SIGUSR1 instead; see script mode.
You can stop a long response and redirect the model without starting over:
‹thinking› I'll inspect the authentication package first...
Reading internal/auth/auth.go
^C
Press Ctrl-C again to exit
‹question› You stopped the model. What now?
1. Continue — Carry on from where it was cut off
2. Stop — End the turn here
Answer (1-2, or your own text): Use the existing token helper instead
‹thinking› I'll continue from the interrupted response using the existing token helper.
...
Continue lets the model resume from the partial response with its context preserved.
Typing your own answer sends it as a correction, and Stop ends the turn.
Edits made before the interruption remain undoable with /undo.
/add <file> ..., /drop, /ls | Pin files you want the model to inspect or change. Strument gives the model their names; the model reads them as needed and can find other project files itself. |
/ask <question> | Ask about the project without giving the model editing tools. /ask on its own switches to ask mode, and /code switches back. |
/yes [add <name> ... | drop <name> ... | reset] | Show or change which prompts are approved automatically. On its own it lists each approval and where it came from: --yes, the config's auto_approve, or this run. Dropping one makes Strument ask again mid-run, which is useful when a turn starts going somewhere unexpected. |
/attach <file> ... | Attach images (PNG, JPEG, GIF, WebP) to your next message, from anywhere on disk. On its own it lists what is attached; /attach drop removes attachments. Unlike pinned files, attachments go with one message only. A model that does not accept images is told an image was there and that it could not see it, rather than the request failing; declare input_modalities for one that can. |
/check [<name>] | Run a project check by name, or all checks if no name is given. Checks run in the order the config lists them and stop at the first failure. On failure or non-empty output, Strument offers to add the transcript to the chat. A successful check with no output is not offered to the chat. |
/consult <alias> <question>, /consult scope [<name>] | Ask another model without switching the active model, then optionally add its answer to the conversation, labeled with the advisor's name. /consult scope shows or sets how much the advisor sees: none, files (the pinned files, the default), or chat (the pinned files and the conversation); --consult-scope sets the starting value. The consultation is billed at the advisor's rates and appears in the cost ledger under its slug. |
/session, /session new|switch|fork|rename|delete <name> | List this project's sessions, or create, switch to, fork, rename, or delete one. See Sessions. |
/notes, /notes generate, /notes drop | Show the session notes, regenerate them from the session record, or discard them. Notes stay in memory and are not saved to disk. They carry context from one session to another; to pick up this session's conversation, use --continue. See doc/sessions.md. |
/read-only <file> ... | Pin a file the model can read but not edit, such as a spec or a header from a sibling repository. This is the way to show the model something outside the project; the search tools see only the project itself. |
/commits [on | off] | Show or change whether a turn that edits a file ends in a commit. With no argument, it shows the current setting. --no-auto-commits starts a session with commits off. With commits off, edits are still written to the working tree, and /undo and /diff still work. |
/undo | Revert the last turn. Restores files changed through Strument's file tools and removes the commit if there was one. |
/rewind [<n>] | Take the last n turns (default 1) out of the conversation, for a turn that went wrong in a way that would steer the next one. Files are not changed, and Strument names any the rewound turns edited; /undo reverts edits. The turns stay in the session record, and --continue restores the conversation without them. Turns folded into a compaction summary cannot be rewound. |
/squash [<n>] | Combine the last n turns' commits into one. |
/usage [<provider> | all] | Show token usage and cost for the last 24 hours, 7 days, and 30 days. Defaults to the current model's provider. See Usage reports. |
/diff, /tokens | Show what changed and how full the context window is. |
/context [<n>] | Show the chat history as the model receives it: compaction summaries followed by recent, unsummarized messages. With n, show only the first n summaries. |
/skill [<name>] | List the available skills, or add a skill's instructions to the chat yourself. See Skills. |
/symbol <name> [definition | reference] | Find where a name is defined or used, using the language parser rather than a text search. |
/editor [<command>] | Write your message in an editor: $VISUAL, then $EDITOR, or the command given. What you save comes back to the prompt to read and send. Ctrl-X Ctrl-E opens it with what you have typed. |
/submit <file> | Send a file's contents as your message, as if you had typed them: the trimmed contents are printed first, then sent. Paths outside the project are allowed. Files over 100 KiB are refused rather than truncated. |
/run <cmd>, /web <url> | Run a command or fetch a page and offer the output to the model. /run keeps your full environment; model-run commands receive an allowlist. /web on its own lists the origins webfetch can fetch from without asking, and /web drop and /web reset revoke those approvals. |
/env, /env add <NAME>..., /env drop <NAME>..., /env reset | Show or change, for this run, which environment variables model-run commands receive. Tab completes variable names. Persistent changes belong in env_allow. |
/model [alias], /reload | Switch models mid-session; reload the configuration without restarting (what a reload applies). |
/help lists all commands.
The model also has a run_code tool, which runs a short JavaScript program in a sandbox, in either mode.
See the run_code tool.
strument -m '<request>' runs a single turn and exits, and --dry-run shows the edits without writing them.
Without a terminal, every prompt is declined unless --yes <name> approves it; read script mode before giving an unattended run --yes bash.
A project can hold several sessions, each with its own conversation, pinned files, and undo history.
-s <name> (--session) picks one, creating it the first time, and a bare strument returns to the last one used.
A session starts with an empty conversation; -c (--continue) restores its conversation from the session record, so your work survives a crash, a closed laptop, or a restart.
Inside Strument, /session lists, switches, forks, renames, and deletes sessions, and /notes carries context from one session into another.
From the shell, strument session list, rename, and delete do the same.
doc/history.md has the details, and doc/sessions.md explains why sessions work this way.
Strument records every session outside your project as JSON Lines: each message, tool call, and result, plus a summary row with the cost of each turn.
strument history list shows a session's runs, strument history markdown renders them as a transcript, and strument history path prints the file for jq, and strument history zip <file> packs a run with its stored tool output for sharing; -b <n> picks one run.
--no-history records nothing.
The record format, stored tool output, strument history strip, and what to do if you rename a project directory are in doc/history.md.
strument usage [<provider>] reports token usage and cost per provider, across every project: usage all covers every provider, and with no argument it reports the default model's provider.
/usage [<provider>] prints the same report inside Strument, defaulting to the current model's provider.
It shows rolling windows (the last 24 hours, 7 days, and 30 days), which will not match a provider's calendar-month invoice; see doc/config.md.
The shell subcommand prints a completion script for Bash or fish.
Load the generated script in your current shell:
# Bash
source <(strument shell bash)
# fish
strument shell fish | source
The -M/--model option completes model aliases from the effective config by running strument config models, and -s/--session completes session names.
Subcommands, their flags, and enumerable option values (--yes, --mode, --consult-scope) complete too, and paths complete where a command takes one.
To load completions automatically, add the command to your shell configuration.
Strument is configured in Starlark, a small sandboxed dialect of Python.
A config file is a short program that builds model objects and assigns values to the configuration variables.
doc/config.md is the reference for the settings and every built-in function specific to Strument.
strument config edit opens your config in your editor (--project for the project's), and strument config path prints where it is.
A more complete configuration:
openrouter = provider("openrouter", api_key=env("OPENROUTER_API_KEY"))
local_llm = provider(
"openai",
name="local",
base_url="http://localhost:8000/v1",
)
def flex(m):
return m.with_extra_params(service_tier="flex")
models = {
"deepseek-flash": model(
openrouter,
"deepseek/deepseek-v4.1-flash",
display_name="DeepSeek V4.1 Flash",
context=1048576,
max_output=384000,
input_cost=0.15,
output_cost=0.6,
cache=True, # OpenRouter reports prompt caching for this model.
# reasoning="high", # Uncomment and set the effort: "low", "medium", or "high".
),
"gpt-luna": flex(
model(
openrouter,
"openai/gpt-5.6-luna",
display_name="GPT-5.6 Luna",
context=1050000,
max_output=128000,
cache=True,
reasoning="high",
),
),
"mimo": model(
openrouter,
"xiaomi/mimo-v2.5",
display_name="MiMo-V2.5",
context=1050000,
max_output=131072,
cache=True,
),
"sonnet": model(
openrouter,
"anthropic/claude-sonnet-5",
display_name="Claude Sonnet 5",
context=1000000,
max_output=128000,
input_cost=2,
output_cost=10,
cache=True, # Cache the prompt prefix (Anthropic honors this).
reasoning="medium",
side_model="mimo", # A cheaper model for commit messages and summaries.
),
"qwen": model(
local_llm,
"qwen/qwen3.6-27b",
display_name="Qwen3.6 27B",
reasoning="high",
reasoning_tag="think", # This model emits reasoning in inline tags.
),
}
models["ds"] = models["deepseek-flash"] # One model, two aliases.
default = "mimo"
cache (off by default) attaches cache-control breakpoints with a one-hour TTL to stable prompt sections.
Anthropic models reached through OpenRouter explicitly honor them.
Other providers may ignore them or implement their own prompt-caching behavior.
When a turn used the cache, the usage line breaks down the figure in parentheses: 12.4k sent (4.2k cache write, 3.2k cache hit).
Cache-write and cache-hit tokens are included in the sent total.
Writing context, max_output, and the costs by hand for every model is tedious.
Instead, strument model-config z-ai/glm-5.3 fetches them from the provider's catalog and prints a model block you can copy into your configuration.
It works before you have a config.
Settings that are your choice (reasoning, reasoning_tag, side_model) appear as commented-out placeholders.
The catalog is fetched on demand and cached.
Some settings live at the top level rather than on a model:
check names the commands that check your project.check_auto lists which checks should run automatically at the end of an editing turn.reasoning_display says how much of the model's thinking to show.check = {
"lint": ["golangci-lint", "run"],
"test": ["go", "test", "./..."],
}
check_auto = ["lint", "test"]
reasoning_display = 10 # "full" (the default), a line count, or "off".
Checks run in the order in which they are listed in check or check_auto, depending on which setting is being used.
They stop at the first failure, so put the fast checks first.
A shell command that exactly matches a configured check runs without a permission prompt. A modified command, such as one with an extra flag, still requires permission.
check = project_checks() fills the dictionary with commands detected from your project's marker files for Go, Rust, Python, Node, Deno, make/task/just, Java, .NET, PHP, Ruby, Elixir, Crystal, and Haskell.
Check detection is opt-in and includes only targets the project defines.
These are your project's own commands: npm test runs whatever your package.json says.
Hiding reasoning is not the same as disabling it.
The reasoning tokens are still generated, logged, and billed.
Set reasoning="off" on a model that supports this to disable it.
On a network that cannot reach a provider directly, a proxy on the provider() call routes requests to that provider through SOCKS5.
A top-level proxy is applied to all providers and every outbound HTTPS connection Strument makes.
proxy="direct" disables the top-level proxy for that provider.
search() calls work the same way.
A project-local config can override any of these settings, once you have run strument trust in the directory.
strument trust shows what the config grants and which skills it found, then asks; --yes skips the question for scripts, and without a terminal it refuses rather than trusting silently.
The same command trusts the project's skills.
Project skills are not loaded until you trust them.
See doc/config.md for details.
On Linux, Strument confines itself with Landlock before the session starts.
Every process it spawns inherits the Landlock sandbox, as does the bash tool.
As a result, your checks and every child process they start can write only to your project, a temporary directory, the session's state directory, and the machine's toolchain caches.
/sandbox lists the effective paths.
sandbox_write in the config adds writable paths; sandbox = "" turns the sandbox off, which is the default on non-Linux platforms.
The sandbox protects integrity, not confidentiality.
While writes are confined, reads are not.
A mistaken or injected command cannot edit your dotfiles or your other repositories, but it can read them.
The sandbox is intended to limit damage from mistakes and prompt injection during supervised use, not to contain a deliberately malicious agent over a long session.
doc/security.md describes the restrictions, exceptions, and remaining risks.
Strument is pre-1.0 and its behavior is not stable. Expect settings to change and read the commit log before upgrading.
Known limits:
SEARCH/REPLACE, fenced, whole-file) have been removed.go build ./cmd/strument # A full build with every bundled tree-sitter grammar.
task build:strument:subset # Release variant: only the grammars Strument uses.
task release # Cross-compile the subset build for every platform.
The subset build compiles in just the 35 grammars the parse layer supports,
via gotreesitter's grammar_subset build tags.
The tag list lives in script/grammar-tags.txt.
A test keeps it in sync with the supported languages.
Strument builds and tests offline, with no API keys or extra setup.
To read aider's source alongside it, run task setup:reference, which clones aider at commit 5dc9490
into a gitignored reference/ directory.
Nothing in the build needs it.
Strument is derived from aider by Paul Gauthier and the aider contributors, licensed under the Apache License 2.0, and carries the same license.
Four components are forked and vendored; three carry a NOTICE recording the changes:
internal/render/) is ported from
streaming-markdown by Damian Tarnawski (MIT)..gitignore pattern matcher (internal/gitignore/) comes from
go-git at v6.0.0-alpha.5 (Apache 2.0).internal/readline/) is a fork of
ergochat/readline v0.1.3 (MIT).
Its redraw algorithm is a non-destructive single-write repaint after
bestline by jart (2-clause BSD):
cells are overwritten in place and only the leftovers erased.Go
95.2%
Python
2.7%
Tree-sitter Query
1.1%