Skip to main content

Tools

There are fifteen tools, and no way to add another from a configuration file. Each one splits its arguments into routing, the part that decides where the effect lands, and content, the part that is merely carried.

ToolRoutingContentAsks you?
read_filepath, path_refnoneonly to trust a quarantined file
list_filesdirectory, pattern, depthnoneno
searchpattern, directory, includenoneno
lspoperation, path, line, characternoneyes, to start a language server
write_filepath, path_refcontents, contents_refyes, every time
edit_filepath, path_refold_text, new_textyes, every time
runcommand, compiled to a planstdinyes, unless vouched for or proven
read_outputrefnoneyes
job_outputjob, killnoneno
fetch_urlurlnoneyes, unless a rule names the host
spawn_processoraboutreads, instructionno
spawn_agentkindtask, eachnot the call, but its writes and runs do
load_skillnamenoneno
ask_userthe questionsnoneit is the question
todo_writenonetodosno

A sixteenth, schedule_next, is offered to a turn inside a self-paced /loop and to no other turn.

An unknown tool is reported to the planner rather than ignored.

There is no shell tool, and there never will be. Nothing the planner writes is handed to an interpreter. run takes a command line, and bravebot compiles it itself. See Shell mode.


read_file

Reads a UTF-8 text file from the workspace and returns its lines.

Parameter
pathworkspace-relative path
path_refa reference to a file whose name the planner was not shown, e.g. ref:2
offset1-based line to start at
limitmaximum lines to return, capped so one read cannot fill the conversation

Long files come back one page at a time. The result says so and gives the offset to continue from. A file that is not text is reported as binary, a picture being the exception.

A picture is quarantined whatever the trust map says, and only a processor looks at it. A file whose extension names a picture or a PDF comes back as a reference saying what kind of thing it is, and vouching for the directory does not change that: what the trust map answers is whether a file's text may be read, and a picture has none. Handed to spawn_processor it arrives as a picture rather than as base64, so the model looks at it, and what the processor says back is quarantined like any other processor's answer.

The reason is that a screenshot carries whatever words are in it, and a picture in the planner's context is restricted to one you put there yourself. A picture you dropped on the terminal is that; a path in model output is not. What kind of file it is comes from the extension and never from the bytes, so a file cannot become a picture by holding something that looks like one, and a picture cannot become text by being called .txt.

A read the planner may not see does not open the file. Where the content would be quarantined, you are offered the chance to vouch for that one file at the moment it matters, unless the path names a picture, since a yes there would grant nothing. An empty file, or one that turns out not to be text, is asked about like any other. See the quarantined-read prompt.

list_files

Lists files under a directory.

Parameter
directoryworkspace-relative; . for the root
patternoptional glob: *, ? and ** are supported, brace groups are not
depthoptional; how many directory levels below directory to walk, 1 being that directory and no further

Set a depth. Without one the walk reaches every file underneath, which in a real repository is thousands of paths. You pay for them in the planner's context, again on every round that resends it, and again in each delegate handed the same question.

A bounded listing names the directories it did not descend into alongside the files, so what comes back describes the shape of the tree. The pattern does not hide those directories: it says which files are wanted, and a directory is where the answer might be rather than an answer.

A listing of a directory nobody vouched for is quarantined, because a filename is content. It returns one reference per entry, not one for the listing. That is what lets the planner read a file, process it and write it back without ever being told what it is called.

The glob is literal and the matcher does not backtrack. A truncated listing says it was truncated.

Finds lines matching a regular expression in workspace files.

