← Back to Tech Practice

AIAgent

Kimi K3 Tool Calls Loop: 2026 Stop-Loss Guide

About 15 min read

Kimi K3 Tool Calls Loop: 2026 Stop-Loss Guide

The same function keeps running, the arguments look split or malformed, and your unattended Agent never reaches a final answer.

Fastest fix: preserve the full trace, verify the assistant message and matching tool_call_id first, then add hard limits for rounds, time, cost, and side effects. Do not rely on a prompt telling the model to stop.

Who should read this: Tool-calling Agent developers fixing repeated function execution. Automation platform engineers adding circuit breakers and recovery. Teams running tools that send messages, create records, charge accounts, or modify data.

Last updated August 3, 2026. This guide was checked against the current Kimi API overview, official Tool Calls guidance, and the MoonshotAI streaming tool-call example. Recheck the implementation if Kimi changes the tool-call format, finish_reason, streaming fields, or duplicate-call recommendations.

Start with evidence, not prompt changes

Before changing your system prompt, stop the worker if the tool has a write side effect. Use a read-only implementation, a test account, or a sandbox version of the tool until you know whether the model actually repeated the call.

A typical redacted trace may look like this:

request_id: req_<placeholder>
assistant.finish_reason: tool_calls
tool_call_id: call_<placeholder>
tool.name: create_record
tool.arguments: {"customer_id":"<placeholder>","plan":"<placeholder>"}
tool.result: {"status":"accepted","record_id":"<placeholder>"}

assistant.finish_reason: tool_calls
tool_call_id: call_<placeholder>
tool.name: create_record
tool.arguments: {"customer_id":"<placeholder>","plan":"<placeholder>"}
tool.result: {"status":"accepted","record_id":"<placeholder>"}

This trace alone does not prove that Kimi K3 generated the same call twice. Your client may have displayed the same event twice. An SDK or queue may have retried the request. Your message history may be missing the assistant message or the matching tool result, causing the next request to repeat the same decision.

Save these fields for every model turn:

  • A redacted request body and the ordered messages array.
  • The model name, request ID, response ID, and HTTP status.
  • finish_reason from every returned choice.
  • Every tool_call_id, tool name, argument string, and parsed argument object.
  • The raw streaming chunks before any parser normalizes them.
  • The tool execution ID, start time, end time, result, error, and retry status.
  • API usage fields returned by the response, when available.

Do not store API keys, customer secrets, payment data, access tokens, or full production payloads in the trace. The current Kimi API documentation explicitly treats the API key as sensitive and recommends environment variables rather than client-side code or public logs. (platform.kimi.ai)

The first decision is whether the repetition exists in the model response, the client trace, or the tool worker. Those are different faults with different fixes.

First milestone: prove the message chain is valid

A common Kimi K3 API failure is not an Agent reasoning problem. It is an incomplete conversation state.

When the model returns an assistant message containing tool_calls, your client must append that assistant message to the conversation before sending the tool result back. For every returned tool call, append a separate message with role: "tool" and the exact corresponding tool_call_id.

The minimum sequence is:

[
  {
    "role": "user",
    "content": "Create a test record for <placeholder>."
  },
  {
    "role": "assistant",
    "tool_calls": [
      {
        "id": "call_<placeholder>",
        "type": "function",
        "function": {
          "name": "create_record",
          "arguments": "{\"customer_id\":\"<placeholder>\"}"
        }
      }
    ]
  },
  {
    "role": "tool",
    "tool_call_id": "call_<placeholder>",
    "name": "create_record",
    "content": "{\"status\":\"accepted\"}"
  }
]

The assistant message must not be replaced with a simplified text such as “The model called create_record.” That removes the structured call information that the next model turn needs. Likewise, do not generate a new ID for the tool result. The result must use the ID returned by the assistant message.

