Skip to content

feat(soul): add /goal slash command with stateful goal management#2276

Open
CommanderCrowCode wants to merge 1 commit into
MoonshotAI:mainfrom
CommanderCrowCode:feat/goal-slash-command-2218
Open

feat(soul): add /goal slash command with stateful goal management#2276
CommanderCrowCode wants to merge 1 commit into
MoonshotAI:mainfrom
CommanderCrowCode:feat/goal-slash-command-2218

Conversation

@CommanderCrowCode
Copy link
Copy Markdown

@CommanderCrowCode CommanderCrowCode commented May 14, 2026

Closes #2218

Implement feature-parity /goal command inspired by Codex's goal system.

Features

  • Goal state persistence in SessionState — objective, status (active/paused/complete/budget_limited), token budget, tokens used, elapsed time
  • Subcommands:
    • /goal <objective> — create or replace the active goal
    • /goal — show current goal status
    • /goal pause — pause the active goal
    • /goal resume — resume a paused goal
    • /goal clear — clear the active goal
    • /goal edit — edit the objective in an external editor
  • Enhanced continuation prompt — rich prompt with completion audit, fidelity instructions, budget tracking, inspired by Codex's continuation.md
  • UpdateGoal tool — model can call update_goal(status="complete") to mark a goal as finished
  • Token budget tracking — optional token_budget; goal transitions to budget_limited when exceeded
  • Auto-continuation on idle — after a user turn completes, if a goal is active and no user input is pending, a continuation turn is automatically triggered
  • Wire events — GoalBegin, GoalEnd, GoalStatusUpdate for UI visibility
  • Telemetry — goal_created, goal_completed, goal_budget_limited

Files changed

  • src/kimi_cli/session_state.py — add GoalState model
  • src/kimi_cli/soul/slash.py — rewrite /goal with subcommands and continuation prompt
  • src/kimi_cli/soul/kimisoul.py — goal token tracking and budget check
  • src/kimi_cli/tools/goal/ — new UpdateGoal tool
  • src/kimi_cli/agents/default/agent.yaml — register UpdateGoal
  • src/kimi_cli/ui/shell/init.py — auto-continuation on idle
  • src/kimi_cli/wire/types.py — GoalBegin, GoalEnd, GoalStatusUpdate events
  • tests/core/test_goal_slash_command.py — comprehensive tests

Testing

All 806 core tests pass.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@CommanderCrowCode CommanderCrowCode force-pushed the feat/goal-slash-command-2218 branch from f6dd119 to 50d5b18 Compare May 14, 2026 08:04
@CommanderCrowCode CommanderCrowCode changed the title feat(soul): add /goal slash command for long-running tasks feat(soul): add /goal slash command with stateful goal management May 14, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 50d5b1815d

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/kimi_cli/soul/slash.py Outdated
user_message = Message(role="user", content=prompt)

