use std::sync::Arc;
use serde_json::json;
use super::{ToolCategory, ToolExample, ToolGuide, ToolGuideSpec};
pub const BUILTIN_GUIDE_NAMES: [&str; 20] = [
"conclusion_with_options",
"Bash",
"BashOutput",
"Edit",
"ExitPlanMode",
"GetFileInfo",
"Glob",
"Grep",
"js_repl",
"KillShell",
"session_note",
"NotebookEdit",
"Read",
"request_permissions",
"Sleep",
"Task",
"WebFetch",
"WebSearch",
"Workspace",
"Write",
];
pub fn builtin_tool_guide(tool_name: &str) -> Option<Arc<dyn ToolGuide>> {
builtin_guide_spec(tool_name).map(|guide| Arc::new(guide) as Arc<dyn ToolGuide>)
}
pub fn builtin_guide_spec(tool_name: &str) -> Option<ToolGuideSpec> {
match tool_name {
"apply_patch" => Some(guide(
"apply_patch",
ToolCategory::FileWriting,
"Use for patch-only in-place updates using SEARCH/REPLACE blocks, aligned with Edit patch mode.",
"Do not use before Read on existing files; do not use for full-file rewrites; do not mix with old_string/new_string style arguments.",
&["Read", "Edit", "Write"],
vec![example(
"Apply a patch block",
json!({"file_path":"/workspace/project/src/main.rs","patch":"<<<<<<< SEARCH\nlet v = 1;\n=======\nlet v = 2;\n>>>>>>> REPLACE"}),
"Use when you want a dedicated patch tool call instead of Edit.",
)],
)),
"conclusion_with_options" => Some(guide(
"conclusion_with_options",
ToolCategory::UserInteraction,
"Ask the user for confirmation or missing input with selectable options. Use this as the final interaction step when wrapping up a task turn, handing off execution, or asking the user to choose next steps.",
"Do not use repeatedly for routine status updates during active execution; reserve it for true clarification points, explicit user decisions, or the required final confirmation flow.",
&["ExitPlanMode"],
vec![
example(
"Confirm before finishing",
json!({
"question":"Any other requests before I finish?",
"conclusion":{
"title":"Conclusion",
"summary":"Core validation is complete and release is ready.",
"key_points":["All targeted tests passed","No blocking regressions"],
"next_steps":["Proceed with release train"],
"confidence":"high",
"mermaid":{"graph":"graph TD\nA[Validation]-->B[Ready to release]"}
}
}),
"Use when user intent is required before finalizing. Defaults to options [\"OK\", \"Need changes\"] when options are omitted.",
),
example(
"Ask the user to choose the next step",
json!({
"question":"Which next step should I take?",
"options":["Implement it","Refine the plan","Stop here","OK"],
"conclusion":{
"summary":"I finished the review and identified two viable implementation paths.",
"key_points":["Minimal change path is lower risk","Broader refactor will simplify future maintenance"],
"mermaid":{"graph":"graph TD\nA[Review done]-->B[Choose next step]"}
}
}),
"Use when the user must choose among concrete next actions instead of receiving plain prose.",
),
example(
"Wrap up a review turn",
json!({
"question":"Want me to proceed with the recommended fix?",
"options":["Proceed","Need changes","OK"],
"conclusion":{
"summary":"The review is complete and I found one blocking issue plus two minor cleanups.",
"key_points":["Blocking issue identified","Fix scope is localized"],
"mermaid":{"graph":"graph TD\nA[Review complete]-->B[Decision needed]"}
}
}),
"Use at the end of review/explanation turns instead of a plain final paragraph.",
),
],
)),
"Read" => Some(guide(
"Read",
ToolCategory::FileReading,
"Use for reading file contents, especially before Edit/Write on existing files.",
"Do not use for writing or cross-project search; do not substitute Bash cat/head/tail for this.",
&["Edit", "Write", "Grep"],
vec![example(
"Read a config file",
json!({"file_path":"/workspace/project/config.toml"}),
"Use before making any edits.",
)],
)),
"Write" => Some(guide(
"Write",
ToolCategory::FileWriting,
"Use for creating files or replacing full file contents.",
"Do not use for small in-place updates; prefer Edit after Read.",
&["Read", "Edit"],
vec![example(
"Write a full file",
json!({"file_path":"/workspace/project/.env.example","content":"API_KEY=\n"}),
"Suitable for full replacements.",
)],
)),
"Edit" => Some(guide(
"Edit",
ToolCategory::FileWriting,
"Use for precise in-place updates in existing files. Prefer patch mode with SEARCH/REPLACE blocks and enough context to target unique locations.",
"Do not use before Read on existing files; do not use for full-file rewrites; do not use ambiguous SEARCH blocks that match multiple locations.",
&["Read", "Write"],
vec![
example(
"Patch a specific block",
json!({"file_path":"/workspace/project/src/main.rs","patch":"<<<<<<< SEARCH\nfn b() {\n let v = 1;\n}\n=======\nfn b() {\n let v = 2;\n}\n>>>>>>> REPLACE"}),
"Preferred for repeated code because context disambiguates the target.",
),
example(
"Legacy single replacement",
json!({"file_path":"/workspace/project/src/main.rs","old_string":"foo","new_string":"bar"}),
"Use only when old_string is known to match exactly once.",
),
example(
"Legacy replacement with line hint",
json!({"file_path":"/workspace/project/src/main.rs","old_string":"value = 1","new_string":"value = 2","line_number":42}),
"Use line_number when old_string may appear in multiple places.",
),
],
)),
"Glob" => Some(guide(
"Glob",
ToolCategory::CodeSearch,
"Find files by glob pattern before deeper content search.",
"Do not use for content search; avoid unbounded root patterns like **/* without narrowing path/pattern.",
&["Grep", "Read"],
vec![example(
"Find all Rust files",
json!({"pattern":"**/*.rs","path":"/workspace/project"}),
"Use before Grep when scope is unknown.",
)],
)),
"Grep" => Some(guide(
"Grep",
ToolCategory::CodeSearch,
"Search project contents using regex; start with files_with_matches then narrow with Read/content mode.",
"Do not run broad content or multiline searches across the full workspace; always narrow with path/glob/type first.",
&["Glob", "Read"],
vec![example(
"Find function usages",
json!({"pattern":"execute_with_context","glob":"**/*.rs","output_mode":"files_with_matches","head_limit":200}),
"First identify candidate files, then use Read or scoped content mode.",
)],
)),
"Bash" => Some(guide(
"Bash",
ToolCategory::CommandExecution,
"Run terminal commands (build/test/git/npm/docker/gh), optionally in background.",
"Do not use for file reads/edits/search when Read/Edit/Write/Glob/Grep can handle it; do not use shell echo/printf to communicate with the user.",
&["BashOutput", "KillShell"],
vec![example(
"Run tests",
json!({"command":"cargo test","timeout":120000}),
"Use for build/test/CLI operations.",
)],
)),
"BashOutput" => Some(guide(
"BashOutput",
ToolCategory::CommandExecution,
"Read incremental output from a background shell.",
"Do not use without a bash_id from Bash.",
&["Bash", "KillShell"],
vec![example(
"Poll output",
json!({"bash_id":"abc"}),
"Use repeatedly until shell completes.",
)],
)),
"KillShell" => Some(guide(
"KillShell",
ToolCategory::CommandExecution,
"Terminate a background shell.",
"Do not use for foreground commands; use the ID returned by Bash(run_in_background=true), not chat session_id.",
&["Bash", "BashOutput"],
vec![example(
"Stop runaway process",
json!({"shell_id":"<bash_id-from-Bash>"}),
"Use when process should no longer run.",
)],
)),
"NotebookEdit" => Some(guide(
"NotebookEdit",
ToolCategory::FileWriting,
"Edit notebook cells by replace/insert/delete.",
"Do not use for non-notebook files.",
&["Read", "Write"],
vec![example(
"Replace first cell",
json!({"notebook_path":"/workspace/project/demo.ipynb","new_source":"print('ok')"}),
"Use with absolute notebook path.",
)],
)),
"SlashCommand" => Some(guide(
"SlashCommand",
ToolCategory::UserInteraction,
"Resolve and execute a slash command template.",
"Do not use for arbitrary shell execution.",
&["Bash", "Read"],
vec![example(
"Run review command",
json!({"command":"/review"}),
"Useful for reusable prompt workflows.",
)],
)),
"Task" => Some(guide(
"Task",
ToolCategory::TaskManagement,
"Create or update the shared task list for the current root session tree (root + child sessions share the same task list).",
"Do not use for trivial one-step requests.",
&["ExitPlanMode"],
vec![example(
"Update shared task statuses",
json!({"tasks":[{"content":"Run tests","status":"in_progress","activeForm":"Running tests"}]}),
"Keep exactly one item in_progress whenever possible.",
)],
)),
"ExitPlanMode" => Some(guide(
"ExitPlanMode",
ToolCategory::UserInteraction,
"Ask for confirmation before leaving plan mode.",
"Do not use if implementation can proceed directly.",
&["Task"],
vec![example(
"Plan complete",
json!({"plan":"1. Do A\n2. Do B"}),
"Use after producing a concrete implementation plan.",
)],
)),
"WebFetch" => Some(guide(
"WebFetch",
ToolCategory::CommandExecution,
"Fetch a webpage by URL when you need cleaned page text from a known target.",
"Do not use for broad discovery queries.",
&["WebSearch"],
vec![example(
"Fetch a target page",
json!({"url":"https://target-host/path","prompt":"Extract setup steps"}),
"The prompt field is context for downstream handling; WebFetch itself returns cleaned text + metadata.",
)],
)),
"WebSearch" => Some(guide(
"WebSearch",
ToolCategory::CommandExecution,
"Search the web with optional domain allow/block filters.",
"Do not use for local codebase search.",
&["WebFetch", "Grep"],
vec![example(
"Search official docs",
json!({"query":"rust async trait object", "allowed_domains":["doc.rust-lang.org"]}),
"Use before WebFetch when URL is unknown.",
)],
)),
"GetCurrentDir" => Some(guide(
"GetCurrentDir",
ToolCategory::CommandExecution,
"Retrieve the session's current workspace directory. (Alias for Workspace with no path argument.)",
"Do not use when absolute paths are already known.",
&["Workspace", "Bash", "Read"],
vec![example(
"Inspect working directory",
json!({}),
"Useful before commands or file ops that rely on relative paths.",
)],
)),
"GetFileInfo" => Some(guide(
"GetFileInfo",
ToolCategory::FileReading,
"Check if a file/dir exists and read metadata (type, size, modified time) without loading content. Returns {exists: false} for missing paths, so this also covers existence checks.",
"Do not use when you need actual content; use Read instead.",
&["Read"],
vec![example(
"Check metadata before processing",
json!({"path":"/workspace/project/logs/app.log"}),
"Use to branch behavior based on file type/size.",
)],
)),
"FileExists" => Some(guide(
"FileExists",
ToolCategory::FileReading,
"Check quickly whether a path exists before reading, editing, or writing conditionally. (Alias for GetFileInfo)",
"Do not use to inspect file content or metadata details.",
&["GetFileInfo", "Read", "Write"],
vec![example(
"Guard before write",
json!({"path":"/workspace/project/.env"}),
"Use as a fast existence probe before deciding create vs update.",
)],
)),
"session_note" | "memory_note" => Some(guide(
if tool_name == "memory_note" {
"memory_note"
} else {
"session_note"
},
ToolCategory::TaskManagement,
"Store durable session-scoped notes and retrieve them across turns. Use it for local context, user preferences, constraints, and compression-resistant reminders within the current workstream.",
"Do not store secrets/tokens, one-turn scratch text, or use it as the primary long-term knowledge base.",
&["Task", "recall"],
vec![
example(
"Persist a durable session constraint",
json!({"action":"append","content":"User prefers pnpm and strict TypeScript."}),
"Use append for stable session/workstream facts; use replace to compress long notes.",
),
example(
"Store notes for a specific topic",
json!({"action":"append","topic":"backend-api","content":"REST endpoints finalized: /users, /orders."}),
"Use topic to keep separate workstreams isolated from each other.",
),
],
)),
"memory" => Some(guide(
"memory",
ToolCategory::TaskManagement,
"Manage Bamboo's unified memory system. Use session_* actions only for current-session continuity notes, and use query/get/write/merge/purge/inspect/rebuild for durable project or global memories backed by canonical topic files.",
"Do not use session actions for long-term project knowledge, and do not dump large bodies through query when query -> get(id) or inspect is more appropriate. Prefer query first, then get the specific durable item you need before writing or merging.",
&["session_note", "recall", "Task"],
vec![
example(
"Read the current session note topic",
json!({"action":"session_read","topic":"default","options":{"max_chars":4000}}),
"Use for continuity state inside the current session without touching durable project/global memory.",
),
example(
"Shortlist durable memories before reading full content",
json!({"action":"query","scope":"project","query":"release freeze mobile","options":{"limit":5,"max_chars":3000}}),
"Prefer query first so the model gets a shortlist summary under budget; call get(id) only for the durable item that needs full context.",
),
example(
"Read one durable memory in full after query",
json!({"action":"get","id":"mem_20260403_001","options":{"max_chars":5000}}),
"Use after query when you need the full body/frontmatter of a single durable memory item.",
),
example(
"Write a durable project memory",
json!({"action":"write","scope":"project","type":"project","title":"Release freeze begins next week","content":"Merge freeze begins on Tuesday for the mobile release cut.","tags":["release","freeze"]}),
"Use when the fact should persist across sessions as canonical project memory and should not live only in session_note.",
),
example(
"Merge follow-up details into an existing durable memory",
json!({"action":"merge","id":"mem_20260403_001","content":"Additional confirmation from a later session.","tags":["confirmed"],"source_memory_ids":["mem_20260403_002"]}),
"Use when new durable evidence belongs on an existing memory item and older overlapping items should be superseded.",
),
],
)),
"SetWorkspace" => Some(guide(
"SetWorkspace",
ToolCategory::CommandExecution,
"Change the current session workspace. (Alias for Workspace with path argument.)",
"Do not use with non-directory or missing paths.",
&["Workspace", "Bash", "Read"],
vec![example(
"Switch session workspace",
json!({"path":"/workspace/project"}),
"Use before running commands or edits in another repo root.",
)],
)),
"Workspace" => Some(guide(
"Workspace",
ToolCategory::CommandExecution,
"Get or set the current session workspace directory. Call without 'path' to read the current workspace; call with 'path' to change it.",
"Do not set workspace to a non-directory or missing path.",
&["Bash", "Read"],
vec![
example(
"Get current workspace",
json!({}),
"Useful before commands or file ops that rely on relative paths.",
),
example(
"Set workspace",
json!({"path":"/workspace/project"}),
"Use before running commands or edits in another repo root.",
),
],
)),
"js_repl" => Some(guide(
"js_repl",
ToolCategory::CommandExecution,
"Execute JavaScript code using Node.js with top-level await support. Each invocation runs in a fresh process.",
"Do not use for tasks better handled by Bash. Requires Node.js installed on the host.",
&["Bash"],
vec![example(
"Evaluate a JS expression",
json!({"code":"console.log(2 + 2)"}),
"Use for quick calculations, JSON manipulation, or any JS-specific task.",
)],
)),
"request_permissions" => Some(guide(
"request_permissions",
ToolCategory::UserInteraction,
"Request elevated permissions from the user when a needed operation would be blocked. The agent loop pauses until the user approves or denies.",
"Do not use pre-emptively for every operation; only request when you know the current permission set is insufficient.",
&["conclusion_with_options"],
vec![example(
"Request file write permission",
json!({"reason":"Need to write deployment config to /etc/nginx","permissions":[{"type":"write_file","resource":"/etc/nginx/conf.d/*"}]}),
"Use when a Write/Edit would be denied due to path restrictions.",
)],
)),
"Sleep" => Some(guide(
"Sleep",
ToolCategory::CommandExecution,
"Pause briefly when waiting for an external state change before polling again.",
"Do not use for normal reasoning pauses or long waits when another tool can fetch status directly.",
&["BashOutput", "WebFetch"],
vec![example(
"Wait before next poll",
json!({"seconds":2,"reason":"wait for background process output"}),
"Use short waits between repeated status checks.",
)],
)),
"load_skill" => Some(guide(
"load_skill",
ToolCategory::TaskManagement,
"Load a selected skill's SKILL.md instructions and metadata by skill_id.",
"Do not use for auxiliary resource files; use read_skill_resource for references/assets.",
&["read_skill_resource"],
vec![example(
"Load skill instructions",
json!({"skill_id":"rust-best-practices"}),
"Call this before following a matched skill's detailed workflow.",
)],
)),
"read_skill_resource" => Some(guide(
"read_skill_resource",
ToolCategory::FileReading,
"Read auxiliary files under a loaded skill directory with optional offset/limit paging.",
"Do not use for SKILL.md itself; call load_skill for primary instructions.",
&["load_skill"],
vec![example(
"Read a skill reference file",
json!({"skill_id":"rust-best-practices","resource_path":"references/chapter_01.md","offset":0,"limit":80}),
"Use when the loaded instructions point to additional files.",
)],
)),
"recall" | "session_inspector" => Some(guide(
if tool_name == "session_inspector" {
"session_inspector"
} else {
"recall"
},
ToolCategory::FileReading,
"Inspect prior Bamboo context from local session storage. Use list/get_meta before deep reads when possible, then read bounded message slices, compressed recall, or search results to recover previous discussion context.",
"Do not use as a broad substitute for local code search, and do not delegate child-session inspection unless the user explicitly asks for delegated work.",
&["session_note", "Read", "Task"],
vec![
example(
"Search prior discussion history",
json!({"action":"search","query":"release checklist","mode":"tail_messages","max_sessions":10,"tail_messages":6}),
"Use when you need to recover what was discussed previously before asking the user to repeat it.",
),
example(
"Inspect a specific session with bounded reads",
json!({"action":"read_messages","session_id":"session-123","from_end":true,"limit":20,"include_system":false,"truncate_chars":200}),
"Prefer bounded slices rather than dumping an entire session at once.",
),
],
)),
"scheduler" | "schedule_tasks" => Some(guide(
if tool_name == "schedule_tasks" {
"schedule_tasks"
} else {
"scheduler"
},
ToolCategory::TaskManagement,
"Manage Bamboo scheduled automation jobs for recurring or delayed work. Use it to create, inspect, modify, run, or delete schedules without going through HTTP.",
"Do not use for normal one-shot task planning inside the current conversation; use Task for active execution tracking instead.",
&["Task", "Workspace"],
vec![
example(
"Create a recurring schedule",
json!({"action":"create","name":"daily-review","trigger":{"type":"interval","every_seconds":86400},"enabled":true,"run_config":{"auto_execute":true,"task_message":"Review new tickets","workspace_path":"/workspace/project"}}),
"Use when work should recur automatically over time.",
),
example(
"Inspect schedule sessions",
json!({"action":"list_sessions","schedule_id":"sch_123"}),
"Use to see the sessions a schedule created or ran.",
),
],
)),
"SubSession" => Some(guide(
"SubSession",
ToolCategory::TaskManagement,
"Create and run a child session asynchronously when the user explicitly requests delegated, parallel, or sub-agent work.",
"Do not use proactively just to speed things up or split work on your own; only use it when the user clearly asked for delegation/sub-agents/parallel agent work.",
&["sub_session_manager", "Task"],
vec![
example(
"Start delegated review work explicitly requested by the user",
json!({"title":"Backend API audit","responsibility":"Review backend API integration points","subagent_type":"general-purpose","prompt":"Analyze the backend API surface and report coupling risks."}),
"Use only after the user explicitly asked for delegation or parallel agent help.",
),
example(
"Create a focused child session",
json!({"title":"Docs verification","responsibility":"Verify docs against implementation","subagent_type":"researcher","prompt":"Compare current docs with implementation details and list mismatches."}),
"Keep the child session responsibility single-purpose and explicit.",
),
],
)),
"sub_session_manager" => Some(guide(
"sub_session_manager",
ToolCategory::TaskManagement,
"Manage existing child sessions under the current root session. Use it to inspect, retry, update, rerun, or message child sessions after they already exist.",
"Do not use it to create new child sessions from scratch; use SubSession for creation, and do not use it inside child sessions.",
&["SubSession", "Task"],
vec![
example(
"List existing child sessions",
json!({"action":"list"}),
"Use before following up on an existing child session when you need to inspect what already exists.",
),
example(
"Send follow-up instructions to an existing child",
json!({"action":"send_message","child_session_id":"child_123","message":"Continue and focus on test failures.","auto_run":true}),
"Use after a child session exists and you want it to continue with updated instructions.",
),
],
)),
_ => None,
}
}
pub fn builtin_guides() -> Vec<ToolGuideSpec> {
BUILTIN_GUIDE_NAMES
.iter()
.filter_map(|name| builtin_guide_spec(name))
.collect()
}
fn guide(
tool_name: &str,
category: ToolCategory,
when_to_use: &str,
when_not_to_use: &str,
related_tools: &[&str],
examples: Vec<ToolExample>,
) -> ToolGuideSpec {
ToolGuideSpec {
tool_name: tool_name.to_string(),
when_to_use: when_to_use.to_string(),
when_not_to_use: when_not_to_use.to_string(),
examples,
related_tools: related_tools.iter().map(|name| name.to_string()).collect(),
category,
}
}
fn example(scenario: &str, parameters: serde_json::Value, explanation: &str) -> ToolExample {
ToolExample::new(scenario, parameters, explanation)
}
#[cfg(test)]
mod tests {
use crate::BUILTIN_TOOL_NAMES;
use super::{builtin_guide_spec, BUILTIN_GUIDE_NAMES};
#[test]
fn every_builtin_tool_has_a_guide() {
for name in BUILTIN_GUIDE_NAMES {
assert!(
builtin_guide_spec(name).is_some(),
"missing guide for {}",
name
);
}
}
#[test]
fn builtin_guides_cover_all_builtin_tool_names() {
for name in BUILTIN_TOOL_NAMES {
assert!(
builtin_guide_spec(name).is_some(),
"missing builtin guide coverage for {}",
name
);
}
}
#[test]
fn memory_server_tool_has_fallback_guide_spec() {
let guide = builtin_guide_spec("memory").expect("memory guide should exist");
assert_eq!(guide.tool_name, "memory");
assert!(guide.examples.iter().any(|example| {
example
.parameters
.get("action")
.and_then(|value| value.as_str())
== Some("merge")
}));
}
}