Configuration

One config file names the model. Provider files next to it say where the models live.

The config is read from ~/.config/task-agent/config.cfg (or $XDG_CONFIG_HOME/task-agent/config.cfg), or from the path given with --config. The format is header-config: a # heading starts a section, everything else is a key and a value separated by a space.

Everything that belongs to one repository — its task queue, its memory, its subagent specs and its permission rules — lives under ~/.config/task-agent/projects/<repository path with the slashes turned into dashes>/, so /home/you/work/thing becomes projects/-home-you-work-thing/. A folder left over from the earlier naming, which used the last path component alone, is renamed on the next start; when both exist, the files of the old one are moved over where the new one has no file of that name and the rest is left in place with a note naming both paths.

That folder is created on the first session in a project, with memory/, tasks/ and subagents/ and a template in each of the two that take one: a memory/project.md paragraph saying what the project memory is for, and a subagents/README showing the shape of a role document. The global memory/ directory beside the config is seeded at the same moment with the five behavior documents that ship with the program. Both steps only ever write what is absent, both name in a startup note what they wrote, revolve init runs them on demand, and --bare runs neither. What the defaults say and how a shipped file is marked is under Sessions, memory, goals.

An older ~/.config/revolve/ directory is read as a fallback for one release, so an existing config, permissions and skills keep working without being moved by hand; a directory named task-agent is preferred the moment it exists.

model deepseek:deepseek-v4-pro

If no config file exists, revolve reads the key of every provider in the compiled catalog and offers every model those keys reach. Which one it starts on is the first provider a key reaches, preferring anthropic, openai, deepseek, zai, zhipuai and mistral in that order and falling back to any other provider whose key is set, and within that provider the model with the widest context window. A Claude Code login counts as a credential of its own. It says at startup which model it picked, so you know you are on the fallback.

A config file decides what a run sees, not only which model it talks to: the skills directory, the installed packages and the memory directories are resolved next to the config that named them. -c /somewhere/config.cfg therefore hands an unattended caller a defined environment — its own skills under /somewhere/skills/ and nothing of the personal set — which is what a cron job or a scripted --json run needs, since the user's own skills cost input tokens on every turn and none of them is about the job. --bare is the stronger form and drops the project's own context as well.

Providers

