Skip to main content

Configuration

Rune Agent is installed as the rune-agent package, so its settings live under extensions.rune-agent.config in your Rune config. Rune manages the extension's entry and path for you; you normally only tweak the nested config block. Every key is optional and falls back to a sensible default.

extensions:
rune-agent:
config:
agents_file: AGENTS.md
attribution:
commit: 'Co-Authored-By: Rune Agent ({{provider}}/{{model}}) <agent@rune.build>'
skills:
- .rune/skills
- ~/.rune/skills
- .agents/skills
- ~/.agents/skills
editor_modal_start_insert: true

Behavior​

KeyTypeDefaultWhat it does
agents_filestringAGENTS.mdName of the project instructions file to discover. The agent walks up from the workspace root looking for files with this name and injects their contents into the system prompt. Set it empty to disable discovery.
attribution.commitstringCo-Authored-By: Rune Agent ({{provider}}/{{model}}) <agent@rune.build>Attribution text Rune Agent is instructed to append to commits it creates. {{provider}} and {{model}} expand to the resolved model. Set it to an empty string to disable commit attribution.
skillslistthe four directories belowOrdered list of skill directories to search. Overrides the default set when present.
max_line_bytesinteger500Truncation limit, in bytes, for a single line of read_file output.
max_tool_output_bytesinteger40000Truncation limit, in bytes, for a single tool's output before it is trimmed in the transcript.
auto_compact_ratiofloat0.85Fraction of the model's context window that must be filled before the agent automatically compacts the conversation.
review_context_linesinteger3How many surrounding lines of code to show around each hunk when you review a chat's changes. Set it to 0 to show only the lines the agent actually changed.
pinned_user_msg_max_linesinteger2How many lines of your last message stay pinned over the top of the chat once it scrolls out of sight. Longer messages are cut short with an ellipsis, and clicking the pin scrolls back to the message. Set it to 0 to disable the pin.
editor_modal_start_insertbooleantrueWhen the modal (vi-style) composer opens, start in insert mode so the first keystroke types instead of requiring i. Set to false to open in normal mode. Only affects the modal composer.
audit_enabledbooleanfalseRecord a per-request LLM token audit log you can review with chatlog or agent chats export --audit.
force_builtin_toolsbooleanpromptWhen set, decides up front whether bare grep-style shell commands are rejected in favor of the agent's built-in search tools. Left unset, the agent asks you the first time and remembers your choice.

Too many permission prompts?​

Rune asks before Rune Agent runs workspace commands or uses other protected workspace APIs. If those prompts are getting a bit overwhelming, you can have Rune authorize workspace commands automatically:

authorizer:
auto_authorize_commands: true

authorizer is a top-level Rune setting, not part of extensions.rune-agent.config. This setting applies to workspace commands from other extensions and plugins too, so only enable it if you trust the code installed in Rune. Existing saved denials still apply.

Rune separately grants non-command extension and plugin permissions automatically by default. Set authorizer.auto_authorize_extensions to false if you want Rune to prompt for those permissions too.

auto_authorize_commands only silences command prompts for extensions from a verified publisher (and only when authorizer.auto_authorize_verified is also on, which it is by default). Unverified extensions always prompt before they run a command. See Verified publishers for the full trust model.

Models are configured host-side rather than in this block. Point the default, query, compact, and dream aliases at real models with the models alias command (see Model aliases), and set the output token budget with agent max_tokens.

Hooks​

hooks wires shell commands or HTTP requests to fire at defined points in the agent loop, letting you run scripts, post to a service, inject extra context, or block an action. The configuration mirrors Claude Code's hook format, so the same block works in either tool.

Events​

A hook is registered against an event. These are the events Rune Agent fires:

EventWhen it firesMatched against
SessionStartA conversation starts.the source (e.g. startup, resume)
SessionEndA conversation ends.the end reason
UserPromptSubmitYou submit a prompt, before the model sees it.(matches all)
PostToolUseA tool call finishes.the tool name (e.g. read_file, bash)
StopThe agent finishes a turn.(matches all)
SubagentStopA spawned sub-agent finishes.(matches all)
PreCompactBefore a conversation is compacted.the trigger (auto or manual)
NotificationThe agent posts a notification.the level (error, warn, info, success)

