会话与存储

Session 与 Agent 两级 scope 的分工、磁盘布局与会话索引、fork、prompt 生命周期,以及 v2 的 DI 与 Feature 接缝。

作者 David更新于 第 10 篇(共 33 篇)

Session ≠ Agent

v1 用 Session 与 Agent 两个类区分职责;v2 把这条边界做成了 DI 的生命周期 scope。src/app/scopes.ts 声明了 LifecycleScope.App、Session、Agent,每个服务注册时写明自己活在哪一级:

归属目录代表服务
Agentsrc/agent/IAgentLoopService、IAgentContextMemoryService、IAgentLLMRequesterService、IAgentProfileService、IAgentToolExecutorService、IAgentFullCompactionService
Sessionsrc/session/IAgentLifecycleService(本会话里的 agent,主 agent 的 ID 是 main)、ISessionSubagentService、ISessionMetadata、终端服务
Appsrc/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 组装。

本页目录