Skip to content
6 changes: 4 additions & 2 deletions crates/tui/assets/skills/best-of-n/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ the user has already chosen the approach.
3. Give every candidate the same task and rubric. Add only a candidate number;
do not steer candidates toward different conclusions unless diversity is an
explicit part of the request.
4. Prefer a session goal (`create_goal` or active `/goal`) when the tournament
spans more than one parent turn.
4. When the tournament spans more than one parent turn, prefer a session goal
if `create_goal` is in your tool list; if it is not, run `tool_search` first
to activate it. In subagent sessions `create_goal` does not exist and
`tool_search` cannot surface it — track progress in your own notes instead.

## Generate Independently

Expand Down
2 changes: 1 addition & 1 deletion crates/tui/assets/skills/help/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Answer from the surface that owns the fact, in this order:

## Working in a Codewhale checkout
When the workspace *is* a Codewhale checkout, `docs/` is present on disk and
`File` with `action: "read"` is the right tool. Read the single most relevant file and quote
`read` is the right tool. Read the single most relevant file and quote
the specific lines. Outside a checkout, `docs/` is usually absent — in that
case rely on `/help`, `/config`, and `doctor`, and say plainly that the
reference docs are not installed locally.
Expand Down
39 changes: 27 additions & 12 deletions crates/tui/assets/skills/mcp-discovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ whether an MCP server already does it. The public MCP Registry ships hundreds
of ready-made servers (filesystems, databases, browsers, media processing,
developer utilities, cloud APIs, SaaS integrations, …).

The discovery and structured start tools are available in the active tool
surface whenever MCP support is enabled.
The discovery and structured start tools are registered when MCP support is
enabled — the start tool once the host's MCP pool is initialized as well —
but hosts may defer them out of your first-turn tool list or restrict them
entirely; check your tool list and follow step 1 either way.

## When to use

Expand All @@ -27,27 +29,40 @@ surface whenever MCP support is enabled.

## Workflow

1. **Check the registry.** Call `registry_sync {}`. It returns the complete
catalog of eligible local stdio packages, including each server's name,
description, and required launch arguments. Packages declaring any
environment variable (including API keys/tokens) are excluded and never
written to the cache.
1. **Check the registry.** Call `registry_sync` with a `query` describing the
specialized capability the task needs, for example
`registry_sync {query: "convert PDF to markdown"}`. It returns at most
eight scored matches from the eligible local stdio package catalog, each
with the server's name, description, and required launch arguments.
Packages declaring any environment variable (including API keys/tokens)
are excluded and never written to the cache.
If `registry_sync` is not in your tool list,
run `tool_search` first to activate it; if `tool_search` cannot
surface it either, MCP Registry access is unavailable in this
session — say so and solve the task with local tools instead of
following the rest of this workflow.
2. **Match from context with a Registry-first bias.** Compare the user's full
task against every server name and description. A candidate is a match when
task against every returned candidate's name and description. A candidate
is a match when
it plausibly covers the task's core specialized capability; wording does not
need to be exact. When such a candidate exists, you **must start it and inspect
its tools before** using `exec_shell`, local programs, custom code, or a manual
its tools before** using `bash`, local programs, custom code, or a manual
implementation. The availability or familiarity of a local alternative is
not a reason to skip the candidate. Skip Registry use only when every entry is
clearly irrelevant, or when a matching server fails to start after the retry
described below.
3. **Install + run transactionally.** Call
3. **Install + run transactionally.** If `start_registry_mcp_server` is not in
your tool list after `registry_sync` succeeded, activate it via
`tool_search` as in step 1; if that fails, registry starts are
unavailable in this session — fall back to local tools. Otherwise call
`start_registry_mcp_server {registry_name: "<exact name>", arguments: {...}}`.
Supply only values listed in `required_args`; omit `arguments` when none
are required. Never install or launch the package through `exec_shell`.
are required. Never install or launch the package through `bash`.
4. **Solve the task with the new tools.** Their complete schemas are added
to the current turn immediately after a successful connection; call the
exact names returned by the start result.
exact names returned by the start result. On a later turn a connected
tool may drop out of your tool list again; run `tool_search` first
before calling it.

