会话与存储
Session 与 Agent 两级 scope 的分工、磁盘布局与会话索引、fork、prompt 生命周期,以及 v2 的 DI 与 Feature 接缝。
Session ≠ Agent
v1 用 Session 与 Agent 两个类区分职责;v2 把这条边界做成了 DI 的生命周期 scope。src/app/scopes.ts 声明了 LifecycleScope.App、Session、Agent,每个服务注册时写明自己活在哪一级:
| 归属 | 目录 | 代表服务 |
|---|---|---|
| Agent | src/agent/ | IAgentLoopService、IAgentContextMemoryService、IAgentLLMRequesterService、IAgentProfileService、IAgentToolExecutorService、IAgentFullCompactionService |
| Session | src/session/ | IAgentLifecycleService(本会话里的 agent,主 agent 的 ID 是 main)、ISessionSubagentService、ISessionMetadata、终端服务 |
| App | src/app/ | ISessionIndex、配置、模型目录、遥测 |
工作区这一级在 src/workspace/:WorkspaceInstanceManager 为每个工作区创建一个 SessionLifecycleService,负责会话的 create、resume、fork、archive、restore、delete。打开会话时,它用 createScopedChildHandle() 造出 Session scope;会话里的每个 agent 再各自拥有一个 Agent scope,主 agent 与子代理的服务实例互不共享。
磁盘布局
数据根目录是 ~/.kimi-code(可用 KIMI_CODE_HOME 覆盖;旧版 ~/.kimi/ 由 migration-legacy 迁移)。一个会话的目录:
~/.kimi-code/sessions/<workspaceId>/<sessionId>/
├── state.json 会话元数据(标题、fork 来源、agent 列表等)
└── agents/
├── main/wire.jsonl 主 agent 的事件日志(只追加)
└── <agentId>/wire.jsonl 每个子代理一份workspaceId 由工作目录算出(src/_base/utils/workdir-slug.ts:17):
export function encodeWorkDirKey(workDir: string): string {
const normalized = workDir.replace(/\\/g, '/').replace(/\/+$/, '');
const base = normalized.split('/').pop() ?? normalized;
const slug = slugifyWorkDirName(base);
const hash = createHash('sha256').update(normalized).digest('hex').slice(0, HASH_LENGTH);
return `${WORKDIR_KEY_PREFIX}${slug}_${hash}`;
}结果形如 wd_<目录名>_<12 位哈希>:目录名方便人认,哈希保证唯一。同一项目被不同写法的路径打开时,app/workspace/workspaceAlias.ts 用 workspaceRootKey()(对 Windows 形态的路径做小写折叠)识别出同一根目录,把这些 key 归并起来。
会话索引
列出、检索会话不去扫目录,而是查 App 级的 ISessionIndex(app/sessionIndex/):listRecent、count、get、remove,状态在 uninitialized、preparing、ready、degraded 之间切换。它的读模型放在 minidb 里,v0.42 起常开。
fork
ISessionLifecycleService.fork() 接收 sourceSessionId,以及可选的 newSessionId、title、metadata、turnIndex:
- 源会话有回合在跑时拒绝 fork,并对所有 agent 取得 quiescence,防止复制途中又有写入。
- 不带
turnIndex:整目录复制,每个 agent 的日志追加一条 forked 标记。 - 带
turnIndex:主 agent 的日志截到该回合,子代理的日志按对应时间截断,截空的子代理不带过去。 - 新会话标题默认是
Fork: <原标题>(原会话没有标题时用它的 ID),state.json里记下forkedFrom。
agent 级也能 fork(IAgentLifecycleService.fork())。继承的历史若以未完成的工具调用结尾,会用 closeTrailingOpenToolExchange() 补上「这个调用在快照时仍在执行,结果未知」的结果,比投影器那条通用的合成结果说得更准。
子代理、git 上下文与 hooks
- 子代理:v1 的
subagent-host.ts、subagent-batch.ts变成 Session 级的ISessionSubagentService(run、planSpawn、spawn,外加onWillStartAgentTask钩子),并行派发在features/swarm/。详见子代理与 swarm。 - git 上下文:
session/agentLifecycle/profile/gitContext.ts的collectGitContext()采集origin地址、当前分支、git status --porcelain与最近 3 条提交(5 秒超时,最多列 20 个改动文件),输出<git-context>块。它现在只作为 explore 子代理 profile 的promptPrefix,不再由会话统一注入。 - Hooks:用户配置的 hooks 做成了一个 Feature(
features/externalHooks/),在 App、Session、Agent 三级各贡献一个服务。详见 Hooks。
切模型
/model 改的是 IAgentProfileService 里的模型别名(setModel())。请求服务在每个回合第一次请求时,给模型配置拍一张快照(llmRequesterService.ts:698):
private getOrCreateTurnConfig(turnId: number): TurnRequestConfig {
for (const id of this.turnConfigs.keys()) {
if (id < turnId) this.turnConfigs.delete(id);
}
let snapshot = this.turnConfigs.get(turnId);
if (snapshot === undefined) {
snapshot = {
resolved: this.profile.resolveModelContext(),
params: this.profile.resolveRequestParams(),
systemPrompt: this.profile.getSystemPrompt(),
};
this.turnConfigs.set(turnId, snapshot);
}
return snapshot;
}所以回合中途切模型,不会让同一回合的 step 前后用两个模型;新模型从下一个回合生效。
prompt 的生命周期
v1 的 PromptService 依赖 @moonshot-ai/protocol;这个包已删除,prompt 的生命周期并入 IAgentLoopService:submit()(可带 steerIfActive)、steer()、cancel()、promptHandle()。状态(loop.ts:144):
export type PromptState =
| 'pending'
| 'running'
| 'steered'
| 'completed'
| 'failed'
| 'cancelled'
| 'blocked';steered 表示这条排队的输入已并入正在跑的回合;blocked 来自 prompt gate,比如 hook 拦下了一次提交。ID 仍用 ULID:newMessageId() 返回 msg_ 加一个 ulid(),按时间单调、天然可排序。状态变化以 PromptSubmitted、PromptQueued、PromptSteered、PromptCompleted 等事件经 IEventDispatcher 发出(agent/prompt/promptEvents.ts);各级 scope 的状态键由脚本汇总在 packages/agent-core-v2/docs/state-manifest.d.ts。
DI 与 Feature
v1 在 services/ 下移植了 VS Code 的依赖注入;v2 把它扩展成 DI × Scope(src/_base/di/)。服务注册长这样(llmRequesterService.ts:945):
registerScopedService(
LifecycleScope.Agent,
IAgentLLMRequesterService,
AgentLLMRequesterService,
ScopeActivation.OnScopeCreated,
'llmRequester',
);createDecorator() 造出服务标识,构造函数参数上的 @IFoo 声明依赖,ScopeActivation 决定 scope 一创建就实例化还是按需实例化。跨多级 scope 的功能写成 Feature,在一个类里把各级服务一起贡献出去(features/externalHooks/externalHooksFeature.ts:16):
constructor() {
super();
this.contributeService(
LifecycleScope.App,
IExternalHooksRunnerService,
ExternalHooksRunnerService,
);
this.contributeService(
LifecycleScope.Session,
ISessionExternalHooksService,
SessionExternalHooksService,
);
this.contributeAgentService(IAgentExternalHooksService, AgentExternalHooksService);
}Scope、Fiber 与贡献点的完整机制见 agent-core-v2。
下一篇:system prompt 组装。