Repository navigation
Guide to Creating Python Tools
Ash can run your own Python scripts as AI tools, alongside allowlisted Unix commands. This page documents the interface a Python tool must implement, using the bundled yfinance.py and wikipedia.py scripts as the reference pattern, and how ash discovers, allowlists, and executes them.
-
In the repository: bundled tools are source-controlled under ash_bootstrap/tools/ (currently headlines.py, wikipedia.py, and yfinance.py), embedded into the ash binary at build time via
//go:embed ash_bootstrap/tools/*in bootstrap_assets.go. -
On disk:
ash installwrites them into~/.ash/tools/(installEmbeddedBootstrapAssetsin bootstrap_assets.go). -
Your own scripts: place them directly in
~/.ash/tools/— no rebuild required.
To make eligible managed scripts available automatically, retain the standalone $TOOLS_DIR_LIST directive in ~/.ash/.ash_tools. This is an internal Ash directive, not an environment variable. In .ash_system, $TOOLS_DIR_LIST and $IF_PYTHON_AVAILABLE are also Ash-internal substitutions and cannot be supplied through the environment. Replace the directive with literal bare script names for a fixed restrictive policy.
Every bundled tool follows the same shape. Study yfinance.py or wikipedia.py end to end, but the required pieces are:
-
Shebang and permissions — the first line must be
#!/usr/bin/env python3, and the file must be a regular file with both read and execute permission bits (for example,chmod 700 tool.py), since ash runs it directly by bare name with no shell. -
argparse-based CLI — expose predictable, named options (--query,TICKER, etc.) rather than positional-only or ad hoc parsing, so the AI can construct calls reliably. Seemain()in both reference scripts. -
An
--ai-docsflag — when passed, the script must print documentation and exit before validating any other required arguments. This is how the AI learns what the tool does, without needing separate, hand-maintained documentation. Comparebuild_ai_docs()in yfinance.py and wikipedia.py. The docs text should cover, at minimum:- Capabilities — what the tool does, in a sentence or two.
- Arguments — every flag/positional, its meaning, and its default.
- Return format — the JSON shape and what each field means.
- Usage guidance — how the AI should interpret and present the result, including error handling.
-
Structured JSON output on stdout — always emit a single JSON object (or object per item), never prose mixed with data. Include a
statusfield (for examplesuccess,error,not_found) so the AI can branch reliably; put failure detail in amessage/errorfield rather than letting the script crash with a traceback. Both reference scripts returnstatuson every code path, including exceptions (see thetry/exceptblocks infetch_stock_dataandwikipedia_search_tool). -
Bounded, well-behaved execution — set network timeouts, handle expected failures (
wikipedia.py'smake_request_with_backoffretries on HTTP 429 with backoff), and keep output bounded — ash also enforces a hard cap independently (see Execution limits below).
A minimal skeleton:
#!/usr/bin/env python3
import argparse
import json
def build_ai_docs() -> str:
return """Capabilities:
- ...
Arguments:
- --thing TEXT: ...
- --ai-docs: Print this documentation and exit.
Return format:
- JSON object with keys: status, ...
Usage guidance for the AI:
- ...
"""
def main():
parser = argparse.ArgumentParser(description="...")
parser.add_argument("--thing", help="...")
parser.add_argument("--ai-docs", action="store_true", help="Print AI usage documentation and exit.")
args = parser.parse_args()
if args.ai_docs:
print(build_ai_docs())
return
try:
result = {"status": "success", "...": "..."}
except Exception as e:
result = {"status": "error", "message": str(e)}
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()test_tool_docs.py runs each bundled tool with --ai-docs and asserts the output contains Capabilities, Return format, and the tool's key argument names. Add a similar case for your own tool if it lives in this repository, or apply the same check manually for tools you keep only in ~/.ash/tools/.
Ash only executes allowlisted executables via run_unix_command/run_unix_pipeline. Add your script's bare filename (for example my_tool.py) to ~/.ash/.ash_tools, one per line or comma-separated, or set ASH_TOOL_ALLOWLIST for a one-off override. See the allowlist documentation in README.md.
-
Discovery via the system prompt —
~/.ash/.ash_system(installed from ash_bootstrap/.ash_system) tells the AI that scripts under~/.ash/tools/are allowlisted and executable directly by bare name, and instructs it to run<script> --ai-docsfirst to learn capabilities and arguments before using a tool. There is no separate Go-side tool registry for Python scripts — the AI itself discovers the interface at conversation time by invoking--ai-docs. -
Invocation — the AI calls the
run_unix_commandtool (defined in tools.go) with the script's bare name and argv, exactly like any other allowlisted Unix executable. -
Allowlist and argument checks —
localToolShim.callRunUnixCommandin tools.go confirms the executable is on the allowlist and rejects any argument containing shell control characters (isBlockedArgument), since ash never invokes a shell. -
Interpreter resolution — for a bundled
.pyscript,managedPythonScriptin python_env.go resolves the bare filename to its installed path under~/.ash/tools/, and the command is run withashPythonInterpreter()— the ash-managed virtualenv interpreter at~/.ash/venvwhen it's ready, falling back to systempython3otherwise. See Python-Environment for how that virtualenv is provisioned and why it exists. Your own scripts in~/.ash/tools/that need third-party packages should either avoid them or have their dependencies available to whichever interpreter ash resolves for them. -
Execution limits — every tool call runs through
toolCommandRunnerwith a bounded timeout (ASH_TOOL_TIMEOUT, default 15s) and a bounded captured output size (ASH_TOOL_OUTPUT_MAX, default 8192 bytes); output beyond that limit is truncated. Design your JSON output to stay well within this bound. -
Result returned to the AI — stdout/stderr and exit status are captured into a
toolCommandResultand handed back to the AI as the tool response, for it to parse and present to the user.
-
Python-Environment — the managed virtualenv used to run bundled
.pytools. - ash_bootstrap/tools/ — full source of the bundled reference tools.
- tools.go and python_env.go — the ash-side execution path described above.