Parameter
patterna regular expression. May be a list, in which case a line matches if it matches any of them
directoryworkspace-relative, defaults to .
includeoptional glob limiting which files are searched: *, ?, ** and brace groups like **/*.{cc,h,mm}
case_sensitivedefaults to true

Supported: literals, ., *, +, ?, |, (...), [...] with ranges and negation, \d, \w, \s and their negations, ^, $, \b, \B, and a backslash before a metacharacter to match it literally.

Two things are absent. Counted repetition (a{2,9}) is not supported and { is an ordinary character. Backreferences are not supported. Captures are never extracted, since a search reports the whole line.

The engine does not backtrack, so a pattern like (a+)+$ that is exponential elsewhere costs nothing unusual here: matching is the line's length times the pattern's size, whatever the pattern. That bound is why the two missing constructs are missing. Counted repetition nested twice multiplies the states a short pattern expands to, and backreferences cannot be matched without backtracking.

A list of patterns is alternation, one more expression to try per line, so the work is the sum of the patterns rather than a power of anything. A pattern that is too long or nested too deeply is refused. Brace groups in include are expanded before the walk. Case is folded by the engine rather than by lowercasing the pattern, which would turn \D, \W and \S into the classes they negate, and a line is reported as written rather than as it was folded to match.

Version control, build output, caches and vendored dependencies are not walked, by name and without reading anything to decide it. The project's own .gitignore is not consulted either: it would decide what to walk from a file in the tree being walked, and a tree that can hide files from a search can hide them from review.

A result touching several files is trusted only if every one of them is. A truncated search tells the planner it is incomplete. A search that found nothing says which kind of nothing it is: no matching lines in the files it read, or an include that selected no files at all. Those are opposite facts, and drawn identically the planner reads one as the other. A glob leaning on syntax the matcher does not have is named for the same reason.

lsp

Asks a language server about a symbol: where it is defined, what refers to it, what implements it, what calls it. Starting a server is put to you.

Parameter
operationone of goToDefinition, findReferences, hover, documentSymbol, workspaceSymbol, goToImplementation, incomingCalls, outgoingCalls
pathworkspace-relative file holding the symbol; required for every operation but workspaceSymbol
line, character1-based position of the symbol, as a search or a read reported it
queryfor workspaceSymbol, the name to look for

This answers what search cannot. A search for a name finds every comment and string that mentions it; this finds the declaration the compiler agrees on.

Nothing works out where a name is. A position is two numbers the planner states, taken from a line it was already shown. A position that no longer holds the symbol answers with nothing found rather than an error, since a file changes under an agent and a stale line number is ordinary.

The operation list is closed and every entry on it is a read. LSP is an open protocol and a server advertises methods of its own, one of which applies edits to your files, so forwarding a name would make what this tool can reach a property of whichever server you installed.

Which server, and what starting one costs

LanguageServer
Rustrust-analyzer
TypeScript and JavaScripttypescript-language-server
Pythonpyright-langserver
Gogopls

The table is fixed and there is nothing to configure. The binary has to be installed and on your PATH.

A server runs with the access your own shell would give it, and is not confined. The prompt says so in those words: it reads the whole tree and the dependency sources, and it runs the build tooling of its ecosystem. For Rust that means build.rs and proc macros out of Cargo.lock execute, which is code from your dependency tree running as you. Go's tooling builds to answer too. A Node or Python server reads and type-checks without running the project.

Confinement is not an option withheld here. A server indexes by running that build tooling, so a profile denying it a subprocess and somewhere to write gives you a server whose index never settles, and every answer from one says it may be short. The choice is a server with your access or no working tool.

What does not turn on your answer is the label on what comes back, and that is the half that matters. A server that can read the disk is not a server that can put words in the planner's context.

A server is started by the first question that needs it, so a session that asks nothing about a language starts nothing.

A location is structure; the text at it is content

A location is a path, a line, a column and the kind of symbol. Those reach the planner whatever the trust map says about the file they name, exactly as a line count does for a file it may not read. There is nowhere in a path and two integers for prose to sit, so somebody who owns vendor/lib.js cannot use goToDefinition to put a sentence in front of the planner.

The text at a location takes the ordinary treatment, because it is bytes the file chose. A signature or a source excerpt from a file nobody vouched for comes back as a reference, so one result can be a visible list of locations whose text the planner may not read.

Hover text is a reference whatever you have vouched for. A hover answer carries the prose and a position and never says which file wrote it, and a doc comment is written where the symbol is defined rather than where you asked: hovering over a call in one file shows prose out of another. With no file named there is no trust map entry to read, and borrowing the queried file's entry would hand the planner bytes out of a vendor directory you deliberately left untrusted. Unattributed text is untrusted, on the footing every other output of a server arrives on. Hover is where most of this tool's value is, which makes this its sharpest cost.

A location is not made routing by having been returned. A read of a path that came back from here is gated as it would be had the planner guessed the path, and a write to it is put to you as a diff like any other. What somebody who owns a file in your tree gains is a say in which path and line the planner is told about, by arranging their code so a symbol resolves where they like. That costs at worst a wasted read of a file the planner could already read.

An answer says which kind of nothing it is

A definition in a dependency, a toolchain source, or anywhere else outside the working directory comes back saying it is outside the workspace, with its path not spelled as though read_file would open it. Naming it does not make it readable: a read of it is refused as for any other path outside the tree.

No server configured for the language, a missing binary, and a server that failed to start are three different answers, and none of them is an empty result. Nothing falls back to searching the tree. An absent server reported as "no references found" is a false negative that reads as proof, and a planner that believes nothing calls a function will delete it.

An answer given while the server is still indexing says it may be short, in the same words a truncated search uses. A request waits for the index up to a bound and then answers from what there is rather than failing.

The index is cached, and it is not small

A server keeps its index under ~/.bravebot/lsp/, keyed by the workspace and never inside your tree, which is what makes the second session in a workspace fast. A question about a symbol should not change the tree you are working in.

Nothing prunes it. A Rust workspace's index runs to a few hundred megabytes, and a machine that has been in many workspaces holds one for each. Deleting the directory costs the next session its indexing time and nothing else.

Nothing in that cache is read as trusted, whatever else ~/.bravebot is trusted for. The bytes derive from workspace files, so they carry those files' labels, and the only thing that reads them is the server. An incognito session writes no cache: its server still runs and still answers, and it re-indexes each time.

write_file

Writes a UTF-8 text file in the workspace. You approve every write before it happens.

Parameter
pathworkspace-relative destination
path_refa reference to the file to write, for a file the planner was never shown the name of
contentsthe complete new contents
contents_refa reference whose quarantined content becomes the whole file

Contents or a reference, never both. A reference that names no file is not a destination. The planner never chooses a destination on its own, and a write through a path_ref is always shown, since you are the only one who sees which file it is.

See Trusted directories for what a write does to the trust map.

edit_file

Replaces an exact passage in an existing file. You approve every edit, as a diff. The agent prefers this to rewriting a whole body.

Parameter
path / path_refthe file
old_textthe exact text to replace, matched byte for byte
new_textwhat goes in its place
replace_allreplace every occurrence instead of requiring exactly one

old_text must occur exactly once unless replace_all is set. An edit refuses rather than guesses.

An edit requires a trusted file, because locating a passage to replace is a decision and a decision may be taken only from trusted content. To change a file the agent may not read, the route is spawn_processor plus write_file.

run

Runs a command line. You approve the compiled plan before anything runs.

Parameter
commandone command line; a newline is refused, since this is a line and not a script
deadline_secondshow long to wait, defaulting to 300 (below)
backgroundstart the line and hand back a job name instead of waiting (below)
git log --oneline -50 | head -20

The line is compiled, never interpreted. No shell sees it at any point: bravebot's own grammar is the only thing that reads it. What comes out is an ordered plan, each step a resolved binary and a literal argument vector, together with every file the line would write. That plan is what runs. A ; or | inside quotes is part of an argument and stays part of it.

A name is looked up on PATH; a path is taken relative to the workspace.

What the grammar takes

pipelines and sequencing|, &&, ||, ;, and ( … ) to group
redirection>, >>, <, 2>, 2>>, 2>&1, &>, each naming one literal file
patterns*, ?, […], **
brace expansion{a,b}, {1..9}
homea leading ~
per-command environmentNAME=literal cmd

Everything else is refused, as an error naming the part of the line that caused it, and a refusal runs nothing. There is no falling back to a shell and no running the prefix that did compile.

RefusedWhy
$(…), backticksa command whose text is computed is a destination nobody saw
$VAR, ${…}the value is not in the line, so the plan is not in the line
$((…))arithmetic needs an interpreter
<(…), >(…)the same, plus a file descriptor nobody named
&backgrounding is a parameter of a call, not a token in a line
<<, <<<a here-document is content, not syntax
eval, source, ., exec, trapthey put an interpreter back in the plan
if, while, for, case, functioncontrol flow is a program
!history expansion is text you typed reaching a line the planner wrote

Quoted, every one of them is an ordinary argument: '$HOME' is six characters that reach the program as one word.

Your answer binds to the plan, not to the line

The prompt draws the plan: each step as the line wrote it, the binary that will actually run underneath, the directory it runs in, and every file the line would create or replace, listed rather than left to be worked out from the steps above. The line the planner wrote is shown above it and marked as context. Compare the two to catch a compiler that read the line wrong, but the line is not what you are agreeing to.

Two lines that compile alike are one thing to agree to. One approval cannot be reused for the same steps joined differently, for the same steps writing somewhere else, or for the same steps in another directory.

Every branch is endorsed before anything runs. a && b may run b, a || b may run b, and a ; b will, so all of them are in the plan and all of them are approved up front. Nothing is put to you part-way through a running line, where you could not tell what state the first half had left behind.

A line that only reads what you vouched for does not ask

wc -l Cargo.toml in a directory you trusted runs unasked, and its output comes back as text rather than as a reference. It cannot write anything, and it reads one file you already answered about, so there was nothing for a prompt to decide.

That holds where every step of the plan is one of nine audited programs (wc, head, tail, cut, tr, grep, basename, dirname, pwd), called with options that program's entry lists, and the plan writes nothing and reads only paths the trust map answers for. The output's label is the map's answer about what the line read. Where any path is one nobody vouched for, the output is untrusted and private and the prompt stands, exactly as for every other run.

A recursive search answers for the whole tree it walks, so grep -r takes its label from the directory it was pointed at and everything beneath it. A directory you refused inside a project you vouched for decides the answer about that project.

The audit is per option rather than per program, and an entry lists the options a call may use rather than the ones to reject. A list to reject fails open: -S makes BSD grep follow every symlink it meets while walking, and grep -A 1 -r TODO walks the working directory while appearing to name a path. An option the entry does not list leaves the step unproven, so the line is asked about as usual.

Four other things leave a step unproven. Naming the program by path rather than by name, since a file called wc in the directory the line runs in would otherwise answer as the audited one. An environment assignment, which decides what a program loads before its own arguments are read. A redirection, which opens a file the argument list does not name. And running anywhere but the directory the trust map's rules are written against.

git is not audited and will not be. A repository's own config can define an alias that runs a command and a pager that runs a command, so git log is an interpreter whose program is a file in the tree being inspected. sed and awk are out for the same reason: their program is an argument, and awk can reach the shell this design excludes. All three stay on the vouching road, where a person answers for them.

This is not an allowlist. A program that is not audited is asked about, never refused. A deny or ask rule you wrote still decides, because proof removes the default prompt and does not overrule a rule you wrote down. Private input still asks.

Patterns become the files they match

Expansion happens against the tree at approval time, so what you read at the prompt is the file list rather than the pattern. A pattern matching nothing is an error rather than an argument passed through unchanged. ** steps over the directories a listing steps over, so it does not descend into .git or node_modules.

Expansion is bounded in both directions: a word standing for more than 100 arguments is refused with the count, and the walk gives up after 4,096 directories. An approval prompt nobody reads grants everything and asks nothing.

A redirection is a write

> out.txt is a file this line creates or truncates. It appears in the plan's write set, it is shown at the prompt, and it takes every rule a write takes: the permission rules, the trust map's answer for that path, and the confinement that keeps a write inside the workspace. >> is a write, and 2>&1 renames a stream and touches no file.

A redirection also records what it wrote. Where the line's output is untrusted, every file the line opened for writing becomes untrusted, which is what stops a program's output being read back as trusted. Where the output is trusted the map is left as it was, because >> keeps whatever the file already held and vouching for a program answers for the program rather than for one file's contents, so a line never raises a path's trust. What is recorded is what the line actually opened rather than the write set, so a branch the line decided against leaves its destination alone.

The one direction has a cost: a file a vouched-for line overwrote stays untrusted until you say otherwise, so reading it back is quarantined and costs a prompt.

< secrets.txt is standard input, so that run asks every time. The file's bytes go into a program, which releases your data somewhere this policy stops governing, so a < meets the private-input gate whatever the trust map says about the path, whatever you have vouched for, and whatever a rule in the settings file allows. Any step's redirection counts, since a step in the middle of a pipeline is handed the file the same way.

a is not offered at that prompt, and an answer given there vouches for nothing. The key records a program and its exact arguments, and a redirection is in neither, so an entry made while one file was read in would have covered the same program fed any other file.

A target must compile to exactly one literal path. A pattern is refused even where it matches one file today, because a destination worked out from what is on disk moves when the tree does, and the plan would stop saying where the bytes go.

A line that writes is put to you every time, whatever you have vouched for. Vouching is keyed on a program and its arguments, and a destination is neither, so a remembered command cannot pick one up unseen.

Something that wants a terminal is refused before it starts

git rebase -i, git add -i, an editor, a pager without --no-pager: refused when the line is compiled, with the thing to do instead. The list is a convenience rather than a guarantee. Something interactive that is not on it reaches the deadline and comes back with what it printed.

Standard input is empty unless the line redirects a file into it or the planner named a reference, so a step that reads it gets nothing rather than the terminal.

The output

Label
the plan (programs, arguments, and the files it writes)(T,pub), because a person approves the compiled plan
standard inputmay be untrusted; a person approves when it is private
standard output and error(U,priv), quarantined
…for a line every step of which a person vouched for(T,priv)
…for a line that proves what it readthe trust map's answer about what it read, private

Output nobody vouched for is not shown to the planner. It comes back as a reference, like a file it may not read, and can be passed to spawn_processor or written to a file with write_file. It is not capped, since none of it enters the conversation.

That result also tells the planner what would lift the quarantine, naming three things in the order they apply: this result through read_output, the next one once a person has vouched for every stage of the exact command, and a file through read_file. Only where a command produced it, so a quarantined read carries no advice about vouching for a command nobody ran. Without those a planner reads one quarantined result as proof that programs are unreadable and stops running them, which is not what happened: the label is about who answered for the command.

Every result says how the run ended, in front of what the program printed: that every step exited zero, which step did not and with what code, or that the line outstayed its deadline and was stopped. It is said for quarantined output too, where the planner holds a reference it may not read. The verdict is read off the processes and the clock, never out of a byte the program printed, so it is structure exactly as a line count is and puts nothing in the planner's context that a program chose.

Output the planner may read comes back as text, capped at 16 KiB. Past the cap the head and the tail are kept and the middle dropped, with a line in between saying how much went. The cap is on what enters the conversation rather than on what the command printed, and the whole of it stays available as a reference.

What a program is handed

A step gets the environment bravebot is running in, less the credentials bravebot authenticates to its own backend with: SERVICES_KEY_AICHAT and BRAVE_SERVICES_KEY_ID. Every step, not only the first, and removed rather than blanked, so a program that tells an unset variable from an empty one sees what a machine that never held the credential sees. You approve the plan, the resolved binaries and the directory. The environment is not among them.

The rest of your environment stays, and that is not an oversight. run aws s3 ls and run gh pr list are ordinary requests, and no rule matching variable names can tell one of those from an exfiltration, so AWS_PROFILE, GITHUB_TOKEN and NPM_TOKEN are left where they are. What the run prompt tells you about the remainder is the truth: a run has the access your own shell has. Name anything of your own you want withheld in run.scrubEnv.

note

This is not confinement. A program that reaches the network is unpoliced and can send anything it can read: a file, the workspace, a credential of your own. What closes here is the narrow part of the gap, the credentials you could not have been shown at the prompt and had no way to withhold. Nothing is established about what the program then does, and the label on its output is unaffected.

A line you typed yourself in shell mode is not this and keeps your whole environment, since it is meant to behave as your own terminal does.

A line has a deadline

Every line is given 300 seconds unless the call names its own. deadline_seconds raises or lowers that, and is held between 1 and 600: a value outside those becomes the nearer of the two, and one that is not a whole number of seconds is refused before anything runs. It has no effect with background: true, which is not waited for at all.

When the deadline runs out the steps are killed, and what they printed before that comes back exactly as it would from a line that ended on its own, under the same label. Reaching the deadline ends a run rather than failing it, so a program that never exits, like a server told to serve a page, still gives you everything it printed. The result says it was stopped, which is how you tell the two apart.

Being cut short neither raises nor lowers the label on the output. Finishing inside the deadline says nothing about what a program did.

See Vouching for a command for what a grants.

Leaving a pipeline running

background: true starts the line without waiting for it and hands back a job name, which job_output reads. This is for the program no deadline can serve: a server told to serve serves, prints as it goes, and never exits, so waiting for one and killing it at the limit leaves no moment at which it is up and can be talked to.

Every gate is the one a foreground run passes, at the same point. The rules, your approval, and the label the output will carry are all settled before anything starts. Being left running is not a reason to ask for less.

deadline_seconds does nothing here, since nothing is waited for.

One pipeline, and no redirection. A line with && or || decides where to go next by waiting on the part before it, and nothing waits here; a redirection names a destination nothing is reading. Both are refused rather than half-honoured.

A job cannot outlive the turn that started it. The turn owns the pipeline and ending the turn kills it. A background program still running afterwards would be an effect nobody is watching, nobody is being asked about, and nobody can stop.

read_output

Asks to be shown what a command printed. You see the output and decide; if you agree, it comes back to the planner as text.

Parameter
refthe reference a run handed back

It works only for output from run. A quarantined file is not readable this way. This is why which, find and uname tell the planner nothing until it asks.

job_output

Reports what a background job has printed since the last look.

Parameter
jobthe job name a run handed back
killstop the pipeline

Each look reports what is new, counted in bytes, and whether the job has ended. Asking about a job that does not exist says so.

The output keeps the label its plan was given when the job started, rather than one worked out again at the moment it is read. What you have vouched for can change while a job runs, and a pipeline started before that must not have its output relabelled because of it. So a job's output is quarantined or not on the same terms a foreground run's is.

fetch_url

Fetches an http or https URL. You approve every fetch, unless a rule names the host.

Parameter
urlthe one URL to fetch

What comes back is quarantined however you answer. The body is a reference: the planner may hand it to spawn_processor or write it to a file with write_file, and it cannot read it or be told what it says. A page saying "ignore your previous instructions" says it to a processor with no tools.

There is no answer at this prompt that trusts a body, because approving a fetch is consent to talk to a host and says nothing about what that host returns. a at a run prompt can trust output: you read one command and answered for both its effect and what it printed. A host answers every later request however it likes.

The prompt draws the URL, and on a line of its own the host it will reach. The host is read out of the URL by the parser rather than taken from what the string looks like, so https://example.com@evil.test/ cannot name one site to somebody skimming it and reach another.

Your answer is bound to that one URL and nothing is remembered. The next fetch asks again, even on the same host. Standing permission is a WebFetch(domain:…) rule you wrote down in advance, which is naming a host rather than answering a question about one page. See A fetch is approved one URL at a time.

A redirect may not leave the approved host. Every hop goes through the same gate, and a hop elsewhere is refused unless a rule allows that host too, since the end of a redirect chain is somewhere nobody was shown. That check applies only while a fetch is in flight: reaching the model endpoint is this program operating rather than something a turn asked for, so a rule about a website cannot stop bravebot talking to its own backend.

A body that is not valid UTF-8 is carried anyway rather than reported as an error, since nothing here reads it. Bodies are size-capped, and a truncated one says so.

spawn_processor

Transforms quarantined content the planner was not shown. It spawns an isolated model with no tools, no memory and nothing to read but the references named. Its output is quarantined as a new reference, which the planner does not see either.

Parameter
readsthe references to give it, e.g. ["ref:0", "ref:1"]; at least one
aboutwhich of those references this call is about; required when reads names more than one
instructionwhat to do with them and what to produce

An answer is for one document and may be written only to the file the call was about. Where the planner said nothing and there was more than one input, the answer belongs nowhere and may be written nowhere.

Everything before the document marker in a processor's reply is a remark for you: it reaches your screen and stops there, is part of no file, and cannot be another processor's input. An answer with no marker names no document and can be written nowhere.

See How Brave Bot works.

spawn_agent

Hands a sub-task to a delegate, a second planner with a narrower set of capabilities, and gets back one report.

Parameter
kindreader, checker or worker
taskthe whole of what the delegate is told
eachoptional; starts one delegate per entry, each told task followed by its own entry
KindHoldsFor
readerreadingfinding something out
checkerreading, and running programsfinding out whether something works
workerreading, running programs, and writing filesfinishing a sub-task

The call answers as soon as the delegate has been approved, so the planner has its round back while the work goes on behind it, and what the delegate says arrives on its own later. Several delegates can be going at once, each numbered in the order the turn started them, and every report says whose work it describes.

A delegate holds its kind's capabilities narrowed by its parent's, so delegation redistributes authority and never creates it, and a kind asking for more gets a delegate without it. The planner supplies the task and nothing else. What a delegate is told about itself is a constant its kind chose, so there is no sentence the planner can write that changes what a delegate is rather than what it is doing.

each fans one task out, so the shared half is written once and only the differing part (one path per entry, say) is repeated. Every delegate it starts is one like any other: it is approved on its own, takes its own number, and holds its own copy of what you vouched for, so a fan-out is several runs rather than one run several times. A call naming more than eight, or naming none, is refused and starts nothing.

The delegate cannot see the conversation the task came from, so a task that leaves something out is a delegate that never learns it. It cannot come back for more, since there is no channel to ask along. A run whose own context has met something untrusted cannot delegate at all.

Delegation saves context, never an approval. Every write and every run a delegate makes reaches you with its own single-use endorsement, so you see the path and the diff whoever proposed them. What you vouched for inside one comes back to the session, because that answer was about your machine rather than about the run that happened to be going.

Nothing but the report crosses back: the exchange, the tool results and the quarantine end with the delegate, and a reference minted inside one names nothing afterwards. A delegate is offered none of this tool, ask_user, a task list, or fetch_url, so it cannot delegate again, puts no question of its own to you, replaces nothing on your screen, and reaches no host. Naming one of them anyway is refused rather than run, since a model naming a tool it was never offered is ordinary. What it could not settle goes in the report, and the planner asks.

Fetching is out because a delegate reaching a website would stop you to approve a host for a sub-task you never set up. Every kind does reach the network, since a planner is a model call and that request is egress like any other, and that is the whole of what the capability buys it.

note

The confirmation for a write shows the path and the diff, as it always does, but it does not say that a delegate rather than the turn is asking. With several running, reading only the prompt means approving a change whose reason is one of the tasks you did not read.

load_skill

Reads one of the skills the planner was listed.

Parameter
namethe name exactly as it was listed, e.g. commit-style

The name selects from a set fixed before the turn started and never becomes a path: a name holding ../ matches nothing and the call is refused. A name close to a real one is refused rather than guessed at. See Skills.

ask_user

Puts up to four questions to you and waits. More than four is refused whole rather than trimmed.

Parameter
questionsat most four, each with a header, a question, optional options, and multiple

Questions are put one at a time. You may choose an option, answer in your own words, or skip. A skipped question is an answer to work with rather than a reason to ask again. An answer is remembered for the session, question by question.

Asking stops once the planner's context has met something untrusted, because at that point the question itself could have been shaped by content nobody vouched for. A quarantined read does not stop it asking, since a reference carries no instruction. Where nobody can be asked, as in a one-shot run, every question is declined rather than answered on your behalf.

This tool is for what the planner cannot find out itself: which of two approaches, whether something is in scope, which of two plausible files you meant. Never for a fact about the machine.

todo_write

Records the task list for what the planner is doing.

Parameter
todosthe complete list, each with content and status

The whole list every time: it replaces the previous one. There is no routing here, because nothing is touched. An unrecognised status reads as outstanding work.

schedule_next

Says when a self-paced /loop should run again. It is offered to a tick of a self-paced loop and to nothing else; a call from any other turn is answered the way any unoffered name is.

Parameter
delay_secondshow long to wait; routing, and required
noopwhether this tick found anything; routing, and required
reasonwhat the turn is waiting on, in its own words; content

There is no argument for what the next run asks. The prompt is the line you typed when you started the loop, and it is sent again unchanged.

The wait is held between a minute and an hour before it is reported back, so the number the planner is told is the number it is getting. A call missing the delay or the verdict is refused rather than filled in, since the count of quiet ticks you are shown is built from the verdict. reason reaches your screen and stops there.


Before adding a tool

Every new tool must have a routing field. A tool whose destination cannot be separated from its payload does not belong on this surface. That is also why the built-in tools stay native rather than arriving over MCP: an opaque call erases the split between the part that decides where a call lands and the part that is merely carried, and these tools depend on it.