## If a server fails to start

Expand Down
2 changes: 1 addition & 1 deletion crates/tui/assets/skills/pdf/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use this skill for any task where a PDF is the primary input or output.
watermark, redact, fill forms, encrypt/decrypt, or create.
2. Preserve originals. Write outputs with explicit names.
3. Use the most reliable available tool:
- the built-in `File` tool (`action: "read"`) for basic text extraction from PDFs
- the built-in `read` tool for basic text extraction from PDFs
- `pdftotext`, `pdfinfo`, `qpdf`, or `mutool` when installed
- Python libraries such as `pypdf`, `pdfplumber`, `PyMuPDF`, or
`reportlab` when available
Expand Down
27 changes: 26 additions & 1 deletion crates/tui/src/commands/groups/core/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ pub fn agent(_app: &mut App, arg: Option<&str>) -> CommandResult {
}
};
let message = format!(
"Launch one sub-agent for this task by calling `agent` with name `slash_agent`, `prompt: {task:?}`, and `max_depth: {max_depth}`. Use `handle_read` on the returned transcript_handle if you need more detail. Verify any claimed side effects before reporting success."
"Launch one sub-agent for this task by calling `agent` with name `slash_agent`, `prompt: {task:?}`, and `max_depth: {max_depth}`. Use `handle_read` on the returned transcript_handle if you need more detail; {handle_read_hint}; if `tool_search` cannot surface it, call `handle_read` directly anyway, since registered deferred tools hydrate when called by name. Verify any claimed side effects before reporting success.",
handle_read_hint = crate::tools::subagent::HANDLE_READ_ACTIVATION_HINT
);
CommandResult::with_message_and_action(
format!("Opening persistent sub-agent at depth {max_depth}..."),
Expand Down Expand Up @@ -124,4 +125,28 @@ mod tests {
};
assert_eq!(agent_id, "agent_123");
}

#[test]
fn forkguard_slash_agent_dispatch_teaches_handle_read_activation() {
// `handle_read` is deferred on stock hosts, so the dispatch brief
// must teach the `tool_search` activation path instead of pointing
// the model at a tool absent from its first-turn catalog (Pinvou
// #490 phantom-tool class).
let mut app = test_app();
let result = agent(&mut app, Some("inspect the failing test"));
let Some(AppAction::SendMessage(message)) = result.action else {
panic!("expected SendMessage action");
};
assert!(message.contains("`handle_read`"));
assert!(
message.contains("activate it via `tool_search` first"),
"the dispatch brief must teach the handle_read activation path:\n{message}"
);
assert!(
message.contains("call `handle_read` directly anyway"),
"allowed_tools-filtered sessions can strip tool_search too; the \
dispatch brief must keep the direct-call fallback instead of \
dead-ending:\n{message}"
);
}
}
14 changes: 13 additions & 1 deletion crates/tui/src/commands/groups/project/goal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ fn goal_command(
CURRENT work. Synthesize the objective from the conversation context (the \
task in flight, recent findings, open items) and set it by calling \
`create_goal` with the full objective (and a token_budget only if one was \
discussed). Then continue working toward it. Only if the conversation \
discussed); if `create_goal` is not in your tool list, activate it via \
`tool_search` first; if `tool_search` cannot surface it, call `create_goal` directly anyway, since registered deferred tools hydrate when called by name. Then continue working toward it. Only if the conversation \
genuinely contains no work yet, ask the user what the goal should be."
.to_string();
CommandResult::with_message_and_action(
Expand Down Expand Up @@ -458,6 +459,17 @@ mod tests {
};
assert!(message.contains("Synthesize the objective from the conversation"));
assert!(message.contains("`create_goal`"));
assert!(
message.contains("activate it via `tool_search` first"),
"create_goal is deferred on stock hosts; the bare /goal brief must \
teach the activation path instead of commanding an absent tool:\n{message}"
);
assert!(
message.contains("call `create_goal` directly anyway"),
"allowed_tools-filtered sessions can strip tool_search too; the \
bare /goal brief must keep the direct-call fallback instead of \
dead-ending:\n{message}"
);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/core/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ fn agent_list_event(manager: &SubAgentManager, active_session_id: &str) -> Event
}