Shape​

hooks maps each event name to a list of groups. A group pairs a matcher with one or more hooks. The matcher decides which of that event's occurrences the group applies to:

  • * or omitted matches every occurrence.
  • A plain name (or |-separated list like read_file|bash) matches those exact values.
  • Anything else is treated as an RE2 regular expression.

Each hook is either a command (a shell command) or an http request:

FieldApplies toPurpose
typebothcommand or http. Required.
commandcommandThe shell command to run. Required for command.
shellcommandShell used to run command. Defaults to bash.
urlhttpURL to POST the payload to. Required for http.
headershttpMap of request headers.
allowed_env_varshttpEnv-var names allowed for ${VAR} interpolation in header values.
timeoutbothPer-hook timeout, e.g. 10s or a number of seconds. Defaults to 60s.

Every hook receives the event payload as JSON: command hooks read it on stdin, HTTP hooks get it as the request body. The payload carries the session ID, workspace path, event name, and event-specific fields such as tool_name, tool_input, and tool_response for PostToolUse, or prompt for UserPromptSubmit. Command hooks also get RUNE_HOOK_EVENT, RUNE_DIALOGUE_ID, and RUNE_PROJECT_DIR in their environment.

Reacting to the result​

A hook can stay silent (exit 0, empty output) or steer the agent:

  • Inject context. For UserPromptSubmit and SessionStart, a command hook's plain-text stdout is added to the model's context. Any hook can also return JSON with hookSpecificOutput.additionalContext to do the same.
  • Block an action. A command hook that exits with code 2 blocks the action, and its stderr becomes the reason shown to the agent. Equivalently, return JSON with "decision": "block" and a "reason".
  • Any other non-zero exit is logged as a non-blocking warning, so a failing hook never wedges the agent.

Examples​

Give every new conversation the current git branch and working-tree status. On SessionStart, a command hook's stdout is folded into the model's context, so the agent starts already knowing where the repo stands:

extensions:
rune-agent:
config:
hooks:
SessionStart:
- hooks:
- type: command
command: git status --short --branch

Run a formatter after every file write, matching only the apply_patch tool:

extensions:
rune-agent:
config:
hooks:
PostToolUse:
- matcher: apply_patch
hooks:
- type: command
command: make fmt
timeout: 30s

Add project context to every prompt, and block edits to a protected file. The UserPromptSubmit hook prints text that is folded into the model's context; the PostToolUse hook exits 2 to reject the tool call:

extensions:
rune-agent:
config:
hooks:
UserPromptSubmit:
- hooks:
- type: command
command: echo "Current sprint goal: ship the auth rewrite."
PostToolUse:
- matcher: apply_patch
hooks:
- type: command
command: |
path=$(jq -r '.tool_input.path // empty')
case "$path" in
*config/secrets.yaml)
echo "Refusing to edit secrets.yaml" >&2
exit 2 ;;
esac

Notify an external service over HTTP, interpolating a token from the environment into a header:

extensions:
rune-agent:
config:
hooks:
Stop:
- hooks:
- type: http
url: https://hooks.internal/agent-done
headers:
Authorization: "Bearer ${AGENT_HOOK_TOKEN}"
allowed_env_vars:
- AGENT_HOOK_TOKEN

Appearance​

These keys style the chat tab and its composer. Each attribute value takes an optional fg (foreground color), bg (background color), and flags (such as dim, italic, or bold).

KeyWhat it styles
background_attrThe chat tab background.
user_msg_attrYour messages in the transcript.
user_msg_background_attrThe background behind your messages.
assistant_msg_attrThe agent's messages in the transcript.
input_box_attrThe composer's text content.
input_box_placeholder_attrThe composer's placeholder text.
input_box_frame_attrThe composer's border.
input_box_frame_charsetThe character set used to draw the composer's border.
input_background_colorThe composer's background color.
completion_matched_text_attrThe matched substring in # context completion results.
completion_focus_element_attrThe selected row in the # context completion list.
completion_element_attrUnselected rows in the # context completion list.
completion_radar_colorThe radar sweep drawn over the composer while # candidates load.
inline_attachment_attrA file or symbol name inserted into the composer from # completion.

The completion_*_attr keys default to the command prompt's styling so both search overlays look the same. completion_radar_color defaults to a neutral gray instead, so the sweep reads the same under every theme.

Accepting a # completion keeps the attachment chip above the composer and also inserts its readable name into the sentence. inline_attachment_attr styles that name while it remains linked to the attachment. Rune maps the link to an internal, turn-local attachment reference when sending the message; that internal reference is not durable across conversation compaction.

extensions:
rune-agent:
config:
background_attr:
bg: default
user_msg_attr:
fg: default
bg: default
flags: dim
user_msg_background_attr:
bg: gray
assistant_msg_attr:
fg: default
bg: default
flags: italic
input_box_placeholder_attr:
fg: red
bg: default
completion_matched_text_attr:
fg: blue
bg: default
flags: bold
completion_focus_element_attr:
fg: purple
bg: default
completion_element_attr:
fg: default
bg: default
flags: dim
completion_radar_color: dimgray
inline_attachment_attr:
fg: aqua
bg: default
flags: underline

Status bar​

Every chat reserves its bottom row for a status bar. It stays put while you scroll, and it stays visible between turns: idle blanks the spinner and reads READY, while the model, effort, context and cache remain. Reopening a conversation fills the context gauge from its history, so the bar reflects how much of the window is already in use before you send anything.

KeyTypeDefaultWhat it does
status_bar.enabledbooleantrueWhether the chat reserves its bottom row for the bar.
status_bar.layoutstringsee belowTemplate describing what the bar shows and where.
status_bar.background_attrattributebg: gray, fg: silverThe bar's base attributes, matching the editor's status bar. bg fills the whole row, including the gaps between elements. fg is the foreground inherited by every layout component that does not name one of its own, so a theme can restyle the row from a single key.
status_bar.statusmapsee belowHow the Spinner and Status elements dress per turn state. The keys are the text Status draws: IDLE, SENDING, REASONING, RECEIVING, EXECUTING, COMPACTING, ASKING, and ERROR. Each takes an attr colouring both elements and an animation of ch, the spinner frames one character per frame, and attr, which overrides the status colours for the spinner alone so the icon can carry a colour of its own. Naming one status, or one of its keys, leaves the rest on their built-in values. ASKING is reported while the agent waits on an answer, and is the one status that outranks a running task's description.
status_bar.gauge_empty_attrattributefg: silver, bg: grayStyles the gauge's track. It sits flush with the bar, and its foreground doubles as the label color over the unfilled part.
status_bar.context_gauge_fill_attrslist of attributegreen, yellow, red on blackThe stops the context gauge's fill ramps through, left edge to right.
status_bar.cache_gauge_fill_attrslist of attributered, yellow, green on blackThe same for the cache gauge, listed backwards because a full cache is good news where a full context window is not.
status_bar.gauge_start_rune / status_bar.gauge_end_runestring"" / ""Brackets around a gauge. Each takes one cell out of gauge_width rather than widening the field, and an empty string drops that bracket and gives its cell back to the track. Blank by default: the ramp already marks where the track starts and ends.
status_bar.gauge_cap_attrattributefg: greenStyles the brackets. They sit on the bar rather than on the track, so leaving bg out inherits background_attr's.
status_bar.gauge_widthinteger18Cell width of each gauge.
status_bar.shaderstringnoneEffect drawn over the bar while a turn runs. blaze, burn, inferno and shine recolor only the text, leaving background colors and glyphs such as the █▓▒░ fade as the layout drew them. pulse gently brightens the whole bar. Leave it unset or empty to keep the bar still.
status_bar.shader_fpsinteger30Cadence the effect is redrawn at. Lower it to spend less time animating the bar.
status_bar.shader_loopduration1200msHow long one visual loop of the effect lasts, written as a Go duration such as 2s or 1200ms. For pulse it is the length of one pulse. The flames of blaze and inferno take a few dozen loops to come round, so a shorter loop makes the fire faster. Lowering shader_fps makes any effect choppier, not slower.

