Tools
How a Kortecx agent reaches anything outside itself, and the ordered authority checks every tool call has to pass before it fires.
An agent on its own can only produce text. A tool is how it reaches anything outside itself — reading a file, searching a corpus, calling a service you have connected. The agentic runtime treats every one of those reaches as an action that has to be authorized, not as something the model gets to decide on its own.
Two words do a lot of work on this page, and they do not mean the same thing:
- Registered — the runtime knows this tool exists. It is in the inventory, with a name, a version, a description, and a declared set of things it needs (a network host, a folder, a credential).
- Fireable — this run is actually allowed to call it, right now, with these arguments.
Registering a tool grants no authority. A ranking score grants no authority. The only thing that authorizes a call is the permit the server built for that step — and the runtime re-checks it at every single call.
The inventory
The registry is the durable list of what tools exist. It is a separate file (tools.db) in the server's catalog directory, which defaults to the folder holding your run journal — so the inventory is not part of your run history.
kx tools discover --limit 50Each row shows the tool's name and version, its kind, whether it is a built-in, its network scope, and its description — the governance view of what exists, not what is allowed.
The built-ins
Three tools ship with the OSS runtime and are re-seeded every time the registry is opened. They cannot be removed:
| Tool | What it does |
|---|---|
fs-read@1 | Reads bytes from a path that the permit's folder scope already covers. Read-only. |
fs-write@1 | Writes the complete intended file content to a path the permit covers. Overwrite-only — no append, no partial writes. |
text-summarize@1 | A deterministic text-summarizing transformation. No network, no filesystem. |
A deregistration attempt against any of them is refused — the command prints not removed (absent or a built-in).
kx tools deregister --name web-search --version 1That works for a tool you registered. It does not work for the three above.
A built-in in the inventory is still not fireable
fs-read@1 being present says only that the runtime knows how to read a file. It reads nothing until a run's permit both grants that exact tool and covers the exact folder the arguments point at.
The authority gate
When a model proposes a tool call, the proposal is untrusted text. The runtime decodes it and then walks an ordered set of checks. Every one has to say yes. The first no stops the action.
The first two checks happen where the turn is settled, before any branch is frozen. If a turn proposes several calls, all of them have to pass — one bad call rejects the whole turn rather than firing the rest.
Is this exact tool resolvable and granted? The check is an exact match on the (name, version) pair. Not a prefix, not a fuzzy match, not "close enough". web-search@2 is not web-search@1. The refusal reads the proposed tool ... is not granted to this run or is no longer registered.
Are the arguments the right shape? The proposed arguments are validated against the tool's declared input schema — the right keys, the right types — before anything runs. The argument bytes are carried through verbatim and checked for shape; they are never executed or interpreted along the way.
The remaining checks belong to the broker that actually dispatches the call, and they run in this order:
Is this tool declared for this step at all? The step names the tools it can use. A name that is not in that contract is unknown, and the call ends here.
Is the tool being used the way it declared it can be used? Each tool declares which dispatch patterns it supports. A request for a pattern it does not support is refused.
Is this exact tool on the permit? The broker re-checks the (name, version) pair against the permit's grants. The earlier settle-time check does not stand in for this one.
Is the address, the folder, and the credential inside what the permit allows? Three subset checks, in that order. The network host the call wants must sit inside the permit's network scope. The path must sit inside the permit's folder scope. The secrets the call needs — both the ones it declares and the ones the tool itself is configured with — must sit inside the permit's secret scope. Any one of the three outside the permit and the call is refused.
Only after all of that does the tool actually run.
A refusal is still a fact
A refused call does not silently vanish, and it does not kill the run. The turn settles as rejected with a reason, and that is a durable record you can read back:
kx react listThe listing is newest-first. To scope it to one run, pass that run's instance id — 16 bytes written as 32 hexadecimal characters:
kx react list --instance 0123456789abcdef0123456789abcdefEach turn shows how it settled — pending, answer, tool, rejected, or dead_lettered — and, for a rejection, why. The model is then re-prompted over the reason and gets to try again, bounded by the run's turn and tool-call budgets. Common reasons and what they mean:
| Reason | What to do |
|---|---|
the proposed tool ... is not granted to this run or is no longer registered | Grant that tool to the recipe or agent, or expect the model to pick one it does have. |
the arguments for ... do not match its inputSchema | The keys or types are wrong. Check the tool's declared schema. |
dead-lettered: tool-call budget exhausted | The run made its allowed number of tool calls without a usable answer. Raise the caps or simplify the task. |
dead-lettered: turn budget exhausted | The same, for the turn cap. |
The granted-tool menu
For a model to propose a sensible tool call, it has to know what it has. On a tool-eligible turn the runtime prepends a short menu to the prompt, built from that run's permit:
You can call the following tools:
name: mcp-calc/calc
version: 1
Bundled deterministic integer arithmetic.
Inputs:
- op (enum, required)
- a (integer, required)
- b (integer, required)
Example: {"op": "add", "a": 0, "b": 0}Each entry leads with the exact callable name and the pinned version, because the runtime matches the version exactly. If the registry cannot resolve a granted tool, the entry degrades to its name, version and the call envelope shape rather than being dropped.
The menu lists only the tools granted to that run — never the whole inventory. A tool this job is not allowed to use never appears in the prompt at all, so a prompt-injection attempt has nothing to name.
The menu is advisory. It shapes what the model is likely to ask for; it authorizes nothing. The checks above still run on every call.
It is on by default. To turn it off, set the variable when you start the server:
KX_SERVE_REACT_TOOL_MENU=0 kx serve --dev-allow-localChoosing a tool
kx tools list and kx tools score are display-only. They rank tools against an intent so a human or an authoring step can choose one. A score is a suggestion.
kx tools listkx tools score takes one intent and one or more tools, each written as name@version. Every tool you name has to be in the inventory, so on a fresh install use the built-ins:
kx tools score --intent "summarize the meeting notes" --tool fs-read@1 --tool text-summarize@1A score never authorizes
A high score does not put a tool on a permit, and a low score does not take one off. Ranking and authority are separate systems on purpose.
Registering an external tool
kx tools register records a single declarative external tool and the host it would talk to. The host is vetted at admission and denied by default — loopback, private, link-local, shared-carrier and unspecified addresses are all refused.
kx tools register --name web-search --version 1 \
--server-host mcp.example.com:443 \
--description "search the web" --param q:str --param k:intThe parameter type after the colon is one of str, bytes, int, bool, or enum; leaving it off defaults to str. The tool's identity is derived by the server — a client cannot name or forge it.
Registering is not connecting
kx tools register on its own does not give you a working integration. It writes an entry in the inventory. It does not dial the host, it does not discover what that host can do, and nothing fires as a result.
The OSS path that actually calls out is kx connections — see Connections.
The filesystem root
The host-side file tools are gated behind an operator variable and are off by default:
KX_SERVE_FS_ROOT=/path/to/readable/dir kx serve --dev-allow-localTwo conditions have to hold for the agent-facing file tools to appear. The variable has to name a directory that resolves, and the server has to be a build that includes serving with a model actually being served — with no model there is no agent loop to drive them. When both hold, the runtime registers the read-only file tools and seeds a recipe whose permit grants them plus a folder scope of exactly that directory.
Paths are canonicalized, so an argument cannot walk out of the granted folder. Outside that scope, the folder check in the gate refuses the call.
Where to go next
Agentic RAG
Give an agent a library card instead of a photocopied packet — it decides when to search your dataset, searches again if the first answer was thin, and every search it ran is recorded and replayable.
Connections (MCP)
Plug an outside service into the agentic runtime — name where it lives, and the runtime dials it, asks what it can do, and writes the answer down.