Skip to content

feat(hooks): add agent:turn:end internal hook event#60734

Closed
mantisai-bot wants to merge 2 commits into
openclaw:mainfrom
mantisai-bot:feat/agent-turn-end-hook
Closed

feat(hooks): add agent:turn:end internal hook event#60734
mantisai-bot wants to merge 2 commits into
openclaw:mainfrom
mantisai-bot:feat/agent-turn-end-hook

Conversation

@mantisai-bot

Copy link
Copy Markdown

Adds an agent:turn:end internal hook event that fires after each completed agent turn (both successful and failed).

Changes:

  • src/hooks/internal-hooks.ts: Add AgentTurnEndHookContext, AgentTurnEndHookEvent types, hasNumberContextField helper, and isAgentTurnEndEvent type guard
  • src/acp/control-plane/manager.core.ts: Extend recordTurnCompletion with sessionKey param and fire hook via fireAndForgetHook — existing stats logic is preserved identically

The hook fires on both success and error paths. It is fire-and-forget so it cannot block the turn lifecycle.

Supersedes #39596 (closed due to messy rebase conflicts).

@greptile-apps

greptile-apps Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an agent:turn:end internal hook event that fires after each completed agent turn (success and error paths) via fireAndForgetHook, so it cannot block the turn lifecycle. The implementation follows established patterns in internal-hooks.ts and correctly threads sessionKey through both recordTurnCompletion call sites.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style suggestions with no correctness impact.

The change is additive, fire-and-forget so it cannot regress the turn lifecycle, and both call sites are correctly updated. Only P2 suggestions remain.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/hooks/internal-hooks.ts
Line: 474-479

Comment:
**`hasNumberContextField` defined away from sibling helpers**

`hasStringContextField` and `hasBooleanContextField` are grouped together around line 350. Defining `hasNumberContextField` ~124 lines later, right before its single use site, breaks the pattern and makes it harder to discover when adding future event types. Move this block up to sit alongside the other two helpers.

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

---

This is a comment left during a code review.
Path: src/hooks/internal-hooks.ts
Line: 463-466

Comment:
**`errorCode` not surfaced in hook context**

`AgentTurnEndHookContext` only carries `success: boolean`, so hook consumers can detect that a turn failed but cannot distinguish error codes (e.g. `ACP_TURN_FAILED` vs `ACP_SESSION_INIT_FAILED`). If any subscriber needs to branch on failure kind or log structured error info, consider adding an optional `errorCode` field while the shape is still easy to extend.

```suggestion
export type AgentTurnEndHookContext = {
  success: boolean;
  durationMs: number;
  errorCode?: string;
};
```

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

Reviews (1): Last reviewed commit: "feat(hooks): add agent:turn:end internal..." | Re-trigger Greptile

Comment thread src/hooks/internal-hooks.ts Outdated
Comment on lines +474 to +479
function hasNumberContextField<T extends Record<string, unknown>>(
context: Partial<T>,
key: keyof T,
): boolean {
return typeof context[key] === "number";
}

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.

P2 hasNumberContextField defined away from sibling helpers

hasStringContextField and hasBooleanContextField are grouped together around line 350. Defining hasNumberContextField ~124 lines later, right before its single use site, breaks the pattern and makes it harder to discover when adding future event types. Move this block up to sit alongside the other two helpers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/hooks/internal-hooks.ts
Line: 474-479

Comment:
**`hasNumberContextField` defined away from sibling helpers**

`hasStringContextField` and `hasBooleanContextField` are grouped together around line 350. Defining `hasNumberContextField` ~124 lines later, right before its single use site, breaks the pattern and makes it harder to discover when adding future event types. Move this block up to sit alongside the other two helpers.

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +463 to +466
export type AgentTurnEndHookContext = {
success: boolean;
durationMs: number;
};

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.

P2 errorCode not surfaced in hook context

AgentTurnEndHookContext only carries success: boolean, so hook consumers can detect that a turn failed but cannot distinguish error codes (e.g. ACP_TURN_FAILED vs ACP_SESSION_INIT_FAILED). If any subscriber needs to branch on failure kind or log structured error info, consider adding an optional errorCode field while the shape is still easy to extend.

Suggested change
export type AgentTurnEndHookContext = {
success: boolean;
durationMs: number;
};
export type AgentTurnEndHookContext = {
success: boolean;
durationMs: number;
errorCode?: string;
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/hooks/internal-hooks.ts
Line: 463-466

Comment:
**`errorCode` not surfaced in hook context**

`AgentTurnEndHookContext` only carries `success: boolean`, so hook consumers can detect that a turn failed but cannot distinguish error codes (e.g. `ACP_TURN_FAILED` vs `ACP_SESSION_INIT_FAILED`). If any subscriber needs to branch on failure kind or log structured error info, consider adding an optional `errorCode` field while the shape is still easy to extend.

```suggestion
export type AgentTurnEndHookContext = {
  success: boolean;
  durationMs: number;
  errorCode?: string;
};
```

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fafab7f47

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1582 to +1584
fireAndForgetHook(
triggerInternalHook(
createInternalHookEvent("agent", "turn:end", params.sessionKey, {

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.

P2 Badge Defer hook dispatch off turn thread

recordTurnCompletion is intended to be fire-and-forget, but triggerInternalHook(...) is invoked immediately while building the argument to fireAndForgetHook. Because triggerInternalHook awaits handler(event), any hook implemented as a synchronous function will run inline before the promise is detached, adding user-visible latency to ACP turn completion (especially for CPU/file-heavy handlers). This makes agent:turn:end capable of blocking the turn lifecycle despite the fire-and-forget wrapper.

Useful? React with 👍 / 👎.

@mantisai-bot mantisai-bot Apr 4, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the review. To clarify — the pattern here matches how message-related hooks are fired in deliver.ts and dispatch-from-config.ts (both use fireAndForgetHook(triggerInternalHook(...))), not all hooks in the codebase. bootstrap-hooks.ts and compaction-hooks.ts correctly use await triggerInternalHook where blocking is intentional.

The key point still holds: triggerInternalHook is async and returns a Promise immediately — the for...await loop inside only runs in subsequent microtasks. fireAndForgetHook detaches that promise, so no handler code can block the caller. No change needed.

- Move hasNumberContextField alongside sibling helpers (hasStringContextField, hasBooleanContextField)
- Add optional errorCode field to AgentTurnEndHookContext so subscribers can distinguish failure kinds
@mantisai-bot

Copy link
Copy Markdown
Author

Superseded by #71399, which rebases this work onto current main on a fresh issue-based branch and carries forward the same narrow scope for #37833. Closing this stale branch to keep review on the fresh replacement PR.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant