Juggler v1.1.1

The Agent Built
for Kimi & GLM

A lean, native Rust agentic CLI for Moonshot.ai (Kimi) and Z.ai (GLM) — with first-class Devscriptor code intelligence over MCP. Not another OpenAI-compatible shim: a harness that speaks each vendor's actual wire protocol.

Native Rust · 2024 editionPreserved Thinking75 Devscriptor MCP toolsOpen-weight models · run on-prem
terminal
# Pick a provider, paste a key — verified before it is saved
$ juggler init
# Then point it at a repo and go
$ juggler -p "Explain this repo" --yes
# Or drop into the REPL
$ juggler --yes
Rationale

Frontier models, lowest-common-denominator plumbing

Kimi and GLM are among the strongest coding models available — and almost every agent talks to them through a generic OpenAI-compatible interface. That interface is a lossy adapter: it silently discards the capabilities these models were built around. Juggler was written to close that gap.

Reasoning is thrown away every turn

Generic client

A generic client drops the model’s reasoning content between turns — the model re-derives what it already worked out.

Juggler

Preserved Thinking replays each stored reasoning block back to the provider verbatim, which is exactly what both vendors require.

Reasoning effort is sent to models that reject it

Generic client

`reasoning_effort` is a GLM-5.2-and-newer parameter. Moonshot’s schema has no such property.

Juggler

Juggler routes it only to models that accept it, and never sends it to a Kimi model.

Temperature is injected where it does not exist

Generic client

The Kimi K2 request schemas have no `temperature` property at all — only the legacy `moonshot-v1-*` models do.

Juggler

Juggler deliberately omits it for K2, and clamps + sets `do_sample: false` for GLM when temperature ≤ 0.

Real tool schemas get rejected

Generic client

Moonshot’s validator is stricter than OpenAI’s. Realistic MCP tool schemas — with `$ref`s and `$defs` — bounce.

Juggler

Juggler normalizes every schema: dereferences `$ref`s, preserves circular refs, prunes orphaned definitions, repairs `enum`/`const` vs `type` conflicts.

The strategic argument

Open weights mean you can own the whole stack

Kimi and GLM are both released as open-weight models. That is not a footnote — it is the whole strategic case. The same flagship model you reach over the API can be downloaded and served on hardware you own, inside a network you control, with no third party in the path.

For a team that genuinely cannot send source code to an external provider, the economics are worth doing honestly: a one-time hardware purchase — on the order of a single senior engineer's annual salary — buys a permanently in-house, frontier-class coding model. No per-token billing, no rate limits, no data-processing agreement, and nothing leaving the building. Set against a recurring per-seat API spend, that capital cost stops looking exotic and starts looking like procurement.

Juggler is built for that end state. Point --base-url at your own OpenAI-compatible server and the agent runs against your self-hosted weights. Pair it with Devscriptor — which is already 100 % local, with no cloud backend — and every layer of the stack sits inside your perimeter: the model, the code index, and the agent that drives them.

The model
Open weights, self-hosted
The code index
Devscriptor — local SQLite
The agent
Juggler — a single binary
Signature feature

Preserved Thinking

Carry a turn's reasoning into the next one instead of discarding it. Juggler replays each stored reasoning block back to the provider verbatim — which is what both vendors require. The two vendors spell it differently, with inverted polarity. Juggler translates.

Kimi (Moonshot)GLM (Z.AI)
Wire fieldthinking.keepthinking.clear_thinking
Enabled"all"false
Disabledfield omittedtrue
Supported bykimi-k2.6 and newer, plus the kimi-for-coding aliasesevery thinking model
Default when unseton, whenever thinking is onthe endpoint decides

Why it matters: a reasoning model that loses its own reasoning every turn re-derives the same conclusions on the next one — burning tokens and latency to arrive back where it started. Precedence is --thinking-keep KIMI_MODEL_THINKING_KEEP thinking_keep in the config file → the model's own default. Models whose schema lacks the field never receive it.

Under the hood

What “provider-native” actually means

Not marketing. These are the concrete things lm-bridge does that a shared OpenAI-compatible client structurally cannot.

Kimi K3 — the 1M-context flagship

Full support for Moonshot’s K3 flagship: 1,048,576-token context, image and video input, always-on reasoning, and low/high/max effort mapped onto --reasoning-effort. On the Coding Plan, K3 is its own model id (k3) — separate from the kimi-for-coding alias, which stays K2.7 at 262k.

Juggler defaults to kimi-for-coding (K2.7), matching the official Kimi Code CLI and avoiding K3’s slower, costlier max-effort default. Select K3 explicitly with `juggler --model k3` (or `export KIMI_MODEL_NAME=k3`, or `model = "k3"` in your config).

Version-aware context windows

