AgentScope第17式·洞府空间

AgentScope第17式·工作区

人设写在 Java 里的那一天,就注定了改一句话要发一次版。

[读者] 已经跑通数字人 Demo 的 Java 后端工程师
[痛点] 数字人身份、边界、知识、MCP 配置全硬编码在 Java 里
[现在读] 第16式之后 Agent 能跑了,但配置还没归位
[读完] 能把 Persona 与配置迁进 Workspace,并说清它和 AgentState 的区别
[旧方案] persona / 业务边界 / MCP 配置写死在 Java 常量与配置类里
    |
    v
[新需求] 改 AGENTS.md 就能换身份换规则,不改一行 Java
    |
    v
[冲突] Workspace 是磁盘上的长驻资产,AgentState 是单次会话的运行态
    |
    v
[后果] 分不清这两层,就会出现改了文件不生效、或误改状态把既有会话搞乱的怪事

我是老李,这几年一直在做 Java 后端的 AI 化改造。前面十六式,我们把最小交互数字人从 Spring Boot + MySQL 一路做到 Harness,Agent 能跑、Tool 能调、状态能存。

但每加一个客户,我们都要复制一份代码、改几个常量、重新打包发版。第二个客户来的时候,产品经理在群里问:能不能让运营自己在后台把开场白和身份描述改了,别每次都排期后端?

这句话把一个被忽略很久的问题顶到了台面上:数字人的身份,到底属于代码,还是属于数据?

如果继续写在 Java 里,改一句话就是一次发版。

如果迁到文件里,新的问题马上来了:文件改了,正在跑的会话要不要跟着变?

选择错了会怎样?把 Workspace 当成热更新配置中心,或者把 AgentState 当成配置存储,两条路都会在真实项目里翻车。

01、故事

第二个客户是连锁门店,要一个品牌数字人迎宾。需求很朴素:品牌标题、主题色、背景图、开场白、结束语各一套,身份描述换成门店导购口吻,可用工具只保留门店查询和预约,其他通用工具全部禁掉。

我们的现状是:chapter/16 分支上,ProjectConfig 是一堆 Java 常量,systemPrompt 由一个 SystemPromptBuilder 拼字符串,工具注册写在一个 @Configuration 里,MCP server 地址散落在 application.yml 的三个 profile 里。

任务是:在不复制代码库、不新建一个微服务的前提下,让第二个客户换一套身份与规则。

第一版我们走的是最省事的路:加一个 application-store.yml,把该客户的开场白、标题、tool 白名单塞进去,用 Spring Profile 切。当天跑通了,运营也确实不用等后端了。

然后变化来了。运营说,他们想试三种开场白,当天轮流投放,还希望顺手把知识库目录换一版。我们要么给三个 profile,要么把文案再往 Java 里搬一点。

到这里,冲突已经很清楚了:我们不是缺一个配置项,我们缺的是把身份、边界、知识、工具这些长驻资产,和一次会话的运行状态分开存放的层级。

一行代码一句词
一次发版一身皮
若要千人千面走
先问身份归谁管

02、问题

旧方案失效的路径很典型。第一阶段用 Java 常量,改文案要改代码;第二阶段用 Spring Profile,改文案不用改代码,但每来一个客户就多一个 profile,yml 越来越长,工具白名单、MCP 地址、开场白混在一份文件里,没人说得清哪个字段属于身份、哪个属于部署环境。

业务影响是两个:交付周期被后端排期绑住;运营的实验能力被冻结,他们想改的其实是内容资产,却要等一次发布。

技术表现有三个可观察点:

第一,同一份身份描述在 Java、yml、数据库项目表里出现三次,改一处忘一处;

第二,工具可见性靠 if-else 拼,新增一个 MCP server 要动 Java;

第三,知识入口没有约定,导购知识是塞进 systemPrompt 的长文本,改一次就要重启。

本篇的完成标准,我给自己定了四条:

改 AGENTS.md 后不去碰任何 Java 代码,重启进程后数字人身份与规则改变,新 Session 行为符合预期,既有 Session 行为在文档里被明确说清;

业务边界、知识入口、MCP 配置都有固定落点,不再散落在 yml 与 Java 之间;

加载失败时有明确报错,不是静默降级成默认人设;

能说清 Workspace 与 AgentState 各自负责什么,并能在代码里指出边界。

改文案要排期
换身份要发版
配置散落三处
无人能说全貌

03、原理

Workspace 是 Agent 在磁盘上的长驻资产目录,AgentState 是单个会话的运行状态。这是本篇唯一必须记住的一句话。

官方把 Workspace 的目录契约写在 agentscope-java 仓库的文档里,固定版本路径是 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/docs/v2/zh/docs/harness/workspace.md ,里面约定的角色大致是:AGENTS.md 放身份与规则,MEMORY.md 放长期记忆入口,memory/ 放记忆文件,knowledge/ 放检索资料,skills/ 放技能定义,subagents/ 放子 Agent 定义,tools.json 放工具与 MCP 配置。具体字段以该版本文档为准,本文不逐项抄写。

AgentScope Java 的 Harness,是 Agent 的运行外壳,负责生命周期、会话、状态与工作区加载。WorkspaceManager 位于 harness 模块,v2.0.3 的源码入口是 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java ,本文涉及框架行为时以该类与同目录实现为准,不靠类名猜行为。

第一个反直觉判断是:Workspace 不是配置中心。它没有推送,没有变更通知,没有灰度。它就是一个目录契约。Agent 什么时候读它、读完缓存在哪、下次要不要重读,全都是宿主代码的责任。把它当 Nacos 用,一定会失望。

第二个反直觉判断是:把身份迁到文件,最大的风险不是读不到,而是读到了旧的。很多实现会在 Session 创建时把 systemPrompt 固化成一份快照,之后无论文件怎么改,这个会话都用旧的那份。这不是 Bug,这是会话一致性的代价。

第三个反直觉判断是:AgentState 越像配置,出事越快。会话状态可以存消息、可以存临时变量,但不该存身份定义。一旦你把身份塞进状态,它就会随会话被序列化、被恢复、被复制,最后出现同一个项目两种人格。

文件不是配置
状态不是记忆
一次读取定稿
生命周期分开

04、架构

[输入] Web 运行页 /run/{projectId} 发起会话
    |
    v
[模块] Realtime Bridge -> AgentScope Harness
    |
    v
[资产] workspace/{projectId}/ AGENTS.md、MEMORY.md、knowledge/、skills/、subagents/、tools.json
    |
    v
[处理] Workspace 加载器读取资产 -> 组装 systemPrompt 与 Tool 配置 -> 创建 Agent
    |
    v
[状态] AgentState 承载本次会话的消息与运行态(与 Workspace 不同层)

边界很清楚。LiveKit 只管 WebRTC 房间与实时音视频数据,Realtime Bridge 只做媒体与消息适配,AgentScope Java 负责推理、Tool、状态,Workspace 负责磁盘上的长驻资产,MySQL 只管业务数据:项目、配置项、会话记录。

收益有三个:身份与规则从代码变成可运营资产;工具与 MCP 配置有统一入口;多客户共用一套 Java,靠目录隔离而不是靠分支隔离。

代价也有三个:编译期对配置的正确性保证没了,错误从编译期挪到运行期;多了一套目录契约与加载逻辑要维护;Workspace 与 AgentState 的边界一旦没画清,会出现改了不生效这类困惑。

适用条件是:多个数字人项目共用一个后端,或者身份与规则会被非开发人员频繁调整。

不适用条件是:只有一个数字人、身份几乎不变、改动频率低于发版频率。这种场景下,把 AGENTS.md 也塞进 jar 里,和写 Java 常量没有本质区别,只是把字符串换了个文件。

资产落磁盘
状态留内存
边界画一条
各归各的位

05、实战一次

环境与版本:教程基线 AgentScope Java v2.0.3,JDK 17 以上,Maven 构建;教程仓库 https://github.com/lifuchun522/agentscopepractice ,本章分支 chapter/17-workspace,从第16式分支继续创建。写作当天官方 Releases 页面的最新正式版可能与基线不同,若你的版本更高,先按官方文档核验 Workspace 目录契约是否变化。

第一步,建立目录。放在项目根之外的独立目录,方便运营直接改:

workspace/
  digital-human-a/
    AGENTS.md
    MEMORY.md
    memory/
    knowledge/
    skills/
    subagents/
    tools.json

AGENTS.md 里写身份、语气、业务边界、拒答规则;tools.json 里写允许的工具与 MCP server;knowledge/ 放门店资料;skills/、subagents/ 本章先留空目录,留给后续章节填充。

第二步,定义一个只承载资产的对象。它不参与会话,只描述一次加载的结果:

package com.example.digitalhuman.workspace;

import java.nio.file.Path;
import java.util.List;

public record WorkspaceAssets(
        Path root,
        String agentsMd,
        String memoryMd,
        List<String> knowledgeFiles,
        List<String> skillFiles,
        List<String> subagentFiles,
        String toolsJson) {
}

第三步,写加载器。这里有两个必须做对的地方:projectId 不能逃出 workspace 根目录,缺失的文件要区分是空还是错。

package com.example.digitalhuman.workspace;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;

public final class WorkspaceLoader {

    private final Path workspaceRoot;

    public WorkspaceLoader(Path workspaceRoot) {
        this.workspaceRoot = workspaceRoot.toAbsolutePath().normalize();
    }

    public WorkspaceAssets load(String projectId) {
        Path root = workspaceRoot.resolve(projectId).normalize();
        if (!root.startsWith(workspaceRoot)) {
            throw new IllegalArgumentException("projectId escapes workspace root");
        }
        if (!Files.isDirectory(root)) {
            throw new IllegalStateException("workspace directory not found: " + root);
        }
        return new WorkspaceAssets(
                root,
                readIfExists(root.resolve("AGENTS.md")),
                readIfExists(root.resolve("MEMORY.md")),
                listNames(root.resolve("knowledge")),
                listNames(root.resolve("skills")),
                listNames(root.resolve("subagents")),
                readIfExists(root.resolve("tools.json")));
    }

    private String readIfExists(Path file) {
        return Files.isRegularFile(file) ? read(file) : "";
    }

    private String read(Path file) {
        try {
            return Files.readString(file, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private List<String> listNames(Path dir) {
        if (!Files.isDirectory(dir)) {
            return List.of();
        }
        try (Stream<Path> stream = Files.list(dir)) {
            return stream.filter(Files::isRegularFile)
                    .map(p -> p.getFileName().toString())
                    .sorted()
                    .toList();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}

第四步,把 AGENTS.md 变成 systemPrompt 的主体,把 tools.json 交给工具注册环节,而不是在 Java 里写死:

public String buildSystemPrompt(WorkspaceAssets assets) {
    StringBuilder sb = new StringBuilder();
    sb.append(assets.agentsMd());
    sb.append(System.lineSeparator());
    if (!assets.knowledgeFiles().isEmpty()) {
        sb.append("可用知识文件:")
          .append(String.join("、", assets.knowledgeFiles()))
          .append(System.lineSeparator());
    }
    return sb.toString();
}

第五步,启动与验证。启动前把 AGENTS.md 写成门店导购口吻,启动后用运行页发一句话。

以下为预期结果,未在当前环境实测:Agent 回复应当使用门店导购口吻;把 AGENTS.md 改成另一个身份并重启进程后,新 Session 应当使用新身份;不重启进程、只改文件时,行为取决于你的缓存与加载策略,这正是下一章要处理的部分。

目录先定契约
文件再定身份
代码只做加载
不掺一句人设

06、排查

第一条证据链,关于改了文件不生效。

现象:改完 AGENTS.md,重启进程,运行页里正在进行的那个会话仍然是旧人设,新开的会话是新设。

怀疑:第一反应是缓存没刷新,于是去检查加载器。

检查:在 WorkspaceLoader 的 load 方法入口打日志,确认每次会话创建时文件都被重新读取,日志里也确实打印了新内容。

证据:日志显示加载到了新人设,但对话输出仍是旧口吻,说明问题不在读盘,而在读完之后被固化在了会话侧。

根因:Workspace 资产在 Session 创建时被组装进 systemPrompt,并随会话保留。既有会话不重读资产,这是会话一致性的设计结果,不是加载器的 Bug。

修复:不要把既有会话重读当成默认行为。明确两件事,新 Session 使用新资产,既有会话保持原样;如果确实需要运营当天切换人设,就提供显式的会话重建入口,而不是偷偷热替换正在进行的对话。

错误尝试: 有人提议直接去改 AgentState 里已存在的 systemPrompt 字段,把新人设塞进去,让当前会话马上换人设。这个做法是错的,原因有两个:一是 AgentState 是运行态,它会在后续轮次被重新组装或被覆盖,改了不一定在下一轮还生效;二是中途换人设会污染已经发生的对话历史,模型会看到前后不一致的指令,输出更容易跑偏。

第二条证据链,关于 tools.json 里的 MCP server 没注册。

现象:tools.json 里加了门店查询的 MCP server,运行页里数字人却说没有这个能力。

怀疑:JSON 写错了,或者字段名不对。

检查:把 tools.json 单独用 JSON 解析器校验,格式合法;再检查日志,发现加载器打印的 server 数量是 0。

证据:日志里打印的文件路径是相对路径,而进程的工作目录是项目根目录,不是 workspace 目录,所以 tools.json 是从一个不存在的位置读的,readIfExists 返回了空字符串,静默降级。

根因:相对路径的解析基准不统一。Workspace 的目录约定是以 workspace 根为基准,而 Java 进程的默认工作目录是启动目录,两者不一致。更糟的是我把缺失当成空处理,掩盖了错误。

修复:一是所有相对路径统一以 workspaceRoot 为基准解析;二是 AGENTS.md 缺失时报错而不是空字符串,tools.json 缺失可以容忍但必须打警告。

上游贡献判断:这两个现象我目前只在自有宿主代码里复现,尚未在官方最小 Demo 中复现,v2.0.3 与 main 的 WorkspaceManager 具体行为需按源码核验,也未在官方 Issues 中检索到对应条目。因此现在只能记录为候选,不能写成上游问题。

先看读没读
再看存没存
别怪框架慢
多半是基准

07、优化

基于第06章的两条证据,形成 V2。第一处修改是路径解析:新增一个解析器,把相对路径统一锚定到 workspace 根。

package com.example.digitalhuman.workspace;

import java.nio.file.Path;

public final class WorkspaceResolver {

    private final Path workspaceRoot;

    public WorkspaceResolver(Path workspaceRoot) {
        this.workspaceRoot = workspaceRoot.toAbsolutePath().normalize();
    }

    public Path resolve(String projectId, String relative) {
        Path resolved = workspaceRoot.resolve(projectId).resolve(relative).normalize();
        if (!resolved.startsWith(workspaceRoot)) {
            throw new IllegalArgumentException("path escapes workspace root: " + relative);
        }
        return resolved;
    }
}

第二处修改是区分缺失与为空,并加显式校验。AGENTS.md 缺失属于致命错误,直接抛出并带路径;knowledge/、skills/、subagents/ 为空属于正常状态;tools.json 缺失打警告。

第三处修改是加载时机与缓存策略。给 WorkspaceAssets 加一个基于文件修改时间的指纹,宿主可以在会话创建时决定沿用缓存还是重读:

public record WorkspaceSnapshot(WorkspaceAssets assets, long fingerprint, long loadedAtMillis) {

    public boolean isStale(long currentFingerprint) {
        return currentFingerprint != fingerprint;
    }
}

指纹由 AGENTS.md、MEMORY.md、tools.json 的 mtime 与各目录文件列表拼接而成。注意这不是热更新:它只是让宿主有机会知道文件变了,要不要让正在进行的会话换人设,仍然是一个产品决策,不是技术默认值。

新行为是:新 Session 一定使用最新资产;既有 Session 保持创建时的资产;文件被改动时日志里能看到指纹变化,运营改完文案可以在管理端点一下重建会话,而不是靠后端重启。

验证方法也很直白:改 AGENTS.md,观察日志指纹变化;开新会话,确认新口吻;在旧会话里继续说话,确认它仍使用旧口吻且不报错。

路径锚一处
缺失分两级
指纹看得见
切换由人定

08、演进

[同一输入] 让数字人换一套身份、边界与工具配置
    |
    +--[V1] Persona / 边界 / MCP 分散在 Java 常量与多份 yml
    |      代价:改文案要发版,多客户靠 profile 与分支隔离
    |
    +--[V2] Persona / 边界 / 知识 / MCP 落在 workspace/{projectId}/
    |      代价:多一套目录契约、加载器、校验与缓存策略
    |
[Trade-off]
得到:身份与规则成为可运营资产,Java 只剩加载与执行
失去:编译期对配置正确性的保证,错误从编译期挪到运行期
适用:多数字人共用一套后端,身份与文案会被非开发人员频繁调整
不适用:单数字人、身份几乎不变、改一次比发一次版还慢

正确性上,V1 的配置来源分散在三处,容易出现改一处漏一处;V2 的资产只有一个落点,代价是运行期校验必须补上。

稳定性上,V1 的问题暴露在发布之后,影响面是全部客户;V2 的问题暴露在单个项目加载时,影响面收敛到一个 workspace。

复杂度上,V1 简单,但复杂度转移到了人脑;V2 多了一个加载器与一层约定,换来的是可运营与可审计。

成本上,V1 的隐性成本是每次改文案的排期;V2 的显性成本是一次性的加载器开发与后续的目录治理。

适用范围上,只有在你确定要长期养多个数字人时,V2 才划算。

遗留问题有三个:Workspace 与数据库里的项目配置谁来当权威源;skills/ 与 subagents/ 的加载语义本章没碰;Workspace 资产的版本化与回滚还没做。

旧法省一事
新法立一约
换来可运营
代价要认账

09、洞见

9.1 Persona 迁出去,迁的其实是权限

反直觉判断:把 AGENTS.md 交给运营,看起来是省了发版,实质是把一部分提示词权限从后端交给了业务。AGENTS.md 里能写拒答规则、能写业务边界、能写工具使用约束,这已经是策略层的东西。

一旦它可被运营修改,就必须回答:谁有权改、改了要不要审批、出事能不能回滚。没有这三个答案,Workspace 只是把风险从代码仓库搬到了一个没版本控制的目录里。

9.2 AgentState 不是配置的第二存储

反直觉判断:会话状态看起来是离 Agent 最近的地方,改起来最快,实际上它是最不该放身份定义的地方。运行态会被序列化、恢复、复制,把身份塞进去,等于给同一个项目埋下多个人格的种子。

判断标准很简单:这个东西如果每个会话都可以不一样,那它属于 AgentState;如果所有会话都应该一样,那它属于 Workspace。

9.3 迁移顺序:先人设,再知识,最后工具

本章我只迁了 AGENTS.md 与 tools.json 的落点,knowledge/、skills/、subagents/ 先留空目录。这不是偷懒,是风险排序。

人设改动只影响语言风格,出错顶多是语气不对;知识改动会影响事实正确性,出错就是答错;工具改动会影响副作用,出错可能已经写了数据库。顺序反过来做,你会同时面对风格、事实、副作用三类问题,排查时根本分不清是哪一层引起的。

9.4 什么时候不应该用 Workspace

什么时候不应该用本章能力?只有一个数字人、人设三个月不动一次、改动必须走代码评审的场景,不该用。这种情况把 AGENTS.md 也塞进代码仓库、跟着 Java 一起发版,反而更安全,因为你有版本、有评审、有回滚。

Workspace 解决的是资产归属问题,不是配置管理问题。缺版本管理和审批的时候,它不会让事情变好。

权限先讲清
状态莫越界
顺序按风险
场景定取舍

10、系统落地

原来有什么:chapter/16 分支上,ProjectConfig 是 Java 常量,systemPrompt 由代码拼接,工具与 MCP 地址分散在 yml 的多个 profile 里,多客户靠分支或 profile 隔离。

本篇新增什么:chapter/17-workspace 分支上,新增 workspace/{projectId}/ 目录契约与 WorkspaceLoader、WorkspaceResolver;Persona、业务边界、知识入口清单、MCP 配置从 Java 硬编码迁到 Workspace;加载失败分级处理,工具与知识以清单形式暴露给宿主。

现在能做什么:改 AGENTS.md,不碰任何 Java 代码,重启进程后数字人身份与规则改变,新 Session 使用新身份;既有 Session 保持创建时的身份,行为边界在日志与文档里说清;tools.json 成为工具与 MCP 的唯一入口,新增一个 MCP server 不再需要动 Java。

还缺什么:Workspace 资产的版本化与回滚、skills/ 与 subagents/ 的加载语义、Workspace 与数据库项目配置的权威源划分、加载结果的审计记录。

下一步如何演进:把记忆与知识接进来,让 MEMORY.md 与 knowledge/ 真正参与推理,而不是只列文件名;再往后把工具权限与 Workspace 资产挂钩,做成按项目的可见性策略。

开源参与点:发现可作为 Issue/PR 候选的问题:Workspace 资产在既有 Session 中是否重读、缺失文件的处理语义(报错还是告警)在官方文档里的描述边界不够清晰,可能值得补一段说明。该候选尚未在官方仓库检索确认是否已有对应 Issue,也需按当前官方文档核验,因此本章不声称已提交任何 Issue 或 PR。

硬编码退场
目录契约登场
身份成为资产
权限紧随其后

11、小结

Q1 → 身份属于数据。它应该落在 workspace/{projectId}/AGENTS.md 这类资产文件里,Java 只负责加载与组装,不负责定义。

Q2 → 文件改了,新 Session 用新资产;既有 Session 保持创建时的快照。需要当天切换时走显式的会话重建,不做隐性热替换。

Q3 → 选错的后果有两类:把 Workspace 当配置中心,会出现改了不生效的困惑;把 AgentState 当配置存储,会出现同一项目多个人格。

状态 → 数字人的人设、业务边界、知识入口与 MCP 配置已从 Java 硬编码迁到 Workspace,chapter/17-workspace 分支可作为后续记忆、技能、子 Agent 章节的底座。

[本章前] Persona / 边界 / MCP 分散在 Java 与 yml
    |
    v
[本章后] Persona / 边界 / 知识清单 / MCP 落在 workspace/{projectId}/
    |
    v
[仍然不变] AgentState 只承载单次会话的运行态,不承载身份定义

一问定归属
二问定生命
三问定代价
三答定边界

12、作业

12.1 理解题:为什么说 Workspace 与 AgentState 不是同一层?

参考答案:Workspace 是磁盘上的长驻资产,生命周期独立于会话,多个 Session 共享同一份;AgentState 是单次会话的运行态,承载消息与运行数据,随会话创建而生、随会话结束而灭。判断标准是:所有会话都应该一样的,放 Workspace;每个会话可以不一样的,放 AgentState。

12.2 实战题:把数字人的开场白与身份描述从 Java 迁到 Workspace,并验证。

参考答案:在 workspace/{projectId}/AGENTS.md 里写身份、语气、业务边界与开场白;Java 侧用加载器读取文件内容,作为 systemPrompt 的主体;tools.json 承载工具与 MCP 配置。验证分两步:改 AGENTS.md 不动 Java,重启进程后新开会话,观察身份是否改变;在同一进程内继续旧会话,观察是否仍用旧身份且不报错。注意区分预期结果与实测结果。

12.3 排障题:tools.json 里的 MCP server 没有注册,日志里加载的 server 数量是 0,怎么定位?

参考答案:先确认文件存在,再确认读取路径的解析基准。常见根因是相对路径以进程工作目录为基准,而期望是以 workspace 根为基准,导致读到了不存在的文件,而缺失又被静默处理成空字符串。修复方式是统一以 workspaceRoot 解析相对路径,并对缺失文件分级处理。

12.4 架构判断题:请打开官方仓库 v2.0.3 的 harness 文档与 WorkspaceManager 源码,判断该版本是否支持 Workspace 资产的动态重载,并说明你的核验依据。

参考答案:应打开 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/docs/v2/zh/docs/harness/workspace.md 与 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/agentscope-harness/src/main/java/io/agentscope/harness/agent/workspace/WorkspaceManager.java ,并对照同目录的测试类,确认资产的读取时机、是否缓存、是否有重载入口;同时对比 main 分支与 Release Notes,区分 v2.0.3 的限制与当前最新版的现状。核验依据必须落到具体方法与测试,而不是文档标题。

一题辨层级
二题练迁移
三题查基准
四题读源码

13、思考

这一篇的核心冲突,说到底是一句话:数字人的身份是代码还是数据。

答案不是二选一,而是分层。身份定义、业务边界、知识入口、工具配置属于资产,放在 Workspace;会话消息、运行变量、临时上下文属于状态,放在 AgentState。把这两层混在一起,你会同时失去可运营性和会话一致性。

更值得记住的是那句反直觉的话:迁到文件不等于自动生效。读过一次的快照会跟着会话走,这是设计,不是缺陷。真正的工程判断是,什么时候该让新会话用新资产,什么时候该提供显式的会话重建,而不是默认热替换正在进行的对话。

会用框架的人,知道 WorkspaceManager 在哪里;能参与框架演进的人,会去读它的加载时机、缺失语义与缓存策略,然后判断哪些是自己的宿主责任,哪些才是值得提给上游的问题。

身份归资产
状态归会话
边界一旦清
演进自有路

posted @ 2026-10-11 11:14  李福春  阅读(3)  评论(0)    收藏  举报