MoonshotAI / MoonshotAI/kimi-cli

feat(hook): include LLM response and stop reason in Stop hook payload

Open
#2,307 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
11.4k
Forks
1.3k
Avg merge
9h 47m
Merged PRs (30d)
2

Description

What feature would you like to see?

Summary

Currently, the Stop hook fires at the end of every agent turn but its payload is minimal:

{
  "hook_event_name": "Stop",
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "stop_hook_active": false
}

There is no way for a hook to inspect what the agent actually said or why the turn ended. This limits use cases such as:

  • Post-turn logging / auditing — recording the agent's final response to an external system
  • Conditional approval — blocking the session if the agent's response contains certain patterns (e.g., "I am not sure", "I cannot help with that")
  • Metrics / analytics — tracking turn outcomes (natural language vs. tool calls) without parsing wire logs

SubagentStop already provides a response field (the subagent's final summary). It would be consistent and useful for the root-agent Stop hook to expose the same class of information.

Proposed behavior

Enhance the Stop hook payload with two new fields:

Field Type Description
response string The agent's natural-language response text from the final assistant message. Empty if the turn ended due to tool rejection or produced no text.
stop_reason string Why the turn stopped. "no_tool_calls" when the agent spoke naturally, "tool_rejected" when a tool call was rejected.

Example payload after the change:

{
  "hook_event_name": "Stop",
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "stop_hook_active": false,
  "stop_reason": "no_tool_calls",
  "response": "The fix is complete. I updated src/main.py and added a regression test."
}
Additional information
  • Precedent: SubagentStop already includes "response" (truncated to 500 chars). This change brings parity to the root-agent lifecycle.
  • Backwards compatibility: The new fields are optional and default to empty strings. Existing hooks that do not read them will continue to work unchanged.
  • Implementation sketch: KimiSoul.run() already receives a TurnOutcome from _turn(). The final_message (a Message object) can have its text extracted via extract_text(" ").strip(), identical to how UserPromptSubmit extracts user text.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in KimiSoul.run(), following the TurnOutcome returned by _turn() and the existing Stop-hook payload construction; inspect final_message and the extract_text(" ").strip() precedent. Done means Stop payloads include response and stop_reason with the specified values and empty-string defaults, while existing fields remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.