The Tool Surface¶
A model that can call fifty tools spends part of every turn reading fifty schemas, and the tool it reaches for depends on how the request was phrased. Codaris binds a fixed surface instead, widens it only on explicit request, and decides at dispatch time what a call may do.
What a turn can call¶
With CODARIS_LEAN_7_TOOLS=1 (the default), every chat turn is bound nine tools and finds the rest. The name of the flag is historical; the surface is the list below, the tools a turn asks for, and the tools it has searched for.
| Group | Tools | Purpose |
|---|---|---|
| Files | read_file, write_to_file, replace_file_content, run_command | Read, write, and edit inside the allowed roots; run a command through the permission gate |
| Plan and build | emit_build_plan, run_pipeline_build | Produce the BuildPlan and its question cards; run the build graph |
| Repair | fix_pasted_error | Fix a pasted error (/repair) through the autopilot's repair ladder, then run until it passes |
| Capabilities | request_capability | Ask you for a grant the policy requires before a tool may act |
| Discovery | search_tools | Find a tool, skill or reference that is not bound, and bind it for the next step |
Everything else is one search away: the other core tools (autopilot_pipeline, run_built_pipeline, check_run_status, analyze_run_log, fetch_test_data, search_geo_sra, validate_pipeline, clone_nfcore_base, and the rest of the registry), every tool of every MCP server (Seqera's read-only set, build_container_image, lint_nextflow, a server you added), and the instruction files and vendored references under skills/. The system prompt tells the model to call search_tools when no bound tool fits and never to guess a tool name.
Finding a tool¶
search_tools(query, kind="any") takes what the model needs in plain words (run the pipeline and repair what breaks, lint nextflow). It ranks one index by name, description and a few keywords, and answers with the top hits, one line each:
Matches for "lint nextflow":
- lint_nextflow [mcp] — Lint a Nextflow pipeline with the Seqera language server and report problems.
- lint_pipeline [core] — Run `nf-core pipelines lint` and return the findings.
Bound for your next step: lint_nextflow, lint_pipeline
kindnarrows the search:tool(core and MCP),core,mcp,skill,reference, orany.- Tools in the hits are bound from the next round of the turn and stay bound for this turn and the two after it (at most twelve at once, the newest kept), so "now fix what it reported" does not search again. The binding is read back from the
search_toolsresults in the conversation, and only from those; a line a file or another tool prints binds nothing. - Skills and references are not bound: the hit gives the path to read with
read_file. - No hit answers with the three closest names.
- Slash-gated tools (
run_compliance_autofix,apply_remediation_plan) are found and described, and a search never binds them: they run only when you type/remediate,/autofixor/fix. - Withheld tools stay withheld: a switched-off MCP tool is not offered, a typed
/auditstill does not getreview_pipeline, and an extension workspace does not get the planner back through a search. - The loop guard treats the same query again (the same words, in any order or case) as no progress and refuses it, saying the tools are already bound; four repeats end the turn like any other loop.
The index is built when the backend starts and again whenever the MCP registry publishes a new tool set (a server added, removed or restarted).
Beyond that, the request adds:
- Slash commands add their own tools for that turn: a bare
/reviewaddsreview_pipelineand nothing else;/audit,/review --complianceand/review --quickadd the compliance audit and nothing else (read-only: the call is pinned toreport_onlyunless/remediateor/autofixis typed); a/reviewthat asks a question, names a path or carries--securityor--lintadds all three review tools (run_compliance_autofix,review_pipeline,lint_pipeline) and the model picks;/remediateand/autofixadd the compliance autofix andapply_remediation_plan;/fixaddsapply_remediation_plan;/rulesaddslist_compliance_rules;/searchDataaddssearch_geo_sraandsearch_pubmed;/extendadds the extension tools;/runadds the full run set (autopilot_pipeline,run_built_pipeline,test_pipeline,check_test_result,prepare_samplesheet_gate,debug_last_run, and the others);/repairaddsautopilot_pipelineandanalyze_run_log. - A pasted link adds the URL fetcher of an MCP server whose
keywordssayurl(thefetchserver), with no search, as the per-prompt retriever always did. - Research turns (
/research, or a turn classified as research) addweb_research,search_geo_sra, andsearch_pubmed. Research is a tool on the surface; it never replaces the agent or takes the workspace away. - MCP servers you register contribute their tools, prefixed with the server name (
demo__echo), to the index. They are found bysearch_tools, using the server'skeywordsas well as the tool's name and description, and still pass the policy at dispatch. A tool switched off in the Tools panel is neither offered nor bound. The built-in surface has no tool that reads an arbitrary URL; MCP servers shows thefetchserver that adds one, and where the registrations are stored. - The built-in MCP server also carries
build_container_image(a frozen container build on Seqera Wave, for a tool with no nf-core module) andlint_nextflow(nextflow lint, reported as "not available in Nextflow" when the installed release lacks it). Like its other domain tools they are off until you switch them on in the Tools panel. build_container_imagenever pushes to a named registry without an operator-listed registry and your approval on a card; see MCP servers.
A slash counts only as a whole token, in any case, with trailing punctuation allowed: /Review. and (/review) are the command, while the /reviews in /home/me/reviews/rnaseq and the /remediate inside /x/pipelines/remediated_rnaseq are path segments and bind nothing.
The backend log records the binding on each turn: Lean tools active: bound N tools to model (A always-on, F forced, S found by search; filtered from M).
Where the fence is¶
The surface decides what the model can see. What a call may do is decided when it is dispatched, by policy.evaluate(name, args, ledger, task), which sees the concrete arguments:
- Allowed roots. File and command tools act only inside
CODARIS_ALLOWED_DIRS(by default the state directory and the current directory) and the session's own workspace. A path outside them is refused with the reason. - Read-only activities. A plan, review, or research turn can read; a write is refused at dispatch however the model phrases it. The refusal names the capability it would need, and
request_capabilityis how the model asks you for it: you get a permission card (Allow once / Allow for session / Deny) naming the capability and the reason, and the call waits for your answer. Reading a file outside the allowed folders isread_outside_fence, writing theremutate_outside_fence, each for one path; a yes lets the next call through for that path and what is under it, a no or no answer grants nothing. - Grants. Some actions (running a pipeline, writing outside the project) need a grant that lives in the task's ledger. The model asks; you decide in the thread; the grant is recorded and replayable.
- Argument shape. A call whose arguments are not an object is refused rather than run, with a message telling the model how to call it again.
Withholding a tool from the binding never stopped a model from emitting its name; a dispatch denial does. That is why the surface is stable across phrasings and the fence lives at dispatch.
Why it is shaped this way¶
- Prompt cost. A turn carries the schemas of the tools it binds. The nine always-on tools are about 5,800 tokens against about 11,200 for the 26-name list they replaced, and an MCP tool costs nothing until a turn finds it. The backend's usage line per turn reports the input tokens and the cache share, so the cost of a change to the surface is measured rather than estimated.
- The prompt cache. The tools array is the always-on tools in a fixed order, then what a slash forced, then what a search found, oldest first. On the direct Anthropic route the cache breakpoint sits on the last always-on tool, so a discovery only appends after it. When the installed Anthropic SDK supports it (
CODARIS_TOOL_SEARCH_API), that route goes further: the long tail rides in the request asdefer_loadingtools and a search is answered withtool_referenceblocks, so the array does not change at all. Anything else (a Perplexity-routed model, OpenAI, a local server) appends, and pays one cache re-write the round a discovery lands. - Determinism. For a fixed task state, two differently worded messages bind the same tools. The slash and research additions are explicit, so they are visible in the message that caused them.
- Auditability. Every dispatch decision has a code and a sentence, and every grant is in the ledger, so a refused or an allowed write can be explained after the fact.