Juggler carries a per-model context table — k3 and glm-5.2 at 1,048,576; kimi-for-coding, kimi-k2.7-code and kimi-k2.6 at 262,144; kimi-k2.5 and kimi-k2-0711-preview at 256,000 — instead of one conservative default.

A new minor version has never lowered a model’s window, so an uncatalogued glm-5.3 inherits glm-5.2’s 1M window rather than collapsing to a 128k default — which would make the agent compact its context far more often than it needs to.

Moonshot schema normalization

A dedicated pass rewrites JSON Schemas so Moonshot’s validator accepts them.

Dereferences local $ref pointers (#, #/$defs/X, #/definitions/X), detects and preserves circular references, drops definition buckets that are no longer referenced, injects missing type fields, and repairs conflicting enum/const vs. type.

Native Z.AI endpoints

Standalone Z.AI /web_search, /tools, and /reader HTTP resources, mirroring the official z.ai SDK — not bolted on through chat completions.

Juggler’s default GLM base URL is Z.AI’s Coding Plan endpoint, which preserves reasoning by default (the standard API endpoint does not). Leaving thinking_keep unset lets that endpoint default stand.

Kimi builtin_function & prompt caching

Kimi’s builtin_function tool type is passed through natively, and prompt_cache_key is set so Moonshot can reuse cached prefixes across turns.

Tool-call IDs are sanitized and deduplicated, and streaming tool-call deltas are routed in parallel so multiple concurrent calls assemble correctly.

Deep integration

Devscriptor, wired in on first run

Juggler auto-detects an installed Devscriptor and registers it as the devscriptor MCP server — 75 tools, zero configuration.

Discovery & startup

  1. 1$DEVSCRIPTOR_BIN — an explicit path
  2. 2$PATH
  3. 3Platform install directories (per-OS)
Started as devscriptor mcp with a 60 s startup timeout and a 180 s per-tool timeout. An explicit [[mcp_servers]] entry named devscriptor always wins over auto-detection. Run juggler doctor to confirm it connects.

A permission policy that fits

Without a policy, an agent would prompt on nearly every Devscriptor call — defeating the point of an indexed code-intelligence server. Juggler ships one tuned for it:

Prompts

lsp_arch_setup · rename_symbol · prune_context · start/stop/restart_server

Allowed for the session

Everything else — scan, analyze, search, navigate, context notes, watcher. Path sandboxing is enforced independently, so an allow rule can never escape allowed_paths.

Registered as dynamic tools. Devscriptor's 75 tools would swamp a tool table on their own. Juggler announces them as one-line summaries and lets the model pull in what it needs with select_tools — so the initial context stays small and the full toolkit is still one call away.

The harness

Scaffolding that earns its keep

A good model still needs somewhere good to run. agent-core is the turn loop, the scheduler, the context manager, and the guardrails.

Conflict-aware parallel tools

The scheduler builds a read/write/read-write tree over tool arguments and runs everything it can in parallel — without letting two writers collide on the same path.

Progressive tool disclosure

Large MCP servers do not flood the tool table. Dynamic tools are announced as one-line summaries; the model calls select_tools to load what it actually needs.

Two-stage context compaction

Proactive compaction fires at 90 % of the token budget; an emergency pass catches a real overflow, including GLM’s 1261 context-length error. Genuine user messages are protected with a head/tail split.

Repeated-call guard

Identical calls inside one step run once. A call repeated across steps draws an escalating reminder at streaks of 3, 5 and 8 — and the turn is stopped outright at 12.

Sub-agents & swarms

agent and agent_swarm delegate work. A headless run holds the turn open until sub-agents reach a terminal state and feeds their results back, instead of exiting and killing them.

Rule-based permissions

Three modes (default, accept_edits, bypass_permissions) plus ordered rules where the first match wins. Path sandboxing is enforced independently, so an allow rule can never escape allowed_paths.

Background work & scheduling

Background tasks outlive the turn that started them. task_list / task_output / task_stop manage them; cron_create schedules recurring work.

Three front-ends

Headless (-p) for scripting and CI, an interactive REPL, and an optional TUI behind a Cargo feature. Sessions persist as JSONL records and export to a ZIP archive.

Tools

26 built-in tools

What a fresh agent advertises before a single MCP server is attached. MCP tools are namespaced mcp__<server>__<tool>.

Filesystem

6
read_filewrite_fileedit_filelist_dirglobgrep

Execution

1
bash

Web

2
fetchweb_search

Planning

3
todo_listenter_plan_modeexit_plan_mode

Delegation

2
agentagent_swarm

Background work

3
task_listtask_outputtask_stop

Scheduling

3
cron_createcron_listcron_delete

Goals

2
create_goalget_goal

Interaction

2
ask_userask_user_question

Media

1
read_media

Tool disclosure

1
select_tools

update_goal and set_goal_budget are also registered but stay hidden until a goal is active. The whole table is pinned by a snapshot test — adding or renaming a tool fails the build until the documented list is updated.

Providers

Three providers, one binary

Out of the box you get each vendor's flagship. Override the model with --model and the endpoint with --base-url. Any other --provider value is rejected at startup.

--provider kimi

Moonshot.ai

Default model
kimi-for-coding
Context window
262,144
Default base URL
https://api.kimi.com/coding/v1
API key
KIMI_API_KEY
--provider glm

z.ai (Zhipu)

Default model
glm-5.2
Context window
1,048,576
Default base URL
https://api.z.ai/api/coding/paas/v4
API key
ZAI_API_KEY
--provider openai

Any OpenAI-compatible endpoint

Default model
gpt-4o-mini
Context window
Default base URL
https://api.openai.com/v1
API key
OPENAI_API_KEY

Self-hosting? The openai provider speaks generic /chat/completions, so pointing --base-url at your own inference server runs Juggler entirely against weights you host yourself.

Setup

Two keys that look identical, and only one of them works

Both vendors sell the same models twice — a Coding Plan subscription and pay-per-token API credits — on separate endpoints. Each endpoint rejects the other's key with invalid_authentication_error, which reads like a typo and sends people off checking their clipboard. juggler init asks which key you hold and routes it to the door that opens.

One command

$ juggler init

  Which provider?
    1) Kimi   (Moonshot)  — kimi-for-coding / K2.7, 262k context
    2) GLM    (z.ai)      — glm-5.2, 1M context
    3) OpenAI-compatible endpoint

  Which key do you have? (they are not interchangeable)
    1) Coding Plan subscription
    2) API credits (pay-per-token)

  Paste your API key: ••••••••
  checking… key accepted
  using kimi-for-coding on https://api.kimi.com/coding/v1

