diff --git a/R/subagent.R b/R/subagent.R index 78a0d98..1a5d301 100644 --- a/R/subagent.R +++ b/R/subagent.R @@ -96,9 +96,13 @@ resolve_subagent_id <- function(input) { #' #' Called once per child just after [worker_init()]. Creates a #' `new_session()` configured with the subagent's provider/model/tools -#' and stores it where [subagent_turn_prompt()] can find it. Subagents -#' deny all tool approvals by default so a subagent can't run bash -#' without the parent opting in. +#' and stores it where [subagent_turn_prompt()] can find it. The +#' child's `approval_cb` denies by default: subagents have no +#' interactive approval channel back to the parent or user, and tool +#' permissions are fixed at spawn time via `tools_filter` (derived +#' from the parent's `preset` or explicit `tools` argument to +#' [subagent_spawn()]). There is no way to grant additional capability +#' mid-run. #' #' @param provider LLM provider name (see [new_session()]). #' @param model Optional model override. @@ -325,14 +329,27 @@ subagent_session_key <- function(parent_key) { #' registry set up. Stores the handle in the package-level registry #' keyed by subagent id. #' +#' Permissions: subagents have no interactive approval channel back +#' to the parent or user. The child's `approval_cb` denies by default +#' and there is no mid-run escalation path. Whatever capability the +#' child needs must be granted at spawn time through `preset` or +#' `tools`. If a task may need shell, write, or network capability, +#' pick a preset that includes it (or pass an explicit `tools` list); +#' otherwise the child should report that it is blocked rather than +#' retry. +#' #' @param task Task description (stored for bookkeeping; not yet fed #' into an agent loop). #' @param model Optional model override (reserved for later use). #' @param tools Optional explicit tool filter (character vector). -#' Overrides `preset` when provided. -#' @param preset Preset name: `"investigate"` (read/search only, default), -#' `"work"` (investigate + bash + write/edit), or `"minimal"` -#' (read_file + grep_files only). +#' Overrides `preset` when provided. Fixed for the lifetime of the +#' child — cannot be expanded after spawn. +#' @param preset Preset name (fixed for the lifetime of the child). +#' `"investigate"` (default): `read_file`, `grep_files`, `r_help`, +#' `web_search`, `fetch_url`. `"work"`: investigate + `bash`, +#' `write_file`, `replace_in_file`, `list_files`, `git_status`, +#' `git_diff`, `git_log`, `run_r`. `"minimal"`: `read_file`, +#' `grep_files`. #' @param parent_session Parent session object; read for #' nested-spawning control and session-key derivation. #' @param config Config list. diff --git a/man/subagent_spawn.Rd b/man/subagent_spawn.Rd index 21a0bb9..e629890 100644 --- a/man/subagent_spawn.Rd +++ b/man/subagent_spawn.Rd @@ -13,11 +13,15 @@ into an agent loop).} \item{model}{Optional model override (reserved for later use).} \item{tools}{Optional explicit tool filter (character vector). -Overrides `preset` when provided.} +Overrides `preset` when provided. Fixed for the lifetime of the +child — cannot be expanded after spawn.} -\item{preset}{Preset name: `"investigate"` (read/search only, default), -`"work"` (investigate + bash + write/edit), or `"minimal"` -(read_file + grep_files only).} +\item{preset}{Preset name (fixed for the lifetime of the child). +`"investigate"` (default): `read_file`, `grep_files`, `r_help`, +`web_search`, `fetch_url`. `"work"`: investigate + `bash`, +`write_file`, `replace_in_file`, `list_files`, `git_status`, +`git_diff`, `git_log`, `run_r`. `"minimal"`: `read_file`, +`grep_files`.} \item{parent_session}{Parent session object; read for nested-spawning control and session-key derivation.} @@ -31,4 +35,12 @@ Subagent ID (character). Starts a fresh `callr::r_session` with corteza loaded and its tool registry set up. Stores the handle in the package-level registry keyed by subagent id. +Permissions: subagents have no interactive approval channel back +to the parent or user. The child's `approval_cb` denies by default +and there is no mid-run escalation path. Whatever capability the +child needs must be granted at spawn time through `preset` or +`tools`. If a task may need shell, write, or network capability, +pick a preset that includes it (or pass an explicit `tools` list); +otherwise the child should report that it is blocked rather than +retry. } diff --git a/man/subagent_turn_init.Rd b/man/subagent_turn_init.Rd index b3b0fc4..bb306ec 100644 --- a/man/subagent_turn_init.Rd +++ b/man/subagent_turn_init.Rd @@ -29,8 +29,12 @@ Invisible TRUE. \description{ Called once per child just after [worker_init()]. Creates a `new_session()` configured with the subagent's provider/model/tools -and stores it where [subagent_turn_prompt()] can find it. Subagents -deny all tool approvals by default so a subagent can't run bash -without the parent opting in. +and stores it where [subagent_turn_prompt()] can find it. The +child's `approval_cb` denies by default: subagents have no +interactive approval channel back to the parent or user, and tool +permissions are fixed at spawn time via `tools_filter` (derived +from the parent's `preset` or explicit `tools` argument to +[subagent_spawn()]). There is no way to grant additional capability +mid-run. } \keyword{internal} diff --git a/vignettes/configuration.Rmd b/vignettes/configuration.Rmd index 57c6095..6679227 100644 --- a/vignettes/configuration.Rmd +++ b/vignettes/configuration.Rmd @@ -125,9 +125,19 @@ All keys shown with type and default, current as of corteza 0.6.3. Most defaults | `subagents.max_concurrent` | integer | `3` | Max parallel subagents | | `subagents.timeout_minutes` | integer | `30` | Subagent kill timeout | | `subagents.allow_nested` | boolean | `false` | Allow nested subagents | -| `subagents.default_tools` | string[] | `["base::readLines", "base::writeLines", "bash", "grep_files"]` | Tools available to subagents | +| `subagents.default_tools` | string[] | `["read_file", "grep_files", "r_help", "web_search", "fetch_url"]` | Tools available to subagents when no preset is given | | `subagents.base_port` | integer | `7851` | Starting port for subagent MCP servers | +**Presets.** `/spawn --preset ` picks a fixed tool list at spawn time: + +- `investigate` (default): `read_file`, `grep_files`, `r_help`, `web_search`, `fetch_url` (read/search/help, plus network reads). +- `minimal`: `read_file`, `grep_files`. +- `work`: investigate + `bash`, `write_file`, `replace_in_file`, `list_files`, `git_status`, `git_diff`, `git_log`, `run_r`. + +`/spawn --tools ` overrides the preset with an explicit tool list. + +**Permission model.** Subagents have no interactive approval channel back to the parent or user. A child's tool list is fixed at spawn time — via the `--preset` or `--tools` flags on `/spawn`, or the `preset` / `tools` arguments to `subagent_spawn()`. There is no mid-run path to escalate to a riskier capability: the child's approval callback denies by default, and the parent cannot grant new tools after the fact. If a task may need shell, write, or network capability, choose a preset or explicit tool list that includes it at spawn time; otherwise the child should report that it is blocked. + ### Workspace | Key | Type | Default | Description | diff --git a/vignettes/retroactive-extraction.Rmd b/vignettes/retroactive-extraction.Rmd index e70da6f..812a962 100644 --- a/vignettes/retroactive-extraction.Rmd +++ b/vignettes/retroactive-extraction.Rmd @@ -105,6 +105,19 @@ The LLM uses normal tool selection to decide between `query_subagent(id, "what did you find at line 42?")` and `spawn_subagent("...")` for new work. +## Permission model + +Holder subagents (the ones the archival runtime creates) carry +`tools = character(0)`: they hold transcript context for +`query_subagent`, nothing more. Worker subagents spawned via +`spawn_subagent(task)` inherit only the tools the parent grants at +spawn time. There is no interactive approval channel back to the +parent or user, and no way to grant capability mid-run — the child's +`approval_cb` denies by default. If a worker may need shell, write, or +network capability, pick a preset or explicit tool list that includes +it when calling `spawn_subagent()`; otherwise the child should report +that it is blocked rather than retry. + ## Recursion A subagent finishing its own query re-evaluates the same triggers