# `/deep-research` — tool policy and result bounds

`/deep-research` runs a multi-agent web-research workflow (`src/deep-research.ts`)
registered as a builtin command in `src/builtin-commands.ts`. This page records
the tool policy, the result bounds, and the deferred private-network decision
added in issue #122 (split out of #121).

## Tool policy — research agents cannot mutate the workspace

The run is launched with `deepResearchSafetyOptions(cwd)`, which sets:

- `tools`: `createReadOnlyTools(cwd)` (read / grep / find / ls) **plus**
  `createWebTools()` (`web_search`, `web_fetch`).
- `readOnly: true` — the run-level read-only fence.

`createReadOnlyTools` returns no `bash`, `edit`, or `write` tool, so a research
subagent has no workspace-mutation capability. The `readOnly: true` fence is
belt-and-suspenders: `applyToolPolicy` (in `src/agent-registry.ts`) strips every
write-capable tool **last**, unconditionally, so an `agentType` allowlist or a
`harness_config` intersection can never re-grant `bash`/`edit`/`write`. This
matches the repo invariant that read-only boundaries are enforced by tool
policy, not by prompt text alone.

> Before #122 the handler passed `createCodingTools(cwd)` (`read, bash, edit,
> write`) to every research agent.

## Result bounds

The generated workflow script enforces modest bounds (`DEEP_RESEARCH_BOUNDS` in
`src/deep-research.ts`). Inputs that violate a bound **fail with a clear `Error`
message** (surfaced to the user by the handler's existing `catch` as
`deep-research failed: <message>`); overflow on fan-in / final response is capped
with an explicit truncation note in the result.

| Bound | Value | On overflow |
|-------|-------|-------------|
| Question length | 1..2000 chars | throw |
| Angles (fan-out) | integer 1..8 | throw |
| `minSupport` | integer 1..angles | throw |
| Gathered sources (fan-in) | cap 32 | truncate + `sourcesTruncated` flag |
| Claims per source | 12; URL ≤ 400 chars; claim ≤ 800 chars | applied before cross-check |
| Verify payload | 60 000 chars | truncate + note (agent reads as text) |
| Final report | 8 000 chars | truncate + `[report truncated …]` note |

The bounds are generous enough that a normal `/deep-research <question>` still
returns a usable report; they only prevent unusually large model output from
overflowing / clipping the agent-result channel.

## Private-network policy for `web_fetch` (documented, deferred)

`web_fetch` executes in the host process, which has network access. Today the
model selects any absolute URL and it is fetched on the host network — including
loopback (`127.0.0.1`, `::1`), link-local, and RFC1918 private ranges (SSRF
exposure).

Issue #122 explicitly says to **decide and document** the intended policy
**before adding network hardening**. The intended future policy is to reject
non-public URLs (resolve the host and reject loopback / private / link-local),
implemented as a guard inside `createWebFetchTool` (`src/web-tools.ts`) rather
than in the workflow script. That hardening is **not** implemented in this
release; the decision is recorded in the `createWebFetchTool` doc comment and
here. It should land behind a small accepted design as a separate change.

## Files

- `src/deep-research.ts` — bounds, `createDeepResearchTools`,
  `deepResearchSafetyOptions`, generated script.
- `src/builtin-commands.ts` — handler uses `deepResearchSafetyOptions(cwd)`.
- `src/web-tools.ts` — documented deferred private-network policy.
- `tests/deep-research.test.ts`, `tests/builtin-workflows.test.ts` — targeted
  tests for the read-only tool pool and the bounds.