Layout​

The layout is a template of elements written as {{ .Name }}. Everything outside an element is literal text drawn as-is, so separators, padding, and powerline glyphs all belong in the layout string. {{ .ShiftRight }} is the alignment pivot: elements before it hug the left edge, elements after it hug the right.

ElementWhat it shows
SpinnerActivity spinner. Blank while the agent is idle. Takes its colors and its animation from status.
StatusWhat the turn is doing, or the running task's description when the agent is working through a plan. Reads IDLE between turns. Takes its colors from status, keyed by the turn's state rather than by the text shown, so a task description keeps the state's color.
ConversationThe name of the open chat, matching its tab label.
ElapsedTime spent in the current turn.
ModelThe model answering the conversation.
ProviderThe provider serving that model.
EffortThe reasoning effort in use. When the conversation sets none, this names the level the provider applies instead — and reads default only for providers that publish no such level.
MaxTokensThe per-response output token budget.
TokensSent / TokensReceivedInput and output tokens for the turn.
ContextTokens / ContextWindowContext occupancy and the model's total window.
ContextPct / CachePctContext occupancy and prompt cache hit rate as percentages.
CacheThe prompt cache hit rate, as 87%. Drops out until the conversation has sent something to report a rate on.
ContextGaugeFixed-width context gauge: the numbers centered on top, bracketed by gauge_start_rune and gauge_end_rune, with a color boundary marking how full the window is.
CacheGaugeThe same gauge for the prompt cache hit rate.
ImageA picture drawn over the bar. See Images.

A gauge marks its fill with color rather than with a texture, so the numbers stay readable on both sides of the boundary.

The fill ramps through the stops in _fill_attrs, spread evenly across the whole field rather than across the part the fill reaches — a cell keeps its color as the gauge grows, so how far along the ramp the boundary sits is itself a reading. Cells landing between two stops blend them, which is what keeps every cell of a wide gauge a slightly different color; name more stops to keep the blend closer to colors the theme defines. Reversing a gauge is a matter of listing its stops backwards; there is no direction setting.

Any element accepts pipe operators to style it: bg, fg, bold, italic, underline, reverse, and dim. bg and fg take a hex value or a W3C color name. For Spinner and Status, status wins over bg and fg written in the layout, which then only apply to a state the palette does not cover.

Shade characters such as █▓▒░ are drawn inverted against the bar background, so placing them next to an element with its own bg fades that element out into the row, and reversing them to ░▒▓█ fades it back in. The default layout uses the first: the spinner and the status share a block that fades out to the right. Give every element in such a block the same bg, or the ones without it leave unpainted gaps. An element with nothing to show takes the literals around it down with it, so a pill never fades into an empty name. Separate two elements with a double space to start a new run; a single space belongs to the element on its left.

The two gauges are fixed-width fields that already centre their label, so they need less padding around them than the text elements do.

Images​

{{ .Image | src "file:///path/to/logo.png" }} draws a picture over the bar. The element takes up reserve cells in the row, and the picture is centered on the first of them, or on the next element's first cell when it reserves none. The bar is the chat's last row, so a picture taller than one row extends over the conversation above it, and whatever falls outside the chat is cut off unless the picture overflows. Images never drop out of a narrow row. With rune --tui, which cannot draw pictures, the alt text is drawn instead.

