Build a Kimi K3 Coding Agent: Tools & Long-Horizon Tasks

Kimi K3 excels at long-horizon coding agents. Learn function calling with tools, forcing calls with tool_choice, dynamic tool loading via system messages, and the multi-turn gotcha, with a repo-wide refactor walkthrough.

Long-horizon coding sits at the very top of Kimi K3’s official positioning — a 1M context window holds an entire repo, always-on reasoning carries multi-step planning, and native tool calling drives the terminal and external functions. That makes it a natural fit for a coding agent: reading code, calling tools, and chaining a long run of actions to finish the job on its own. This post walks through wiring up an agent with K3’s tool calling, plus the one trap that bites hardest on long-horizon tasks.

Function calling basics

K3 is compatible with OpenAI’s tools spec. Define a tool and let the model decide when to call it:

import os, json
from openai import OpenAI

client = OpenAI(api_key=os.environ["MOONSHOT_API_KEY"],
                base_url="https://api.moonshot.cn/v1")

tools = [{
    "type": "function",
    "function": {
        "name": "read_file",
        "description": "Read the contents of a file in the repo",
        "parameters": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"],
        },
    },
}]

messages = [{"role": "user", "content": "Check whether auth.py has any hardcoded secrets"}]

resp = client.chat.completions.create(
    model="kimi-k3", messages=messages, tools=tools,
)

When the model decides to call a tool, the returned assistant message carries tool_calls. You run the matching function locally, feed the result back as a role:"tool" message, and request the next turn.

The key gotcha: in multi-turn, append the full assistant message unchanged

This is where people migrating from K2 trip up the most: in multi-turn conversations and tool calls, append the complete assistant message the API returned, unchanged, to your next request — don’t keep only content.

With K3’s always-on reasoning, the assistant message carries reasoning/tool state on top of content. Passing back only content gives the model “amnesia” — the reasoning chain breaks and tool calls get scrambled. The correct pattern:

# Append the entire assistant message (including tool_calls / reasoning state) unchanged
messages.append(resp.choices[0].message)

# Run the tools and feed results back as tool messages
for call in resp.choices[0].message.tool_calls or []:
    result = run_tool(call.function.name, json.loads(call.function.arguments))
    messages.append({
        "role": "tool",
        "tool_call_id": call.id,
        "content": json.dumps(result),
    })

# Request the next turn with the full history
resp = client.chat.completions.create(
    model="kimi-k3", messages=messages, tools=tools,
)

Remember: append the whole message object — don’t hand-build a new one with only content.

Forcing calls and loading tools dynamically

  • Forcing calls: to make the model use a tool on this turn, set tool_choice="required" (or name a specific function) — ideal for “look it up before answering” flows.
  • Dynamic tool loading: when you have many tools, put the tool definitions in a system message and surface them on demand, instead of stuffing the tools array full every turn — it saves context and stays more flexible.
  • Official tools: Moonshot AI also ships a set of built-in tool capabilities, accessed through official endpoints like /tools and /fibers; follow the official docs for details.

Why long-horizon tasks suit K3

A coding agent that runs many turns keeps piling up context: repo contents + turn after turn of tool results + reasoning history. Two K3 features catch exactly this:

  • 1M context holds the whole repo plus a long history, so you never drop context mid-run by overflowing the window (for organizing long context, see the 1M context guide);
  • Caching on by default lets the unchanging prefix (repo, system, tool definitions) hit again and again, pushing a long-horizon agent’s input cost down to ¥2/M — otherwise resending the prefix every turn gets expensive (do the math in the K3 pricing breakdown).

So when you organize messages, put the repo contents and tool definitions up front and keep them stable to max out the cache; only append what changes each turn.

Repo-wide refactor in practice

A typical “repo-wide refactor” agent loop:

  1. Feed the full picture: drop the repo (or the relevant parts retrieved via RAG) + system instructions into context in one shot;
  2. Let it plan: have K3 output a refactor plan first (which files to change, the risk points) — always-on reasoning pays off most here;
  3. Execute with tools: open read_file / write_file / run_tests and let it edit and run tests step by step;
  4. Append unchanged: each turn, feed the full assistant message + tool results back to keep the reasoning chain intact;
  5. Wrap-up check: have it self-review against the original plan for missed edits and a fully green test suite.

You can also run this whole flow on an aggregator gateway — hand planning to K3 and route certain subtasks to models like Claude Opus (see the head-to-head in K3 vs Claude vs GPT) — all under a single key.

Wrap-up

The core playbook for a K3 coding agent comes down to three things: drive actions with tools, cut cost with caching + front-loading, and append the full assistant message unchanged across turns. Get those three right and long-horizon tasks run reliably. For full integration and error troubleshooting, see the Kimi K3 API tutorial. To route freely between K3 and other models under one key, start at the GetModel dashboard.