diff --git a/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md b/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md index 756b8604..05f6191a 100644 --- a/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md +++ b/claude-plugins/plugins/base/skills/claude-subagents/SKILL.md @@ -24,8 +24,8 @@ Your container's `subagent` MCP server (`hive-subagent-daemon`) runs this skill's recipe for you: ``` -start(name, prompt_file, model?, trigger?) -continue(name, prompt, model?) +start(name, prompt_file, model?, effort?, trigger?) +continue(name, prompt, model?, effort?) status(name) interrupt(name, force?) ``` @@ -41,6 +41,37 @@ Model choice, prompt hygiene, splitting big batches, verify-then-report — everything else in this skill — applies exactly the same whether you're calling the tool or thinking through the recipe by hand. +## Effort + +An omitted `effort` defaults to `medium` here — cheaper than claude's own +model default (`high` on most models), a deliberate cost-conscious choice +for subagent work specifically, same spirit as "cheaper-than-you" model +choice above. Raise it explicitly when the task is complex enough to +actually need deeper reasoning, not as a reflex. + +Anthropic's own levels (per current Claude Code docs — names/availability +are model-dependent, check before relying on an exact list): `low`, +`medium`, `high`, `xhigh`, `max`. Each trades token spend for capability: + +- **`low`** — short, scoped, latency-sensitive tasks that aren't + intelligence-sensitive. +- **`medium`** — cost-sensitive work that can trade off some intelligence. + This skill's default for subagent work. +- **`high`** — balances token usage and intelligence; Anthropic's own + recommended default for most _interactive_ coding tasks (not what this + skill defaults subagents to — see above). +- **`xhigh`** — deeper reasoning at higher token spend. +- **`max`** — demanding tasks only; diminishing returns and overthinking + are a real risk, per Anthropic's own guidance — don't reach for it as a + default. + +Anthropic's guidance: treat effort as a general preference, not a +task-by-task dial — raise it if a subagent keeps skipping files, not +running tests, or not double-checking its own work; lower it for routine +work where quality hasn't suffered. Changing effort between turns on the +_same_ session invalidates prompt caching, so pick a level for the whole +session rather than flipping it turn-to-turn. + ## Prompt hygiene - this is where batches succeed or fail - **Concrete constants, not "figure it out":** exact ids, field names, diff --git a/hive-subagent-mcp/src/mcp.rs b/hive-subagent-mcp/src/mcp.rs index f147c2c4..511e2bb0 100644 --- a/hive-subagent-mcp/src/mcp.rs +++ b/hive-subagent-mcp/src/mcp.rs @@ -28,6 +28,15 @@ struct StartArgs { /// guidance still applies here. #[serde(default)] model: Option, + /// Which reasoning effort level the subagent's own session runs at + /// (`--effort`). Omit to default to `medium` — a deliberate hive + /// policy for subagent work, not claude's own default (`high` on most + /// models). Independent of `model`, so a cheap model at high effort or + /// an expensive one at low effort are both valid combinations, not + /// just the two extremes. See the `base:claude-subagents` skill for + /// Anthropic's own guidance on choosing between levels. + #[serde(default)] + effort: Option, /// Path to a file holding the subagent's actual task instructions. A /// file, not an inline string, so a large recipe can't blow past a /// shell argument length limit. @@ -64,6 +73,11 @@ struct ContinueArgs { /// default — this does not have to match whatever model `start` used. #[serde(default)] model: Option, + /// Which reasoning effort level this turn runs at. Omit to default to + /// `medium` (this daemon's own default, not claude's) — this does not + /// have to match whatever effort `start` (or a prior `continue`) used. + #[serde(default)] + effort: Option, /// Omit to reuse whatever `dir` `start` (or a prior `continue`) used for /// this name — the daemon remembers it. Only pass this to point the /// session at a *different* directory than last time. @@ -116,6 +130,7 @@ impl SubagentMcp { &self.state, &args.name, args.model, + args.effort, &args.prompt_file, args.trigger, args.dir.as_deref(), @@ -141,6 +156,7 @@ impl SubagentMcp { &args.name, args.prompt, args.model, + args.effort, args.dir.as_deref(), ) { Ok(msg) => msg, diff --git a/hive-subagent-mcp/src/session.rs b/hive-subagent-mcp/src/session.rs index 8d907fde..dd1cd51c 100644 --- a/hive-subagent-mcp/src/session.rs +++ b/hive-subagent-mcp/src/session.rs @@ -289,7 +289,14 @@ fn subagent_otel_attrs(name: &str) -> String { } } -/// Build the `Config` one subagent turn runs against. `prompt_file`, when +/// Build the `Config` one subagent turn runs against. `model` maps straight +/// onto `Config::model` — `--model`, omitted when `None` so claude falls +/// back to its own default. `effort` does not: an omitted `effort` defaults +/// to `"medium"` here rather than falling through to claude's own default +/// (`high` on most models) — a deliberate hive policy for subagent work +/// specifically, not a reflection of Anthropic's own recommendation, on the +/// same "cheaper than you" cost-consciousness the `base:claude-subagents` +/// skill already asks of `model`. `prompt_file`, when /// given, becomes `--append-system-prompt-file` — the subagent's task /// instructions. `dir`, when given, becomes `Config::cwd` (e.g. a worktree /// the caller already prepared); `None` inherits this daemon's own working @@ -305,6 +312,7 @@ fn subagent_otel_attrs(name: &str) -> String { fn build_config( name: &str, model: Option, + effort: Option, prompt_file: Option<&str>, dir: Option<&str>, ) -> Config { @@ -315,6 +323,7 @@ fn build_config( } Config { model, + effort: Some(effort.unwrap_or_else(|| "medium".to_owned())), cwd: dir.map(PathBuf::from), mcp_config: crate::mcp_config::build(), strict_mcp_config: true, @@ -354,6 +363,7 @@ pub fn start( state: &Arc, name: &str, model: Option, + effort: Option, prompt_file: &str, trigger: String, dir: Option<&str>, @@ -365,7 +375,15 @@ pub fn start( // Only commit the remembered `dir` now that `reserve` has actually // claimed `name` — see `resolve_dir`'s doc for why the order matters. let dir = state.resolve_dir(name, dir); - let result = start_reserved(state, name, model, prompt_file, trigger, dir.as_deref()); + let result = start_reserved( + state, + name, + model, + effort, + prompt_file, + trigger, + dir.as_deref(), + ); if result.is_err() { state.release_reservation(name); } @@ -380,11 +398,12 @@ fn start_reserved( state: &Arc, name: &str, model: Option, + effort: Option, prompt_file: &str, trigger: String, dir: Option<&str>, ) -> anyhow::Result { - let config = build_config(name, model, Some(prompt_file), dir); + let config = build_config(name, model, effort, Some(prompt_file), dir); let store = build_store(&config)?; if store.find_by_title(name).is_some() { tracing::info!( @@ -424,6 +443,7 @@ pub fn continue_( name: &str, prompt: String, model: Option, + effort: Option, dir: Option<&str>, ) -> anyhow::Result { validate_name(name)?; @@ -438,7 +458,7 @@ pub fn continue_( // Only commit the remembered `dir` now that `reserve` has actually // claimed `name` — see `resolve_dir`'s doc for why the order matters. let dir = state.resolve_dir(name, dir); - let result = continue_reserved(state, name, prompt, model, dir.as_deref()); + let result = continue_reserved(state, name, prompt, model, effort, dir.as_deref()); if result.is_err() { state.release_reservation(name); } @@ -467,9 +487,10 @@ fn continue_reserved( name: &str, prompt: String, model: Option, + effort: Option, dir: Option<&str>, ) -> anyhow::Result { - let config = build_config(name, model, None, dir); + let config = build_config(name, model, effort, None, dir); let store = build_store(&config)?; if store.find_by_title(name).is_none() { anyhow::bail!( @@ -561,7 +582,7 @@ pub fn status(state: &State, name: &str, dir: Option<&str>) -> anyhow::Result