Skip to content

feat(hooks): sequential pipeline for pre_tool_use, before_llm_call, and tool_response_transform - #4207

Merged
dgageot merged 1 commit into
docker:mainfrom
dgageot:worktree-board-ffa9e0ba6b051632
Sep 9, 2026
Merged

dgageot merged 1 commit into
docker:mainfrom
dgageot:worktree-board-ffa9e0ba6b051632

Conversation

@dgageot

@dgageot dgageot commented Sep 9, 2026

Copy link
Copy Markdown
Member

Hooks for pre_tool_use (default lane), before_llm_call, and tool_response_transform previously ran concurrently and used a first-rewrite-wins strategy. This made it impossible for one hook to build on another's output: a redaction hook and a normalization hook on the same event would race, and whichever wrote first would see its work discarded or its changes overwritten silently. The only workaround was to bundle all logic into a single monolithic hook.

These three events now execute hooks sequentially in configuration order. Each hook receives the accumulated result of all prior accepted rewrites — the updated tool_input, messages array, or tool_response — and can further transform it. Verdicts continue to aggregate across the full sequence (deny > ask > allow), and a blocking verdict does not short-circuit remaining hooks. All other events, including preempt_yolo checks and before_compaction, remain concurrent.

The updated_input field for pre_tool_use changes from a full replacement to a shallow-merge patch: only the top-level keys present in the returned object are applied; omitted keys are preserved from the current input. This is a compatibility change — previously any omitted key was silently dropped. Key deletion is not supported by this patch protocol.

The automatic limit_large_tool_results builtin is now appended after all configured tool_response_transform hooks and automatic redact_secrets injection, so spill files written to disk already contain scrubbed content rather than raw secrets. This restores the correct ordering that was previously broken because the limiter was prepended before user-configured transforms.

The new pkg/hooks/pipeline.go implements the sequential executor. Tests added cover the argument-preservation invariant (a second hook sees the first hook's patch merged over the original), the redact-before-spill invariant (spill file on disk is clean), and the ordering of builtin injection in ApplyAgentDefaults. The docs and the redact_secrets_hooks.yaml example are updated to reflect the new composition semantics.

…ool_response_transform

- pre_tool_use (default lane), before_llm_call, and tool_response_transform
  hooks now execute sequentially in config order; each hook sees the
  accumulated result of prior accepted rewrites
- updated_input is a shallow-merge patch: omitted keys are preserved,
  not deleted — compatibility change from the previous replace-all semantics
- large-result limiter (limit_large_tool_results) is appended after all
  configured transforms and automatic secret redaction, so spill files
  contain scrubbed content rather than raw secrets
- other events (preempt_yolo, before_compaction, etc.) remain concurrent;
  guard/approval semantics unchanged
- add pkg/hooks/pipeline.go implementing the sequential execution logic
- add pipeline_test.go, builtins regression test and runtime integration
  test covering the argument-preservation and redact-before-spill invariants
- update docs and example to reflect new composition semantics
@dgageot
dgageot requested a review from a team as a code owner September 9, 2026 07:25

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

@aheritier aheritier added area/docs Documentation changes area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection area/core Core agent runtime, session management kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Sep 9, 2026
@dgageot
dgageot merged commit 46587a2 into docker:main Sep 9, 2026
21 of 22 checks passed
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
docker-agent 1.137.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre>This release removes the `session_plan` toolset, adds audio/video/image input and output capability handling, expands hook functionality with new builtins and sequential pipelines, and includes several bug fixes and safety improvements.

## Breaking Changes
- Removes the `session_plan` toolset, including its tool handlers, stream event, plans service, TUI `/plans` browser, and `--session`/`--scope` addressing on the `plans` command

## What's New
- Adds detection and filtering of audio/video input modalities per request model, stripping unsupported media parts and preserving provider-specific fallbacks
- Adds `output_capabilities.image` model override to resolve image output capability from models.dev metadata
- Adds guard for image-output requests across Google (gateway, direct Gemini API, and Vertex) surfaces, rejecting incompatible custom tools and structured output before dispatch
- Adds `add_context` builtin hook for dependency-free Go template-based context injection into the model's conversation
- Adds sequential pipeline execution for `pre_tool_use`, `before_llm_call`, and `tool_response_transform` hooks, replacing the previous concurrent first-rewrite-wins strategy
- Adds 56 new safe and 27 new destructive shell safety patterns, covering Git read operations, GitHub CLI queries, Go tooling, `rg`/`ripgrep`, and common inspection commands

## Bug Fixes
- Fixes Gemini keepalive SSE events (`event: keepalive` with `data: {}`) being passed to the SDK parser, causing parse failures; these frames are now dropped at the transport layer
- Fixes image-output-capable models being incorrectly excluded from session title generation
- Fixes shell metacharacter detection and corrects `gh`/`rg` pattern classifications
- Fixes SQLite stores not being closed deterministically on Windows, causing `TempDir` cleanup failures in tests
- Fixes `pkg/session` importing the SQLite driver, keeping the package free of that dependency

## Technical Changes
- Adds request-shape diagnostics for Gemini image requests (excluding prompt, schema, media-payload, and credential fields)
- Adds shared UTF-8-safe display-name sanitization helpers
- Classifies Gemini API 400 errors into bounded actionable categories
- Adds documentation tip for injecting session ID into model context using a `session_start` hook
---

## What's Changed
* docs: update CHANGELOG.md for v1.136.0 by @docker-read-write[bot] in docker/docker-agent#4202
* fix: drop Gemini keepalive SSE events before SDK parsing by @dgageot in docker/docker-agent#4203
* feat(plan)!: remove the session_plan toolset by @trungutt in docker/docker-agent#4199
* docs: add tip for injecting session ID with a session_start hook by @dgageot in docker/docker-agent#4205
* feat(hooks): add add_context builtin for dependency-free template context injection by @dgageot in docker/docker-agent#4206
* feat(hooks): sequential pipeline for pre_tool_use, before_llm_call, and tool_response_transform by @dgageot in docker/docker-agent#4207
* feat(#3996): resolve and filter input media per request model by @aheritier in docker/docker-agent#4016
* fix(#3996): diagnose Gemini requests and sanitize API failures by @aheritier in docker/docker-agent#4017
* feat(#3996): resolve image output capability from models.dev by @aheritier in docker/docker-agent#4019
* feat(#3996): guard image-output requests across Google surfaces by @aheritier in docker/docker-agent#4020
* fix(#3996): keep image-output models eligible for session titles by @aheritier in docker/docker-agent#4021
* fix(#3996): filter gateway SSE keepalives and test image requests by @aheritier in docker/docker-agent#4022
* feat(safety): expand shell safety patterns and harden substitution checks by @dgageot in docker/docker-agent#4209
* fix(tui): close SQLite stores deterministically so Windows can delete t.TempDir by @aheritier in docker/docker-agent#4210


**Full Changelog**: docker/docker-agent@v1.136.0...v1.137.0
</pre>
  <p>View the full release notes at <a href="https://github.com/docker/docker-agent/releases/tag/v1.137.0">https://github.com/docker/docker-agent/releases/tag/v1.137.0</a>.</p>
</details>
<hr>

See merge request: Harmonybrew/homebrew-core!18818
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Core agent runtime, session management area/docs Documentation changes area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants