* feat: support a configurable secondary model for subagents
* refactor: move the subagent model config to a consumer-neutral [secondary_model]
The secondary model becomes a model-domain concept next to default_model so
future consumers beyond subagents can share it: [secondary_model] model /
effort in config.toml, KIMI_SECONDARY_MODEL / KIMI_SECONDARY_EFFORT env
overrides, and the Agent / AgentSwarm per-spawn choice renamed from
"subagent" to "secondary".
* docs: replace "v2 engine only" notes with the concrete effective surfaces
State that [secondary_model], its env overrides, the Agent/AgentSwarm
model parameter, and SYSTEM.md take effect only under kimi web and
experimental kimi -p (the TUI ignores them), and that --agent /
--agent-file are available only under experimental kimi -p. Also drop
the SYSTEM.md claim of parity with --agent/--agent-file, which was
inaccurate: SYSTEM.md is an agent-core-v2 app-domain feature and also
applies under kimi web, while the flags are gated at the CLI.
* fix: review follow-ups for the subagent secondary model
- Drop the "cheaper" claim from the Agent/AgentSwarm model parameter
descriptions and the advertised model list — the secondary model is
not necessarily the cheaper one.
- Downgrade the changeset to patch, note the kimi web / experimental
kimi -p effective surface, and tighten the wording.
- Remove the onWillRestore stub fields from two lifecycle stubs; they
belong to upcoming lifecycle work, not to this change.
* fix(agent-core-v2): prevent ghost agents from invalid model bindings
* fix: narrow the secondary-model error hint to missing-alias failures
The model catalog's not-configured throw now carries details.model, and
wrapSubagentModelError only decorates errors whose details.model matches
the bound model. Malformed [models.*] entries and unrelated config.invalid
failures during agent creation pass through untouched instead of being
misattributed to an invalid secondary-model alias.
* fix: mark subagent resume semantics as breaking
* chore(agent-core-v2): follow header-only comment convention
* fix(agent-core-v2): await agent restore preparation
* feat(agent-core-v2): support agent model preferences
* Update subagent-secondary-model.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* fix: validate secondary models before agent creation
* Delete .changeset/secondary-model-startup-warning.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* Add secondary_model config section for subagents
Individual agents can override this via the new `model_preference` field in their agent file.
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat(agent-core-v2): support override patches in [secondary_model]
The recipe is now `model` plus the flattened ModelOverride field set.
With any patch field set, a config overlay synthesizes a derived
registry entry (base copy, patch merged into overrides, aliases
dropped) so subagent spawning rides the standard effectiveModelConfig
merge; with none, subagents bind the pointed entry directly.
`default_effort` replaces `effort` (KIMI_SECONDARY_EFFORT rebinds)
and doubles as the explicit subagent thinking; unset, thinking
resolves naturally instead of inheriting the caller. The overlay
strips the derived entry (and any defaultModel pointer to it) from
writes, and the kap-server GET /models route hides it from pickers.
* fix(agent-core-v2): fire section events for overlay-rewritten domains
rebuildEffective only committed the caller-named domains, so a
ConfigEffectiveOverlay or section env binding that rewrote a sibling
domain (setting [secondary_model] synthesizes a derived models entry;
removing the recipe retracts it) left consumers of the models section
stale. Widen the commit candidates with every domain the recompute
actually changed; commit() deepEqual-guards each candidate, so the
widening costs nothing.
* feat(agent-core-v2): gate secondary model behind experimental flag
* Update subagent-secondary-model.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat: configure web search/fetch services via KIMI_WEB_* env vars
KIMI_WEB_SEARCH_BASE_URL / KIMI_WEB_SEARCH_API_KEY and
KIMI_WEB_FETCH_BASE_URL / KIMI_WEB_FETCH_API_KEY overlay the
[services] config section field by field in both engines (env wins
over config.toml), so the WebSearch and FetchURL backends can be
pointed at a Moonshot service without OAuth login. The v2 engine now
also honors the explicit [services.moonshot_fetch] section
(config > managed OAuth > local), which it previously parsed but
never consumed.
* fix(agent-core-v2): guard stripServicesEnv against clearing the services section
config.replace(SERVICES_SECTION, undefined) — the logout deprovisioning
path in authService — passes undefined straight into the section strip,
and the composed stripServicesEnv dereferenced it (value[key]), throwing
TypeError and aborting logout mid-cleanup. Add the same isPlainObject
guard the other strip implementations (stripProvidersEnv,
stripEnvBoundFields) already have, plus a regression test that also
locks in the env overlay staying effective after the file value is
cleared.
* style(agent-core-v2): follow header-only comment convention
* fix: isolate env web service credentials
* Add env vars for web search and fetch services
The new environment variables `KIMI_WEB_SEARCH_BASE_URL`, `KIMI_WEB_SEARCH_API_KEY`, `KIMI_WEB_FETCH_BASE_URL`, and `KIMI_WEB_FETCH_API_KEY` take priority over the corresponding fields in `config.toml`. The `kimi web` backend now honors the `[services.moonshot_fetch]` config section.
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat: add global default MCP server startup timeout config
Add a `[mcp] startup_timeout_ms` config.toml section with a
`KIMI_MCP_STARTUP_TIMEOUT_MS` env override as the global default MCP
server connection (startup + tool discovery) timeout. Precedence:
per-server `startupTimeoutMs` in mcp.json > env var > config.toml >
built-in 30s default.
* feat: add global default MCP tool call timeout config
Extend the `[mcp]` section with `tool_timeout_ms` and the
`KIMI_MCP_TOOL_TIMEOUT_MS` env override as the global default for
single MCP tool calls, mirroring the startup timeout: a per-server
`toolTimeoutMs` in mcp.json still wins, and unset entries fall back to
the SDK built-in 60s default.
* chore: shorten the mcp timeouts changeset
* feat(agent-core): add global default MCP server timeout configs
Port the `[mcp]` section (`startup_timeout_ms` / `tool_timeout_ms`)
and the `KIMI_MCP_STARTUP_TIMEOUT_MS` / `KIMI_MCP_TOOL_TIMEOUT_MS` env
overrides to agent-core (v1), mirroring the v2 semantics: per-server
fields in mcp.json > env vars > config.toml > built-in defaults. The
v1 TOML loader gains explicit `mcp` read/write mappings, and both
connection-manager construction sites (Session, testGlobalMcpServer RPC)
pass the resolved defaults through.
* fix(agent-core): validate and apply MCP timeout defaults
* fix: propagate MCP startup timeout to SDK requests
* refactor(agent-core-v2): resolve MCP default timeouts at connect time
Keep the session connection manager synchronously lazy instead of gating
its existence on config readiness: resolveDefaultTimeouts is read from the
mcp config section at each (re)connect, so AgentMcpService's eager
construction stays unconditionally safe and reconnects pick up changed
preferences. The initial connect still awaits config.ready for a
deterministic snapshot. Also restore the ISessionMcpService method docs,
bump the changeset to minor, and fix the v1 env-parse comment.
* chore(agent-core-v2): regenerate config manifest for the mcp section
* Add global default MCP server timeouts configuration
Specify the new global default MCP server timeouts in both the config file and environment variables.
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* docs(cron): drop references to the non-existent `kimi resume` command
- point user docs at the real resume command `kimi --session`
- reword cron tool descriptions and code comments in agent-core and
agent-core-v2 to describe session resume without naming a subcommand
* chore: add changeset for cron docs wording fix
* fix: gate always-thinking inference to OpenAI wires, plus review follow-ups
- catalog: strip the inferred alwaysThinking marker on non-OpenAI wires so
Claude/Gemini keep their native off; verified against live models.dev
- v1 provider-manager: honor per-alias baseUrl on kimi/google-genai/vertexai
- thinking: rewrite the PHASE-6 contract comment, drop three dead
cannot-disable warning branches, normalize requested effort in v1 to
match v2
- tests: input-cap compaction preference in both engines, kap-server WS
status cap, v2 legacy status input cap
- docs + changeset: new model fields, catalog-refresh behavior, kap-server
* fix: strip always-thinking only where the wire encodes a true off
The previous gate kept the marker only on the OpenAI wires, which wrongly
stripped it from Gemini 3 on the Google wires: its floor is
thinkingLevel MINIMAL with suppressed thoughts — still reasoning — so an
Off option there would be a lie. The criterion is now the wire's encoding,
not its family: strip only on anthropic and kimi, the two wires with a
protocol-level `thinking: {type: 'disabled'}` that the catalog's effort
list can never show. Verified against live models.dev data: google now
marks exactly the gemini-3 family (10), anthropic and moonshotai stay 0.
* docs(agent-core-v2): move the strict-validation contract into the thinking.ts file header
The scoped guide keeps comments in the top-of-file block only; the
function JSDoc shrinks to a short what-it-answers note matching its
neighbors. No behavior change.
* fix: stop advertising Claude thinking efforts for non-Claude models
Models served over the Anthropic protocol whose names carry no Claude
marker (e.g. a catalog-imported Kimi K3) no longer inherit the latest
Opus effort list, so the model selector stops offering levels the model
does not accept. The models.dev catalog import now also parses
reasoning_options and records the declared effort levels on the model
alias, so K3 offers its real levels (low / high / max).
* fix: consume deprecated, override, and input-limit metadata from the models.dev catalog
- Models declared status=deprecated in the catalog are no longer
offered for import.
- Per-model provider overrides on gateway providers (an npm package
targeting an Anthropic SDK plus a usable endpoint) now land as alias
protocol and base_url, so those models are served over the right
protocol and endpoint; overrides without a usable URL are skipped.
- A declared limit.input now sizes the context budget instead of the
larger total context window (e.g. gpt-5: 272k instead of 400k).
The model alias schema gains an optional base_url field (not accepted
in overrides) that Anthropic wire resolution prefers over the
provider-level base URL.
* fix: honor thinking-disable semantics and the OpenAI-compatible fallback in catalog imports
- reasoning_options 'none' is the model's off encoding: off_effort flows
from the catalog through the model alias to the OpenAI wire providers,
so turning thinking off sends 'none' instead of omitting the effort
field; models with effort levels but no way to disable thinking are
imported as always_thinking and no longer offer an Off option.
- Bare Claude family aliases (e.g. sonnet-latest) recover the inferred
Anthropic effort profile; v2 comment conventions restored.
- Providers whose SDK the catalog does not type now fall back to the
OpenAI-compatible wire (with a visible "guessed" note) instead of
being refused; imports lacking a usable endpoint ask for one
(--base-url on the CLI, a prompt in the TUI). Proprietary SDKs
(Amazon Bedrock), unrecognized explicit types, and env-placeholder
URLs are refused with a clear reason.
* fix: align catalog imports with the reference models.dev consumer
- A JSON null tier in declared effort values is now read as the 'none'
off-encoding (previously such models were wrongly imported as
always-thinking with no way to turn reasoning off).
- Alpha-status models are filtered out alongside deprecated ones.
- Models whose per-model provider override targets a wire that cannot be
expressed per-model (e.g. Claude on google-vertex, whose wire here is
Gemini-mode Vertex, or gpt entries on an Anthropic provider) are
skipped instead of being imported under the silently wrong protocol.
- interleaved: true no longer pins reasoning_content: the provider's
default three-field scan is wider and the pinned key only narrowed
reasoning parsing for gateways answering with another field name.
* fix: require endpoints for Anthropic-compatible catalog imports and honor --base-url
- catalogProviderNeedsBaseUrl now covers the Anthropic wire: a
non-official Anthropic-compatible vendor without a concrete catalog
endpoint (e.g. google-vertex-anthropic) must supply --base-url / the
TUI prompt instead of silently falling back to the default Anthropic
endpoint.
- --base-url now takes precedence over the catalog-declared endpoint,
and an empty --base-url is rejected instead of persisting a blank
endpoint.
* fix: enforce always-on thinking on every wire and refuse Cohere at import
A model that declares always_thinking (e.g. a catalog-imported gpt-5)
no longer resolves to a dishonest off state via thinking.enabled=false
or an SDK/ACP off request: resolution clamps to the model's default
effort on every wire instead of letting upstream keep reasoning while
the UI reports Off. The Anthropic warn-and-send path for unlisted
effort levels is unchanged. Cohere's proprietary SDK joins Amazon
Bedrock on the import-refusal list instead of being guessed as
OpenAI-compatible.
* fix: harden catalog import edge cases
- An explicit but unrecognized catalog type is now refused before
npm/id inference, so a future catalog protocol is never silently
miswired through the OpenAI fallback.
- User-supplied --base-url values for Anthropic-wire providers get the
same trailing-/v1 normalization as catalog endpoints, avoiding
/v1/v1/messages requests.
- The TUI import prompt rejects env-placeholder base URLs like the CLI
does.
* fix: await the floating assertion promise in the catalog add CLI test
* refactor: unify catalog import resolution into a single decision function
Wire-type inference, the OpenAI-compatible fallback, proprietary-SDK
refusal, endpoint adaptation, and the base-URL requirement are now
produced together by resolveCatalogImport, one pure resolver consumed
by both the CLI and the TUI — replacing the cooperating predicates
(inferWireType, isGuessedWireType, catalogProviderNeedsBaseUrl) whose
permutations kept producing edge cases. No behavior change.
* fix: close configured-off clamp hole, keep inferWireType compat, carry same-wire override endpoints
- A configured thinking.effort = "off" no longer bypasses the
always-on clamp: it is treated as absent and the model default
applies, mirrored on both engines.
- The previously public inferWireType stays as a deprecated
compatibility wrapper over resolveCatalogImport so existing SDK
consumers do not break on a patch release.
- Catalog model overrides that stay on the provider's wire but declare
their own endpoint now persist it on the alias (and the v1 OpenAI
wire branches honor alias-level base URLs like the Anthropic branch).
* fix: split total window from input cap and close override/endpoint gaps
- max_context_tokens once again means the total context window (used by
completion budgeting); a model's declared input limit is tracked as
max_input_tokens, which compaction, context-splice and usage-ratio
checks prefer — fixing the over-clamping introduced when the input
cap was stored as the context budget.
- A catalog endpoint declared only as an env placeholder now always
produces needs-base-url (official SDK included), so credentials are
never sent to the public vendor host by default.
- api-only per-model overrides are honored as same-wire endpoint
changes; overrides targeting another known but inexpressible wire
(e.g. google-genai on an OpenAI gateway) are skipped; same-wire
models whose declared endpoint is an unusable placeholder are
skipped instead of silently rerouted.
* style: drop a function-level comment from the v2 thinking resolver
* chore: consolidate the PR's changesets into two user-facing entries
* feat(config): add env overrides for loop control and background task limits
Add three operational environment overrides, resolved as env > config.toml
> default in both engines (agent-core and agent-core-v2), matching the
existing KIMI_IMAGE_MAX_EDGE_PX / KIMI_SUBAGENT_TIMEOUT_MS pattern:
- KIMI_LOOP_MAX_STEPS_PER_TURN overrides loop_control.max_steps_per_turn
- KIMI_LOOP_MAX_RETRIES_PER_STEP overrides loop_control.max_retries_per_step
- KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS overrides background.max_running_tasks
Invalid values are ignored and fall back to the config value. The v2 engine
resolves them through config section env bindings (effective-only, never
persisted); the v1 engine resolves them at the consumption point.
* fix(config): strip env-bound fields before persisting config writes
Environment overrides resolved into the effective config could be echoed
back through IConfigService.set/replace (e.g. GET then POST
/api/v1/config) and persisted into config.toml, outliving the env var.
This affected the pre-existing image / subagent / keep-alive bindings as
well as the new loop control and max-running-tasks bindings.
Extend ConfigStripEnv with a getEnv parameter and add a shared
stripEnvBoundFields helper: while a field's env var is set, writes
restore the field's raw on-disk value (or drop it) instead of persisting
an echoed env value; when unset, normal writes persist. Register it for
the loopControl, task/background, image, and subagent sections.
Also fix ConfigService.stripEnv looking up rawSnake by the camelCase
domain key; on-disk sections are keyed snake_case.
* fix(config): honor binding parsers when stripping env fields on persist
An invalid env value (e.g. KIMI_LOOP_MAX_STEPS_PER_TURN=abc) is ignored
on the read path but still marked the field env-owned on the write path,
so a config write for that field was silently dropped.
stripEnvBoundFields now derives the guard from the section's envBindings
and skips fields whose env value fails the binding's parse, so invalid
env values are ignored on both paths — and the duplicated field/env
descriptor list is gone.
Also drop function-level comments added beside helpers; agent-core-v2
keeps comments solely in the top-of-file block, so the strip semantics
now live in the config.ts / configService.ts headers.
* fix(config): re-apply env overlays from the env-free base on every read
Two follow-ups from review:
- ConfigService.get()/getAll() re-applied section env bindings on the
already-overlaid effective cache (and get() mutated it in place), so a
valid override degraded to invalid or unset kept serving the stale
value until the next reload — and an echoed stale value could then be
persisted by a config write. Reads now recompute from a cached
env-free validated base, so degraded or removed env values fall back
to the file immediately.
- stripEnvBoundFields restored env-owned fields from the raw snake
sub-object, missing values persisted under legacy keys (e.g.
max_steps_per_run). Section stripEnv now receives the env-free,
fromToml-normalized raw base, so legacy aliases are honored.
* test(kap-server): retry temp-home cleanup in auth tests
auth.test.ts removed its temp home with a plain recursive rm, which races
late async writers in the server shutdown path and flakes with ENOTEMPTY
(seen on main CI and locally on main). Harden the cleanup with the same
maxRetries/retryDelay options sessions.test.ts already uses.
* chore(changeset): consolidate the env-override changesets into two entries
One feature entry (loop/background env overrides, both engines plus the
CLI) and one fix entry (env values persisting on writes and sticking
after degrade/unset, agent-core-v2 plus the CLI).
* fix(config): clear fully-stripped sections and refresh overlay domains on get()
Two follow-ups from review:
- stripEnvBoundFields returned an empty object when every written field
was env-owned, so a section with a non-empty default (e.g. subagent)
stored raw = {} and the default stopped applying until the next
reload. A fully stripped result now clears the raw section instead.
- get(domain) only recomputed env overlays for sections with env
bindings, so domains written solely by a ConfigEffectiveOverlay
(models / defaultModel from KIMI_MODEL_NAME) kept serving stale cached
values after the env changed. get() now derives every non-memory
domain from the fresh env-free base, matching getAll().
* test(config): drive the get() overlay freshness test with an inline overlay
The previous version imported the model env overlay to activate it, which
the CI runners failed to resolve from this test file (both tsgo and
vitest, while the same specifier resolves elsewhere — not reproducible
locally). An inline ConfigEffectiveOverlay double exercises the same
ConfigService contract with a tighter seam and no module dependency.
* fix(config): preserve unknown fields on full strip and clone nested env targets
Two follow-ups from review:
- stripEnvBoundFields cleared the raw section whenever the stripped
result was empty, so an env echo write could drop unknown
forward-compatible fields from the TOML table. An emptied section now
keeps its raw table while the env-free base still holds other fields,
and is cleared only when nothing remains (defaults keep applying).
- applyEnvBindings reused nested child objects in place, so a nested
env binding on a persistable key would mutate the env-free validated
base and serve stale values after the env var is removed. Children
are now cloned before descending.
* fix(config): keep the env-free base when a strip leaves nothing to persist
Returning {} for a fully stripped write stored the empty object into the
raw layer while the TOML table survived via rawSnake, so the bases
diverged; a second echo write then saw an empty base, cleared the
section, and deleted the table — dropping unknown forward-compatible
fields. When nothing persistable remains, the write is now a no-op for
the section (the env-free base is kept as-is), and the section is
cleared only when the base is empty.
* fix(config): revalidate stripped results so restored raw values never persist
A strip may restore on-disk values from the unvalidated raw base (e.g.
an env-masked invalid field), smuggling them past the merge-time
validation into the stored config: the section would then fail the next
buildValidated pass, dropping accompanying valid edits from the runtime
while the invalid value stayed persisted. set() now revalidates the
stripped result — discarding the parse output so unknown fields survive
— and rejects the write instead, matching replace().
* feat: support custom agent files
Discover Markdown+frontmatter agent definitions from user, project, and configured directories; merge them into a session-level profile catalog (priority: builtin < user < extra < project < explicit). Custom agents work as subagents via the Agent tool and as the main agent.
- agent-core-v2: new agentFileCatalog domain (discovery/parsing/profile factory, mirroring skillRoots/parseFrontmatter) and sessionAgentProfileCatalog merged view; AgentProfile.tools becomes optional (undefined = all tools) and gains disallowedTools deny list, evaluated in profileService.isToolActive and persisted in session wire records so resume keeps the gate even if the file is gone
- CLI: restore --agent/--agent-file for the v2 print runner (KIMI_CODE_EXPERIMENTAL_FLAG); the v1 TUI rejects them with a clear v2-only error
- kap-server/protocol: optional profile field on prompt submission with first-bind semantics (same-name no-op, different name rejected)
- docs: custom agents section (en/zh) + config/CLI references + changeset
* chore: shorten custom agents changeset
* fix(agent-core-v2): stringify caught errors in agent catalog log calls
* docs: drop v2 engine notes from custom agents docs
* fix: align custom agent binding semantics across engine and edges
- agent-core-v2: bind now owns the first-bind invariant — switching
profiles after bind throws profile.already_bound (checked again in the
synchronous segment before the first wire dispatch, so concurrent binds
cannot both pass); unknown names throw profile.unknown; same-name
rebinds keep the persisted thinking effort.
- kap-server / CLI: edges degrade to error mapping / same-name no-op
instead of their own divergent guards.
- agent files: reject non-string mode values, honor disallowedTools in
the append-mode Skill probe, pass --agent-file through unresolved so
the engine can expand ~, reject empty --agent-file values.
- session catalog: ready is recoverable via reload() after a fatal
source failure, and agent-file discovery is kicked at session
materialize so resumed sessions see file agents from the first turn.
- docs: first-bind semantics, name: agent override, tools: [] meaning,
--agent-file last-wins.
* fix: tighten custom agent behavior
* fix: address custom agent review findings
* fix(agent-core,agent-core-v2): keep active-tool wire records replayable by v1
Binding a profile without a tool allowlist (the default for file-defined
custom agents) persisted a tools.set_active_tools record with no `names`
key. v1 clients discover v2 sessions through the shared session index and
replay newer wire versions without migration, so the record crashed v1
resume with a TypeError and wedged the session permanently.
The v2 engine no longer writes the record when the base set is already
"every tool active" (its absence encodes the same state), and v1 replay
now skips records that lack `names` as defense in depth for wires already
written by preview builds. A same-name rebind that resets an allowlist to
"all tools" has no v1-safe encoding and is left as a documented gap (no
production caller today); a future tools.reset_active_tools Op is safe
because v1 silently no-ops unknown record types.
* fix(agent-core-v2): tolerate unreadable directories in agent-file discovery
A single unreadable subdirectory (EACCES anywhere in the scanned tree)
previously aborted the whole discovery pass, zeroing every agent of that
source on every session start with one path-less warning. The walker now
skips-and-warns per directory below the root (mirroring the skill
discovery it parallels), root-level failures are isolated per root so one
bad root no longer takes its siblings down, and only a genuinely transient
whole-fs outage (os.fs.unavailable) still propagates so the session
catalog keeps its previous contribution. Source-level warnings now name
the offending path, and repeated skip warnings are capped with a summary
that samples the suppressed paths.
Also consolidates the path primitives (~ expansion, base-relative
resolution, realpath type probes) shared by the root resolvers, the
walker, and the explicit-file source into agentFileCatalog/paths.ts, and
tightens parser diagnostics: frontmatter null is treated as absent, and a
present-but-wrong-typed name/description reports a type error instead of
"missing".
* fix(agent-core-v2): warn when a same-name builtin suppresses a file profile
A directory-discovered agent file colliding with a builtin profile
without override: true was silently dropped at merge time. The suppression
now logs a warning naming the profile and the opt-in.
* refactor(agent-core-v2): pass skillActive explicitly to renderSystemPrompt
The third parameter was a full tool list used only for includes('Skill'),
which forced the agent-file profile factory to answer a boolean question
with sentinel lists. The template now takes an explicit skillActive flag;
a skillActiveFor helper keeps builtin call sites derived from their tool
arrays.
* refactor(agent-core-v2): fall back to the configured default model in bind
BindAgentInput.model is now optional: the engine resolves a missing model
against the configured defaultModel and throws model.not_configured when
neither is set, so edges no longer each re-implement the fallback.
* fix(agent-core-v2,kap-server): reject unsupported thinking atomically at first bind
A REST prompt carrying profile + an unsupported thinking effort bound the
session first and failed setThinking after, wedging the session on an
identity the user never successfully used. The effort is now validated up
front when the caller marks it as an explicit request (strictThinking):
the bind rejects before any await or state mutation, and the requested
effort rides along in the bind instead of a separate setThinking. Internal
spawn/fork paths pass inherited thinking without the flag and keep the
previous clamp behavior — a persisted effort that drifted out of the
model's support list must not break subagent spawning. The route's
now-redundant model fallback is dropped in favor of the engine-side
default.
* fix(agent-core-v2): await the agent profile catalog at session materialize
The catalog's ready promise was only kicked, so a resumed session's first
turn could render the Agent tool description without the file-defined
agent types. Discovery is local-fs and cheap, so materialize now awaits
it; ready only rejects for a fatal explicit-source error, which is exactly
the case that should fail fast. A failure there now also removes and
disposes the half-materialized handle instead of leaving it registered in
the session cache.
* test: cover the --agent-file fatal path and tidy profile registration hygiene
The v2 print CLI now has a test asserting an invalid --agent-file fails
before any turn. The denylist profiles in binding.test.ts register in a
beforeAll (idempotent, scoped to the describe's run window) instead of at
module scope during collection.
* docs: align custom agent docs with v2-engine gating
--agent/--agent-file are rejected without the v2 engine, so restore the
requirement in the Agents and Command Reference pages (and use
KIMI_CODE_EXPERIMENTAL_FLAG=1 in the examples), note that tool lists only
shape model-visible disclosure (permission rules are the enforcement
layer), and remind authors of delegation-bound agents to state the
handoff contract in the prompt body.
* test(agent-core-v2): revert unrelated style churn in fs/workspace tests
Keep these files' diff limited to the realpath fakes the feature needs;
the lint-preference rewrites belong to a separate cleanup.
* feat(agent-core-v2): add permanent system prompt override via SYSTEM.md
Read $KIMI_CODE_HOME/SYSTEM.md on every startup and inject it as the default main-agent profile (name "agent", override: true), replacing the builtin default system prompt while inheriting builtin tools and description. Missing or empty files are ignored; unreadable files warn and fall back to the builtin profile.
The body supports variable substitution (${skills}, ${agents_md}, ${cwd}, ${cwd_listing}, ${os}, ${shell}, ${now}); unknown variables pass through verbatim.
Priority: --agent-file / --agent / project override > SYSTEM.md > same-name user-scope scan files.
* feat(agent-core-v2): gate tools globally and accept session disabledTools
Add a [tools] config section: "enabled" acts as a global allowlist (empty = unconstrained), "disabled" as a denylist applied on top, both intersected with the active profile's policy in isToolActive (mcp glob supported).
Plumb a session-persistent disabledTools parameter through the stack: v2 RPC PromptPayload, REST "disabled_tools" (protocol and kap-server parallel schemas), klient contract/facade, and node-sdk. The server applies it via profileService.setSessionDisabledTools, which replaces the client-owned denylist, keeps the profile's own deny, persists across resume, and rejects calls before a profile is bound with profile.not_bound (mapped to 40001). v1 core-api gains a type-only field and ignores it.
* fix: enforce session tool policy across agents
* fix(agent-core-v2): enforce tool policy at execution
* fix(agent-core-v2): align subagent tool descriptions with policy
* fix(agent-core-v2): harden custom agent policy state
* fix(agent-core-v2): harden custom agent lifecycle
* refactor(agent-core-v2): persist profile binding in a single profile.bind record
* fix(agent-core-v2): skip unreadable paths during agent file discovery
* fix(agent-core-v2): exempt select_tools from the executor policy guard
- share one composed profile/global/session tool-policy evaluation between
the executor gate and prompt rendering instead of two verbatim copies
- tolerate context-build failure in system prompt refresh instead of
rejecting callers (config watcher void-fire, session policy fan-out)
* test(agent-core-v2): resolve profile and tool-policy SUTs by interface
- drop the Object.assign patching of tool-policy methods onto the shared
profile service; rename describes so the SUT ownership is accurate
- classify profile.bind as v2-only with the accepted v1-replay tradeoff
documented, un-red the wire vocabulary guard test
- cover the select_tools guard exemption with an executor-level test
* fix(agent-core-v2): enforce explicit select_tools policy
* feat(agent-core-v2): accept Claude-style tool lists and rename agent-file mode to promptMode
* feat(agent-core-v2): unify prompt templating on ${var}
- Replace the nunjucks renderer with a single ${var} regex renderer
(unknown placeholders pass through verbatim) and drop nunjucks from
agent-core-v2.
- Merge the variable tables into one catalog shared by the builtin
system.md, SYSTEM.md, and agent file bodies; adds additional_dirs_info
plus code-composed blocks (windows_notes, additional_dirs_section,
skills_section).
- Replace the agent-file promptMode field with ${base_prompt}: bodies
are always rendered as templates, and ${base_prompt} expands to the
effective default profile prompt (honoring the SYSTEM.md override).
- Migrate the builtin system.md, goal reminders, compaction instruction,
and tool description templates to the same syntax.
* docs: complete agent priority chain and link SYSTEM.md precedence
* test: fix invalid custom agent fixture
* fix(cli): reject multiple agent selectors
* feat(agent-core-v2): add subagents allowlist to agent files
* fix(agent-core-v2): persist the subagent allowlist in the profile binding
The delegation allowlist now rides the profile.bind record like the tool
denylist, so a resumed session keeps enforcing it even when the source
agent file was deleted or changed. Agent/AgentSwarm resolve the caller's
allowlist from the persisted binding data instead of looking the profile
up in the live catalog.
* feat(agent-core-v2): warn on tool patterns that never match
Profile bind/apply and [tools] config changes now statically flag
entries that can never activate anything — wildcards without the mcp__
prefix (a bare * in an allowlist disables everything, in a denylist
nothing), incomplete mcp__ literals, and names no registered or
builtin-profile tool has — via a tool-pattern-no-match warning event,
once per pattern, instead of letting the tool set silently shrink. The
known-name vocabulary is the live registry plus literal names from the
builtin profiles, so flag-gated tools stay known and a typo in one agent
file cannot legitimize the same typo in another.
* docs: align --agent-file docs with the single-selector CLI
The flag accepts exactly one file and conflicts with --agent, but the
docs still described the earlier repeatable, composable design.
* docs: note the agent-file trust model and never-matching tool patterns
Spell out that project-scoped agent files can replace the default main
agent's whole system prompt (unlike AGENTS.md reference injection), and
list the three tool-pattern shapes that never match and now raise a
warning.
* chore: slim changeset wording to user-facing language
Drop wire record names, enforcement mechanics, and template syntax from
the entries; split the v1 resume fix into its own patch changeset; add a
patch entry for the tool-pattern warnings.
* chore: shorten changeset entries to one-line summaries
* Delete .changeset/v1-resume-v2-sessions.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* test: drop class-instance spread in sessionLifecycle test stub
* chore: clear the comments
---------
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
- the /web slash command now always starts a new server instead of offering to reuse a running one
- remove the kimi web kill and kimi web ps subcommands; foreground servers stop with Ctrl+C
- keep kimi server kill as a deprecated fallback that only stops servers started by a version before 0.28.0 (recorded in the legacy server lock)
* feat(kap-server): enable multi-server shared home by default
- always register kap-server instances under server/instances and drop the
legacy single-instance lock (acquireLock/getLiveLock/ServerLockedError)
- remove the multi_server experimental flag and its
KIMI_CODE_EXPERIMENTAL_MULTI_SERVER env var from agent-core-v2
- discover running servers via the instance registry in server
ps/kill/rotate-token, kimi web daemon reuse, and the desktop app
- remove the pending minidb changesets
* feat(cli): add per-instance targeting to server kill and ps
- `kimi server kill [serverId]` stops only the matching instance; without
an id it still stops the longest-running one, and an unknown id errors
with the live server ids listed
- `kimi server ps` lists connections grouped per server id (`--json`
nests them under a per-server object); an unreachable instance degrades
to a per-server note instead of failing the whole listing
- update the zh/en command reference and the multi-server changeset
* feat(cli): replace kimi server with the kimi web command tree
- `kimi web` now runs the local server in the foreground and opens the
browser; the background daemon (ensureDaemon / spawn / idle-exit) is
removed, so repeated runs simply start another instance on the next
free port
- drop the OS-service lifecycle (install/uninstall/start/stop/restart/
status) together with kap-server's svc layer
- `kimi web kill [serverId]`, `kimi web ps`, and `kimi web rotate-token`
manage instances from the registry
- the TUI /web command now connects to an already-running instance
instead of spawning a background daemon
- update the zh/en command reference, dev scripts, and tests
* feat(cli): route kimi server invocations to a deprecation notice
Any `kimi server …` call — bare or with any legacy subcommand/flags —
now prints a deprecation notice pointing at `kimi web` and exits 1,
instead of failing with an opaque "unknown command". The shim is
scheduled for removal in the next major version.
* feat(cli): add the `all` keyword to kimi web kill
`kimi web kill all` stops every live instance in the registry (ULIDs
can never collide with the keyword). Each instance still gets the API
shutdown + SIGTERM/SIGKILL treatment; a failure on one instance does
not stop the sweep and is reported at the end.
* docs(changeset): drop the web-foreground-default changeset
The kimi web command tree replaces the foreground-default behavior this
entry describes: --background, daemon reuse, and the version-mismatch hint
no longer exist, so the pending entry would contradict the actual release
notes.
* docs(changeset): tighten the multi-server entry wording
* feat(cli): let /web pick a running server or start a new one
The /web picker now lists the live instances from the registry with
their versions (flagging a CLI mismatch) instead of only connecting to
the longest-running one, and offers starting a new server: that one
runs in the foreground attached to the terminal after the TUI exits,
via the restored exit-takeover wiring. formatReadyBanner is exported
and adapts its Stop hint to Ctrl+C for the attached case.
* feat(cli): skip the /web picker and start a new server when none is running
* feat(cli): run kimi web and the TUI /web command in the foreground by default
kimi web and /web now start the server attached to the terminal (Ctrl+C
stops it) instead of backgrounding a daemon; --background opts back into
the daemon behavior. kimi server run is unchanged. A foreground kimi web
reuses an already-running server instead of failing to bind, the /web
handoff prints the same ready banner as kimi web (plus the session deep
link), and the banner Stop hint adapts to the hosting mode.
* fix(cli): resolve the server token after the server is up in /web
The token lookup had moved ahead of ensureDaemon()/startServerForeground()
during the foreground-default refactor, so a first-time start (no
server.token on disk yet) opened the browser without the #token= fragment
and left the user at the auth gate. Resolve the token after the daemon is
healthy, and inside the foreground ready hook.
* docs(changeset): flag the web foreground default as breaking in the changelog
The bump stays minor, but the entry now calls out the behavior break and
the --background mitigation for scripts that expect kimi web to return
immediately.
* feat(cli): flag a version mismatch when reusing a server from an older CLI
After an upgrade, an older still-running server is reused as-is. The
lock's host_version is now surfaced: kimi web / server run print a
version-mismatch line next to the reuse notice, /web shows the same as a
status warning, and the docs plus changeset tell users a single
kimi server kill switches them onto the new version.
* fix(tui): correct YOLO and Auto permission mode descriptions
* fix: unify YOLO and Auto permission mode descriptions across CLI, ACP, web, and docs
* docs: correct YOLO and Auto mode descriptions in the interaction guide
* fix: correct YOLO mode notices in session replay and vscode extension
* feat(vscode): rename /afk command to /auto, keeping afk as hidden alias
Also correct the stale 'afk' mode reference in the built-in MCP config
skill guidance of both agent engines.
* fix(vscode): forward engine approval requests instead of blanket-approving them
The extension-level approval handler auto-approved every request when
legacy yolo/afk was on, silently swallowing the sensitive-file,
plan-review, and ask-rule prompts the engine yolo mode still sends.
Forward every request to the user and let the engine permission mode do
the auto-approving, matching TUI and web behavior.
* feat: refresh model list for API-key providers at the managed endpoint
* docs: simplify changeset wording
* docs: add kimi-code/k3 to the default config example
* fix: clear stale default model when a refresh drops its alias
* fix: clear refresh defaults via replace; set(undefined) cannot delete
* docs: drop the auto model refresh paragraph from providers page
* docs: simplify changeset wording
* feat(tui): add /copy slash command to copy the last assistant message
The command copies the latest assistant reply (text parts only, skipping thinking, tool-call-only turns, error and internal messages) to the clipboard. Clipboard writes now also emit an OSC 52 sequence (tmux-aware) so copying keeps working over SSH and in containers where no native clipboard tool exists.
* fix(tui): gate /copy to idle and report OSC 52-only copies as unverified
During streaming the in-flight assistant text is not in getContext() history yet, so /copy would silently copy an older message; make the command idle-only like /export-md. copyTextToClipboard now returns how the text was delivered so /copy can say when only an unverified OSC 52 escape carried it.
* fix(tui): source /copy text from the visible transcript
After /compact the model context keeps only user messages plus a user-role summary, so scanning it found no assistant message even though the last reply is still on screen. Read the last assistant transcript entry instead — it matches what the user sees and survives compaction and resume.
* fix(tui): mark real model text in the transcript so /copy skips synthetic cards
Hook-result and goal-completion cards are also 'assistant' transcript entries appended after the real reply, so /copy could copy a hook card instead of the answer. Tag the single entry-creation site for genuine model text (both live and replay flow through it) and have /copy only accept tagged entries.
* feat(agent-core): align coder subagent tools with v2 and drain background tasks
- align the bundled coder subagent profile tools with agent-core-v2
CODER_TOOLS (Skill, Agent, AgentSwarm, Task* trio, plan-mode tools,
TodoList); cron tools stay declared-but-undelivered on sub agents,
matching v2
- rebuild builtin tools in setActiveTools: profile-gated capabilities
(Bash/Agent allowBackground via the Task* trio) were baked at
construction with an empty enabled set, so profiles applied later
(every subagent) silently lost them
- hold subagent completion until the child agent's background tasks
settle (print-mode drain semantics) and suppress their terminal
notifications, so no unobserved follow-up turn runs on a finished
subagent; the run's timeout/cancel signal bounds the drain
- cover with real-Session e2e (tool execution, nested Agent/AgentSwarm,
drain blocking and cancel) and update profile/subagent-host tests
* chore: add changeset for coder subagent tool alignment
* docs(agents): sync sub-agent capabilities with expanded coder tool set
- coder sub-agents can now dispatch nested sub-agents and use background
tasks, todo lists, Plan mode, and skills; drop the stale claim that
sub-agents cannot schedule nested sub-agents
- note that a sub-agent run reports completion only after its background
tasks settle
* fix(agent-core): close drain race for tasks settled before completion
A background task that terminated before the drain's suppression pass was
excluded from the active-only list, so its terminal notification escaped
suppression and could still steer an orphan turn onto the finished
subagent. Suppress every child task (including settled ones whose
notification may still be in flight) and run the pass both before and
after the settle wait; notification delivery re-checks suppression after
its async output snapshot, which makes the block deterministic. Cover the
mid-turn delivery path with an e2e case.
* feat: raise default per-step LLM retry budget to 10 attempts
The default of 3 attempts only produced ~1.5s of backoff (0.5s/1s), so
sustained provider overload (429) surfaced to the user almost
immediately. With 10 attempts the exponential ramp (500ms base, x2,
32s cap, 25% jitter) waits out multi-minute overload windows before
failing the turn. Applies to both agent-core (chatWithRetry) and
agent-core-v2 (stepRetry service); loop_control.max_retries_per_step
still overrides.
* test(agent-core): pin retry-dependent tests to the new default budget
- error-paths: the warn-log attempt field now reads 1/10 with the
default 10-attempt budget
- goal-session pause tests: every LLM call throws a retryable error, so
the default 10-attempt backoff (~2min) blew the 5s test timeout; pin
maxRetriesPerStep=1 since the pause behavior does not depend on the
retry count
* feat(agent-core): drop default timeouts for print-mode background tasks
- add `bash_task_timeout_s` config under [background] (0 = no timeout),
covering the background Bash default and the re-arm after a foreground
command is moved to the background on timeout
- allow `[subagent] timeout_ms = 0` (no timeout) and thread it through
Agent/AgentSwarm task registration and the swarm batch timer
- fill both with 0 in print-mode config defaults so `kimi -p` never kills
background work by wall-clock and only the model stops a task;
interactive defaults are unchanged
- sync the Bash tool description/parameter text with the effective
default and update user docs (en/zh) plus changeset
* test(agent-core): satisfy KimiConfig providers requirement in print-defaults tests
* fix(agent-core): clear the foreground deadline on detach when the background timeout is disabled
`detach()` only re-arms when `detachTimeoutMs` is defined, so passing
`undefined` with `bash_task_timeout_s = 0` kept the armed foreground
deadline and a manually detached command was still killed at its
foreground timeout. Pass `0` instead so the reset clears the timer.
Caught by Codex review on #1737.
* fix: preserve provider thinking effort values
* fix: resolve Kimi thinking effort fallbacks
* fix: use resolved Kimi effort in v2 requests
* fix: align Kimi effort resolution paths
* fix: synchronize forced Kimi effort state
* fix: synchronize forced Kimi effort in v2 state
* fix: tolerate unresolved models in v2 status
* fix(agent-core): mark auto-approved plan exits as not user-reviewed
In auto permission mode ExitPlanMode is approved without user
involvement, but its result read "## Approved Plan:" exactly like a
genuine user approval and the transcript showed a green "Approved"
chip. The model treated that as a signal to start executing, even
when the user had asked it to stop after planning.
- Emit an "auto-approved, not user-reviewed" result with a note that
execution follows the user's original instructions (v1 + v2).
- Extend the auto-mode reminder: plan approvals are automatic and are
not a user signal to proceed (v1 + v2).
- Render an "Auto-approved" warning-toned chip in the TUI, keeping
backward compatibility with the old result marker.
- Document the Auto mode plan-exit behavior in interaction guides.
* fix(agent-core): only mark plan exits as auto-approved in auto permission mode
Address review feedback on the auto-approved plan exit change:
- Branch the direct-execution output on the permission mode in both
engines: only auto mode yields the "auto-approved, not
user-reviewed" result and telemetry outcome. In manual / yolo modes
the direct path means a configured or session allow/ask rule let the
call through — an explicit user decision that keeps the user-approved
output, the Approved transcript chip, and the approved outcome.
- Move the v2 rationale out of the method body into the file header,
per the agent-core-v2 comment convention.
- Drop the absolute "only Auto mode" wording from the interaction
guides (en/zh).
* fix(agent-core-v2): fix wire migration rewrite race and reject unversioned logs (8 files)
- await the migration rewrite in wireRecordService.restore() so restore only resolves once the migrated log is durable and rewrite failures surface
- serialize AppendLogStore.rewrite() behind the log flush so appends arriving during the whole-file replace land after the new content
- reject wire logs missing the metadata envelope (missingWireMetadataError) in restore, session fork, and agent create instead of fabricating a current-version envelope over legacy records
* fix(agent-core-v2): stop background tasks on session close (10 files)
- add IAgentTaskService.stopAllOnExit: suppress terminal notifications, then SIGTERM → grace → SIGKILL for every active task (v1 stopBackgroundTasksOnExit parity, gated by keepAliveOnExit)
- call stopAllOnExit from agentLifecycle.remove before scope disposal, and abort live tasks in AgentTaskService.dispose as a last resort for disposal paths that bypass the graceful close
- honor [task] killGracePeriodMs in the stop grace window and bind the v1 KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT env override for keepAliveOnExit
* fix(agent-core-v2): root task persistence at the owning agent's scope
- persist task records under <sessionScope>/agents/<agentId>/tasks/ (v1's
per-agent layout) so tasks written by older versions are found on resume
- stop one agent's restore from loading, marking lost, or re-notifying
another agent's tasks
- add a per-agent isolation test covering loadFromDisk + reconcile
* fix(agent-core-v2): address task lifecycle review findings
* fix(agent-core-v2): preserve queued appends across rewrites
* test(agent-core-v2): cover rewrite cutover flush
* fix(agent-core-v2): surface append durability failures
* fix(agent-core-v2): retire released append log buffers
* fix(agent-core-v2): preserve session-level task records
* fix(agent-core-v2): harden append log cutover recovery
* fix(agent-core-v2): suppress only background exit notifications
---------
Co-authored-by: qer <wbxl2000@outlook.com>
* feat(agent-core): make subagent timeout configurable and raise default to 2h
- add `[subagent] timeout_ms` config (env `KIMI_SUBAGENT_TIMEOUT_MS` overrides) to replace the hardcoded 30-minute cap for Agent / AgentSwarm subagents
- raise the default subagent timeout from 30 minutes to 2 hours
- thread the value through tool construction so foreground and background subagents use it, with the timeout message reflecting the effective value
* feat(background): add print_background_mode with steer for multi-turn -p runs
- add `[background].print_background_mode` (`exit`/`drain`/`steer`) and
`print_max_turns`; when unset, falls back to `keep_alive_on_exit = true`
mapping to `drain`, preserving existing behavior
- core: `Session.handlePrintMainTurnCompleted()` returns `finish`/`continue`;
in `steer` mode the run stays alive so a background-task completion
`turn.steer`s the main agent into a new turn (matching background
subagents), bounded by `print_wait_ceiling_s` and `print_max_turns`
- cli: print driver follows every main turn instead of only the first and
defers `finish()` until the run quiesces or a limit is hit
- plumb `handlePrintMainTurnCompleted` through node-sdk RPC; docs + tests
* docs(changelog): sync 0.23.5 from apps/kimi-code/CHANGELOG.md
* chore: update config model doc
* docs: update config-files example with new models and services
---------
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
* feat(kosong): classify HTTP 413 request-body-too-large as a dedicated error type
* feat(agent-core): lower default image downscale cap to 2000px and make it configurable
* feat(agent-core): strip media to text markers and retry when the compaction request is too large
* feat(agent-core): cap model-initiated image reads with a configurable byte budget
* feat(agent-core): resend with degraded media when the provider rejects the request body as too large
* test(agent-core): add explicit timeouts to encode-heavy image budget tests
* feat: add WebP decoding support with wasm integration
- Introduced a new WebP decoding module using @jsquash/webp's wasm decoder.
- Implemented functions to decode WebP images and check for animated WebP formats.
- Updated image compression tests to include scenarios for WebP handling, including encoding and decoding.
- Enhanced error handling for API request size limits to accommodate various error messages.
- Updated pnpm lockfile to include new dependencies for WebP encoding and decoding.
* chore(changeset): consolidate this PR's entries into one
* fix(nix): update pnpmDeps hash for merged lockfile
* feat(agent-core): refuse HEIC/HEIF reads with platform-matched conversion guidance
* feat(kosong): honor explicit anthropic max output override
Add claude-opus-4-8 output ceiling and treat explicit max_output_size/KIMI_MODEL_MAX_OUTPUT_SIZE as the final Anthropic max_tokens value. Sync configuration and environment variable docs.
* feat(kosong): drop claude-opus-4-8 ceiling
Revert the newly added claude-opus-4-8 default output ceiling while keeping explicit max_output_size overrides for Anthropic.
* feat(agent-core): record llm request trace in wire.jsonl
Add three observability record types so every request sent to the model
can be reconstructed from the wire log at the logical-request level:
- llm.tools_snapshot: content-addressed snapshot of the top-level tools
table as sent (post deferred-strip), written once per unique table
- llm.request: one record per outbound request (retries, strict resends,
and compaction rounds included) carrying the effective request params
and hash links to the system prompt and tools snapshot
- mcp.tools_discovered: the server's verbatim tools/list result plus the
agent's gating (allow-list, collisions), deduplicated by content hash
Observability records never feed state rebuild; replay only restores the
write-dedup cursors. The records/types.ts contract now documents the two
record classes explicitly (persisted is not the same as replayed).
Recording happens at the single Agent.generate choke point. The
LLMRequestLogFields side channel gains kind/projection/maxTokens/
droppedCount, chatWithRetry preserves caller-set fields, and compaction
tags its requests. The vis wire view renders the new record kinds.
* fix(agent-core): record the provider-clamped completion cap in the request trace
The llm.request trace recorded the client-requested budget cap, but
chat-completions providers tighten the actual wire value inside
withMaxCompletionTokens (remaining-context sizing, transport ceilings,
model-default resolution) — with the default budget the clamp is active
on nearly every non-empty-context request, so the recorded value did not
match what was sent.
Providers now expose the effective cap they computed as a readonly
maxCompletionTokens field on the clone, and the recorder reads it from
the effective provider at the Agent.generate choke point. This replaces
the side-channel recomputation, which is removed along with the
appliedCompletionBudgetCap helper.
* fix(agent-core): park pre-replay MCP discovery records and hash the collision outcome
Two wire-hygiene fixes for the mcp.tools_discovered trace:
Parking: the real Session ordering connects MCP servers concurrently with
agent construction, so ToolManager can observe a connected server before
agent.resume() has replayed the wire. Recording at that point bypassed
the restored dedup cursor (duplicating a 1-50KB record on every resume)
and appended a stray metadata record ahead of replay. AgentRecords now
exposes a one-shot opened latch — set when replay completes (after the
migration rewrite flushes) or when the first live record is logged — and
ToolManager parks discoveries until then, re-running the dedup check at
drain time. A frozen range-limited replay never opens; those agents are
transient previews.
Collision hashing: the dedup hash now covers the collision outcome, not
just the raw list and allow-list. Collisions depend on which other
servers hold a sanitized qualified name at registration time, so a
server can re-register with identical tools but a flipped outcome; that
gating change must produce a new record instead of being suppressed.
* fix(agent-core): skip the request trace for pre-flight-aborted calls
Mirror kosong generate()'s pre-flight abort check at the Agent.generate
choke point: a call whose signal is already aborted never reaches the
wire (generate throws before dispatching), so it must not leave an
llm.request/llm.tools_snapshot trace or a diagnostic log line claiming a
request was sent. Recording stays before dispatch for every call that
passes the gate, preserving the crash-safety of the trace.
* chore(agent-core): remove a leftover adaptive-thinking override hook
The adaptiveThinkingOverride option was a temporary local hook explicitly
marked for removal before commit. Nothing passes it, so resolution falls
back to the alias-level adaptiveThinking value in all cases; drop the
option and the dead indirection.
* fix(kosong): derive the exposed completion cap from generation kwargs
maxCompletionTokens was a field stored only by withMaxCompletionTokens,
so caps that reach the wire through other paths were invisible to the
request trace: with completion budgeting disabled via env, Anthropic
still sends the constructor-resolved max_tokens (required by the
Messages API), and constructor-level kwargs like OpenAILegacyOptions
maxTokens were likewise unreported.
Replace the stored field with a getter derived from each provider's
generation kwargs — the single source the request body reads — covering
constructor defaults, direct withGenerationKwargs configuration, and
budget application in one place. Kimi mirrors its request-time legacy
max_tokens alias normalization; openai-legacy reuses the same
normalizeGenerationKwargs the request path uses.
* feat(agent-core): add thinkingKeep passthrough for Kimi providers and update tests