DirectiveDefaultWhat it does
srcrequiredThe file://, http:// or https:// URI of the picture.
width2Cells wide, or a percentage of the bar such as "100%".
height1Rows tall.
reservethe widthCells the element takes up in the row: the width in cells, or 0 for a percentage.
x_offset0Cells to move the picture right, negative for left, or a percentage of the bar such as "50%".
y_offset0Rows to move the picture down, negative for up.
x_pixel_offset / y_pixel_offset0Device pixels to move the picture further right and down, negative for left and up, to align it with the text around it.
fitcontaincontain keeps the picture's proportions, fill stretches it over its cells.
z_index0Stacking, as in the kitty graphics protocol: 0 or more draws over the text, negative under it, and under -1073741824 under the backgrounds too. Where pictures overlap, the higher one is on top, and the later one in the layout on ties.
overflowoffLets the picture extend past the chat, over the windows, frames and bars around it. Floating windows still cover it.
alta crossDrawn over the picture's cells when it cannot be shown. "" draws nothing. Pixel offsets do not move it.
ttl24hHow long a downloaded picture is reused before it is fetched again.

Sizes and cell offsets are at most 4096 cells, and pixel offsets at most 65536 pixels, offsets in either direction. The alt text takes no control characters except newlines, which break it into lines. Control characters in the literals around the picture, such as tabs, are drawn as spaces.

A badge after the model, nudged 3 pixels up:

layout: ' {{ .Spinner }} {{ .Status | bold }} █▓▒░ {{ .Model | fg "white" }} {{ .Image | src "file:///path/to/logo.png" | y_pixel_offset -3 }}{{ .ShiftRight }}{{ .ContextGauge }} '

Narrow terminals​

When the row cannot fit everything, elements drop out in a fixed order rather than the status text being cut short: the output budget first, then the conversation name, the cache gauge, the cache percentage, the provider, the effort, the token counts, the context numbers, the model, the context gauge, and finally the elapsed time. The spinner and the status text are never dropped; the status text clips to whatever room is left.

extensions:
rune-agent:
config:
status_bar:
enabled: true
layout: ' {{ .Spinner }} {{ .Status | bold }} █▓▒░ {{ .Model | fg "white" }} {{ .Effort }}{{ .ShiftRight }}󰗂 {{ .Cache }} {{ .Elapsed }} {{ .Conversation | bold | fg "white" }} {{ .ContextGauge }} '
shader: pulse
background_attr:
bg: gray
fg: silver
status:
IDLE:
attr:
fg: white
bg: silver
flags: bold
animation:
ch: ""
attr:
fg: yellow
SENDING:
attr:
fg: black
bg: teal
flags: bold
animation:
ch: "⡘⡌⠆⢃⢡⠰"
attr:
fg: default
REASONING:
attr:
fg: white
bg: navy
flags: bold
animation:
ch: "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
attr:
fg: default
RECEIVING:
attr:
fg: black
bg: magenta
flags: bold
animation:
ch: "⢡⢃⠆⡌⡘⠰"
attr:
fg: default
EXECUTING:
attr:
bg: red
flags: bold
animation:
ch: "⢀⢀⣀⢄⢂⢀⣀⣠⣤⣦⣧⣶⣦⣧⣦⣤⣴⣼⣴⣶⣧⣦⣤⣴⣼⣴⣶⣧⣦⣤⣴⣼⣴⣶⣧⣦⣤⣄⣀⣀⡠⡠⠔⠊⠁⠁ "
attr:
fg: default
COMPACTING:
attr:
bg: gray
flags: bold
animation:
ch: "⣉⠶⠶⠒⠒⠒⠶⠶⣉"
attr:
fg: default
ASKING:
attr:
fg: black
bg: yellow
flags: bold
animation:
ch: "⠁⠂⠄⡀⢀⠠⠐⠈"
attr:
fg: default
ERROR:
attr:
bg: maroon
flags: bold
animation:
ch: ""
attr:
fg: red
gauge_empty_attr:
fg: silver
bg: gray
gauge_start_rune: ""
gauge_end_rune: ""
gauge_cap_attr:
fg: green
context_gauge_fill_attrs:
- fg: black
bg: green
- fg: black
bg: yellow
- fg: black
bg: red
cache_gauge_fill_attrs:
- fg: black
bg: red
- fg: black
bg: yellow
- fg: black
bg: green
gauge_width: 18
Ask Rune Agent