@@ -14,6 +14,7 @@ consumer crates.
1414- ` server.rs ` defines server transport and connection defaults.
1515- ` logging.rs ` defines logging and rolling file-log settings.
1616- ` skills.rs ` defines skill discovery settings.
17+ - ` hooks.rs ` defines external hook event and command configuration.
1718- ` experimental.rs ` defines opt-in experimental feature gates.
1819- ` error.rs ` defines app and provider config error types.
1920- ` provider.rs ` re-exports provider config APIs and contains provider-focused
@@ -88,6 +89,7 @@ without clearing every omitted provider field from user config.
8889- ` updates.enabled = true `
8990- ` updates.check_on_startup = true `
9091- ` updates.check_interval_hours = 24 `
92+ - ` hooks = {} `
9193- ` project_root_markers = [".git"] `
9294- ` projects = {} `
9395
@@ -154,6 +156,14 @@ check_interval_hours = 24
154156
155157[projects ."/path/to/project" ]
156158permission_preset = " default" # read-only, default, auto-review, or full-access
159+
160+ [[hooks .PreToolUse ]]
161+ matcher = " exec_command"
162+
163+ [[hooks .PreToolUse .hooks ]]
164+ type = " command"
165+ command = " hooks/pre-tool-use.sh"
166+ timeout = 30
157167```
158168
159169` logging.file.directory ` is optional. Relative logging directories resolve under
@@ -176,6 +186,105 @@ permission_preset = "default" # read-only, default, auto-review, or full-access
176186Provider-specific validation happens while resolving or mutating provider
177187config.
178188
189+ ## Hooks
190+
191+ External hooks are configured under the top-level ` [hooks] ` table. Each hook
192+ event contains matcher entries, and each matcher entry contains one or more hook
193+ commands:
194+
195+ ``` toml
196+ [[hooks .PostToolUse ]]
197+ matcher = " exec_command|read_file"
198+
199+ [[hooks .PostToolUse .hooks ]]
200+ type = " command"
201+ command = " hooks/post-tool-use.sh"
202+ shell = " bash"
203+ timeout = 30
204+
205+ [[hooks .UserPromptSubmit ]]
206+
207+ [[hooks .UserPromptSubmit .hooks ]]
208+ type = " command"
209+ command = " hooks/check-prompt.sh"
210+ async = false
211+ ```
212+
213+ Command hooks receive one JSON object on stdin. The common fields are
214+ ` hook_event_name ` , ` session_id ` , ` transcript_path ` , and ` cwd ` . Runtime contexts
215+ may also include ` permission_mode ` , ` agent_id ` , and ` agent_type ` , followed by
216+ event-specific fields such as ` tool_name ` , ` tool_input ` , ` tool_use_id ` ,
217+ ` tool_response ` , ` prompt ` , ` source ` , ` trigger ` , ` reason ` , ` file_path ` , ` event ` ,
218+ ` old_cwd ` , and ` new_cwd ` .
219+
220+ Hook command results follow Claude Code-style blocking semantics:
221+
222+ - Exit status ` 0 ` succeeds unless stdout contains a blocking JSON decision.
223+ - Exit status ` 2 ` blocks the triggering action. The block reason is read from
224+ stdout JSON or stderr.
225+ - Stdout JSON shaped as ` {"decision":"block","reason":"..."} ` blocks even when
226+ the process exits successfully.
227+ - Claude-style ` hookSpecificOutput ` denial JSON blocks for ` PreToolUse ` and
228+ ` PermissionRequest ` .
229+ - Stdout JSON shaped as ` {"continue":false,"stopReason":"..."} ` is treated as a
230+ blocking stop for lifecycle events that consume blocking decisions.
231+ - Other non-zero exits are logged as non-blocking hook failures.
232+
233+ The ` command ` hook type is executed by the runtime. ` prompt ` , ` agent ` , and
234+ ` http ` hook definitions are parsed so config files remain forward compatible,
235+ but they are currently logged as unsupported and not executed. ` shell ` accepts
236+ ` bash ` and ` powershell ` . ` timeout ` is in seconds and defaults to ` 600 ` .
237+ ` async = true ` and ` asyncRewake = true ` spawn the command in the background and
238+ do not wait for a blocking decision. ` if ` , ` status_message ` , and ` once ` are
239+ preserved in config but are not interpreted by the current runtime.
240+
241+ All 27 hook event names are accepted by config:
242+
243+ - ` PreToolUse ` , ` PostToolUse ` , ` PostToolUseFailure `
244+ - ` Notification ` , ` UserPromptSubmit `
245+ - ` SessionStart ` , ` SessionEnd ` , ` Stop ` , ` StopFailure `
246+ - ` SubagentStart ` , ` SubagentStop `
247+ - ` PreCompact ` , ` PostCompact `
248+ - ` PermissionRequest ` , ` PermissionDenied `
249+ - ` Setup ` , ` TeammateIdle ` , ` TaskCreated ` , ` TaskCompleted `
250+ - ` Elicitation ` , ` ElicitationResult `
251+ - ` ConfigChange ` , ` WorktreeCreate ` , ` WorktreeRemove `
252+ - ` InstructionsLoaded ` , ` CwdChanged ` , ` FileChanged `
253+
254+ The current runtime triggers hooks where Devo has a matching lifecycle point:
255+ tool execution, prompt submission, server setup, session start and resume,
256+ session shutdown, turn stop and failure, subagent start and stop, manual
257+ compaction, permission request and denial, config writes through ` provider/upsert `
258+ and ` skills/set_enabled ` , per-turn cwd changes, and file changes reported by
259+ ` write ` /` apply_patch ` tool metadata.
260+
261+ Runtime-triggered events:
262+
263+ - ` PreToolUse ` , ` PostToolUse ` , ` PostToolUseFailure `
264+ - ` UserPromptSubmit `
265+ - ` SessionStart ` , ` SessionEnd `
266+ - ` Stop ` , ` StopFailure `
267+ - ` SubagentStart ` , ` SubagentStop `
268+ - ` PreCompact ` , ` PostCompact `
269+ - ` PermissionRequest ` , ` PermissionDenied `
270+ - ` Setup `
271+ - ` ConfigChange `
272+ - ` CwdChanged ` , ` FileChanged `
273+
274+ Config-ready but not currently triggered:
275+
276+ - ` Notification ` : Devo has protocol notifications, but no single user-facing
277+ notification lifecycle equivalent to Claude's external notification hook.
278+ - ` TeammateIdle ` , ` TaskCreated ` , ` TaskCompleted ` : the standalone ` devo-tasks `
279+ crate is not wired into the server runtime task lifecycle.
280+ - ` Elicitation ` , ` ElicitationResult ` : MCP elicitation is currently handled
281+ inside the MCP manager with an automatic response and no server-session hook
282+ bridge.
283+ - ` WorktreeCreate ` , ` WorktreeRemove ` : Devo currently has no worktree lifecycle
284+ API.
285+ - ` InstructionsLoaded ` : Devo discovers AGENTS-style instructions during context
286+ assembly, but does not expose a hookable per-file instruction-load event.
287+
179288## Provider Config
180289
181290Provider config is part of ` config.toml ` and is modeled by ` ProviderConfigSection ` .
@@ -275,6 +384,26 @@ the provider default endpoint and result count. Compatibility aliases
275384` websearch ` and ` web-search ` route to ` web_search ` , but aliases are not exposed
276385to the model.
277386
387+ ## Web Fetch
388+
389+ ` [tools.web_fetch] ` controls whether a turn exposes URL fetching to the model.
390+ It resolves with the same priority as web search:
391+
392+ 1 . ` [model_bindings.<id>.web_fetch] `
393+ 2 . ` [providers.<id>.web_fetch] `
394+ 3 . ` [tools.web_fetch] `
395+
396+ Supported modes:
397+
398+ - ` disabled ` : do not provide provider-hosted web fetch and do not expose the
399+ local ` webfetch ` function tool.
400+ - ` provider ` : let the active provider adapter inject provider-hosted fetch into
401+ the request. OpenAI Responses uses hosted tool ` {"type":"web_fetch"} ` ;
402+ OpenAI Chat Completions uses ` web_fetch_options ` ; Anthropic Messages uses
403+ server tool ` {"type":"web_fetch_20250910","name":"web_fetch"} ` .
404+ - ` local ` : expose the existing local ` webfetch ` function tool. This is the
405+ default to preserve the existing local fetch behavior.
406+
278407## Provider Resolution
279408
280409` resolve_provider_settings_from_config_and_auth ` chooses the active model
0 commit comments