The official guidance shows the same control flow: inspect finish_reason, append the assistant message, execute the returned tool, and append a role="tool" message with the matching tool_call_id. It also notes that the ending value of finish_reason can vary across engines, so your loop should not assume that a single string is universal forever. (github.com)

Does a wrong tool_call_id cause a tool loop? It can. More precisely, it can leave the model without a valid result for the call it just made. The next request may then repeat the call, fail validation, or enter an application retry path. Log the ID at creation, dispatch, result append, and persistence time. Compare the strings exactly, including case and punctuation.

Run a minimal non-streaming request before investigating your Agent framework. Use one harmless read-only function, one user message, and one model turn. If the non-streaming path produces a correct assistant message followed by a matching tool result, your problem is probably in streaming assembly, middleware, retries, or message persistence.

The current API uses an OpenAI-compatible chat completion shape and documents the /v1/chat/completions endpoint. Confirm your base_url, model identifier, and SDK behavior against the current API documentation instead of assuming that every OpenAI-compatible wrapper handles Kimi-specific fields identically. (platform.kimi.ai)

Second milestone: assemble streamed Tool Calls by index

Streaming introduces a separate failure mode. A tool call can arrive across multiple chunks. The function name and ID may appear in an early chunk, while the JSON argument string arrives in later chunks. If your code executes as soon as it sees the function name, it may run with incomplete parameters.

How should streaming Tool Calls parameters be joined? Keep a buffer for each tool-call index. Append argument fragments in arrival order. Do not merge fragments from different indexes into one object.

A safe internal representation looks like this:

calls = {}

for chunk in completion:
    for delta_call in chunk.choices[0].delta.tool_calls or []:
        index = delta_call.index

        if index not in calls:
            calls[index] = {
                "id": None,
                "name": None,
                "arguments": ""
            }

        if delta_call.id:
            calls[index]["id"] = delta_call.id

        if delta_call.function and delta_call.function.name:
            calls[index]["name"] = delta_call.function.name

        if delta_call.function and delta_call.function.arguments:
            calls[index]["arguments"] += delta_call.function.arguments

Only after the stream has ended should you validate that:

  • Every call has an ID.
  • Every call has a function name.
  • The argument string is complete JSON.
  • The index-to-call mapping is intact.
  • The parsed object matches the tool schema.
  • The call has not already been dispatched.

The official Tool Calls material identifies index as the field used to distinguish multiple streamed calls and says that tool_call.function.arguments must be concatenated correctly. The GitHub guidance likewise instructs clients to collect streamed tool-call information until a complete call is available. (moonshot-ai.gitbook.io)

Do not silently repair malformed JSON by dropping characters, adding missing braces, or guessing a parameter. Preserve both the raw fragments and the final assembled string. If parsing fails, return a structured tool error to the Agent or terminate the turn for human review. A silent repair can transform a parser defect into a duplicate write.

Also check whether your framework makes two requests for one stream. Common causes include:

  • Reconnecting after a partial network response without a request key.
  • Treating a stream timeout as proof that no tool call was received.
  • Running both a callback handler and a normal iterator.
  • Persisting the assistant message after dispatch, then replaying the same event from a queue.
  • Retrying a completed tool execution because the model response was not acknowledged.

For long-running jobs, persist the stream state before dispatching a side-effecting tool. The record should indicate whether the call was observed, parsed, authorized, dispatched, completed, and acknowledged.

Third milestone: identify a real no-progress loop

Why does Kimi K3 keep calling the same function? After message order and streaming assembly are correct, compare the sequence of calls rather than looking at the final transcript.

A useful duplicate key contains:

tool name
normalized arguments
relevant tool-result state

Normalize only fields where order or formatting is semantically irrelevant. For example, object-key order can usually be normalized, while a raw SQL string, command line, or user-provided text may not be safe to rewrite. Hash the normalized representation for logging, but retain the redacted original for review.

