Newer
Older
navi-1 / manuals / write_tool.md

write_tool — Manual

What it does

Writes Python source code to tools/<name>.py and immediately reloads it into the system. The new tool becomes a permanent part of your capabilities — available in every future session.

Parameters

  • name (string, required) — tool filename without .py, e.g. "task_manager"
  • code (string, required) — full Python source code (see format below)

Required code format

Every tool file must define exactly these four things at module level, in this order:

name = "tool_name"          # must match the filename
description = "When and why to use this tool. Be specific — this is what you read to decide whether to call it."
parameters = {
    "type": "object",
    "properties": {
        "param1": {"type": "string", "description": "What this parameter is for"},
        "param2": {"type": "integer", "description": "..."},
    },
    "required": ["param1"],
}

async def execute(params: dict) -> str:
    value = params["param1"]
    # your implementation here
    return "result as a plain string"

Rules:

  • NO classes
  • NO print() at module level
  • execute MUST be async
  • execute MUST return a plain str — not dict, not None
  • Raise an exception to signal failure (do not return error dicts)
  • Put imports inside execute() or at the top of the file — both are fine

Data persistence

If the tool needs to store data between calls, use a JSON file inside the tools/ directory:

import json, os

DATA_FILE = os.path.join(os.path.dirname(__file__), "my_tool_data.json")

def _load():
    if os.path.exists(DATA_FILE):
        with open(DATA_FILE) as f:
            return json.load(f)
    return {}

def _save(data):
    with open(DATA_FILE, "w") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

Full example — a simple note-taking tool

import json, os

name = "notes"
description = (
    "Save and retrieve short text notes by key. "
    "Use this to remember things across sessions: facts, preferences, reminders. "
    "Actions: save (store a note), get (retrieve by key), list (show all keys)."
)
parameters = {
    "type": "object",
    "properties": {
        "action": {"type": "string", "enum": ["save", "get", "list"]},
        "key":   {"type": "string", "description": "Note identifier"},
        "value": {"type": "string", "description": "Note content (for save)"},
    },
    "required": ["action"],
}

_FILE = os.path.join(os.path.dirname(__file__), "notes_data.json")

async def execute(params: dict) -> str:
    action = params["action"]
    data = json.loads(open(_FILE).read()) if os.path.exists(_FILE) else {}

    if action == "save":
        key, value = params["key"], params["value"]
        data[key] = value
        open(_FILE, "w").write(json.dumps(data, ensure_ascii=False, indent=2))
        return f"Saved: {key}"

    if action == "get":
        key = params["key"]
        if key not in data:
            raise KeyError(f"Note '{key}' not found. Use action=list to see all keys.")
        return data[key]

    if action == "list":
        if not data:
            return "No notes saved yet."
        return "Saved notes: " + ", ".join(data.keys())

    raise ValueError(f"Unknown action: {action}")

What write_tool checks before writing

  • name, description, parameters, async def execute must all be present in the code
  • Name must not start with _

If the check fails, you get a clear error with the list of what's missing. If the file loads but has a Python error, you get the exact traceback.

After a successful call

The tool is registered and added to tools/enabled.json. It will be available starting from the next user message — not the current one.