model names a provider and a model id. Providers live in providers/*.cfg next to the config file, one file per provider, named after it:

endpoint https://api.deepseek.com/v1/chat/completions
api_key_env DEEPSEEK_API_KEY
format openai

# Models

deepseek-v4-flash enabled
deepseek-v4-pro enabled

# Model Options

deepseek-v4-pro:thinking_effort high
deepseek-v4-pro:context_window 1000000
KeyMeaning
endpointThe URL requests go to
formatThe wire format: anthropic or openai
api_key_envThe environment variable the API key is read from
auth claude_codeInstead of api_key_env: take the OAuth token out of Claude Code's credentials

Every model id under Models appears in /model. Under Model Options, a key is <model-id>:<option>. A model is always addressed by the id the provider itself uses; the compiled catalog supplies every provider and model, so a provider you do not write a file for is available just by setting its API key, and the Models lines are only needed to define a provider the catalog does not know or to trim its list.

Model optionMeaning
thinking_effortHow much reasoning the model is asked for, where the provider supports it
context_windowThe model's context size in tokens

context_window and when compaction happens

Automatic compaction triggers once the last request came within one turn of filling the declared window. The window of every model any speakable provider offers is compiled in, taken from the models.dev catalog, so naming a model id is usually enough and declaring the window is only needed for one nobody publishes. Where providers disagree about a model the most frequently reported window wins, since a single gateway naming its own ceiling is not the model's. A model that is in no catalog defaults to 60k.

The headroom is absolute rather than a percentage, because what it has to survive is absolute: the answer the model is about to write plus the tool results that come back with it. A tenth of a 200k window is less than one such answer and would overflow; a tenth of a million-token window would throw 100k away every time.

Billing against a Claude subscription

An auth claude_code line replaces api_key_env and takes the OAuth token out of Claude Code's ~/.claude/.credentials.json, so calls bill against a subscription rather than API credit. The token is read per request rather than at startup, which means a dead subscription costs only the models that use it: revolve starts, warns once, and refuses the request with the reason when one of those models is actually asked to answer. It never sends an expired token, and it asks you to start the claude command line once to refresh it.

Retries

A failed request is repeated, and how long it waits is the server's answer rather than a guess: the retry-after header of the failing response decides. A 429 that names no wait uses a default. A wait longer than the maximum is not retried at all, because the error naming the wait is more use to you than a process asleep for an hour.

A retry can happen after text has already reached your screen. When it does, the half-written answer belongs to a dead attempt and is thrown away rather than glued to the next one; the retrying event carries a discarded flag saying so.

Turn budget

# Turn Budget

rounds 300
output_tokens 1000000
seconds 3600

The ceiling one turn may spend. --budget rounds=8,seconds=120 overrides it for one run; 0 turns a limit off. See turn budgets.

Prompt features

# Prompt Features

memory off
skills off

Each block the system prompt is assembled from can be switched off by name, and only the value off does anything — a key with any other value leaves the block where it was, and a name that is not a block is ignored. There are seven: memory, skills, tasks, goal, budget, mcp and context.

What this is for is the input token bill, since every one of these is paid on every turn: a config for an unattended run can drop the skill list and the memory it will never use and keep the project context it needs. It is the graded form of --bare, which drops all of them at once and does not read your config at all.

Settings

The switches and numbers the frontends read live in settings.cfg beside the config file. The file used to be called tui.cfg, and it still is for anyone who has one: a directory with a tui.cfg and no settings.cfg keeps being read from and written to under the old name, so nothing a user set stops applying, and a fresh installation writes the new name. The keys are the same either way, and the keys of an existing tui.cfg are read whichever section they sit under.

# Settings

markdown on
context on
usage on
todos on
git-status on
detail-rows 12
role-file-bytes 20000
history-lines 2000
todo-rows 6
ask-hold-ms 750
prune-result-chars 8192
spill-result-chars 16384

# Terminal

line-highlight on
suggestions on
completion off
activity-row on
recall-entries 512

# Shortcuts

help Alt+H
goal Alt+G
list_agents Alt+T
quit Ctrl+Q

The two sections are the split between the program and the terminal, and it is the reason the file is no longer named after one frontend. Everything under Settings describes the program's behavior and both frontends read it: whether an answer is styled as markdown, whether the status bar carries the context reading and the rate limits, whether the todo list is shown, whether a short git status of the working directory is put into every turn, how many rows of a tool detail are folded out, how many lines of history are kept, how much of a role's own file is carried in the system prompt, how long a permission prompt ignores the keyboard before it takes an answer, how large a tool result may be before a compaction cuts it to a head and a tail, and how large it may be before it is written to a file the model can read instead of being carried whole. Everything under Terminal only exists on a screen made of rows: the caret line highlight, the typing suggestion, word completion, the hint row of the status block and how many recalled inputs the arrow keys walk.

A number below its floor is refused and said out loud at startup rather than applied, and so is a value that is not a number; ask-hold-ms may not go below 250, because a hold short enough to be beaten by a keystroke already on its way is the failure the hold exists to prevent.

The terminal writes the file back when you toggle something in its settings panel, which is where the descriptions above come from. The window reads it and never writes it.

The Shortcuts section is the keyboard half of the same idea: one line per command that carries a key, written as the help panel shows it (Ctrl+, Alt+ and Shift+ before the key, in that order). The name on the left is the command's own name in the capability registry, so quit Ctrl+Alt+Q moves quitting off Ctrl+Q for both frontends at once; a command nobody rebinds keeps the key the registry gives it, and the help panel shows the key in force beside the command it belongs to. A function key is deliberately never a default, because a laptop takes those for its own.

Colors

A file named theme beside the config colors the terminal front end. Every line is a role and a color, either a name or a six-digit hex value:

scheme matrix
tool green
heading #7a4b1e

A scheme line picks a starting point and later lines still override it, so a theme is usually one scheme and the two or three roles you disagree with. There are four: default, light for a light terminal background, high-contrast, and matrix for green on black.

The roles are user, answer, thinking, tool, detail, error, status, context_ok, context_warn, context_danger, heading, subheading, code, added, removed, selection, search_match, the background a match of the conversation search is painted with, and peer, an agent-to-agent message.

A color that cannot be read, a role that does not exist and a scheme that was never defined are each named as a startup note and skipped; the rest of the file still lands and the session starts. A theme file that is not there is not an error.

Syntax highlighting inside code blocks is not part of this. It comes from the editor library and stays with it, because a theme that half-overrides a highlighter produces colors nobody chose.

Hooks

# Hooks

before-turn /home/me/bin/inject-context
before-tool /home/me/bin/gate-tools

A hook is a program named per hook point. It receives a JSON object on standard input, and whatever it prints on standard output is used:

HookWhat its output does
before-turnAppended to the user message
before-toolPrefixed to the tool result, and denies the call outright when it starts with deny
after-toolAppended to the tool result
after-turnIgnored

A hook that fails or prints nothing changes nothing.

One hook ships with the program and needs no configuration: before every turn, a short git status of the working directory — the branch and up to twelve changed files — is put into the turn the way a before-turn hook's output is, so an agent that owns files sees when they moved under it. It is off with git-status off under Settings, and a working directory that is not a git repository yields nothing. A configured before-turn hook runs beside it rather than instead of it, and its output follows.

MCP servers

# MCP Servers

blender python3 -m mcp_server_blender
filesystem npx @modelcontextprotocol/server-filesystem /srv/data

Each configured server is started at launch and speaks the Model Context Protocol revision 2026-07-28 over standard input and output: newline-delimited JSON-RPC, no initialize handshake, with the protocol version and client identity travelling in _meta on every request. Revolve calls server/discover first, which also negotiates the version down if the server offers an older one, and then tools/list.

A server's tools join the model's tool list as <server>__<tool>, so two servers may each expose a search without colliding, and the server's own instructions are appended to the system prompt. A server that fails to start becomes a startup note rather than an error: revolve runs on without it, and /info lists which servers are live and how many tools each contributed. A tool call times out after a minute.

What the agent knows about itself

The system prompt names the model and provider the turn runs on, so the model can answer which model it is without guessing, and one short line describes the machine — kernel family, distribution, the first package manager whose binary exists, and the absence of systemd where /run/systemd/system is missing. That line is in the prompt rather than behind a tool because the failure it prevents, reaching for systemctl on a machine that has none, happens in the same breath as the thought, before anything would have been looked up.

The same report names the project directory of this session and says when the session is the one that created it, so an agent can tell a project it has worked in before from one it is meeting for the first time.

Everything else about the running process is behind the AgentInfo tool, which costs nothing until the model asks and is current at the moment of the call: after a /model switch or a /name, the next call reflects it. /info prints the same report for you.