The key is verified against the endpoint before anything is written, and never echoed. The config is saved mode 600. Re-run it any time to switch provider or rotate a key — it edits the file rather than replacing it, so your permission rules and MCP servers stay put.

Where each key goes

--billingModelEndpoint
kimi
subscriptiondefault
kimi-for-codinghttps://api.kimi.com/coding/v1
kimi
api
kimi-k2.7-codehttps://api.moonshot.ai/v1
glm
subscriptiondefault
glm-5.2https://api.z.ai/api/coding/paas/v4
glm
api
glm-5.2https://api.z.ai/api/paas/v4

--region china selects the mainland deployments (api.moonshot.cn, open.bigmodel.cn). Kimi's Coding Plan has a single endpoint, so the region does not apply to it. kimi-for-coding and kimi-k2.7-code are the same K2.7 generation under the two names each endpoint serves it as.

If the endpoint rejects your key, Juggler offers the other plan rather than leaving you to guess — re-verifies against it, and saves whichever one actually worked. The same scoping holds afterwards: model, base_url, and api_key belong to the provider they were configured for, so switching provider re-derives them instead of sending a Kimi key to Z.AI.

Configuration

Configure once, or not at all

juggler init writes this for you. Juggler reads ~/.config/juggler/config.toml or .juggler.toml in the current directory. CLI arguments override environment variables, which override file values.

provider = "glm"
api_key  = "zai-..."

# Which quota the key is billed against: subscription (default) | api.
# This picks the endpoint, and with it the default model.
billing  = "subscription"
region   = "global"        # global (default) | china

# Both optional: leaving them out lets provider + billing choose the
# flagship and its endpoint, so a newer flagship is picked up without
# editing anything. Set them to pin a specific one.
# model    = "glm-5.2"
# base_url = "https://api.z.ai/api/coding/paas/v4"

# Sampling / budget
thinking         = true
thinking_keep    = false   # Preserved Thinking
reasoning_effort = "high"  # GLM-5.2 and newer only
max_tokens       = 8192

# Filesystem and context
allowed_paths                 = ["/srv/data"]
additional_dirs               = ["../sibling-repo"]
project_context_files         = ["NOTES.md"]
project_context_budget_tokens = 2048

permission_mode = "accept_edits"

# Devscriptor is auto-detected — no entry needed here.
[[mcp_servers]]
name = "http-tools"
url  = "http://localhost:8000/mcp"

# Permission policy; first matching rule wins.
[[permissions.rules]]
name   = "allow-reads"
tool   = "read_file"
action = "allow"
Permission modes
default · accept_edits · bypass_permissions
Reasoning effort
none → minimal → low → medium → high → xhigh → max
Subcommands
init · doctor · version · completions · export · tui
Free for individual & non-commercial use

Download Juggler 1.1.1

One universal binary per platform. No package managers, no build-from-source.

Free forever for personal and non-commercial work. Commercial use is free today too — see licensing.

Better together

Juggler drives the model. Devscriptor gives it a map of your codebase. Install both and the agent starts with 75 code-intelligence tools already on the table.