const MCP_REGISTRY_FIRST_INSTRUCTION_SOURCE: &str = "runtime:mcp-registry-first";
const MCP_REGISTRY_FIRST_INSTRUCTION: &str = "## MCP Registry-first policy\n\nFor any task centered on a specialized capability, including media or document conversion, data transformation, browser automation, database or service access, or a developer utility, you must call `registry_sync` with a `query` describing that capability before `bash`, the `Web` tool, code execution, local programs, custom code, or a manual implementation. It scores the local Registry snapshot host-side and returns at most eight matches; the full catalog never enters the conversation. Treat a returned server as a match when it plausibly covers the core capability; wording need not be exact. If any plausible match exists, you must call `start_registry_mcp_server` with its exact name and inspect its tools before considering a local alternative. If nothing matches, refine the query once; a still-empty refined result means every Registry entry is clearly irrelevant. An installed or familiar shell command is not a reason to skip Registry discovery. Use local tools directly only for ordinary repo-native work and simple file operations, or after the matching server fails to start.";
const MCP_REGISTRY_FIRST_INSTRUCTION: &str = "## MCP Registry-first policy\n\nFor any task centered on a specialized capability, including media or document conversion, data transformation, browser automation, database or service access, or a developer utility, you must call `registry_sync` with a `query` describing that capability before `bash`, the `Web` tool, code execution, local programs, custom code, or a manual implementation. If `registry_sync` is not in your tool list, run `tool_search` first to activate it; if it cannot be surfaced or called at all, Registry access is unavailable in this session — use local tools instead. It scores the local Registry snapshot host-side and returns at most eight matches; the full catalog never enters the conversation. Treat a returned server as a match when it plausibly covers the core capability; wording need not be exact. If any plausible match exists, you must call `start_registry_mcp_server` with its exact name and inspect its tools before considering a local alternative; activate it via `tool_search` as well, since activating `registry_sync` does not activate it. If nothing matches, refine the query once; a still-empty refined result means every Registry entry is clearly irrelevant. An installed or familiar shell command is not a reason to skip Registry discovery. Use local tools directly only for ordinary repo-native work and simple file operations, or after the matching server fails to start.";
const ISOLATED_CHAT_ENGINE_PROMPT: &str = "You are Codewhale Chat. Answer the user's request directly and conversationally. This isolated chat-only session has no local workspace, project, memory, skill, account, credential, path, runtime context, or tools.";

fn sanitize_isolated_chat_attachments(mut text: String) -> String {
Expand Down
7 changes: 5 additions & 2 deletions crates/tui/src/core/engine/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,12 @@ fn compact_subagent_tool_result_for_context(tool_name: &str, raw: &str) -> Optio

let mut out = String::from("[sub-agent result summarized for parent context]\n");
out.push_str(
"Child results are self-reports; verify side effects with `File` actions like `read` or `list` before claiming success.\n",
"Child results are self-reports; verify side effects with `read` or `bash` before claiming success.\n",
);
out.push_str("Use `handle_read` on `transcript_handle` for bounded transcript slices when the returned summary is not enough.\n");
out.push_str(&format!(
"Use `handle_read` on `transcript_handle` for bounded transcript slices when the returned summary is not enough — {handle_read_hint}; if `tool_search` cannot surface it, call `handle_read` directly anyway, since registered deferred tools hydrate when called by name.\n",
handle_read_hint = crate::tools::subagent::HANDLE_READ_ACTIVATION_HINT
));
for (idx, snapshot) in snapshots.iter().enumerate() {
if idx >= 8 {
out.push_str(&format!(
Expand Down
Loading
Loading