Skip to content

Conversation

@xingyaoww
Copy link
Collaborator

@xingyaoww xingyaoww commented Jan 16, 2026

Summary

This PR refactors the hooks example (#33_hooks) to use the untyped HookConfig.from_dict() approach instead of the typed Pydantic fields, while preserving the typed approach in comments for reference.

Changes

  • Changed the active example to use HookConfig.from_dict() with an untyped dictionary
  • Commented out the typed approach (using HookMatcher and HookDefinition) for reference
  • Added a comment noting that the typed approach is the recommended one for better type safety

Context

This is a follow-up to PR #1726, which added typed Pydantic fields to HookConfig. While the typed approach is recommended for better type safety and IDE support, this example demonstrates the untyped .from_dict() method which:

  1. Shows backward compatibility with existing JSON configuration files
  2. Demonstrates the PascalCase to snake_case conversion
  3. Shows the legacy {"hooks": {...}} wrapper format
  4. Provides an alternative for users who prefer dictionary-based configuration

Both approaches are valid and fully supported. The typed approach remains in the code as commented-out reference.

Related

@xingyaoww can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.12-nodejs22 Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:b07dae2-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-b07dae2-python \
  ghcr.io/openhands/agent-server:b07dae2-python

All tags pushed for this build

ghcr.io/openhands/agent-server:b07dae2-golang-amd64
ghcr.io/openhands/agent-server:b07dae2-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:b07dae2-golang-arm64
ghcr.io/openhands/agent-server:b07dae2-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:b07dae2-java-amd64
ghcr.io/openhands/agent-server:b07dae2-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:b07dae2-java-arm64
ghcr.io/openhands/agent-server:b07dae2-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:b07dae2-python-amd64
ghcr.io/openhands/agent-server:b07dae2-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-amd64
ghcr.io/openhands/agent-server:b07dae2-python-arm64
ghcr.io/openhands/agent-server:b07dae2-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-arm64
ghcr.io/openhands/agent-server:b07dae2-golang
ghcr.io/openhands/agent-server:b07dae2-java
ghcr.io/openhands/agent-server:b07dae2-python

About Multi-Architecture Support

  • Each variant tag (e.g., b07dae2-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., b07dae2-python-amd64) are also available if needed

…yped approach

- Changed hooks example to use HookConfig.from_dict() with untyped dictionary
- Commented out the typed approach (HookMatcher/HookDefinition) for reference
- Both approaches are valid, but .from_dict() demonstrates backward compatibility

Co-authored-by: openhands <openhands@all-hands.dev>
Copy link
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

Test review

Copy link
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

Test review

Copy link
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

This PR has some pedagogical and usability concerns. The main issue is that it demonstrates the non-recommended approach as the primary example, which contradicts best practices for educational code. See inline comments for details.


from openhands.sdk import LLM, Conversation
from openhands.sdk.hooks import HookConfig, HookDefinition, HookMatcher
from openhands.sdk.hooks import HookConfig
Copy link
Collaborator

Choose a reason for hiding this comment

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

🟠 Important: The imports for HookMatcher and HookDefinition have been removed, but they are still referenced in the commented code (lines 108-153). If a user tries to uncomment the "recommended" typed approach, they will get NameError exceptions.

Consider keeping these imports or adding a comment at line 104 instructing users to uncomment the imports if they want to use the typed approach:

Suggested change
from openhands.sdk.hooks import HookConfig
from openhands.sdk.hooks import HookConfig, HookDefinition, HookMatcher

Comment on lines +51 to +105
# Configure ALL hook types using .from_dict() with untyped dictionary
hook_config = HookConfig.from_dict(
{
"hooks": {
"PreToolUse": [
{
"matcher": "terminal",
"hooks": [
{
"command": str(SCRIPT_DIR / "block_dangerous.sh"),
"timeout": 10,
}
],
}
],
)
],
post_tool_use=[
HookMatcher(
matcher="*",
hooks=[
HookDefinition(
command=(f"LOG_FILE={log_file} {SCRIPT_DIR / 'log_tools.sh'}"),
timeout=5,
)
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"command": (
f"LOG_FILE={log_file} {SCRIPT_DIR / 'log_tools.sh'}"
),
"timeout": 5,
}
],
}
],
)
],
user_prompt_submit=[
HookMatcher(
hooks=[
HookDefinition(
command=str(SCRIPT_DIR / "inject_git_context.sh"),
)
"UserPromptSubmit": [
{
"hooks": [
{
"command": str(SCRIPT_DIR / "inject_git_context.sh"),
}
],
}
],
)
],
stop=[
HookMatcher(
hooks=[
HookDefinition(
command=(
f"SUMMARY_FILE={summary_file} "
f"{SCRIPT_DIR / 'require_summary.sh'}"
),
)
"Stop": [
{
"hooks": [
{
"command": (
f"SUMMARY_FILE={summary_file} "
f"{SCRIPT_DIR / 'require_summary.sh'}"
),
}
],
}
],
)
],
}
}
)

# Alternative: Typed approach (commented out)
# This is the recommended approach for better type safety and IDE support
Copy link
Collaborator

Choose a reason for hiding this comment

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

🟠 Important: This creates a confusing learning experience. The active example (lines 51-102) uses the untyped .from_dict() approach, while the comment at line 105 states the typed approach is "recommended for better type safety and IDE support."

Examples should demonstrate best practices, not legacy patterns. Users learning the SDK will naturally copy the active code, not the commented-out version.

Recommendation: Reverse this - make the typed approach the primary example and either:

  1. Remove the .from_dict() version entirely, or
  2. Show it as a brief commented alternative with a note like "For backward compatibility with JSON config files, you can also use HookConfig.from_dict({...})"

This aligns with the PR description which states "the typed approach is the recommended one for better type safety" - so it should be the one actually demonstrated.

Comment on lines +104 to +153
# Alternative: Typed approach (commented out)
# This is the recommended approach for better type safety and IDE support
# hook_config = HookConfig(
# pre_tool_use=[
# HookMatcher(
# matcher="terminal",
# hooks=[
# HookDefinition(
# command=str(SCRIPT_DIR / "block_dangerous.sh"),
# timeout=10,
# )
# ],
# )
# ],
# post_tool_use=[
# HookMatcher(
# matcher="*",
# hooks=[
# HookDefinition(
# command=(
# f"LOG_FILE={log_file} "
# f"{SCRIPT_DIR / 'log_tools.sh'}"
# ),
# timeout=5,
# )
# ],
# )
# ],
# user_prompt_submit=[
# HookMatcher(
# hooks=[
# HookDefinition(
# command=str(SCRIPT_DIR / "inject_git_context.sh"),
# )
# ],
# )
# ],
# stop=[
# HookMatcher(
# hooks=[
# HookDefinition(
# command=(
# f"SUMMARY_FILE={summary_file} "
# f"{SCRIPT_DIR / 'require_summary.sh'}"
# ),
# )
# ],
# )
# ],
# )
Copy link
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Having two complete versions of the same configuration (50 lines each) creates significant maintenance burden and makes the example unnecessarily long. If the hook API changes, both versions need updating.

Consider these alternatives:

  1. Preferred: Show only the typed approach as the primary example (since it's recommended)
  2. Add a brief comment or docstring explaining that .from_dict() exists for JSON config file compatibility
  3. If both patterns must be shown in full detail, consider separate example files (e.g., 33_hooks_typed.py and 33_hooks_from_dict.py)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@OpenHands please do show only the typed approach as the primary example (since it's recommended)
AND add a brief comment explaining that .from_dict() (maybe just use one dict with only ONE hook matcher as example.

Copy link

Choose a reason for hiding this comment

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

Uh oh! There was an unexpected error starting the job :(

}
)

# Alternative: Typed approach (commented out)
Copy link
Collaborator

Choose a reason for hiding this comment

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

🟢 Nit: This comment is contradictory. "Alternative" suggests it's a secondary option, but line 105 says it's "the recommended approach." Consider clearer labeling:

Suggested change
# Alternative: Typed approach (commented out)
# Typed approach (RECOMMENDED for type safety and IDE support)

Though ideally, the recommended approach should be the active code, not commented out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants