Skip to main content
对话与会话

记忆

用原生记忆让 Agent 跨轮次、跨会话延续知识,或由应用接管记忆的生成与消费。

记忆让 Agent 把一次会话中获得的知识保留下来,供后续会话使用。项目的构建命令、测试目录结构、代码约定这类信息,无需在每次会话中重新探索。 Qoder Agent SDK 的记忆由生成与消费两部分组成,可分别配置:
组成作用何时运行
生成(Generation)将 Agent 获得的知识写入记忆文件一轮结束后,由后台记忆 Agent 执行
消费(Consumption)将记忆文件加载进 Agent 的上下文会话初始化时,以及显式刷新时
语言可用性:TypeScript、Python、Java。 本页的能力由 TypeScript 的 memory 选项、Python 的 QoderAgentOptions.memory、Java 的 SessionOptions.Builder.memory(MemoryOptions),以及各自的运行时方法提供:TypeScript 和 Java 为 flushMemory() / refreshMemory(),Python 为 flush_memory() / refresh_memory()。Python 的配置字段使用 snake_case(如 user_scope、on_result),回调输入、结果和初始化信息中的字段保留 camelCase(如 turnIndex、writtenFiles)。 记忆仅支持本地 CLI runtime,不支持 experimental Cloud Agent runtime:TypeScript 和 Python 在启用 Cloud Agent 时传入 memory,会在连接前抛错;启用 Cloud Agent 时调用 flushMemory() / refreshMemory()(Python 为 flush_memory() / refresh_memory())也会报错。

启用原生记忆

mode: 'native'(TypeScript)/ {"mode": "native"}(Python)/ MemoryOptions.nativeBuilder()(Java)将全部行为交由运行时的默认策略处理,建议作为起点使用:由运行时判断哪些内容值得记录、存放在何处、何时加载。
import { query } from '@qodercn-ai/qodercn-agent-sdk';

for await (const message of query({
  prompt: '增加一个健康检查接口,并跑一遍测试',
  options: {
    memory: { mode: 'native' },
  },
})) {
  console.log(message);
}
native 模式下可配置的仅有两个作用域开关和结果回调。存储位置、生成提示词、加载预算等由运行时掌握,并随版本持续优化。Java 的 MemoryOptions Builder 另有 drainTimeoutMs(long),限定显式 flush 和正常关闭时等待记忆处理的时长,默认 60000 毫秒。

选择记忆作用域

原生记忆有两个作用域,默认均启用。
作用域根目录 id存放内容
用户级user跟随使用者、跨项目通用的知识
项目级project仅属于当前工作目录的知识
可在单次 Query 中关闭其中一个:
// 仅使用项目级知识,不读写用户级记忆
options: {
  memory: { mode: 'native', userScope: false },
}
native 模式要求至少启用一个作用域,同时关闭两个会抛出异常:
native mode requires at least one enabled scope; use memory: {} to disable SDK memory
Python 在连接 CLI 之前以 TypeError 拒绝,报错为 native memory requires at least one enabled scope; use memory={} to disable。Java 在 MemoryOptions 的 build() 时抛出 IllegalArgumentException,不会启动 CLI。

关闭记忆

空对象是显式的关闭开关,等同于不传 memory,不会向运行时下发任何记忆配置。Python 传 memory={} 或省略 memory。Java 用 MemoryOptions.disabled(),它也是默认值;memory(null) 抛出 NullPointerException。
options: {
  memory: {},
}

覆盖生成与消费

mode: 'custom'(TypeScript)/ {"mode": "custom"}(Python)/ MemoryOptions.customBuilder()(Java)仅覆盖显式给出的部分,其余沿用运行时默认行为。当记忆的存放位置或记录标准需要由应用决定时,使用该模式。

控制写入什么

options: {
  memory: {
    mode: 'custom',
    generation: {
      roots: [
        { id: 'team', path: '/srv/knowledge/team', access: 'read' },
        { id: 'service', path: '/srv/knowledge/checkout', indexFile: 'INDEX.md' },
      ],
      prompt: '只记录部署步骤与故障特征。',
    },
  },
}
每个 root 对应一个记忆 Agent 可访问的目录:
字段类型默认值说明
idstring—稳定标识,用于提示词、初始化和生成结果
pathstring—运行 QoderCN CLI 的机器上的目录路径
access'read' | 'read-write''read-write'记忆 Agent 是否可写入该 root
indexFilestring—root 内的相对索引路径。若所有文件均为内容文件则省略
Java 用 MemoryRoot.builder(id, path) 创建 root,access 取 MemoryRoot.Access.READ 或 READ_WRITE(默认)。indexFile 必须是 root 内的相对路径,绝对路径或含 .. 的路径会抛出 IllegalArgumentException;roots 不能为空,id 不能重复。 Python 的 root 字段为 id、path、access、index_file。index_file 必须位于 root 内,roots 不能为空,id 不能重复,不符合时以 TypeError 拒绝。

