Skip to content

Feature Request: agent:turn:end internal hook event #37833

Description

@mantisai-bot

Summary

Add an agent:turn:end internal hook event that fires once after a complete agent turn cycle (user message → agent processing → all tool calls → delivery) has finished.

Motivation

The gateway already tracks turn completion internally via AcpSessionManager.recordTurnCompletion(), including duration, success/failure, and error codes. This state is used for observability snapshots but is not exposed to the hook system, leaving a gap between the sub-turn cognitive events (agent:thinking:*, agent:response:*, agent:tool:*) and session-level events (session:end).

There is currently no way for a hook to reliably know when an entire turn is done. The closest alternatives don't fit:

  • message:sent fires per outbound message — multiple times if the agent sends several messages in one turn
  • agent_end / agent:response:end fires when LLM processing finishes, before delivery, and potentially multiple times per turn in multi-step tool-call loops
  • session:end fires when the entire session terminates, not per-turn

Use Cases

  • Post-turn memory extraction: Summarize or persist context after each turn without blocking the agent loop
  • Session pruning: Implement summary-and-forget strategies that trigger after turn completion, when it's safe to modify context
  • Turn-level analytics: Track turn duration, success rates, and tool usage patterns per turn
  • Workflow integration: Signal external systems (e.g., orchestrators) that a unit of work is complete

Proposed Event

Following the existing agent:*:start/end naming convention established in #7724:

{
  type: 'agent',
  action: 'turn:end',
  sessionKey: string,
  timestamp: Date,
  messages: string[],
  context: {
    success: boolean,
    durationMs: number,
    errorCode?: string,
  }
}

Implementation Notes

The natural hook point already exists in the codebase. AcpSessionManager.recordTurnCompletion() is called on both the success and error paths after the turn loop completes:

// Success path
this.recordTurnCompletion({ startedAt: turnStartedAt });
await this.setSessionState({ cfg, sessionKey, state: "idle" });

// Error path
this.recordTurnCompletion({ startedAt: turnStartedAt, errorCode: acpError.code });
await this.setSessionState({ cfg, sessionKey, state: "error" });

This method already computes durationMs and tracks completed vs failed counts. Emitting a triggerInternalHook call here would surface an already-tracked lifecycle moment without introducing new state management.

Relationship to Existing Work

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions