Skip to content

feat(hooks): add agent and tool lifecycle boundaries#12583

Closed
vincentkoc wants to merge 14 commits intoopenclaw:mainfrom
vincentkoc:vincentkoc-code/hooks-agent-tool-lifecycle
Closed

feat(hooks): add agent and tool lifecycle boundaries#12583
vincentkoc wants to merge 14 commits intoopenclaw:mainfrom
vincentkoc:vincentkoc-code/hooks-agent-tool-lifecycle

Conversation

@vincentkoc
Copy link
Member

@vincentkoc vincentkoc commented Feb 9, 2026

Why

Agent execution lacked complete lifecycle boundaries around thinking, response generation, and tool execution, reducing traceability for observability and policy systems. This PR adds those boundaries and ensures tool lifecycle hooks carry toolCallId context when available. lobster-biscuit

Split Context

This PR was split from closed umbrella PR #9761: #9761.

Detailed Changes

  • Added internal agent lifecycle hook emissions:
    • agent:thinking:start / agent:thinking:end
    • agent:response:start / agent:response:end
    • agent:tool:start / agent:tool:end
  • Added runAfterToolCallHook wiring for tool execution lifecycle completion
  • Propagated toolCallId through before/after tool hook contexts where available
  • Extended client tool definition adapter to pass toolCallId and run after-tool hook flow
  • Added focused tests for agent lifecycle emissions and tool lifecycle behavior

Related Links, Issues and Resolution

Greptile Overview

Greptile Summary

This PR adds explicit lifecycle boundaries around embedded agent execution and tool calls.

  • Introduces internal hook emissions for agent model thinking and response generation (agent:thinking:*, agent:response:*) inside the embedded attempt runner, with a run-scoped fallback session key when sessionKey is missing.
  • Adds internal + plugin tool lifecycle boundaries by emitting agent:tool:start/agent:tool:end around tool execution, and wiring after_tool_call via a new runAfterToolCallHook.
  • Propagates and normalizes toolCallId through tool hook contexts. When upstream toolCallIds are missing/blank, wrappers generate a canonical hook-<uuid> ID and use it consistently.
  • Updates client tool adapter to always run after_tool_call in a finally block and include toolCallId in the pending tool result.
  • Adds focused tests validating the new lifecycle emissions and tool hook propagation.

Overall, the changes fit into the existing hooks architecture by expanding internal hook events for observability and enhancing plugin hook context for tool lifecycle correlation.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk.
  • Changes are localized to hook/tool lifecycle wiring, include consistent toolCallId fallback behavior, and add targeted tests. I did not find any new must-fix functional regressions in the changed paths beyond already-discussed review threads.
  • No files require special attention

@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Feb 9, 2026
@vincentkoc vincentkoc marked this pull request as ready for review February 9, 2026 10:41
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@vincentkoc vincentkoc marked this pull request as draft February 9, 2026 16:43
@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Feb 9, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 9, 2026

Additional Comments (1)

src/agents/pi-embedded-runner/run.ts
Client toolCallId dropped
runEmbeddedAttempt now returns clientToolCall.toolCallId, but this code ignores it and generates a new pending tool id via ``id: call_${Date.now()}```. For client-delegated tools, this breaks correlation between the returned pendingToolCalls[].id` and the tool lifecycle hooks (`before_tool_call`/`after_tool_call` + internal `agent:tool:*`) that use the real `toolCallId`. Consider using `attempt.clientToolCall.toolCallId` for `pendingToolCalls[].id` so downstream consumers can correlate consistently.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run.ts
Line: 846:852

Comment:
**Client toolCallId dropped**
`runEmbeddedAttempt` now returns `clientToolCall.toolCallId`, but this code ignores it and generates a new pending tool id via ``id: `call_${Date.now()}```. For client-delegated tools, this breaks correlation between the returned `pendingToolCalls[].id` and the tool lifecycle hooks (`before_tool_call`/`after_tool_call` + internal `agent:tool:*`) that use the real `toolCallId`. Consider using `attempt.clientToolCall.toolCallId` for `pendingToolCalls[].id` so downstream consumers can correlate consistently.

How can I resolve this? If you propose a fix, please make it concise.

@vincentkoc
Copy link
Member Author

runEmbeddedAttempt now returns clientToolCall.toolCallId, but this code ignores it and generates a new pending tool id via id: call_${Date.now()}```. For client-delegated tools, this breaks correlation between the returned ``pendingToolCalls[].id and the tool lifecycle hooks (before_tool_call`/`after_tool_call` + internal `agent:tool:*`) that use the real `toolCallId`. Consider using `attempt.clientToolCall.toolCallId` for `pendingToolCalls[].id` so downstream consumers can correlate consistently.

@greptileai used call_${Date.now()} instead of the actual client tool call id as you suggested.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 9, 2026

Perfect! The fix looks correct. Using attempt.clientToolCall.toolCallId on line 849 ensures the same ID flows through the entire lifecycle:

Before (broken correlation):

Tool lifecycle hooks: toolCallId = "hook-abc123"  
Returned pendingToolCalls: id = "call_1234567890"  ❌ mismatch

After (consistent correlation):

Tool lifecycle hooks: toolCallId = "hook-abc123"  
Returned pendingToolCalls: id = "hook-abc123"  ✅ same ID

This maintains the correlation chain established in src/agents/pi-tool-definition-adapter.ts:151 where the ID is first generated/captured, through the hook system, and now to the consumer-facing pendingToolCalls metadata.

The change is minimal, safe, and directly addresses the observability gap you identified.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@vincentkoc
Copy link
Member Author

@greptileai Tool hook ID fallback generation updated as per your ask to use attempt.clientToolCall.toolCallId?.trim() || \call_${Date.now()} which resolves the final issue. Please update PR score if good to merge.

@vincentkoc vincentkoc force-pushed the vincentkoc-code/hooks-agent-tool-lifecycle branch from 8da2dd3 to f91ed2a Compare February 14, 2026 16:21
@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

27 similar comments
@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@vincentkoc
Copy link
Member Author

Moved to #18889

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

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser cli CLI command changes commands Command implementations docker Docker and sandbox tooling docs Improvements or additions to documentation extensions: device-pair extensions: diagnostics-otel Extension: diagnostics-otel extensions: llm-task Extension: llm-task extensions: lobster Extension: lobster extensions: memory-lancedb Extension: memory-lancedb extensions: phone-control gateway Gateway runtime scripts Repository scripts size: XL

Projects

None yet

1 participant