按轮决定是否写入

生成默认在每轮结束后触发。通过 shouldGenerate,应用可以否决单次生成,例如跳过无价值的轮次、按租户控制预算,或在只读评审场景中不写入记忆。
options: {
  memory: {
    mode: 'custom',
    generation: {
      turnComplete: {
        shouldGenerate: async (input, { signal }) => {
          if (input.prompt.length < 40) {
            return { run: false, reason: '提示词过短,不值得记录' };
          }
          return { run: true };
        },
        timeoutMs: 5_000,
        onGateError: 'skip',
      },
    },
  },
}
回调接收本轮的 prompt 与 response,以及 sessionId、cwd 和从 1 开始的 turnIndex。返回 run: false(Java 为 MemoryGenerationGateResult.skip(reason))时不启动后台 Agent,改为产生一条 skipped 结果。 Java 的回调返回 CompletionStage<MemoryGenerationGateResult>,在客户端的 callbackExecutor 上执行;第二个参数是取消令牌,CLI 取消或回调超时后生效,回调应检查它并停止不再需要的工作。onGateError 对应 OnGateError.SKIP / REPORT_FAILED。 Python 的闸门 should_generate(turn, context) 可以是同步或异步函数。turn 是本轮输入,字段保留 camelCase(如 turnIndex);CLI 取消请求时,context["signal"](asyncio.Event)被设置。返回值必须是带布尔值 run 的 dict,否则向 CLI 报告错误。对应的配置字段为 should_generate、timeout_ms、on_gate_error。
字段类型默认值说明
enabledbooleantrue是否启用轮次结束生成
shouldGenerate回调—应用侧闸门。省略时仅使用运行时内置的保护规则
timeoutMsnumber10000闸门回调的最长耗时
onGateError'skip' | 'report_failed''skip'回调报错或超时的上报方式

控制加载什么

显式传入 files 会在本次 Query 中替换原生自动记忆,静态指令仍照常加载。
options: {
  memory: {
    mode: 'custom',
    consumption: {
      files: [
        { id: 'conventions', path: '/srv/knowledge/CONVENTIONS.md', required: true },
        { id: 'runbook', path: '/srv/knowledge/RUNBOOK.md' },
      ],
      maxTokens: 4_000,
      overflow: 'truncate',
      failureMode: 'fail_query',
    },
  },
}
文件按数组顺序注入,id 作为注入内容的小节名。
字段类型默认值说明
enabledbooleantrue是否加载记忆
filesMemoryConsumptionFile[]—显式文件列表,会替换原生自动记忆
maxTokensnumber—所有显式文件共享的 token 预算
overflow'truncate' | 'fail_query''truncate'内容超出 maxTokens 时的处理方式
failureMode'best_effort' | 'fail_query''best_effort'读取失败时的处理方式。仅标记 required 的文件会导致 Query 失败
Python 的字段为 files、max_tokens、overflow、failure_mode,取值与上表相同。 Java 的 overflow 和 failureMode 分别取 MemoryConsumptionOptions.Overflow 和 MemoryConsumptionOptions.FailureMode 的枚举值。files 不能为空,id 不能重复。

查看记忆的执行结果

记忆在后台运行,其结果应显式暴露给应用,不宜默认成功。有两种获取方式。 一是注册回调,native 和 custom 模式均支持:
options: {
  memory: {
    mode: 'native',
    generation: {
      onResult: (result) => {
        console.log(`[memory] ${result.status},耗时 ${result.durationMs}ms`);
        for (const file of result.writtenFiles) {
          console.log(`  已写入 ${file.rootId}:${file.path}`);
        }
      },
    },
    consumption: {
      onResult: (result) => {
        for (const file of result.files) {
          console.log(`  已加载 ${file.id}: ${file.status}`);
        }
      },
    },
  },
}
二是从消息流中读取,对应 subtype 为 memory_generation 和 memory_consumption 的 system 消息。Python 中是 MemoryGenerationMessage 和 MemoryConsumptionMessage(SystemMessage 的子类),结果在 .result 中。Java 中是 MemoryGenerationMessage 和 MemoryConsumptionMessage,用 result() 取得类型化结果:
for await (const message of q) {
  if (message.type === 'system' && message.subtype === 'memory_generation') {
    console.log(message.result.status);
  }
}
Java 的结果回调返回 CompletionStage<Void>,在 callbackExecutor 上执行。回调抛出异常不会影响消息流中的结果消息,也不会终止会话。 Python 的结果回调可以是同步或异步函数;回调抛出的异常只记录到日志,不影响消息流。 生成会上报以下五种结果之一:
状态含义
saved所有待写文件均写入成功
partial至少一个文件写入成功、一个失败,需检查 failedFiles
no_change已执行,但未发现需要记录的新内容
skipped未执行,例如闸门返回了 run: false
failed已执行,但没有任何文件写入成功
消费会上报 success、partial 或 failed,并给出每个文件的状态:loaded、missing、failed 或 truncated。missing 不属于错误,首次运行时记忆文件可能尚未创建。 Java 用枚举表示这些状态:MemoryGenerationResult.Status、MemoryConsumptionResult.Status 和 MemoryConsumptionResult.FileStatus,取值为上表的大写形式(如 SAVED、NO_CHANGE)。CLI 返回无法识别的状态时为 UNKNOWN,原始值可从 wireStatus() 读取。

在运行时控制记忆

查询对象提供两个方法,用于处理后台生成带来的时序问题。
const q = query({ prompt: userMessages(), options: { memory: { mode: 'native' } } });

// 退出进程或校验文件内容前,等待未完成的轮次生成结束
await q.flushMemory();

// 外部进程修改记忆文件后,重新加载
await q.refreshMemory();
flushMemory() 在 CI 和测试场景中尤为重要:若进程在收到最终 result 消息后立即退出,可能中断正在进行的写入。 Java 的 QoderQuery 和 QoderSession 都提供这两个方法,它们会自行等待初始化完成:
  • flushMemory() 等待 CLI 已触发的记忆处理结束,上限为 drainTimeoutMs。完成不代表每个文件都写入成功,仍需检查生成结果。
  • refreshMemory() 按现有加载配置重新读取记忆文件,不改变配置,超时使用 controlRequestTimeoutMs。
  • 会话没有启用 Memory 时,两个方法都以 UnsupportedCliCapabilityException 失败,不发送请求。
  • 一次性查询在最终回答后会等待已触发的记忆处理,期间继续投递 Memory 消息,处理完成或超时后才结束消息流。收到 ResultMessage 不代表消息流已经结束,应读到流结束。
Python 在 QoderSDKClient 上提供 flush_memory() 和 refresh_memory():
  • 并发调用 flush_memory() 共用同一次请求。
  • 未使用 daemon 选项时,单次 query() 在结束输入前自动 flush,客户端在 disconnect() 或退出 async with 时自动 flush。自动 flush 失败只记录日志,关闭照常进行;需要确认成功时显式调用 flush_memory()。
  • 使用 daemon 选项时,如果 CLI 声明了 daemon_async_session_teardown_v1,单次 query() 结束和客户端关闭时都不自动 flush,关闭完成不代表记忆已经写完。关闭后要立即校验记忆文件或启动下一个会话时,改用 QoderSDKClient,在关闭前调用 await client.flush_memory() 并检查生成结果。
  • CLI 没有为 SDK 启用记忆时,两个方法抛出 RuntimeError,不发送请求。

校验实际生效的配置

记忆选项需与运行时协商确定,因此应读回实际生效的配置,而非依赖传入值:
const init = await q.initializationResult();
console.log(init.memory);
// {
//   enabled: true,
//   requester: 'sdk',
//   mode: 'native',
//   generationEnabled: true,
//   turnCompleteEnabled: true,
//   consumptionEnabled: true,
//   roots: [{ id: 'user', access: 'read-write' }, { id: 'project', access: 'read-write' }]
// }
memory 省略或设为 {} 时,init.memory 为 undefined。Python 从 get_server_info() 返回值的 memory 读取,字段保留 camelCase。Java 未启用 Memory 时 memory() 返回 Optional.empty()。 Java 显式启用 Memory 后,CLI 必须在初始化时确认已为 SDK 启用记忆,SDK 才发送首条输入;CLI 未确认时,初始化以 UnsupportedCliCapabilityException 失败。

下一步