Skip to content

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
  • kind narrows the search: tool (core and MCP), core, mcp, skill, reference, or any.
  • 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_tools results 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, /autofix or /fix.
  • Withheld tools stay withheld: a switched-off MCP tool is not offered, a typed /audit still does not get review_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 /review adds review_pipeline and nothing else; /audit, /review --compliance and /review --quick add the compliance audit and nothing else (read-only: the call is pinned to report_only unless /remediate or /autofix is typed); a /review that asks a question, names a path or carries --security or --lint adds all three review tools (run_compliance_autofix, review_pipeline, lint_pipeline) and the model picks; /remediate and /autofix add the compliance autofix and apply_remediation_plan; /fix adds apply_remediation_plan; /rules adds list_compliance_rules; /searchData adds search_geo_sra and search_pubmed; /extend adds the extension tools; /run adds the full run set (autopilot_pipeline, run_built_pipeline, test_pipeline, check_test_result, prepare_samplesheet_gate, debug_last_run, and the others); /repair adds autopilot_pipeline and analyze_run_log.
  • A pasted link adds the URL fetcher of an MCP server whose keywords say url (the fetch server), with no search, as the per-prompt retriever always did.
  • Research turns (/research, or a turn classified as research) add web_research, search_geo_sra, and search_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 by search_tools, using the server's keywords as 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 the fetch server 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) and lint_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_image never 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_capability is 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 is read_outside_fence, writing there mutate_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 as defer_loading tools and a search is answered with tool_reference blocks, 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.