mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
feat(agent): give the Kimi profile Kimi Code's tool descriptions
Edit and Write already carried Kimi-specific descriptions; the other four built-ins were still fabro's one-liners, roughly 150-220 characters against Kimi Code's 1-5KB. Port Bash, Read, Grep, and Glob the same way. Bash is the largest and the most useful: most of its length is an explicit translation table steering shell usage to the dedicated tools -- cat to Read, sed to Edit, find to Glob, grep to Grep -- under the names this profile exposes. It also states that each call runs in a fresh bash process, so `cd` and environment variables do not persist, and that a command which timed out needs a raised `timeout_ms` rather than a retry. Two of the observed K3 tool failures were shell timeouts. The port stays subtractive. Kimi Code's Bash documents background execution, TaskOutput, TaskStop, and a `cwd` argument; fabro's shell has none of those, so none of it is claimed. Read drops Kimi Code's media and paging specifics that do not match fabro's offset/limit, and gains the fact that reading a file is what clears it for writing. Grep deliberately does not promise ripgrep syntax: fabro falls back to POSIX grep when rg is absent, so the description asks for portable patterns instead. Bash quotes the timeouts this profile actually enforces by interpolating them from NativeToolOptions, so the description cannot drift from behavior. Tests assert the interpolation rendered, that the translation table names the exposed tools, and that no background-execution guidance leaked in. Parameter names stay fabro's. Kimi Code's differ (`path` and `line_offset` where fabro has `file_path` and `offset`), but across roughly 1200 tool calls in two observed K3 runs there were no schema or missing-parameter errors, so the model reads the schema it is given. Renaming parameters would be churn against a hypothesis the data does not support. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9604d2ac9d
commit
5349e9d99c
1 changed files with 115 additions and 0 deletions
|
|
@ -37,6 +37,76 @@ files that have not been read, and the call will fail. Prefer Edit for any incre
|
|||
change: Write replaces the entire file, so using it to make a small edit discards \
|
||||
everything you did not restate.";
|
||||
|
||||
const BASH_DESCRIPTION: &str = "Execute a bash command. Use this for shell semantics — pipes, \
|
||||
env, processes, git, package managers, build and test runners.
|
||||
|
||||
Translate these to a dedicated tool instead:
|
||||
- `cat` / `head` / `tail` on a known path → Read
|
||||
- `sed` / `awk` for an in-place edit → Edit
|
||||
- `echo > file` / heredoc → Write
|
||||
- `find` or recursive `ls` to locate files by name → Glob (plain `ls <dir>` is fine)
|
||||
- `grep` / `rg` to search file contents → Grep
|
||||
|
||||
The dedicated tools cap their output, so they keep large raw dumps out of the conversation. That \
|
||||
is why they are worth reaching for whenever one fits.
|
||||
|
||||
Output: stdout and stderr are combined and returned as a string, truncated if very long.
|
||||
|
||||
Guidelines:
|
||||
- Each call runs in a fresh bash process in the working directory. Environment variables and `cd` \
|
||||
do NOT persist between calls — use absolute paths, or `cd <dir> && <command>` within one call.
|
||||
- Do not run interactive commands, or commands that never exit.
|
||||
- A long-running command needs a raised `timeout_ms`, not a retry. The default is \
|
||||
{default_timeout_ms}ms and the maximum is {max_timeout_ms}ms. Retrying a command that timed out \
|
||||
once will simply time out again.
|
||||
- Chain genuinely dependent steps with `&&`. Issue independent read-only commands as separate \
|
||||
parallel calls in one response rather than chaining them, so their output stays separate.
|
||||
- Quote paths containing spaces.
|
||||
- Avoid `..` to reach outside the working directory, and do not modify files outside it unless \
|
||||
explicitly asked.
|
||||
- Never run commands requiring superuser privileges unless explicitly asked.";
|
||||
|
||||
const READ_DESCRIPTION: &str = "Read a text file from the workspace.
|
||||
|
||||
Reading a file is also what clears it for writing: Edit and Write refuse a file that has not been \
|
||||
read in this session.
|
||||
|
||||
- If you have a concrete path, call Read directly. Do not Glob or `ls` first to check that it \
|
||||
exists — a missing path returns an error you can handle.
|
||||
- When you need several files, emit multiple Read calls in one response rather than one per turn.
|
||||
- Returns `<line-number>\\t<content>` per line. Drop the number and tab when you take text for an \
|
||||
Edit `old_string`.
|
||||
- `limit` defaults to 2000 lines. Page a larger file with `offset` (1-based first line) and \
|
||||
`limit`.
|
||||
- Use Bash or an MCP tool for binary formats; this tool reads text.";
|
||||
|
||||
const GREP_DESCRIPTION: &str = "Search file contents with a regular expression.
|
||||
|
||||
Use Grep when you are looking for unknown content or an unknown location. If you already know the \
|
||||
path, use Read instead. Prefer this over running `grep` or `rg` through Bash: it caps its output, \
|
||||
so it will not flood the conversation.
|
||||
|
||||
- Backed by ripgrep when available and POSIX `grep` otherwise, so keep patterns portable across \
|
||||
both rather than relying on ripgrep-only syntax.
|
||||
- `path` chooses the search root, `glob_filter` limits which files are searched, \
|
||||
`case_insensitive` folds case, and `max_results` caps the output.";
|
||||
|
||||
const GLOB_DESCRIPTION: &str = "Find files by name using a glob pattern, most recently modified \
|
||||
first.
|
||||
|
||||
Use this instead of `find` or recursive `ls` through Bash. Prefer patterns with a literal anchor \
|
||||
— an extension or a subdirectory — over bare wildcards.
|
||||
|
||||
Good patterns:
|
||||
- `*.rs` — an extension at any depth below the search root
|
||||
- `src/*.rs` — directly inside `src/`, not recursive
|
||||
- `src/**/*.rs` — recursive walk under a subdirectory
|
||||
- `{src,tests}/**/*.rs` — brace expansion works
|
||||
|
||||
Avoid recursing into dependency or build output (`node_modules/**`, `target/**`): those produce \
|
||||
thousands of matches and waste context. Narrow to a specific subpath instead. Results are files, \
|
||||
so to locate a directory, glob for something inside it.";
|
||||
|
||||
pub struct KimiProfile {
|
||||
base: BaseProfile,
|
||||
}
|
||||
|
|
@ -61,6 +131,23 @@ impl KimiProfile {
|
|||
registry.register(make_edit_file_tool());
|
||||
registry.redescribe(NativeTool::EditFile, EDIT_FILE_DESCRIPTION);
|
||||
registry.redescribe(NativeTool::WriteFile, WRITE_FILE_DESCRIPTION);
|
||||
registry.redescribe(NativeTool::ReadFile, READ_DESCRIPTION);
|
||||
registry.redescribe(NativeTool::Grep, GREP_DESCRIPTION);
|
||||
registry.redescribe(NativeTool::Glob, GLOB_DESCRIPTION);
|
||||
// The Bash description quotes the timeouts this profile actually
|
||||
// enforces, so the two cannot drift.
|
||||
registry.redescribe(
|
||||
NativeTool::Shell,
|
||||
BASH_DESCRIPTION
|
||||
.replace(
|
||||
"{default_timeout_ms}",
|
||||
&options.default_command_timeout_ms.to_string(),
|
||||
)
|
||||
.replace(
|
||||
"{max_timeout_ms}",
|
||||
&options.max_command_timeout_ms.to_string(),
|
||||
),
|
||||
);
|
||||
|
||||
// Kimi Code drives todos with one replace-whole-list call. The
|
||||
// Anthropic task tools model the opposite interaction -- incremental
|
||||
|
|
@ -283,6 +370,34 @@ mod tests {
|
|||
);
|
||||
}
|
||||
assert!(describe("Edit").contains("never reconstruct it from memory"));
|
||||
|
||||
// Bash steers shell usage toward the dedicated tools, under the names
|
||||
// this profile actually exposes.
|
||||
let bash = describe("Bash");
|
||||
for expected in ["→ Read", "→ Edit", "→ Write", "→ Glob", "→ Grep"] {
|
||||
assert!(bash.contains(expected), "Bash should map {expected}");
|
||||
}
|
||||
// Timeouts are interpolated from the options this profile enforces, so
|
||||
// the description cannot drift from behavior.
|
||||
let options = NativeToolOptions::for_profile(AgentProfileKind::Kimi);
|
||||
assert!(
|
||||
bash.contains(&options.default_command_timeout_ms.to_string()),
|
||||
"Bash should quote the real default timeout"
|
||||
);
|
||||
assert!(
|
||||
!bash.contains("{default_timeout_ms}"),
|
||||
"placeholder left unrendered"
|
||||
);
|
||||
// Fabro has no background shell; promising one would be a lie.
|
||||
assert!(!bash.contains("run_in_background"), "{bash}");
|
||||
assert!(!bash.to_lowercase().contains("background task"), "{bash}");
|
||||
|
||||
// Read explains that reading is what clears a file for writing.
|
||||
assert!(describe("Read").contains("refuse a file that has not been read"));
|
||||
// Grep must not promise ripgrep syntax: fabro falls back to POSIX grep.
|
||||
let grep = describe("Grep");
|
||||
assert!(grep.contains("POSIX"), "{grep}");
|
||||
assert!(describe("Glob").contains("most recently modified"));
|
||||
// The shared description is untouched for other profiles.
|
||||
assert!(!describe("Read").contains("refuses writes"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue