mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-30 19:45:39 +00:00
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(agent-core-v2): insert Workspace lifecycle scope and remove mutable cwd paths
- Insert LifecycleScope.Workspace between App and Session
- Delete session/workspaceCommand domain (addAdditionalDir) and node-sdk RPC
- Remove profile cwd mutation; cwd is fixed at creation
- Make ISessionWorkspaceContext read-only; seed additionalDirs at creation
- Remove TUI and vscode /add-dir commands (to return workspace-scoped)
* feat(agent-core-v2): add Workspace scope with handler-owned session lifecycle
- Add IWorkspaceLifecycleService (App scope): handler registry,
create-or-get handlerFor with inflight join
- Add workspace/workspaceContext seed and workspace/workspaceHandler
(session create/resume/fork as handler child scopes)
- Delete App-level ISessionLifecycleService; callers compose
index -> handlerFor -> handler via sessionLookup helpers
- Slim IBootstrapService; persistence addressing via handler chain
(disk layout byte-identical)
- kap-server routes rewire internally; /api/v1 wire unchanged,
debug surface gains workspace addressing
- Pin red line in domain lint: session/agent must not import
workspace domains
* feat(agent-core-v2): collect workspace resources into the handler scope
- Add Workspace-scope catalogs for skills and agent profiles,
instructions service, and a shared MCP connection manager
(built at materialization, refreshed by watch/plugin events)
- Session catalogs keep their APIs but read seeded snapshots and
refresh via change events; ISessionMcpService removed
- Session create options carry no mcpServers; MCP sources are
config file (wins on name conflicts) and plugins only
- Agent profile/mcp consume the seeded providers
* feat(agent-core-v2): restore add-dir as a workspace-level capability
- Add workspace/workspaceDirs: shared additional-dir set with
addDir({path, persist}); persist=true writes .kimi-code/local.toml,
local.toml watch drives cross-process refresh
- ISessionWorkspaceContext becomes a live read view fed by the
ISessionWorkspaceInfo seed contract and change events
- Restore Session.addAdditionalDir in kimi-code-sdk 1:1, mapping to
the workspace service; restore TUI/vscode /add-dir verbatim
* feat(agent-core-v2): collect os-level services into the workspace scope
- Move fs service, fs watch (shared subscription fan-out), process
runner, and a git facade to Workspace scope; sessionFs domain removed
- Add IWorkspaceToolPolicy with workspace veto wired through tool
activation, execution guard, composed evaluation, and profile
prompt projection; injected via ISessionToolPolicyGate seed
- kap-server fs routes and fs.watch bridge remap to the workspace
services; wire unchanged
* refactor(agent-core-v2): clean up workspace-domain leftovers and docs
- Drop dead code: v2 mergeCallerMcpServers, the transitional
ISessionContext.additionalDirs field, an unreachable guard
- Fix stale domain references in comments; correct test names
- Give the fs-watch refresh test a realistic wait budget under load
- Document the four-scope model and workspace domain in AGENTS.md,
agent-core-v2 docs, and the agent-core-dev skill
* test(node-sdk): wait for the initial MCP connect to settle in the parity list test
v1 connects in the background after create resolves while v2 awaits it
inside create, so an immediate list can catch either side still pending
under CI load
* refactor(agent-core-v2): extract git work-tree discovery into the git domain
- add the pure findGitWorkTree probe in app/git/workTree and expose it
as IGitService.findWorkTree
- switch the git permission policies off the local
findLocalGitWorkTreeMarker helper to the DI service
- reuse findGitWorkTree for AGENTS.md project-root discovery in
agent/profile/context.ts
- add findWorkTree coverage to gitService.test.ts
* feat(kimi-inspect): add Workspace Services view
- add WorkspaceServicesView rail view with a workspace picker on top;
proxies resolve workspace-scope Services on the /workspace/:id route
- extend ChannelScope, ServiceTarget, and ServicePanelDef scope with
'workspace', routed via client.workspace(id).service
- wire the new view into NavRail and App
* refactor(agent-core-v2): extract mcpCore and workspaceMcpConfig domains
- move the scope-agnostic MCP connection layer (stdio/http/sse clients,
connection manager, oauth, config schema, tool naming) from agent/mcp
to the new mcpCore domain
- move the [mcp] config section to app/mcpConfig and OAuth credential
persistence to app/mcpConfig/oauthStore
- introduce the workspace/workspaceMcpConfig domain owning the effective
MCP server set (mcp.json files + plugin contributions, refreshed by
fs watch); workspaceMcp keeps pure connection orchestration
- update the plugin domain, session MCP handle, klient/node-sdk
contracts, and tests accordingly
* refactor(agent-core-v2): remove the fault-injection experimental feature
- delete the faultInjection domain (flag definition, IFaultInjectionService
contract, FaultInjectionService implementation)
- drop the requester-side take() injection point and the constructor
dependency from llmRequester
- remove the flag-gated test cases and the IFlagService stub they needed
- regenerate the state manifest without the faultInjection state keys
* feat(agent-core-v2): gate project-level MCP config behind workspace trust
Add the Workspace-scope IWorkspaceTrust service: an explicit, per-workspace
trust marker persisted under the home (IAtomicDocumentStore, keyed by
encodeWorkDirKey(root)) so a checked-out tree cannot pre-trust itself.
While a workspace is untrusted, workspaceMcpConfig skips the project-level
.mcp.json and .kimi-code/mcp.json files (user-level config and plugin
contributions still load); a trust flip reuses the reload path, so project
servers connect on trust and disconnect on untrust.
Expose the state over kap-server REST: GET /workspaces/{id}/trust,
POST /workspaces/{id}/trust, POST /workspaces/{id}/untrust.
* feat(kimi-inspect): replace the workspace picker with a directory browser
The Workspace Services view now keeps a server-side directory browser in a
left sidebar (over IHostFolderBrowser) instead of a <select> of registered
workspaces. Entries that are registered workspaces carry a workspace badge
plus their IWorkspaceTrust trust state; selecting an unregistered folder
registers it on demand via IWorkspaceService.createOrTouch.
* fix(agent-core-v2): resolve the effective cwd into the profile binding
A default-bound agent recorded no cwd in its profile.bind payload, and no
caller configures ProfileServiceOptions.cwd, so the profile service's cwd
getter fell through to '' and refreshSystemPrompt() rebuilt the prompt
from the server process's cwd: an AGENTS.md edit dropped the workspace
instructions (or swapped in unrelated ones).
bind() now persists the resolved effective cwd (the input's, or the
session's when the input omits it) into profile.bind — the Model's cwd
stays creation-fixed and is always set. The getter's last resort is the
session's own cwd (the value legacy bindings resolved against) instead of
a bare ''.
* refactor(agent-core-v2): introduce the contribution/registry/catalog extension point for agent profiles
- App-scope IAgentProfileRegistry: any scope can register an
AgentProfileContribution keyed by (sourceId, workspaceKey); dedup per
source id, change events drive catalog re-projection
- workspaceAgentProfileLoader domain owns agent-file discovery end to end
(parse / roots / SYSTEM.md / explicit runtime files) with five
Workspace-scope loaders (workspace / user / plugin / extra / explicit)
tagged with the handler's workspaceId; internals live under internal/
- SessionAgentProfileCatalog projects the registry directly (name dedup,
priority adjudication, builtin override rule, inspect()); the
workspace-catalog + sessionData seed relay is gone
- builtin code contributions register as the 'builtin' entry via
BuiltinAgentProfileLoader; plugin agent roots are provided by the
plugin domain as PluginAgentRoot
- remove cwd from the profile binding chain (BindAgentInput /
ProfileBindingSnapshot / AgentConfigData / ProfileModelState /
profile.bind op) — it is always the session's frozen cwd; legacy
wire.jsonl records replay fine (the schema strips the field)
- share markdown frontmatter parsing via _base/text/frontmatter
* fix(agent-core-v2): reconcile the workspace refactor with main
- restore the branch's klient workspaceId scope extension lost to a
file-level conflict resolution (main had no further changes there)
- stub the plugin system-prompt dependencies main added to the profile
service in the profileOps / skillCatalog tests
- correct PLUGIN_SKILL_SOURCE_ID to the App skillSource domain (Agent
scope must not import the Workspace domain)
- kap-server workspaceLayout test supplies the now-required hostIdentity
- regenerate wire/state/config manifests
* fix(agent-core-v2): export the agent-file parse primitives the v2 print CLI consumes
The internal/ split kept parseAgentFileText / resolveAgentPath off the
package entry, but apps/kimi-code's v2 print runner imports them from
@moonshot-ai/agent-core-v2 for --agent-file. Export the two symbols by
name; everything else under internal/ stays domain-private.
* feat(agent-core-v2): return cwd listing for empty fs:search query
An empty fs:search query used to fail request validation (query had a
minimum length of 1), so @-mention pickers had no starting set right
after typing "@". The workspace fs service now answers an empty query
with the workspace root's top-level entries — directories first,
hidden entries excluded, gitignore and exclude_globs honored — mapped
into the search-hit shape (score 1, empty match positions) and capped
by limit. The mirrored protocol wire schema is relaxed in sync.
* test: cover cron-fired steer context and titled session creation
- agent-core-v2: e2e asserting a cron-fired steer turn carries earlier
tool results (the CronCreate job id) into the provider request
- klient: conformance case creating a titled session through implicit
workspace materialization
28 KiB
28 KiB
Repository-level Agent Guide
Reply in the same language as the user.
This is a TypeScript monorepo built for agent-assisted development. Keep the root AGENTS.md limited to hot-path rules: the project map, hard constraints, and workflow requirements — things every task needs to know.
Working Principles
- Think from first principles. Start from real requirements, code facts, and verification results; if the goal is unclear, discuss it with the user first.
- Treat code, not documentation, as the source of truth. Unless the user explicitly says otherwise, do not read ordinary Markdown just to understand the implementation.
- Before making code changes, read the relevant code and the most recent constraints, and follow the nearest
AGENTS.mdin the directory tree. - Keep changes focused. Do not slip in unrelated refactors along the way.
- When committing, do not add any co-author attribution, and do not reveal the identity of the agent in commit messages, PR descriptions, or any explanatory text.
Project Map
apps/kimi-code: the CLI / TUI application. It consumes core capabilities through@moonshot-ai/kimi-code-sdkand must not depend directly on@moonshot-ai/agent-core. When writing or modifying its terminal UI, use thewrite-tuiskill (.agents/skills/write-tui/SKILL.md).apps/kimi-web: the browser web UI, a peer to the TUI. Vue 3 + Vite + vue-i18n; talks to the server over REST + WebSocket under/api/v1. It must not depend on@moonshot-ai/agent-core(wire types are re-implemented locally). Debug against the two engines via the rootpnpm dev:v1/pnpm dev:v2backend scripts — the dev Sidebar shows the active backend and switches it at runtime. Seeapps/kimi-web/AGENTS.md.apps/vis,apps/vis/server,apps/vis/web: visual debugging tools for sessions and replays.apps/kimi-inspect: web inspector for the kap-server/api/v1/debugRPC surface — workspace/session browser, per-session chat, and Service panels (data + trigger buttons) for the Session and Agent scopes. A left icon rail (src/components/NavRail.tsx) switches top-level views: the Chat workspace, the global message search (src/components/SearchView.tsx— cross-session full-text search overPOST /api/v1/search, cursor-paged via a manual Load more; an exact-match checkbox maps to the API'smode: 'literal'substring search, which ignores sort and orders newest-first; alive/indexbadge on the results shows which server route served them (in-memory session transcript vs the persisted index)), the Model Catalog (src/components/ModelCatalogView.tsx— every Provider with its Models and the default marker, viaIModelCatalog/IModelServicechannel proxies), and App Services (src/components/AppServicesView.tsx— the app-scope Service reflection, full width, joined by the Workspace Services view (src/components/WorkspaceServicesView.tsx) — the workspace-scope counterpart with a left sidebar directory browser (src/components/WorkspaceDirBrowser.tsx— server-side fs browsing over the App-scopeIHostFolderBrowser, marking entries that are registered workspaces with theirIWorkspaceTrusttrust state, and registering a picked folder on demand viaIWorkspaceService.createOrTouch), its proxies riding the/workspace/:idroute, which materializes the handler on demand viaIWorkspaceLifecycleService.handlerFor; the Agent scope stays in the Chat view's right dock (src/components/RightPanel.tsx) across two tabs: theAgenttab (Inspector: agent switcher + a Plan lookup card —PlanCardinsrc/components/Inspector.tsx— queryingGET /sessions/{id}/transcript/plan(one tool_call_id, or every plan of the agent) viasrc/transcript/api.ts'sfetchTranscriptPlan— plus the agent Service panels) and theStatetab (every key an Agent Service registered into the agent-state container, polled live viaIAgentStateService.snapshot()— the same live diff-tree view as the session State tab, sharingStateCardfromsrc/components/StateCard.tsx), while the Session scope has its own column right next to the session-list sidebar (src/components/SessionPane.tsx) with two tabs: Services (the pending-interactions card —src/components/InteractionsCard.tsx— plus the session Service panels) and State (every key a Session Service registered into the session-state container, read on demand viaISessionStateService.snapshot())). Expanding a Model opens the model inspector inside that view: provider/model config layers plus the resolved runtime view with per-value provenance (config / override / builtin / env / synthesized), served on demand byIModelCatalog.inspect— the same resolution pass the runtime'sgetserves, traced viaResolutionTraceCollectorand assembled bykosong/model/inspection.ts. Built on its own old-klient-style channel layer (src/channel/: the VS CodeProxyChannelmodel — service-boundIChannel, HTTPProxyChannelfor calls routed to/api/v1/debug), typed byagent-core-v2Service interfaces;GET /api/v1/debug/channelsloads the whole wire protocol 1:1 (every scoped Service, no whitelist). There is no Service-event push channel: panels fetch/refresh on demand (Sidebarpolls react-query on a 15 s interval), and a connection failure shows a blocking "Debug surface unavailable" screen instead of falling back anywhere. Session-level coarse status is the one exception:src/activity/holds a second/api/v1/wsclient (GlobalEventsWs) that subscribes to nothing and consumes the server-pushed global facts —event.session.work_changedupdates a per-session activity map (SessionActivityHub+ subscribe/version store, seeded on connect/reconnect fromGET /api/v1/sessions), whileevent.session.created/session.meta.updatedinvalidate the['sessions']query; theSidebarsession rows renderrunning/approval/question/failedbadges from it viauseSessionActivities. The Vite dev server proxies/apito a running kap-server (KIMI_SERVER_URL, defaulthttp://127.0.0.1:58627) and exposesGET /__inspect/servers(vite/serverDiscovery.ts), which scans the local kap-server instance registry (~/.kimi-code/server/instances+ legacylock) and the home token so the app can zero-config auto-connect and switch servers from the header dropdown at runtime. The per-session chat (src/components/ChatView.tsx) renders turn-granularly from the transcript surface instead of context memory and carries an in-chat search bar (src/components/ChatSearchBar.tsx): it searches the current session viaPOST /api/v1/searchwithcontainer: { session_id }(usually served by the live route, since selecting a session resumes it), and a result click funnels through the app shell'sopenSearchHit— the same agent-switch +ChatJump(page-back, scroll, flash) path the global search view uses; full state is read fromGET /api/v1/sessions/{id}/transcript(initial load = newest page, refreshes re-read from the tail backwards), older history auto-pages withbefore_turnvia an IntersectionObserver sentinel at the top of the scroll view, and each timeline item is wrapped incontent-visibility: auto+contain-intrinsic-sizeso the browser virtualizes off-screen rendering natively (no windowing library);/api/v1/wsis an incremental channel (transcript.ops, gradeblock— the cheapest grade that still carries whole-state frame upserts, dropping per-tokenappendframes;transcript.resetis ignored by the store, surfaced only to the audit recorder via the optionalonResethandler). The channel tracks the op-batch watermark: a dedicatedsubscribe_v2control frame carries the per-agent grades and thetranscript_sincecursor, a seq gap / reconnect /resync_required/ append gap triggers a point-to-point catch-up (fetchTranscriptOps→GET .../transcript/ops?since_seq=), and any legacy/incomplete answer falls back to the full REST refresh. Convergence reuses@moonshot-ai/transcript's L2 reducer (src/transcript/: REST/WS clients + store; the data model and reducer come from the package, nothing is re-implemented locally). The Transcript audit panel (src/components/audit/, theAudittab of the chat view's right dock —src/components/RightPanel.tsx, fed the trail byChatView'sonTrailChange) replays how the visible store was built: anAuditTrail(src/audit/) records every step — each REST page (request + replace/prepend), every WS frame (transcript.opslive/buffered/flushed/catchup,transcript.reset), loss signals, and prompt/cancel actions — with the resulting immutableAgentStateper entry; the panel offers a draggable timeline plus a Diff tab (structural diff vs the previous entry: added/modified/removed colored, long strings tail-truncated, all fields kept), a full State view, and the raw Event payload.packages/agent-core: the unified agent engine, including Agent, Session, profile, skills, tools, plan, permission, background, records, the in-process DI service layer (src/services/), and other core capabilities.packages/agent-core-v2: the DI × Scope agent engine (the v2 port behind kap-server). FourLifecycleScopetiers —App/Workspace/Session/Agent(_base/di/scope.ts). Theworkspace/domain owns one Workspace scope per materialized workspace handler: the App-scopeIWorkspaceLifecycleServicekeeps the live handler registry (create-or-get + join, handlers never closed), and each handler'sIWorkspaceHandlerServiceowns session create/resume/fork/close as its child scopes — there is no App-level session lifecycle facade, callers composeISessionIndex→handlerFor→ the handler. Workspace-scope services hold the handler-shared resources loaded once per handler and refreshed by fs watch: skills / AGENTS.md (workspaceSkillCatalog/workspaceInstructions), the workspace agent-profile loader (workspaceAgentProfileLoader— agent profiles follow the Contribution / Registry / Catalog extension point: the domain owns agent-file discovery end to end (parse / roots / SYSTEM.md / explicit files) and its Workspace-scope loaders (workspace/user/plugin/extra/explicit) registerAgentProfileContributions into the App-scopeIAgentProfileRegistry, tagged with the handler'sworkspaceId; the App-scopebuiltinAgentProfileLoadercontributes the code-defined profiles, and each Session-scopesessionAgentProfileCatalogprojects the registry directly (name-level dedup + builtin-override rule in the projection), seeded with only the workspace key), one shared MCP connection set (workspaceMcp, pure connection orchestration over the scope-agnosticmcpCorelayer; the effective server set is owned byworkspaceMcpConfig— mcp.json files + plugin contributions, fs-watch refreshed — and MCP persistence, the[mcp]config section plus OAuth credentials, lives inapp/mcpConfig, the same wrapper shape askosongConfigover kosong), fs / fs-watch / process runner / git (workspaceFs/workspaceFsWatch/workspaceProcess/workspaceGit), the additional-directory set (workspaceDirs, backed by.kimi-code/local.toml), the os-level tool veto (workspaceToolPolicy), and the trust marker (workspaceTrust— persisted under the home, keyed byencodeWorkDirKey(root); while a workspace is untrusted,workspaceMcpConfigskips the project-level.mcp.json/.kimi-code/mcp.jsonfiles, and the state flips through kap-server'sGET|POST /workspaces/{id}/trust+POST /workspaces/{id}/untrustroutes). Session/Agent scopes consume these through session-domain seed contracts with change events (session/mcp,session/workspaceInfo,session/sessionSkillCatalogdata, …) — Session/Agent never import the Workspace domain. Seepackages/agent-core-v2/AGENTS.mdand use theagent-core-devskill (.agents/skills/agent-core-dev/SKILL.md) when developing here.packages/node-sdk: the public TypeScript SDK and harness.packages/kosong: the LLM / provider abstraction layer.packages/kaos: the execution environment and file/process abstractions.packages/oauth: Kimi OAuth and managed auth utilities.packages/telemetry: shared client-side telemetry infrastructure.packages/transcript: the isomorphic transcript rendering data layer — agent-granular L1 store, idempotent L2 operations,off/turn/block/deltaL3 subscription granularity, framework-free L4 view registry, and turn-cursor pagination. Pure TypeScript (browser-safe, no engine imports) and the sole owner of all transcript contract types (src/contract/); consumed bypackages/kap-server(engine events → transcript, REST + WS surface; live stores backfill history from the persisted per-agent wire records — main on first attach, any agent on demand, cold sessions rebuild any agent — with 0-based turn ordinals matching the engine's). The cold rebuild is a two-level fold overwire.jsonlas the single source of truth:history/groupTurns.ts(context messages → turn tree) plushistory/foldFacts.ts(non-context records → tasks, interactions, todos, goal/plan/swarm meta, and end-appended markers/taskrefs; interactions left pending at shutdown fold tocancelled). Plan content is a recorded fact too: each ExitPlanMode review submission offloads the document toagents/<agentId>/plan/<planId>/v<N>.mdand persists a reference-onlyplan.revisionrecord ({id, version, path, sha256, bytes}), which projects — live and cold — to aplan.revisionmarker and themodes.planbadge ({reviewPath, version}). It also owns the op-batch sequencing contract (transcriptSeqSchemaincontract/schema.ts): a per-(session, agent) monotonic batchseqontranscript.ops/transcript.reset/ the REST transcript response, thetranscript_sincesubscription cursor, and theGET .../transcript/opscatch-up response shape — every field optional so pre-seq peers fall back to loss-signal-driven refreshes. Beyond the timeline, the model carries wire-equivalent detail: steps carryusage/finishReason/timing(LLM latencies) /retry/ interrupt reason, turns carrydurationMs/error/usage, tool frames carry the streamedinputTextand the latestprogress, tasks carry subagentresultSummary/error/stateReason/usage,meta.agentmirrors the agent status slices (model / usage / context / permission / phase), a globalpromptsentity (opprompt.upsert) tracks the prompt queue, andhook.resultlands as a'hook'marker. These live-projected fields are NOT backfilled by the cold rebuild (known limitation).packages/kap-server: the Kimi Code server, backed by the DI × Scope agent engine (@moonshot-ai/agent-core-v2— four scopes, App/Workspace/Session/Agent; session create/resume/fork routes composeISessionIndex→IWorkspaceLifecycleService.handlerFor→ the handler'sIWorkspaceHandlerService, and the fs routes resolve session → handler → the Workspace-scope fs services). Exposes sessions over REST + WebSocket (/api/v1+/api/v1/ws); bootstrapped fromsrc/start.tsand consumed byapps/kimi-code. The RPC surface is/api/v1/debug/*— a reflection dispatcher over the ENTIRE scoped DI registry (every Service callable, no whitelist, Workspace scope addressable alongside App/Session/Agent;src/transport/registerDebugRoutes.ts+serviceDispatcherRoutes.ts), mounted only with--debug-endpointson a loopback bind and gated by the global bearer auth; repo dev scripts pass the flag. Its transcript surface implements the op-batch sequencing contract:TranscriptService.dispatchOpsassigns every dispatched batch a per-agent consecutiveseqand retains it in a bounded in-memory journal (TRANSCRIPT_OPS_JOURNAL_CAPACITY, dies with the live store); WStranscript.ops/transcript.resetpayloads carry the seq/watermark, atranscript_sincesubscription cursor (carried, with the per-agent grades, by thesubscribe_v2control frame — the only transcript subscription channel; its agent-grained counterpartunsubscribe_v2detaches listed agents' streams, or the whole session's whenagent_idsis absent, letting the detached agents' legacy events flow again) replays journaled batches instead of a baseline reset when the journal covers it, andGET /sessions/{id}/transcript/ops?since_seq=serves point-to-point catch-up (complete: false= journal can't cover or session cold → caller falls back to a full refresh). Beside the paged route,GET /sessions/{id}/transcript/plan?agent_id=[&tool_call_id=]projects an agent's ExitPlanMode plan info (content / path / options / review outcome;tool_call_idnarrows to one call, omitted lists every recoverable plan) from the first available fact — the linked approval interaction's persisted request display, the live tool frame's display, or the tool result output text. The baselinetranscript.resetitself is items-empty (TRANSCRIPT_RESET_TAIL_TURNS = 0): it carries only global state + the watermark +has_more_older, because history always pages in over REST. When a WS connection subscribes to the transcript protocol (grade ≠offfor an agent), the broadcaster suppresses the transcript-projectedsession_eventtypes for that connection × agent (TRANSCRIPT_PROJECTED_EVENT_TYPES+suppressedByTranscriptinsessionEventBroadcaster.ts; cursor replay viagetBufferedSinceapplies the same filter). Suppression is only a per-connection send view — the journal still records everything, and connections without transcript grades are unaffected. The session's work aggregate behindevent.session.work_changed(busy/main_turn_active/pending_interaction/last_turn_reason) is owned by the core'sISessionActivityView(sessionActivitydomain, Session scope): the broadcaster only schedules the wire emission around turn frames (busy:falselands afterturn.ended), andresolveSessionFacts(src/routes/sessions.ts) reads the same view — never fold per-agent activity at the edge. Delivery split on/api/v1/ws: global events (session.meta.updatedand theevent.session.*/event.workspace.*/event.config.*families, including every activated session'sevent.session.work_changed) fan out to EVERY established connection —WsConnectionV1registers itself viabroadcaster.addGlobalTargeton construction and unregisters on close — while session/agent-grained events only reach connections subscribed to that session (subject toagent_filterand the transcript suppression above); transcript frames are a separate channel governed by the per-agent grades alone and bypassagent_filterentirely. The global search surface isPOST /api/v1/search(src/search/+src/routes/search.ts): a cross-session full-text search over user messages, assistant text, and session titles, backed by a single minidb database at<home>/search-index(IGlobalSearchService, App scope — the write-lock holder is the indexer, other processes open read-only and catch up via WAL). It serves two modes:terms(the default — minidb's inverted text index over ASCII words + CJK uni/bigrams, no positions, term-level AND) andliteral(substring-exact search: a hashed 2/3-gram index supplies candidates, every candidate's text is then confirmed withincludes, so hits carry zero false positives; literal ignoressortand returns newest-first, and a candidate set truncated atLITERAL_CANDIDATE_CAPis flaggedincomplete: 'candidate_cap'). Whencontainer.session_idis provided and that session is live in this process (TranscriptService.forSessionLivereturns a store, wired viasetLiveTranscriptSourceinstart.ts), BOTH modes instead scan the in-memory transcript store (turn prompts + assistant text frames, history established viawhenReady/ensureAgentHistory) — no index involved; terms-mode live hits are scored Σ log(1+tf) (comparable only within a route, per theGlobalSearchSourcecontract), live-route errors never fall back to the index, and the response'ssource: 'live' | 'index'field (also mixed into the page-token fingerprint, so a mid-pagination route flip invalidates the old token) tells the caller which route served the page.packages/klient: the client SDK — a contract-driven facade over agent-core-v2 with aggregatedglobal.*/session(id).*/agent(id).*methods, zod validation on every call, and klient-level typed event forwarding. Transport is chosen once at creation via subpath entry (@moonshot-ai/klient/ipc|memory); both return the sameKlient. The package also hosts the e2e suites: the legacy/api/v1live suites (test/e2e/legacy/) and the docker e2e runner (pnpm --filter @moonshot-ai/klient docker:e2e). Seepackages/klient/AGENTS.md.packages/server-e2e: live e2e tests and scenarios against a running server (KIMI_SERVER_URL, defaulthttp://127.0.0.1:58627). Seepackages/server-e2e/AGENTS.md.packages/tree-sitter-bash: a pure-TypeScript bash parser (no runtime deps, no wasm) that produces a syntax tree with tree-sitter-bash 0.25.0 named-node type names and UTF-16 code-unit offsets.parse(source, { timeoutMs, maxNodes })runs under a deterministic budget (default 50 ms / 50k nodes, plus per-chain recursion depth caps) and returns a discriminatedParseResult({ ok, rootNode, hasError }or{ ok: false, reason: 'aborted' }) — callers must treat aborted/hasError trees as "cannot analyze" and degrade. Parser only, no safety judgments; consumers (e.g. Bash tool permission matching) live elsewhere. Known deviations from the reference are tracked in the package README's "Known differences" section, pinned by differential fixtures tested against the realtree-sitter-bashwasm (dev-only).packages/minidb: the embedded JSON document store (MiniDb) behind kap-server's search index — snapshot + WAL persistence with an exclusive write lock (losers open read-only and catch up from the WAL), plus a larger-than-RAM full-text layer:src/text-index.tsis the inverted index (in-RAM dictionary + delta, on-disk postings insrc/text-postings.ts, rebuilt from the Store on open and on compaction) with an injectabletokenizer/queryTokenizer; the default tokenizer keeps ASCII words and CJK uni/bigrams, whilesrc/trigram.tsprovides the hashed 2/3-gram tokenizer (NFKC + lowercase, code-point windows) that backs substring-exact search. Text-index definitions (including the tokenizer name) persist indb.textindexes.json.
Environment Requirements
- Node.js:
>=24.15.0(from the rootpackage.jsonengines;.nvmrcis24.15.0, used by nvm / fnm / mise to pick the minimum recommended version). - pnpm:
10.33.0(from the rootpackage.jsonpackageManager). pnpm installwill fail when the Node version is not satisfied, because.npmrcsetsengine-strict=true.
Monorepo Workspace Maintenance
pnpm-workspace.yamlis the source of truth for workspace membership, butflake.nixalso contains hardcodedworkspacePathsandworkspaceNameslists.- Whenever you add or remove a workspace package, you MUST update both
pnpm-workspace.yamlandflake.nix— for every package, including leaf / test / e2e packages that nothing depends on.pnpm-workspace.yamluses globs (packages/*,apps/*), so most packages land there automatically;flake.nixis fully manual and is where omissions happen.- Missing a path in
flake.nix'sworkspacePathswill silently drop files from the Nix build'ssrcfileset. - Missing a name in
flake.nix'sworkspaceNameswill breakpnpmConfigHookbecause dependencies for that workspace will not be fetched.
- The automated "Check flake.nix workspace sync" (
scripts/check-nix-workspace.mjs) only validates the transitive dependency closure of@moonshot-ai/kimi-code. A leaf package outside that closure (e.g. an e2e package nobody imports) slips through even when it is missing fromflake.nix. A green check is therefore NOT proof thatflake.nixis fully in sync — keep it updated by hand on every add/remove, do not rely on the check to catch omissions.
General Coding Rules
- For optional object properties, pass
undefineddirectly instead of using conditional spread.- YES:
{ user } - NO:
{ ...(user ? { user } : undefined) }
- YES:
- Optional object properties do not need to additionally allow
undefinedin the type.- YES:
interface Options { user?: User } - NO:
interface Options { user?: User | undefined }
- YES:
- Internal methods with only a single parameter should not be turned into options objects just for stylistic uniformity.
- Except for a package's
index.ts, otherindex.tsfiles should preferexport * from './module';. - The
Agentclass inpackages/agent-core/src/agentmust be usable on its own. The constructor must not force the caller to create aSessioninstance, nor require anagentIdorsession. It may accept an optionalsessionIdas a request-config hint — for example mapped to the provider'sprompt_cache_key— but the instance must not holdsessionId, and must not depend on the Session lifecycle, metadata, or parent/child relationship logic. - Do not add too many new test files. Prefer adding tests to the existing test file of the corresponding component or module.
- When a test fails because of a user modification, default to fixing the test first; do not change the implementation to satisfy an old test unless the implementation truly has a bug.
- Do not sacrifice code quality for external compatibility unless the user explicitly asks for it. Breaking changes go through changesets and a
majorbump, gated by the rule below.
Experimental Features
- Gate a not-yet-public feature behind an experimental flag. Add the flag to the registry at
packages/agent-core/src/flags/registry.ts, then check it withflags.enabled('my-feature'). Flags are env-driven and default off:KIMI_CODE_EXPERIMENTAL_<NAME>toggles one,KIMI_CODE_EXPERIMENTAL_FLAGenables all. Release by flipping the entry'sdefaulttotrue.
Where to Update Instructions
- Hard rules that affect almost every task: update the root
AGENTS.md. - Rules that only affect a specific directory: update the nearest sub-directory
AGENTS.md. - Keep instruction updates focused and supported by code facts.
Workflow Requirements
- Prefer
rg/rg --fileswhen reading code. - When designing changes, follow existing boundaries and local patterns first.
- In public text and test data, replace real internal identifiers with neutral placeholders such as
example.com,example.test, andYOUR_API_KEY. Before opening a PR, ask a read-only agent to audit the diff for context-specific internal identifiers. - When creating a PR, the PR title must follow Conventional Commit style, e.g.
chore: remove legacy format commands. - When an AI agent opens or updates a PR, fill in
.github/pull_request_template.md— link the related issue or explain the problem, then describe what changed. Do not leave placeholder text or submit a generic summary of the diff. - Do not submit vague AI-generated PR text. The human author must understand the change well enough to explain the code, edge cases, and why the approach fits this repository.
- After finishing a task and before submitting a PR, you must run the
gen-changesetsskill (see.agents/skills/gen-changesets/SKILL.md) and generate a changeset under.changeset/according to its rules. - When generating a changeset, never decide on a
majorbump on your own. When you judge a change to meet the major criteria (breaking changes, incompatible user configuration, renamed or removed commands/arguments, changed behavior semantics, etc.), you must stop and explain it to the user and ask for confirmation. Only writemajorafter the user has explicitly agreed. Otherwise default tominor(and fall back topatchifminoris unclear). See the "Hard rule: confirm with the user before writingmajor" section in.agents/skills/gen-changesets/SKILL.mdfor details. - Prefer importing via
import ... from '#/...', which serves the same purpose asimport ... from '@/...'. - Do not commit throwaway scratch or exploratory files. Never stage:
- Agent working notes or handoff/summary documents (e.g.
HANDOVER-*.md,HANDOFF-*.md,handoff.md). - Throwaway UI/UX prototypes or design mockups (e.g.
*-designs.html,*-mockup.html,*-demo(s).html) at the repo root or under adesign/folder. The only tracked.htmlfiles should be Viteindex.htmlentrypoints. Before committing or opening a PR, rungit statusandgit diff --staged --statand remove anything matching these patterns. Put scratch work under.tmp/(gitignored) instead of the repo root or the source tree.
- Agent working notes or handoff/summary documents (e.g.