Conversation
- fix(soul): merge ralph_loop ephemeral context to preserve cross-turn memory - fix(ui): gate usage URL early-return to Kimi Code providers only - fix(llm): remove duplicate custom_headers that clobber Authorization - fix(llm): apply KIMI_MODEL_* env overrides for agent-gw providers - fix(auth): refresh Anthropic default_headers Authorization on OAuth token rotation - test: update ralph loop test expectation for merge mode
- fix(auth): mutate _custom_headers instead of default_headers property when refreshing Anthropic Authorization header on OAuth token rotation. default_headers is a property that returns a new dict each call, so mutating it had no effect. - fix(soul): handle ephemeral context compaction in _merge_ephemeral_to_main. If compact_context() runs during a flow, the ephemeral history is rebuilt and can become shorter than _parent_history_len. Fall back to merging all ephemeral messages so flow results are not lost.
- fix(soul): replace main context file on compaction during auto-ralph flow to prevent duplicate messages in ephemeral-to-main merge - fix(soul): merge paused auto-Ralph ephemeral history into main context so context is not lost when user interjects - fix(mcp): propagate StepInterrupted as an error to MCP callers instead of returning a partial success result - fix(kosong): pass VideoURLPart through Anthropic provider for agent-gw video-in support
|
@codex review @devin-ai-integration review |
- Fix MCP StepInterrupted propagation: don't break on StepInterrupted, let the underlying exception surface to callers instead of returning partial success. - Fix ToolCallPart attribute name: arguments_part (was arguments). - Reset FlowRunner._paused at start of run() to prevent stale state across reused runner instances. - Base convergence detection on actual task assistant messages from context history, not the decision turn output, avoiding false convergence on stable CONTINUE rationales. - Add regression test for pause reset behavior.
- Populate tool_call_names in IterationFingerprint from assistant message tool calls, and include them in similarity scoring. This prevents false convergence when the model emits stable text but makes different tool calls across iterations. - Update last_task_message from every node (not just non-decision nodes) so ralph_loop decision-only self-loops get fresh convergence input on each iteration instead of re-hashing the same R1 message forever. - Preserve original flow_decision tool object when restoring toolset after a flow run, instead of replacing a custom implementation with a fresh FlowDecisionTool instance.
|
@codex address that feedback |
|
To use Codex here, create an environment for this repo. |
|
@codex address that feedback |
|
To use Codex here, create an environment for this repo. |
|
@chatgpt-codex-connector I did create an environment. Can you please re-review the latest commit? |
|
@chatgpt-codex-connector I already created an environment. Please re-review the latest commit (dfc66df). |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
…ence - P1: Detect compaction by comparing parent prefix hash instead of relying solely on history length. Compaction can rewrite history such that final length exceeds the original parent length, causing the index-based merge to use wrong offsets. - P2: Pass tool results (reconstructed from tool messages in context history) into convergence detector so tool output changes are included in similarity scoring.
…g 0.53.0 Upstream changes: - fix(yolo): unblock AskUserQuestion; add orthogonal afk mode (#2045) - chore(release): bump kimi-cli to 1.40.0 and kosong 0.53.0 (#2108) The yolo/afk refactor separates 'away from keyboard' mode from 'yolo' (auto-approve) mode, making them orthogonal concerns. No merge conflicts with our RalphFlow changes. All 44 tests pass (11 ralph_loop + 33 afk/yolo).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13bfecc68f
ℹ️ 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".
| for tool_name in list(toolset._tool_dict.keys()): | ||
| if tool_name != "flow_decision" and toolset.hide(tool_name): | ||
| hidden_tools.append(tool_name) |
There was a problem hiding this comment.
Keep non-decision tools enabled on CONTINUE path
The decision-node tool masking hides every tool except flow_decision, but Ralph Loop routes CONTINUE from R2 back to R2 (outgoing["R2"] self-loop), so subsequent iterations cannot execute Shell/file/web tools at all and can only emit another decision. In practice this turns CONTINUE into a no-op loop (re-decide repeatedly) instead of “keep working and refining,” which can prematurely stall multi-iteration runs after only the first task node.
Useful? React with 👍 / 👎.
Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92e8ca973d
ℹ️ 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".
| async def __call__(self, params: Params) -> ToolReturnValue: | ||
| return ToolOk( | ||
| output="", | ||
| message="No archived context is available for this session.", | ||
| ) |
There was a problem hiding this comment.
Implement archive lookup before exposing ArchiveSearch
This tool is now added to all default agents, but its handler always returns the same hardcoded "No archived context" response regardless of query or session state. In long sessions where compaction has already removed earlier turns, the model will call this tool expecting retrieval and get no data every time, so references to prior decisions/files cannot be recovered and flow quality regresses. Either connect this to a real archive backend or keep the tool out of default toolsets until it is functional.
Useful? React with 👍 / 👎.
| scores: list[float] = [] | ||
|
|
||
| if a.assistant_text_hash == b.assistant_text_hash: | ||
| scores.append(1.0) | ||
| else: | ||
| scores.append(0.0) |
There was a problem hiding this comment.
🔴 Convergence similarity inflated by ignored text, causing false auto-stop
When ignore_text=True is passed to IterationFingerprint.from_turn, both fingerprints get assistant_text_hash="". In _compute_similarity, the empty-string equality check at line 170 contributes a free 1.0 to the scores list, inflating the average similarity. The intent of ignore_text is to exclude text from comparison, but the current code still includes it with a perfect score. This inflates the similarity denominator from 2 (tool names + tool outputs) to 3, and can trigger false convergence — e.g., if tool names match (1.0) but outputs differ moderately (0.55): (1.0 + 1.0 + 0.55) / 3 = 0.85 >= threshold, triggering convergence. The correct result excluding text would be (1.0 + 0.55) / 2 = 0.775 < 0.85, which should NOT converge. This causes the flow to auto-stop prematurely when the model is still making progress (different tool outputs).
| scores: list[float] = [] | |
| if a.assistant_text_hash == b.assistant_text_hash: | |
| scores.append(1.0) | |
| else: | |
| scores.append(0.0) | |
| scores: list[float] = [] | |
| if a.assistant_text_hash or b.assistant_text_hash: | |
| if a.assistant_text_hash == b.assistant_text_hash: | |
| scores.append(1.0) | |
| else: | |
| scores.append(0.0) |
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| def run_mcp_server() -> None: | ||
| """Entry point for `kimi mcp serve`.""" | ||
| enable_logging(debug=False, redirect_stderr=True) |
There was a problem hiding this comment.
🟡 MCP serve enable_logging with redirect_stderr=True hides startup errors from stdio transport
In run_mcp_server() at src/kimi_cli/mcp_serve/__init__.py:205, enable_logging(debug=False, redirect_stderr=True) redirects stderr to the log file via dup2. Because the MCP server uses stdio transport (server.run(transport="stdio")), both stdin and stdout are occupied by the MCP protocol. Redirecting stderr means that any startup errors (e.g., missing config, import errors in tools) that would normally print to stderr are silently swallowed into the log file. The MCP client sees no error, and the server appears to hang. redirect_stderr=False should be used to preserve stderr as the only error channel available during stdio-based MCP communication.
| enable_logging(debug=False, redirect_stderr=True) | |
| enable_logging(debug=False, redirect_stderr=False) |
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f4ce36675
ℹ️ 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".
| and llm.provider_config.type == "kimi" | ||
| and llm.provider_config.base_url | ||
| and "agent-gw" in llm.provider_config.base_url | ||
| ): |
There was a problem hiding this comment.
Upload agent-gw videos for Anthropic provider configs
The new fallback uploader is gated on llm.provider_config.type == "kimi", so sessions configured as provider.type="anthropic" with an agent-gw base URL skip video upload and always embed local videos as base64 data: URLs. Fresh evidence in this revision is create_llm now explicitly supports Anthropic providers pointed at agent-gw, so this path is reachable and can produce oversized prompts/request failures for normal multi‑MB clips instead of using the lightweight uploaded video_url reference.
Useful? React with 👍 / 👎.
| async def _cleanup_ephemeral_context(self) -> None: | ||
| if self._paused: | ||
| # Keep temp file around for resume | ||
| return | ||
| if self._tmp_file and self._tmp_file.exists(): | ||
| self._tmp_file.unlink(missing_ok=True) | ||
| self._ephemeral_context = None | ||
| self._tmp_file = None |
There was a problem hiding this comment.
🟡 Ephemeral context temp file leaked when PAUSE is followed by a new user turn
When the flow model selects PAUSE, FlowRunner._cleanup_ephemeral_context (src/kimi_cli/soul/kimisoul.py:1561-1568) intentionally preserves the temp file for a potential resume. However, on the next user message, KimiSoul.run() creates a brand-new FlowRunner via FlowRunner.ralph_loop() — the old runner (and its preserved temp file path) is garbage-collected without cleanup. The orphaned flow_anonymous_<uuid>_context.jsonl file remains in the session directory indefinitely.
This accumulates one leaked file per PAUSE event across auto-Ralph turns. While each file is bounded in size and lives in the session directory (cleaned up on session deletion), repeated PAUSE events could accumulate significant disk usage in long-lived sessions.
Prompt for agents
In FlowRunner._cleanup_ephemeral_context (src/kimi_cli/soul/kimisoul.py:1561-1568), when _paused is True, the temp file is preserved for potential resume. But when a new auto-Ralph FlowRunner is created on the next user turn, the old runner is discarded without deleting its temp file.
Possible fixes:
1. Add a __del__ or explicit cleanup method to FlowRunner that deletes the temp file on garbage collection.
2. Track temp files at the session level and clean up stale flow temp files when a new flow starts.
3. In _setup_ephemeral_context, scan the session directory for stale flow temp files (matching the pattern flow_*_context.jsonl) and delete them before creating the new one.
Option 3 is the simplest and most robust since it doesn't rely on GC behavior.
Was this helpful? React with 👍 or 👎 to provide feedback.
Update vulnerable Python dependency pins and keep FastMCP OAuth storage working after the FastMCP 3.2 upgrade. Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
Update the locked Black formatter dependency to the patched 26.3.1 release required by GitHub Dependabot. Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
| # Hide all non-flow_decision tools to prevent the model from | ||
| # calling shell/file/etc. instead of making a flow decision. | ||
| for tool_name in list(toolset._tool_dict.keys()): # pyright: ignore[reportPrivateUsage] | ||
| if tool_name != "flow_decision" and toolset.hide(tool_name): | ||
| hidden_tools.append(tool_name) |
There was a problem hiding this comment.
🔴 Flow decision node unhides tools that were intentionally hidden by external code
In _execute_flow_node, when processing a decision node, all non-flow_decision tools are hidden via toolset.hide(). The finally block then unhides every tool in the hidden_tools list. However, hide() returns True and records the tool even if it was already hidden by external code (e.g., the Wire server hiding AskUserQuestion because the client doesn't support questions at src/kimi_cli/wire/server.py:574-578, or plan mode tools at src/kimi_cli/wire/server.py:583-602). After the finally block calls unhide() on these already-hidden tools, they become visible to the LLM on subsequent turns — even though the Wire server intentionally hid them.
Example scenario
- Wire server hides
AskUserQuestionbecause client doesn't support questions - RalphFlow decision node hides all tools (including already-hidden
AskUserQuestion) and records them inhidden_tools - Finally block unhides everything in
hidden_tools, includingAskUserQuestion - On the next task node (after CONTINUE), the LLM sees
AskUserQuestionin its tool list even though the client can't handle it
Same issue affects EnterPlanMode/ExitPlanMode when client doesn't support plan mode.
| # Hide all non-flow_decision tools to prevent the model from | |
| # calling shell/file/etc. instead of making a flow decision. | |
| for tool_name in list(toolset._tool_dict.keys()): # pyright: ignore[reportPrivateUsage] | |
| if tool_name != "flow_decision" and toolset.hide(tool_name): | |
| hidden_tools.append(tool_name) | |
| # Hide all non-flow_decision tools to prevent the model from | |
| # calling shell/file/etc. instead of making a flow decision. | |
| # Only track tools that were NOT already hidden so the finally | |
| # block does not accidentally unhide externally-hidden tools. | |
| for tool_name in list(toolset._tool_dict.keys()): # pyright: ignore[reportPrivateUsage] | |
| if tool_name != "flow_decision" and tool_name not in toolset._hidden_tools and toolset.hide(tool_name): # pyright: ignore[reportPrivateUsage] | |
| hidden_tools.append(tool_name) |
Was this helpful? React with 👍 or 👎 to provide feedback.
Remove the RECLONE/REPO_URL flow from the RHEL clean builder so the script only operates on an existing checkout and fails closed when SRC is not a git tree. Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d49c11551
ℹ️ 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".
| for i in range(start, len(self._ephemeral_context.history)): | ||
| message = self._ephemeral_context.history[i] | ||
| prov = self._ephemeral_context.get_provenance(i) | ||
| source = prov.source if prov else "flow" | ||
| ts = prov.timestamp if prov else None | ||
| await soul._context.append_message( # type: ignore[reportPrivateUsage] | ||
| message, source=source, timestamp=ts | ||
| ) |
There was a problem hiding this comment.
Persist flow metadata when replay-merging ephemeral context
This merge path replays only Message objects into the main context file via append_message, so _checkpoint and _usage records produced during the flow are dropped from disk even though in-memory counters are later overwritten. The session appears correct until restart, then Context.restore() rebuilds state from a file that is missing those metadata records, which can rewind checkpoint IDs (breaking D-Mail/back-to-checkpoint references) and lose token-baseline markers for subsequent compaction decisions.
Useful? React with 👍 / 👎.
Detect available CPUs, export parallel build settings, and let the RHEL clean builder drive prepare/build/binary/install steps with explicit opt-in flags. Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db55a868a3
ℹ️ 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".
| if self._tmp_file is not None and self._tmp_file.exists(): | ||
| self._tmp_file.unlink(missing_ok=True) | ||
| self._tmp_file = session_dir / f"flow_{safe_name}_{unique_suffix}_context.jsonl" |
There was a problem hiding this comment.
Preserve paused flow context across reruns
The PAUSE path is currently non-resumable: _cleanup_ephemeral_context explicitly keeps the temp context for resume, but the next run() immediately deletes self._tmp_file and creates a brand-new ephemeral context from the main history. When a flow chooses PAUSE, all paused-loop progress is discarded on the next invocation, so users cannot actually continue from where they paused despite the advertised PAUSE semantics.
Useful? React with 👍 / 👎.
Restore the Unix-socket Wire IPC entrypoint and runtime controls that RalphFlow persistent lanes depend on. Add coverage for the socket initialize path plus runtime instructions and AFK control. Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
Mirror stdio Wire JSON-RPC validation in the IPC socket server so protocol errors reply to the originating client instead of hanging callers. Replace Anthropic custom headers during OAuth token refresh so Authorization updates persist when the headers object is typed as a mapping. Co-authored-by: Open Research and Development Laboratories | https://ordl.org | ordl@ordl.org
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74c251634a
ℹ️ 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".
| socket_dir = os.getenv("KIMI_WIRE_IPC_SOCKET_DIR") | ||
| if socket_dir: | ||
| Path(socket_dir).mkdir(parents=True, exist_ok=True) | ||
| return os.path.join(socket_dir, f"kimi-{os.getpid()}-{id(object()):x}.sock") |
There was a problem hiding this comment.
Replace id(object()) suffix with a real unique token
_make_socket_path() uses id(object()) as the uniqueness source, but CPython can immediately reuse object IDs, so repeated calls in the same process often produce the same value. That makes the generated socket path non-unique for multiple IPC server instances in one process, causing collisions (the later server can unlink or fail to bind the earlier server’s socket). Use a stable unique source like uuid.uuid4().hex (or tempfile.mkstemp) for the suffix.
Useful? React with 👍 / 👎.
Summary
This PR introduces the RalphFlow architecture — an automated iteration framework for the Kimi Code CLI agent that prevents infinite loops while enabling robust multi-step workflows.
Key Changes
flow_decisiontool that models choose CONTINUE/STOP/PAUSE.agent-gw.kimi.comendpoints, with OAuth token refresh and custom base_url preservation.kimi mcp servecommand exposes the CLI as an MCP server via stdio transport.Safety
commit_mode="discard").Tests
tests/core/test_kimisoul_ralph_loop.py— 9 scenarios covering ephemeral context, flow decision tool, convergence, cancel, pause, and max retries.tests/test_mcp_serve.py— MCP server result collector and tool registration tests.