# Run the ralph loop with a single iteration (decision: continue or stop)
runner = FlowRunner.ralph_loop(user_message, max_ralph_iterations=1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Let /goal continuation run beyond first CONTINUE

Using FlowRunner.ralph_loop(..., max_ralph_iterations=1) makes max_moves equal to 2, which only covers the first task node and first decision node. If the model follows the decision prompt and returns CONTINUE (the expected path for unfinished work), the next loop iteration immediately raises MaxStepsReached, so long-running goals abort after a single cycle instead of continuing automatically.

Useful? React with 👍 / 👎.

Comment thread src/kimi_cli/soul/kimisoul.py Outdated
Comment on lines +224 to +227
if goal is None:
return
goal.tokens_used += tokens
goal.updated_at = time.time()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict goal token accounting to active goals

_update_goal_tokens claims to update the “active goal” but only checks for goal is None; paused or completed goals still accumulate tokens on every LLM step. This makes /goal pause ineffective for budget tracking and can mutate post-completion metrics with unrelated later turns.

Useful? React with 👍 / 👎.

@CommanderCrowCode CommanderCrowCode force-pushed the feat/goal-slash-command-2218 branch from 50d5b18 to 6678e4f Compare May 14, 2026 13:38
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 6678e4fa8e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/kimi_cli/ui/shell/__init__.py Outdated
Comment on lines +1485 to +1488
from kimi_cli.soul.slash import _trigger_goal_continuation

logger.info("Auto-continuing active goal: {objective}", objective=goal.objective[:50])
await _trigger_goal_continuation(self.soul)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Run goal continuation through run_soul to preserve wire context

_maybe_continue_goal calls _trigger_goal_continuation directly after run_soul_command has already finished, which means the run_soul wire context has been torn down before the continuation starts. The continuation path immediately emits wire_send(TurnBegin(...)), so this can raise the wire is not None assertion and crash the shell whenever an active goal tries to auto-continue after a normal user turn.

Useful? React with 👍 / 👎.

Comment on lines +755 to +758
goal = self._runtime.session.state.goal
if goal is not None:
goal.time_used_seconds = self._goal_time_elapsed()
self._runtime.session.save_state()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Freeze goal elapsed time once goal stops being active

This post-turn save block recalculates time_used_seconds for every non-None goal, including paused and complete states that remain stored in session state. As a result, unrelated later turns keep increasing the recorded duration, so final goal timing is no longer trustworthy (including data used by GoalEnd/status reporting). Restrict elapsed-time updates to active goals or snapshot it at terminal states.

Useful? React with 👍 / 👎.

@CommanderCrowCode CommanderCrowCode force-pushed the feat/goal-slash-command-2218 branch from 6678e4f to 5f3fe0b Compare May 14, 2026 14:12
@CommanderCrowCode
Copy link
Copy Markdown
Author

Reviewer Notes — Codex Review Feedback Addressed

This PR has been reviewed by Codex (OpenAI). Below is a summary of the 4 issues raised and how they were resolved in the latest commit.


1. ✅ Let /goal continuation run beyond first CONTINUE

Issue: max_ralph_iterations=1 only covered the first task node + first decision node. If the model returned CONTINUE (the expected path for unfinished work), the loop immediately raised MaxStepsReached.

Fix: Changed to max_ralph_iterations=-1 (effectively unlimited) in src/kimi_cli/soul/slash.py:

  • Before: runner = FlowRunner.ralph_loop(user_message, max_ralph_iterations=1)
  • After: runner = FlowRunner.ralph_loop(user_message, max_ralph_iterations=-1)

The loop now continues until the model explicitly chooses STOP via <choice>STOP</choice> or calls update_goal(status="complete").


2. ✅ Restrict goal token accounting to active goals

Issue: _update_goal_tokens checked goal is None but not goal.status, so paused/completed goals kept accumulating tokens on every LLM step. This made /goal pause ineffective for budget tracking and mutated post-completion metrics.

Fix: Added goal.status != "active" guard in src/kimi_cli/soul/kimisoul.py:

  • Before: if goal is None: return
  • After: if goal is None or goal.status != "active": return

3. ✅ Run goal continuation through run_soul to preserve wire context

Issue: _maybe_continue_goal called _trigger_goal_continuation directly after run_soul_command had already finished, meaning the wire ContextVar was torn down. The continuation path immediately emits wire_send(TurnBegin(...)), which would hit the "wire is not None" assertion and crash the shell.

Fix: Changed _maybe_continue_goal in src/kimi_cli/ui/shell/__init__.py to call run_soul_command(prompt) instead of _trigger_goal_continuation(self.soul). The continuation prompt is now passed as a normal user turn, so run_soul sets up the wire context before any wire_send calls:

  • Before: await _trigger_goal_continuation(self.soul)
  • After: prompt = _build_goal_continuation_prompt(goal); await self.run_soul_command(prompt)

4. ✅ Freeze goal elapsed time once goal stops being active

Issue: The post-turn save block recalculated time_used_seconds for every non-None goal, including paused and complete states. Unrelated later turns kept increasing the recorded duration, making final goal timing untrustworthy.

Fix: Restricted elapsed-time updates to active goals in src/kimi_cli/soul/kimisoul.py:

  • Before: if goal is not None:
  • After: if goal is not None and goal.status == "active":

All 806 core tests pass. Ready for maintainer review.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 5f3fe0bc63

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +314 to +315
runner = FlowRunner.ralph_loop(user_message, max_ralph_iterations=-1)
await runner.run(soul, "")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop goal continuation when status leaves active

/goal and /goal resume start an effectively unbounded Ralph loop (max_ralph_iterations=-1), but goal status is checked only once before the loop starts. If the goal becomes budget_limited (via token accounting) or complete (via UpdateGoal) during any iteration, this runner keeps executing additional turns until the model explicitly returns STOP, which can continue spending tokens after the goal has already reached a terminal state.

Useful? React with 👍 / 👎.

Comment thread src/kimi_cli/soul/kimisoul.py Outdated
Comment on lines +753 to +755
if goal is not None and goal.status == "active":
goal.time_used_seconds = self._goal_time_elapsed()
self._runtime.session.save_state()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Persist goal state even after terminal status transitions

Goal persistence is gated on goal.status == "active", so when _update_goal_tokens flips a goal to budget_limited during a turn, the updated status and latest token usage are never saved at turn end. This leaves state.json stale (often still active), so reopening the session can resume with incorrect goal status and counters.

Useful? React with 👍 / 👎.

@CommanderCrowCode
Copy link
Copy Markdown
Author

Round 2 — Codex Review Feedback Addressed (commit 060083d3)

Two additional issues were raised on the previous fix commit. Both are now resolved.


5. ✅ Stop goal continuation when status leaves active

Issue: /goal and /goal resume start an effectively unbounded Ralph loop (max_ralph_iterations=-1), but goal status is checked only once before the loop starts. If the goal becomes budget_limited (via token accounting) or complete (via UpdateGoal) during any iteration, the runner keeps executing additional turns until the model explicitly returns STOP, continuing to spend tokens after the goal has already reached a terminal state.

Fix: Added a terminal-state check inside FlowRunner.run() in src/kimi_cli/soul/kimisoul.py after each _execute_flow_node completes:

goal = soul.runtime.session.state.goal
if goal is not None and goal.status in ("complete", "budget_limited"):
    logger.info(
        "Goal reached terminal state {status}, stopping ralph loop.",
        status=goal.status,
    )
    return

The ralph loop now exits immediately when the goal reaches a terminal state, preventing wasteful token spend.


6. ✅ Persist goal state even after terminal status transitions

Issue: Goal persistence in _turn was gated on goal.status == "active", so when _update_goal_tokens flips a goal to budget_limited during a turn, the updated status and latest token usage were never saved at turn end. This left state.json stale (often still showing active), so reopening the session could resume with incorrect goal status and counters.

Fix: Changed the post-turn save block in src/kimi_cli/soul/kimisoul.py to always persist goal state, while only updating time_used_seconds for active goals:

# Before:
if goal is not None and goal.status == "active":
    goal.time_used_seconds = self._goal_time_elapsed()
    self._runtime.session.save_state()

# After:
if goal is not None:
    if goal.status == "active":
        goal.time_used_seconds = self._goal_time_elapsed()
    self._runtime.session.save_state()

Terminal transitions (e.g. activebudget_limited) are now always written to disk.


All 806 core tests pass.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 060083d336

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +222 to +226
if current_goal.status == "active":
wire_send(TextPart(text="Goal is already active."))
return
current_goal.status = "active"
current_goal.updated_at = time.time()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict /goal resume to paused goals

/goal resume currently reactivates any non-active goal, not just paused ones. If a goal is already complete or budget_limited, this branch resets it to active and immediately calls continuation, which can restart work and consume additional tokens after a terminal state was reached (including budget-stop scenarios when token_budget is set). This violates the command contract in the docstring and undermines terminal-state enforcement.

Useful? React with 👍 / 👎.

Comment on lines +238 to +240
if goal is None or not goal.created_at:
return 0.0
return time.time() - goal.created_at
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude paused intervals from goal time accounting

time_used_seconds is derived from time.time() - created_at, so after a goal is paused and later resumed, the next active-turn update includes the entire paused wall-clock interval. That inflates “time spent pursuing goal” in status/prompt reporting and any downstream metrics, even though no work happened during the pause. Track active duration incrementally (or adjust on pause/resume) instead of using raw creation-to-now elapsed time.

Useful? React with 👍 / 👎.

…onshotAI#2218)

Implement feature-parity /goal command inspired by Codex's goal system.

Key features:
- Goal state persistence in SessionState (objective, status, token budget, usage)
- Subcommands: /goal <obj>, /goal pause, /goal resume, /goal clear, /goal edit
- Enhanced continuation prompt with completion audit and fidelity instructions
- UpdateGoal tool for the model to mark goals complete
- Token budget tracking with budget_limited status
- Auto-continuation on idle (triggers after user turns when goal is active)
- Wire events: GoalBegin, GoalEnd, GoalStatusUpdate
- Telemetry: goal_created, goal_completed, goal_budget_limited

Closes MoonshotAI#2218
@CommanderCrowCode CommanderCrowCode force-pushed the feat/goal-slash-command-2218 branch from 060083d to 3aa0dba Compare May 14, 2026 15:12
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 3aa0dbadfc

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +203 to +206
if current_goal.status == "paused":
wire_send(TextPart(text="Goal is already paused."))
return
current_goal.status = "paused"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject pausing goals that are no longer active

/goal pause is specified as “Pause the active goal,” but this branch only rejects None and already-paused, then unconditionally sets status = "paused". That lets terminal goals (complete/budget_limited) be rewritten into a resumable state, which breaks terminal-state enforcement and can allow additional automated work/token spend after a goal should have ended.

Useful? React with 👍 / 👎.

Comment on lines +755 to +756
if goal.status == "active":
goal.time_used_seconds = self._goal_time_elapsed()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist final elapsed time when goal leaves active state

Goal elapsed time is only written when goal.status == "active" at turn end, so if a goal becomes complete (via UpdateGoal) or budget_limited during the turn, time_used_seconds is never updated with that final interval. In the common case where a goal finishes on its first turn, persisted duration can remain 0.0, which makes stored goal timing inaccurate for later status/reporting.

Useful? React with 👍 / 👎.

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.

是否可以支持类似codex 上的/goal? || Can it support /goal similar to codex?

1 participant