English · 简体中文
Translated to English for v0.3.2. Source-of-truth: the OCaml modules in lib/tools/.
Tool API Reference¶
This document describes the 20 built-in tools provided by the P-A-R SDK. New tools are registered through Runtime.register_tool; the tool description gets injected into the LLM's system prompt so the model knows when to call which tool.
Version: v0.3.1
Total tools: 20 (19 from v0.3.0 plus bash added in v0.3.1)
Overview¶
Each tool carries the following metadata at registration time:
type tool_descriptor = {
name : string; (* tool name, referenced by the agent call *)
description : string; (* description, injected into the LLM system prompt *)
input_schema : Yojson.Safe.t; (* JSON Schema *)
permission : tool_permission; (* Allow / Confirm / Deny / ... *)
timeout : float option; (* seconds; None means no timeout *)
concurrency_limit : int option; (* max concurrent invocations *)
on_update : (string -> unit) option; (* v0.3+ progress callback *)
}
The tools fall into four categories by purpose:
| Category | Count | Tools |
|---|---|---|
| math | 1 | calculator |
| utility | 9 | get_time / echo / generate_uuid / hash_text / generate_password / string_stats / json_format / convert_temperature / url_encode |
| web | 3 | fetch_url / read_webpage / web_search |
| fs (read) | 4 | read / ls / find / grep |
| fs (write) | 2 | write / edit |
| exec | 1 | bash (added in v0.3.1, the only one with a security policy) |
All tools except bash use permission = Allow. The file-path tools (read / ls / find / grep / write / edit) reject absolute paths and any path containing :.
Quick Index¶
| Tool | Category | Risk | Timeout | Notes |
|---|---|---|---|---|
calculator |
math | low | 5s | +/-/*// expression evaluation |
get_time |
utility | low | 2s | returns UTC time in ISO 8601 |
echo |
utility | low | 2s | echoes back the input string |
generate_uuid |
utility | low | 1s | UUID v4 |
hash_text |
utility | low | 2s | md5 / sha1 / sha256 (default sha256) |
generate_password |
utility | low | 1s | length 4-128, symbols optional |
string_stats |
utility | low | 1s | character / word / line count |
json_format |
utility | low | 2s | validate + pretty-print |
convert_temperature |
utility | low | 1s | C / F / K conversion |
url_encode |
utility | low | 1s | encode / decode |
fetch_url |
web | medium | 15s | HTTP GET, 10MB cap |
read_webpage |
web | medium | 15s | fetch + HTML parse, strip script/style |
web_search |
web | medium | 15s | DuckDuckGo lite |
read |
fs (read) | low | 30s | 10MB cap; binary returns base64 |
ls |
fs (read) | low | 10s | directory listing, sorted by name |
find |
fs (read) | low | 30s | glob pattern, skips .git / _build etc. |
grep |
fs (read) | low | 30s | regex match, output path:line:text |
write |
fs (write) | medium | 30s | optional create_dirs for auto mkdir -p |
edit |
fs (write) | medium | 30s | batch replace; overlapping ranges are rejected |
bash |
exec | high | 60s | added in v0.3.1, 9-layer safety mechanism |
math category¶
calculator¶
Evaluates arithmetic expressions, supports +, -, *, /, and parentheses.
Input:
Output (number; irrationals are kept as float):
Note: the implementation is a hand-written lexer plus recursive descent (no external eval library). It accepts only numbers and the four basic operators. Whitespace is ignored, but negative number prefixes are not supported ("-1+2" parses as 1 + 2; write it as "0-1+2" instead).
utility category¶
get_time¶
Returns the current UTC time in ISO 8601 format.
Input: {} (no parameters)
Output:
echo¶
Echoes back the input text (useful for debugging or inter-agent communication).
Input:
Output:
generate_uuid¶
Generates a random UUID v4.
Input: {}
Output:
hash_text¶
Hashes text. Supports md5 / sha1 / sha256 (default sha256; case-insensitive algorithm name).
Input:
Output:
Security note: MD5 and SHA1 are no longer collision-resistant. Use them only for non-security purposes (deduplication, cache keys).
generate_password¶
Generates a random password, length 4-128 (values outside this range are clamped automatically). Symbols are included by default.
Input:
Output:
Note: uses Random.State.make_self_init, so there's no cryptographic-strength guarantee. For production, use a dedicated library.
string_stats¶
Counts characters, words, and lines. Words are split on whitespace.
Input:
Output:
json_format¶
Validates and pretty-prints a JSON string.
Input:
Output:
Note: parse failure returns Error (Invalid_input "Invalid JSON: ...") with retryable = false.
convert_temperature¶
Converts between C, F, and K.
Input:
Output:
url_encode¶
URL-encodes or URL-decodes. Encode is the default; pass decode: true to reverse. On decode, + is treated as a space (form-encoding compatible).
Input:
Output:
web category¶
The three network tools share validate_url (only http:// and https:// are allowed), a max_download_size of 10MB, system CA certificates, and TLS hostname verification.
fetch_url¶
HTTP GET, returns the raw text.
Input:
Output:
{
"url": "https://example.com",
"status": 200,
"content": "...",
"content_length": 1234,
"truncated": false
}
read_webpage¶
Fetches the URL, parses the HTML, strips <script> / <style> / <noscript>, and returns the plain text.
Input:
Output:
{
"url": "https://example.com",
"title": "Example Domain",
"text": "...",
"text_length": 567,
"truncated": false
}
Note: depends on lambdasoup. HTTP 4xx / 5xx returns Error (External_failure "HTTP <code>"); 5xx and 429 are flagged with retryable = true.
web_search¶
DuckDuckGo lite search.
Input:
Output:
{
"query": "ocaml lwt tutorial",
"results": [
{ "title": "...", "url": "...", "snippet": "..." }
],
"result_count": 5
}
Note: scrapes DuckDuckGo lite HTML (no API key required). Network or parse failure returns Error (External_failure ...).
fs (read) category¶
The four read tools reject absolute paths and any path containing : (Windows drive-letter guard). find / grep skip .git / node_modules / _build / _opam by default.
read¶
Reads file contents with an optional line offset and line limit.
Input:
Output (line-numbered, similar to cat -n):
Limits:
- File size <= 10MB (exceeding the cap returns Error (Invalid_input "File too large"))
- Path must be CWD-relative
ls¶
Lists directory contents.
Input:
Output:
{
"path": ".",
"entries": [
{ "name": "src", "type": "dir", "size": null, "modified": 1717... },
{ "name": "README.md", "type": "file", "size": 4321, "modified": 1717... }
]
}
Note: entries are sorted by name ascending. type is one of dir / file / link / other / unknown. A non-directory path returns Error (Invalid_input "Not a directory").
find¶
Glob match on filenames (** crosses directories; * crosses a single path component and does not include /).
Input:
Output:
grep¶
Regex search across file contents. The path directory is searched recursively; glob filters by filename.
Input:
Output (one path:line:match per entry):
Note: the regex syntax is OCaml Str (POSIX extended regex). The current implementation ignores context_lines (the parameter is kept for future extension).
fs (write) category¶
write¶
Writes a file. Pass create_dirs: true to run mkdir -p automatically.
Input:
Output:
Note: overwrites an existing file. If the directory is missing and create_dirs is false, the tool returns an error.
edit¶
Batch exact-string replace. Overlapping ranges are rejected (this avoids ambiguity in edit ordering).
Input:
{
"path": "src/main.ml",
"edits": [
{ "old": "let x = 1", "new": "let x = 2" },
{ "old": "foo", "new": "bar" }
]
}
Output:
Note:
- Every old value must be an exact substring of the file (including spaces, newlines, and indentation)
- Uses Str.replace_first, so it does not modify later occurrences of the same string
- Overlap detection: if two old ranges overlap in the file, the tool rejects them with Error (Invalid_input "Overlapping edits")
bash (added in v0.3.1)¶
An LLM calling a shell is the most dangerous built-in tool. v0.3.0 deliberately skipped bash, then v0.3.1 designed it as a standalone piece. The core idea: move from "raw shell string + blacklist" to "typed Safe_command ADT + policy functor + blacklist", pushing safety checks from runtime back toward compile time.
Purpose¶
Executes shell commands with three layers of defense:
- Type layer:
argvis forced to bestring list, with noExec_raw_shellconstructor (shell injection is unrepresentable in the type) - Policy layer:
POLICY.filtervalidates at runtime. PickCoder/ReadOnly/ReadOnlyNoNetto fit the use case - Blacklist layer: 31 regex rules in
Bash_blacklistas a final safety net (catchesrm -rf /,dd of=/dev/sda, fork bombs, and similar)
Input¶
Field reference:
- argv (required): argument array, not a shell string
- cwd: CWD-relative path, default "."
- timeout: max execution seconds, default 30, hard cap 600
Output¶
truncated: true means the output was clipped at 50KB / 2000 lines (a marker is appended at the end).
Three preset policies¶
| Policy | Network | Write | Use case |
|---|---|---|---|
Coder (default) |
yes | yes | "AI writes code", only blacklist hits are blocked |
ReadOnly |
yes | no | pure read-only tools (ls / cat / find / grep) |
ReadOnlyNoNet |
no | no | maximum safety (sensitive code base review) |
Custom policies¶
Implement the Bash_policy.POLICY module type and pass it to Runtime.create:
module type POLICY = sig
val name : string
val filter :
Bash_safe_command.command ->
(Bash_safe_command.command, Types.error_category) result
val max_cpu_seconds : float
val max_memory_kb : int
val allow_network : bool
val allow_write : bool
end
module MyStrictPolicy : Bash_policy.POLICY = struct
let name = "MyStrict"
let allow_network = false
let allow_write = false
let max_cpu_seconds = 10.0
let max_memory_kb = 524288
let filter cmd =
(* your extra checks: blacklist, whitelist, argv limits, ... *)
Ok cmd
end
(* Runtime.create ~bash_policy:(module MyStrictPolicy) *)
Installation¶
The bash tool is not registered automatically with Runtime.create. You need to call install_bash_tool after Runtime.create:
let () = Eio_main.run (fun env ->
Eio.Switch.run (fun sw ->
let mgr = Eio.Stdenv.process_mgr env in
let clock = Eio.Stdenv.clock env in
match Runtime.create ~config:my_config sw with
| Error _ -> failwith "runtime create failed"
| Ok rt ->
(match Runtime.install_bash_tool ~process_mgr:mgr ~clock rt with
| Ok () -> () (* bash tool is ready *)
| Error e -> Printf.failwithf "bash install failed: %a"
Yojson.Safe.pp (Types.error_category_to_yojson e))
))
Idempotent: a second call returns Error (Invalid_input "bash tool already installed").
Required parameters:
- process_mgr: Eio.Stdenv.process_mgr env, used for Eio.Process.spawn
- clock: Eio.Stdenv.clock env, used to enforce the timeout (without a clock, timeouts do nothing)
The 9-layer safety mechanism¶
| # | Mechanism | Implementation |
|---|---|---|
| 1 | CWD lockdown | sandboxed_path abstract type; the constructor rejects .., absolute paths, :, and sensitive prefixes like /etc and ~/.ssh |
| 2 | Blacklist | 31 regex rules in Bash_blacklist (rm -rf /, dd of=/dev/sda, :(){:|:&};:, and similar) |
| 3 | Whitelist (optional) | implement whitelist logic in a custom POLICY |
| 4 | Timeout | Eio.Process.spawn + Eio.Fiber.first race; hard cap 600s |
| 5 | Process group cleanup | Eio.Process + setpgid; the timeout sends killpg to the whole group |
| 6 | Environment scrubbing | Bash_policy.sanitize_env strips *_SECRET* / *_KEY* / AWS_* / OPENAI_API_KEY / ANTHROPIC_API_KEY / GITHUB_TOKEN and similar |
| 7 | Output truncation | 50KB byte cap + 2000 line cap; a marker is appended when triggered |
| 8 | ANSI stripping | removes CSI (ESC[...]) and OSC (ESC]...BEL) sequences |
| 9 | Audit log | the event bus emits Bash_invoked / Bash_completed events (carrying a risk rating and argv) |
Security recommendations¶
- The default
Coderpolicy is the right choice for "AI writes code" workflows (it matches pi's default behavior) - For code review where the LLM should only inspect, use
ReadOnly - For sensitive code bases, use
ReadOnlyNoNet - Never disable the timeout. It's the last line of defense against fork bombs and network hangs
- Environment variables are scrubbed automatically. If you need to pass a secret, write it to a file and have the agent use the
readtool (don't put it in theenvfield) - The blacklist is a last resort, not the main defense. For safety-critical setups, set
allow_write:falseandallow_network:falsein a customPOLICY - OS-level sandboxing (bwrap / landlock) is not provided in v0.3.1
Risk scoring¶
Bash_safe_command.assess_risk returns Low / Medium / High / Critical, attached to the risk field of the Bash_invoked event.
Registering a custom tool¶
A tool is a simple { descriptor; handler } pair. Runtime.register_tool is the convenience function; under the hood, you can register directly through Tool_registry.register:
The full example (with tool_descriptor field reference) is in agent.md.
Security audit checklist¶
Self-check before submitting a new tool:
- [ ] The
permissionfield is set (Allow/Confirm/Deny/Role_based/Condition_based) - [ ] Long-running tools have a
timeoutset - [ ] Resource-constrained tools have a
concurrency_limitset - [ ] Network / write tools emit audit events through the event bus
- [ ] File-path tools reject absolute paths and paths containing
: - [ ] Dangerous tools go through
Bash_policy(when applicable) - [ ] The
descriptionincludes an input example (so the LLM knows how to call the tool)
See also¶
agent.md-- Agent definitions, Runtime API, tool registrationoverview.md-- SDK architecture overviewlib/tools/bash_safe_command.mli-- FullSafe_commandADT APIlib/tools/bash_policy.mli-- POLICY interface plus the 3 presetslib/tools/bash_blacklist.mli-- the 31 blacklist regex rules