A repeated call becomes a strong loop signal when all of these conditions hold:

  • The same tool is selected.
  • The normalized arguments are unchanged.
  • The returned result contains no new state or progress marker.
  • The next assistant turn requests the same action again.
  • No external event has changed the task state.

Do not classify every repeated tool name as a loop. An Agent may legitimately call get_status several times while a job moves from queued to running to completed. The important condition is no progress, not repetition by itself.

Can a prompt reminder stop the loop? It can reduce the likelihood of an unnecessary call, but it is only a soft intervention. The Kimi prompt guidance recommends clear instructions, explicit steps, and structured context, but those instructions do not replace application-level enforcement. (platform.moonshot.ai)

When your detector fires, return an explicit state such as:

{
  "status": "stopped_no_progress",
  "reason": "repeated_tool_call",
  "tool": "create_record",
  "recovery": "human_review_required"
}

Do not fabricate a successful result to make the model leave the loop. A false success hides whether the business operation happened and can cause a later reconciliation failure.

First day: protect side effects with idempotency

How do you stop an Agent from repeating a side-effecting tool? Put the protection inside the tool boundary, not only in the model loop.

For sending email, creating tickets, charging a customer, placing an order, or changing records, require an idempotency key derived from the logical task. Do not use a random key generated separately for every retry. If the same logical operation is retried, the key should remain stable.

A side-effecting tool should follow a sequence like this:

  • Validate the caller, task, and authorization scope.
  • Validate the argument schema and business constraints.
  • Check whether the idempotency key already has a completed result.
  • Reserve or lock the operation inside a transaction boundary.
  • Perform the side effect.
  • Persist the result before acknowledging completion.
  • Return the stored result for later retries using the same key.

Add a confirmation step for irreversible actions. For example, the Agent may prepare a payment or message, but a policy layer must approve the final dispatch. For lower-risk writes, use a sandbox or draft state until the task passes validation.

You also need four independent stop-loss controls:

  • Round limit: stop after the configured number of Agent-tool turns.
  • Time limit: stop when the task exceeds its execution deadline.
  • Cost limit: stop when accumulated API usage reaches the task budget.
  • Side-effect limit: stop when the workflow reaches its permitted write count or risk level.

Do not copy a threshold from another Agent and call it a universal Kimi K3 setting. The appropriate value depends on tool risk, expected workflow length, retry policy, and business recovery options. Use a conservative value in the sandbox, then raise it only after recorded test cases show measurable progress.

Create a human takeover path. The operator should see the last safe checkpoint, the pending tool call, the exact redacted arguments, the latest tool result, and the reason the circuit breaker opened. Recovery should resume from that checkpoint, not replay the entire conversation blindly.

First week: run production acceptance tests

The first week of production validation should use four deliberately different samples:

  • A normal tool call that completes.
  • The same tool with changed arguments and a valid state transition.
  • The same normalized call with an unchanged result.
  • A network retry after the tool has already committed its side effect.

For every sample, verify the following:

  • The assistant message is persisted before the tool result is appended.
  • Every tool result carries the exact returned tool_call_id.
  • Streaming arguments are preserved before parsing.
  • A malformed argument stops execution rather than triggering a repair loop.
  • A repeated no-progress call opens the circuit breaker.
  • No background worker continues after the task is marked stopped.
  • A retry returns the stored idempotent result instead of repeating the side effect.
  • request_id, tool execution ID, and API usage can be joined into one audit trail.

The Kimi API overview documents structured error fields such as error.type and error.message, as well as common HTTP statuses including authentication, rate-limit, and server-error responses. Record these fields with the Agent state so that a transport failure is not mistaken for a model loop. (platform.kimi.ai)

The acceptance question is not “Did the model eventually answer?” It is “Can you prove which tool ran, why it ran, whether it changed state, and why the workflow stopped?”

Choose the right stop-loss response

Use this decision list during incident handling:

  • If the assistant message is missing or altered, fix message persistence first and rerun a minimal non-streaming request.
  • If a tool_call_id does not match, reject the tool result, repair the message chain, and do not replay a side effect automatically.
  • If arguments are incomplete only in streaming mode, disable streaming for the affected workflow until indexed buffering is correct.
  • If the same normalized call returns new state, continue under a strict time, round, cost, and side-effect budget.
  • If the same normalized call returns no new state, open the circuit breaker and create a recovery record.
  • If the tool has irreversible side effects and no idempotency key, use a sandbox or read-only substitute until the tool contract is changed.
  • If the fault appears only after network retries, inspect queue acknowledgements and idempotency before changing the prompt.
  • If you cannot reconstruct the trace, stop production execution and add evidence capture before further tuning.

Compare debugging environments before the final rollout

A local laptop is fine for a short reproduction. It becomes a weak choice when you need unattended regression runs, persistent logs, remote access, or a stable environment that remains available while a test matrix executes.

Environment Best use Main limitation Stop-loss requirement
Local development machine Minimal non-streaming reproduction and parser tests Sleep, network changes, and local process restarts can interrupt evidence collection Save traces before every tool dispatch
Shared CI runner Repeatable parser and message-chain tests Ephemeral workspaces can remove logs after a failed job Upload redacted artifacts and execution IDs
Remote Mac environment Long-running Agent regression, dashboard access, and persistent debugging sessions Requires access control, cleanup, and cost governance Set task budgets and retain only required logs
Production worker Real business workflows A loop can create duplicate writes or unbounded API usage Enforce all four hard limits and idempotency

If you need a stable place to run a long regression sample, review Kvmkit’s remote Mac environment options before placing the Agent on a personal laptop. For a United States-based workflow, the US East Mac rental option is the more relevant starting point for evaluating an always-available test worker. Treat the environment as a controlled execution node, not as a substitute for application-level circuit breakers.

Record the acceptance result in a reusable format

Check Pass condition Evidence to retain Failure action
Message order Assistant tool-call message is followed by matching tool results Redacted ordered messages Fix persistence and replay only in sandbox
ID matching Every tool_call_id matches exactly Dispatch and result logs Reject unmatched results
Stream assembly Arguments are complete before execution Raw chunks and assembled JSON Disable streaming for that workflow
Duplicate detection Same tool and arguments are stopped only when there is no progress Duplicate key and result hash Open circuit breaker
Side-effect safety Retry does not repeat a committed operation Idempotency record and tool execution ID Add transactional protection
Budget control Round, time, cost, and write limits are enforced Budget counters and stop reason Mark task for review
Recovery Operator can resume from the last safe checkpoint Checkpoint and takeover record Block unattended execution

Do not mark the Agent ready because one happy-path test passed. It is ready only when the normal call, changed-argument call, no-progress call, and post-commit network retry all produce the expected state transitions.

Your next move: reproduce once in a sandbox

If Kimi K3 Tool Calls keep looping, start with a safe reproduction, not a larger prompt. Preserve the raw response, verify the assistant and tool messages, match every ID, repair indexed streaming assembly, then add duplicate detection and hard limits. This order prevents you from masking a client bug as a model problem.

Your current setup may look cheaper or simpler, but a laptop can sleep, a shared CI runner can discard logs, and an unmanaged worker can keep retrying after a side effect has already committed. For long-running regression samples, a continuously available remote Mac environment from Kvmkit can give you a more suitable place to keep the Agent online and preserve diagnostic artifacts. The application still needs idempotency and circuit breakers, but the execution environment no longer becomes the first source of missing evidence.

Run CI/CD on M4 Mac mini — the hassle-free way

Xcode, Fastlane, CocoaPods, and SPM are first-class on macOS. Mac mini M4 unified memory keeps signing and archiving smooth; ~4W standby power suits 24/7 build nodes.

View Kvmkit plans

Need technical support or sizing advice?

If you run into issues with Mac instances or CI/CD pipelines, check the Help Center first; see Pricing for plans.