Skip to content

Sharing Custom Tools

William Koch edited this page Sep 30, 2026 · 5 revisions

Here's one suggested way to publish Custom Tools for other people to use. Keryx has no registry, marketplace or install-from-URL command. keryx tools only lists and checks the files already in your tools directory. keryx plugins install knows only Keryx's own four plugins. What follows is a convention that works with those commands as they exist today. It isn't a feature.

Before you install anyone's tool

A tool file is code that runs inside the Keryx service, at the start of every call. It can reach everything the service can: your files, your signed-in agents, secrets.toml and the network. The loader checks who can write the file. It can't check what the file does. Installing someone's tool is like running their script as yourself, so:

  • Read the file before you install it, and read it again for each update.
  • Pin a commit. Install from a commit you've read, not from whatever a branch points to today.
  • Prefer tools that keep needs_pin=True. A needs_pin=False tool answers anyone who calls your number. It should only read information that's public anyway, and its README should explain why.

Publishing: a repository of tool files

A public git repository with one self-contained .py file per tool works well:

keryx-tools/
├── README.md
├── .gitignore          # *.key, and any filled-in settings
├── tides.py
├── tides.toml          # settings template: defaults only, a comment on every key
└── countdown.py

Keep each tool to one file. The loader reads only *.py files directly in the tools directory and imports each as a separate module. A tool can't import a helper that sits beside it, and a subdirectory is ignored. Use only the standard library and what Keryx already depends on (httpx, numpy, pydantic). The owner's Keryx environment has nothing else, and they shouldn't install packages into it for a tool. From Keryx, import only custom_tool and ToolUnavailable from keryx.tools.custom. Nothing else in Keryx is a stable interface.

For each tool, the README should say:

  • What it does and what it reads. Include every URL it calls and every file it opens.
  • Whether it needs the PIN, and if it doesn't, why that's safe.
  • What it needs from the owner: settings, and which keys to put in its .toml. It should also name each secret and the file it belongs in, such as tides.key with mode 0600 beside the tool.
  • Its tool name, so the owner can check it doesn't clash with a built-in tool, a plugin or one of their own tools. A distinctive name, such as harbor_tides rather than tides, avoids that. When two files define the same name, the first file in alphabetical order wins and the second file is refused.

Never publish a secret, including in the .py file, the settings template, a test or the history. A tool reads its key from a file the owner creates. .gitignore should exclude *.key and any filled-in settings. A tool that's missing its key can raise ToolUnavailable with a sentence that says where the key goes. keryx tools then reports that sentence instead of a traceback.

Installing: clone, read, copy

git clone https://github.com/SOMEONE/keryx-tools ~/src/keryx-tools
cd ~/src/keryx-tools
git checkout <commit you have read>

install -m 600 tides.py tides.toml ~/.local/share/keryx/tools/
$EDITOR ~/.local/share/keryx/tools/tides.toml     # fill in the settings
# any key goes in its own file, 0600, as the README says

cd <your Keryx checkout> && uv run keryx tools

Keep the clone outside the tools directory. Copy only the tool files, not the whole repository. Every .py file in that directory is imported, including a setup.py or a test file. Don't turn the tools directory into a git checkout. keryx tools should list the new tool with the PIN setting you expected and exit 0. The tool is available from your next call.

To update, git fetch, read the diff (git diff <old>..<new> -- tides.py), check out the new commit and copy the file again. Your settings .toml and key files are already in the tools directory, so don't copy the template over your filled-in settings.

Why copy rather than symlink

A symlink loads because the loader follows it. It checks the file the link points to, which must be yours and not writable by anyone else. It doesn't check the directory that file is in. More importantly, a symlink into a clone means a git pull changes the code your next call runs, with no step where you read the change. A copy makes each update a deliberate step, as the approval bridge does for its hook. If you use a symlink anyway, point it at a checkout that only you can write to, and never pull without reading.

Offering a tool upstream instead

A tool that almost everyone would want belongs in Keryx itself, as a plugin with tests. Open an issue or a pull request; CONTRIBUTING.md says how.

Clone this wiki locally