最近我在折腾 Agent 和大模型相关的小工具时,突然有一个很朴素但很烦人的问题冒出来:

模型到底是真的“记住了上下文”,还是只是看起来像记住了?

平时随便聊两句的时候,这种问题其实不明显。你问一句,它答一句,感觉都挺正常。但一旦场景换成长文本、多人物、多时间点、多物件、多轮追问,问题就开始冒头了:前面讲过的人名会不会混?顺序会不会错?没写过的东西会不会乱补?上一轮明明答得很稳,下一轮为什么突然开始答非所问?

于是我干脆不跟它客气了,直接自己做了一个 “上下文记忆压测器”
用 Java 读入一个长篇故事文件,把故事先塞给模型,再一轮一轮追问它细节,看它到底能扛多久。现在这版程序的核心做法很直接:读取 storyT.txt,把系统规则、故事正文和后续问答都不断追加到 conversationHistory 里,再整体作为单条 user 消息发给 DeepSeek 的 chat/completions 接口,拿到返回后再用一个手写的 extractContent 方法把正文抠出来。

这玩意写完之后,效果很有意思。
它不是那种“高大上”的 Agent 项目,也没有什么炫技式的多工具联动,但它特别适合做一件事:

把模型的上下文能力从“听起来不错”拖到“经不经得住盘问”这个层面。


一、为什么我要专门做这个东西

一开始我其实只是想做一个简单的上下文测试。思路也很朴素:

  1. 给模型一段故事。
  2. 后面不断问它细节。
  3. 看它答得稳不稳。

但真开始做之后,我发现“上下文测试”这件事,远没有想象中那么简单。

因为如果你的素材只是几百字、两三个人、一个地点、一个事件,那模型其实很容易装得很像“全记住了”。这种测试区分不出它是认真记住了,还是靠局部匹配糊过去了。所以我后来干脆反着来:不怕故事长,就怕故事不够脏。

于是我专门造了一篇长故事。里面不是单纯堆字数,而是故意往里塞:

  • 多个人物
  • 多个地点
  • 大量时间点
  • 颜色、物件、编号
  • 前后顺序
  • 关系变化
  • 故意容易混淆的细节

比如故事一开头就定下了羊壹这个人物:他住在北港区旧钟街二十三号,二楼朝南,会修表、换锁、代写信,屋里有长桌、书架、上锁的右二抽屉,还有一只背后刻着“玖”字的银色怀表。
再往后我又继续塞进苏绫、周雁回、段迟、许槐、许澄、程九言、陆白萤这些人,以及鹤渡桥、沉水河、临水档案馆、城北车站旧寄存处、西仓这些地点,把一整套“谁见过谁、谁什么时候去哪儿、什么东西到底属于谁”全部搅在一起。

最后这份 storyT.txt,就不再是“给模型讲个故事”,而更像是:

一张专门用来拷打上下文的试卷。


二、先别想太复杂,第一版其实可以很土

如果你是第一次做这种东西,我建议不要上来就搞什么复杂框架、多轮消息对象、向量记忆、外部数据库、工具路由。

先用最土的办法把第一版跑起来。

我这个版本就是典型的“先干出来再说”:

  • 一个 ContextStrengthTest.java
  • 一个 storyT.txt
  • 控制台输入问题
  • 每轮都把故事和历史记录继续追加
  • 发给模型
  • 打印回答

它的启动流程非常直白:

先从文件里把故事读出来,如果文件不存在或者是空的,就直接抛异常;读成功后,把系统设定和故事正文一起塞进 conversationHistory;之后每一轮用户输入都继续 append,模型回复也 append。

这个结构有一个非常大的优点:

你能极快地把“能不能测”这件事先跑通。

很多人做工具时,一上来就想架构优雅、目录规范、接口抽象、组件分层。结果最后往往是:目录很好看,工具没跑起来。

而这种小实验工具,第一优先级其实不是优雅,是:

先证明这个方向值不值得继续做。

在这一点上,我很喜欢“从 0 到 1”式的写法,因为它不会一开始就把问题写成论文,而是先把一个最小可运行版本搭出来,再看哪里值得继续打磨。你那篇 JavaSwing 横版射击游戏的公开摘要也是这种味道:先把基础玩法框架搭起来,再谈敌人、Boss、音效和关卡扩展。

这个上下文记忆压测器,我也是同样的思路。


三、这个版本到底做了什么

现在这版程序,从功能上看,其实只做了四件事。

1. 读取测试素材

程序先读取 src/storyT.txt,也就是你要拿来压测模型的故事文本。
这一步很重要,因为它把“上下文”从写死在代码里,变成了一个可以独立替换的素材文件。你今天可以用羊壹这篇故事,明天也可以换成别的案件、设定、文章或者课程笔记。

2. 预先给模型下规则

代码里一开始就塞了一段很明确的系统设定,大意是:

  • 这是一个上下文记忆测试
  • 你要尽量准确记住人物、地点、时间、数字、颜色、物品、关系、事件顺序
  • 如果故事里没写,就说“故事中没有明确提到”
  • 不要脑补,不要编造

这段规则本身不是万能药,但它会让模型至少知道:
你现在不是在让它自由发挥,而是在考它对材料的忠诚度。

3. 把整段上下文整体发给模型

程序并没有维护一个“规范的 messages 数组”,而是采用了最简单粗暴的方式:
把系统设定、故事正文、用户追问、模型回答,全都串成一个大字符串,然后作为单条 user 消息发给接口。请求走的是 HttpURLConnection,目标接口是 DeepSeek 的 chat/completions

这招非常土,但也非常有效。
至少在第一版里,它让你用最低的工程成本就拥有了一台“上下文压力锅”。

4. 手工解析返回

返回值没有引入 Jackson,也没有用完整 JSON 映射,而是直接用一个 extractContent 方法从响应字符串里去找 "content":"..." 这一段,再做一层基础转义处理。

这当然不是工业级写法。
但对于这种自用实验工具来说,它有一个巨大优势:

轻。

不用先配一堆依赖,不用先解决 IDE 里 fasterxml 爆红的问题,先把实验跑起来再说。等你确认这个方向值得做,后面再去补 Jackson、Maven、消息结构重构,都不迟。


四、为什么这个故事比普通对话更适合测“记忆强度”

真正把这个程序变成“刑具”的,不是 Java 代码,而是那份故事素材。

这份故事从一开始就不是为“好看”写的,而是为“难问”写的。
比如羊壹这个人,光开头就已经给你塞了很多可以追问的东西:

  • 年龄三十二
  • 北港区旧钟街二十三号
  • 二楼朝南
  • 门口木牌写着“修表、换锁、代写信”
  • 早上六点四十起床,七点整开工
  • 有一只银色怀表
  • 屋里有长桌、书架、虎尾兰和薄荷
  • 右二抽屉永远上锁

这些信息单独看不难,但一旦故事往后推,越来越多人物和物件混进去,模型就开始暴露问题了。

比如后面又出现段迟:
第一次来时穿黑色短外套,里面是酒红色真丝衬衫,提米白色硬皮箱,右手戴珍珠戒指,还带着黑皮笔记本、钥匙和旧照片线索。
再往后又有陆白萤的笔记本、17/48/71 寄存牌、鹤渡桥东栏第三节的铁盒、烧黑布料上的酒红丝线、冲洗出的胶卷照片、周雁回承认自己是“桥上没回头的人”、段迟母亲的身份、最后珍珠戒指有没有被收下这些东西。

这时候你去问模型:

  • “段迟第一次来时,戒指戴在哪只手?”
  • “酒红色这个线索在故事里一共出现了几层?”
  • “故事有没有明确写程九言确定死在西仓?”
  • “羊壹最终有没有收下珍珠戒指?”
  • “右二抽屉里的信为什么他说不是写给自己的?”

模型如果是真记住了,就能答得很稳。
如果只是模糊读过,它就特别容易在这些题上原地翻车。


五、最有意思的,不是答错,而是“答非所问”

我用这套东西实际测的时候,最让我印象深的,不是某个细节答错,而是一类更离谱的现象:

模型会开始自己换题。

比如我明明问的是:

下面这 6 个判断里,哪些是“故事明确写了”,哪些只是“高度可能但没有实锤”,哪些是“根本不能这么说”?

正常情况下,它应该老老实实按 A、B、C、D、E、F 逐条分类。
结果它有时候会直接无视你给的 A-F,自己脑补出另一套“1、2、3、4、5、6”判断题来答。

这种情况特别有意思,因为它说明问题已经不只是“记忆丢失”,而是:

  1. 当前问题绑定不够牢
  2. 历史对话污染开始加重
  3. 模型开始被自己前几轮的模板带跑偏
  4. 最后形成一种“看起来结构完整,其实题都换了”的答非所问

这其实非常像“移动手感调参”那类问题:
不是说人物能动就算成功,而是你要看它在不同速度、加速度、优先级和碰撞状态下,会不会开始出现违和。那篇“如何制作手感良好的移动算法?”的公开摘要里,核心就是强调参数、优先级和体验调校,这个思路放到上下文测试上同样成立。

因为上下文能力也不是二元的“有/没有”,它更像一套动态手感:

  • 文本短的时候很稳
  • 轮数一多开始飘
  • 题目一复杂开始偷换
  • 历史回答一堆叠开始自我污染

它不是一下子崩,而是慢慢开始“手感变差”。


六、这版程序最大的优点和最大的坑

优点:简单、直给、起效快

它的优点非常明显:

  • 单文件就能跑
  • 输入输出都能直接看
  • 故事素材可替换
  • 很适合做实验
  • 对“上下文到底扛不扛打”这件事,反馈特别直接

而且你不需要先懂什么复杂 Agent 框架。
只要你会 Java,会发 HTTP 请求,会读文件,就够把第一版做出来了。
这点和课程材料里“先实现记忆功能,再拓展外部工具”的路线其实是一致的:先把核心链路打通,再谈拓展。

坑:越测越脏,越脏越容易跑偏

它的问题也很明显:

你把所有历史都塞回去了。

这意味着随着对话轮数增长,模型看到的不是“故事 + 当前问题”,而是:

  • 故事
  • 你前面所有问题
  • 它自己前面所有回答
  • 以及这些东西混在一起的一大坨文本

这样做当然能测“长上下文下还稳不稳”,但也特别容易把模型带偏。
因为当它前一轮已经开始模板化,下一轮你又把这段模板化输出继续喂回去,它就更容易顺着错误惯性一路滑下去。

所以这个版本很适合做“第一阶段压力测试”,但不适合直接当长期方案。


七、如果我要做第二版,我会改什么

这部分我很想按“游戏手感调优”的方式来讲,因为它真的不是“推倒重来”,而是“把几个关键参数拧对”。

1. 不再把 AI 旧回答无脑塞回去

如果目标是测“模型对故事的记忆”,那真正重要的其实是:

  • 故事正文
  • 当前问题

而不是模型上一轮是怎么组织语言的。
所以第二版里,我会考虑:

  • 保留故事全文
  • 保留用户追问
  • 但不强制把所有历史 AI 回答都继续塞回去

这样可以明显减轻“自我污染”。

2. 当前问题要强行高亮

有些题答非所问,不是因为它不认识字,而是因为“当前题目边界太软”。

所以我会把每轮输入改成更硬的结构,比如:

  • 当前唯一需要回答的问题开始
  • A-F 逐条判断
  • 不允许新增条目
  • 不允许改写题目
  • 若故事未明确,请直接写“未明确”

你会发现,很多看似“记忆差”的问题,实际上是“问题绑定不够硬”。

3. temperature 要更保守

这种“按原文核对”的题,不适合高温度。
我会更倾向于把温度压低,让它尽量老实一点。你之前参考的那张表里,代码生成/数学题偏 0.0,而你这个场景本质上也更接近“封闭式事实核对”,不是自由创作。

4. 代码层面别再把 Key 写死

这点说起来有点扫兴,但真很重要。
你当前版本里接口 Key 是直接常量写在代码里的。这个写法做课堂实验无所谓,真要发博客或放仓库,最好立刻改成环境变量。


八、它和 Agent 有什么关系

很多人一听“Agent”,会先想到特别高级的东西:会调工具、会规划步骤、会联网、会操作文件、会自己决定下一步做什么。

但说实话,Agent 的起点其实没那么玄。

它最早就可以非常朴素:

  • 有输入
  • 有上下文
  • 有规则
  • 有一套外部能力
  • 能在多轮里维持任务目标

从这个角度看,这个上下文记忆压测器虽然还很初级,但它已经碰到 Agent 很核心的一层了:

状态管理。

如果一个系统连“前面给过的故事材料”都守不住,那你很难指望它后面在复杂任务里一直稳稳地拆步骤、调工具、做判断。

所以我会觉得,这个项目最大的意义,不只是测“模型记性好不好”,而是让你非常具体地看到:

一个看起来会聊天的模型,离一个真正稳得住的 Agent,中间到底还隔着多少细活。


九、这东西值不值得做

我自己的答案是:值。

原因不是它多高级,而是它足够诚实。

很多时候,我们对模型能力的判断,会被“它刚才那句说得挺像样”骗过去。
但一旦你把问题变成长文、多轮、细节、陷阱、反问、分类和留白边界,它就会露出很真实的一面。

这类工具最大的价值,就是把那层“看起来挺聪明”的滤镜撕掉一点。

让你知道:

  • 它什么时候是真记住了
  • 什么时候只是看起来像
  • 什么时候开始偷换题目
  • 什么时候开始自我污染
  • 什么时候其实应该从提示词和消息结构下手,而不是一味怪模型笨

从这个意义上说,这个项目虽然长得朴素,但特别适合练手。
它不需要巨大工程量,却能逼着你同时思考:

  • 上下文怎么组织
  • 长文本怎么测试
  • 提问边界怎么设计
  • 温度怎么调
  • 结果怎么判
  • 模型错的到底是哪一层

这已经不是“调用一下 API”那么简单了。
它更像是你真正开始摸到 Agent 开发那层硬骨头的地方。


十、写在最后

如果让我给这次实验下一个很短的结论,我会这么说:

上下文能力不是“能不能记住”的问题,而是“在多脏、多长、多轮、多陷阱的材料里,还能不能稳住”的问题。

这个 Java 小工具的第一版,做的事情其实很简单:
给模型一篇够长、够绕、够容易混淆的故事,然后不讲武德地一直追问它。

但也正因为它简单,所以它特别适合当一个起点。
你可以从它出发,继续往下做:

  • 更规范的 message 结构
  • 专门的“只保留故事、不保留 AI 历史回答”模式
  • 自动评分
  • 错误分类
  • 多模型对比
  • 题库化压测
  • 工具调用型 Agent 结合上下文测试

第一版不需要完美。
第一版最重要的,是让你先把刀磨出来,然后真地去砍一轮。

而这件事,本身就很有意思。

import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Scanner;

public class ContextStrengthTest {

    // 改成你的 DeepSeek API Key
    private static final String API_KEY = "sk-d9fcbee8b59549b2b8a6cbf30b681174";

    // 接口地址
    private static final String API_URL = "https://api.deepseek.com/chat/completions";

    // 故事文件名(与本类运行目录同级)
    private static final String STORY_FILE = "src/storyT.txt";

    // 用来保存整个对话上下文
    private static final StringBuilder conversationHistory = new StringBuilder();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8);

        try {
            String storyText = loadStoryFromFile(STORY_FILE);

            System.out.println("=== 上下文强度测试开始 ===");
            System.out.println("已读取故事文件:" + STORY_FILE);
            System.out.println("输入 exit 退出程序。");
            System.out.println();

            // 系统设定
            conversationHistory.append("系统设定:你正在参与一个“上下文记忆测试”。")
                    .append("你必须尽量准确记住用户提供故事中的人名、地点、时间、数字、颜色、物品、关系、事件顺序和细节。")
                    .append("如果后面用户提问涉及故事内容,优先严格依据故事回答,不要随意脑补,不要编造。")
                    .append("如果故事里没有明确写到,就直接说“故事中没有明确提到”。")
                    .append("\n\n");

            // 故事正文作为初始上下文
            conversationHistory.append("以下是需要记住的故事内容:\n");
            conversationHistory.append(storyText).append("\n\n");

            System.out.println("故事已载入。你现在可以开始提问,例如:");
            System.out.println("1. 某个人住在哪里?");
            System.out.println("2. 某个物品是什么颜色?");
            System.out.println("3. 某件事发生在前还是后?");
            System.out.println();

            while (true) {
                System.out.print("我:");
                String userInput = scanner.nextLine();

                if ("exit".equalsIgnoreCase(userInput.trim())) {
                    System.out.println("测试结束。");
                    break;
                }

                conversationHistory.append("用户:").append(userInput).append("\n");

                String reply = callDeepSeek(conversationHistory.toString());

                System.out.println("AI:" + reply);
                System.out.println();

                conversationHistory.append("AI:").append(reply).append("\n");
            }

        } catch (Exception e) {
            System.out.println("程序运行失败:");
            e.printStackTrace();
        } finally {
            scanner.close();
        }
    }

    /**
     * 从同级目录读取故事文本
     */
    private static String loadStoryFromFile(String fileName) throws Exception {
        Path path = Path.of(fileName);

        if (!Files.exists(path)) {
            throw new RuntimeException("未找到故事文件:" + fileName + "。请把 storyT.txt 放到程序运行目录下。");
        }

        String text = Files.readString(path, StandardCharsets.UTF_8);

        if (text == null || text.isBlank()) {
            throw new RuntimeException("故事文件为空:" + fileName);
        }

        return text;
    }

    /**
     * 调用 DeepSeek,并返回 content 文本
     */
    private static String callDeepSeek(String fullContext) throws Exception {
        String jsonBody = """
                {
                  "model": "deepseek-chat",
                  "messages": [
                    {
                      "role": "user",
                      "content": %s
                    }
                  ],
                  "stream": false
                }
                """.formatted(toJsonString(fullContext));

        URL url = new URL(API_URL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
        conn.setRequestProperty("Authorization", "Bearer " + API_KEY);
        conn.setDoOutput(true);
        conn.setConnectTimeout(15000);
        conn.setReadTimeout(30000);

        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8);
            os.write(input);
        }

        int responseCode = conn.getResponseCode();
        InputStream is = (responseCode >= 200 && responseCode < 300)
                ? conn.getInputStream()
                : conn.getErrorStream();

        String responseText = new String(is.readAllBytes(), StandardCharsets.UTF_8);

        if (responseCode != 200) {
            return "请求失败,HTTP 响应码:" + responseCode + "\n原始返回:" + responseText;
        }

        String content = extractContent(responseText);
        if (content == null || content.isBlank()) {
            return "未能解析出 content,原始返回:" + responseText;
        }

        return content;
    }

    /**
     * 把普通字符串转成 JSON 安全字符串
     */
    private static String toJsonString(String text) {
        String escaped = text
                .replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
        return "\"" + escaped + "\"";
    }

    /**
     * 简单提取 choices[0].message.content
     */
    private static String extractContent(String json) {
        String key = "\"content\":\"";
        int start = json.indexOf(key);
        if (start == -1) {
            return null;
        }

        start += key.length();
        StringBuilder result = new StringBuilder();

        boolean escaping = false;
        for (int i = start; i < json.length(); i++) {
            char c = json.charAt(i);

            if (escaping) {
                switch (c) {
                    case 'n' -> result.append('\n');
                    case 'r' -> result.append('\r');
                    case 't' -> result.append('\t');
                    case '"' -> result.append('"');
                    case '\\' -> result.append('\\');
                    case '/' -> result.append('/');
                    case 'b' -> result.append('\b');
                    case 'f' -> result.append('\f');
                    default -> result.append(c);
                }
                escaping = false;
            } else {
                if (c == '\\') {
                    escaping = true;
                } else if (c == '"') {
                    break;
                } else {
                    result.append(c);
                }
            }
        }

        return result.toString();
    }
}
长林市住着一个叫羊壹的男人。

他三十二岁,个子不高,左眼眼尾有一颗很淡的痣,平时住在北港区旧钟街二十三号、二楼朝南的那间小屋里。那屋子门口挂着一块褪了漆的木牌,牌子上用墨蓝色写着“修表、换锁、代写信”,字是他自己写的,末尾那个“信”字总爱多带一横。羊壹会修表,但修得最好的其实不是表,是那些年岁久了、内部齿轮卡住却又舍不得扔掉的留声机和挂钟。旧钟街的人都知道他有个习惯:每天早晨六点四十起床,先用铜壶烧一壶水,泡半杯很淡的茉莉花茶,再坐在窗边把前一天没修完的东西看十分钟,七点整才真正开始动手。街口卖烧饼的孟嫂说,他像一只从不迟到的旧钟,连叹气都像按着刻度来的。

羊壹并不是长林市本地人。他十二年前从岚河县搬来,来的那天下着雨,身上只带了一个深灰色帆布包、一把黑柄折伞、一张写着“旧钟街二十三号”的纸条和一只银色怀表。那只怀表背面刻着一个很小的“玖”字,很多人都问他那是谁的东西,他从来没正面回答,只说“不是我的,但现在归我保管”。有人猜是情人的,有人猜是死人的,有人猜是债主留下的,羊壹都不解释。时间久了,这只怀表反而成了旧钟街最有名的谜。

他屋里东西不算多,却摆得特别讲究。靠东的墙边是一张胡桃木长桌,桌上从左到右依次摆着:一盏黄铜台灯、三个带编号的小抽屉盒、一只蓝边白瓷笔洗、一卷墨绿色细绳、两把镊子、一套九件的钟表螺丝刀、一台边角磕掉一点的黑色收音机。长桌最下面那层抽屉有四格,左一放账本,左二放印泥和火漆,右一放修表零件,右二永远上锁。很多年来,连最熟的人都不知道右二里放的是什么。羊壹睡觉的床靠西墙,床头有个旧书架,共四层,第一层是地图和县志,第二层是修理手册,第三层放信封、便签和装相片的铁盒,第四层反而空着,只放了一只没有灯泡的玻璃灯罩。窗台上有两盆植物,一盆是虎尾兰,一盆是薄荷。虎尾兰不怎么长,薄荷倒是年年疯长,夏天一开窗,满屋都是清苦的香气。

旧钟街的人都爱找羊壹写信。因为他字好,也因为他嘴紧。给外地念书的孩子写家信,给吵架不肯见面的夫妻写道歉信,给借了钱又不好当面催的人写催账信,甚至有人想给死去的人补一封没来得及说出口的话,也会托到他这儿。羊壹写信收费不高,普通一封两块,急信三块,加火漆封口另收五角。他替人写的时候从来不多问,只在信纸左下角用极小的字记一个编号,方便自己存底。很多年过去,他的铁盒里已经攒了四百多张抄底纸,按月份和颜色分开:白纸是公事,米黄色是家信,浅蓝色是不能给别人看的私信,最少的是淡粉色,只有七张,全都没有署名。

和羊壹最熟的人叫苏绫。苏绫二十八岁,在旧钟街尽头经营一家小花店,店名叫“迟春”,门头是浅绿色木框,门上挂两只铃铛,一只声音清脆,一只声音发闷。她留着刚过肩的黑发,常穿月白色或灰绿色的棉裙,左手食指上有一道很细的刀口疤,是前几年修玫瑰刺篱时划的。她每天早晨七点二十开门,比羊壹晚二十分钟。她卖花,也卖干花书签和自己配的香包,最受欢迎的是桂花乌梅香和檀木薄荷香。旧钟街的年轻姑娘喜欢找她包花,老人则爱找她配丧仪用的白菊和素绢。苏绫和羊壹认识七年,最开始只是羊壹帮她修坏掉的壁钟,后来她每周三中午都会给他送一小碗热汤,有时是冬瓜排骨,有时是番茄蛋花,冬天最常送的是白萝卜牛肉汤。羊壹不怎么会说谢,通常只会在第二天清晨把她店门口松掉的螺丝拧紧,或者把她坏掉的门铃拆开重新接线。

除了苏绫,旧钟街还有个总来找羊壹的人,叫周雁回。周雁回三十七岁,是临水档案馆的管理员,住在栖霞巷十号,是个说话慢但记性极好的人。他总穿深棕色中山装,夏天也不例外,只是会把袖口卷到小臂。他鼻梁高,唇色偏淡,戴一副圆框眼镜,镜片左边有一道不仔细看就发现不了的裂纹。周雁回最出名的不是他管档案,而是他能背出长林市过去二十年的街巷改名史:比如旧钟街以前叫钟鼓巷,九年前才改名;鹤渡桥在十五年前翻修过一次,桥栏由青石换成了灰白色水泥;北港区原本归城西,四年前行政重新划分后才独立出来。周雁回来找羊壹,通常不是为了修东西,而是为了问一些看起来很零碎的问题,比如“十二年前二月那场火灾烧掉的是北仓还是西仓”“钟塔第三次停摆是在春天还是秋天”“一个人能不能把自己的名字从一封旧信上剪掉而不留痕”。羊壹有时回答,有时只看他一眼,说“你该去问馆里,不该问我”。

旧钟街往东走十五分钟,有一座桥叫鹤渡桥。桥下是沉水河,河面宽,风大的时候水会呈灰绿色。桥北头有一家面馆,老板许槐,四十六岁,微胖,喜欢穿洗得发白的藏青围裙,做的姜丝牛肉面是长林市一绝。许槐有个女儿叫许澄,十五岁,在长林三中念书,数学很好,作文却总写得像说明书。她每周一和周四放学后会到羊壹那里坐半小时,抄一篇字帖,再问他一道几何题。有一次她问为什么他会修那么多东西,羊壹说:“因为坏掉的东西比人诚实,哪儿断了就是哪儿断了。”许澄没听懂,但把这句话记在了本子背面,后来还拿它做了作文结尾,得了全年级第六。

羊壹的生活看起来一成不变,真正的变化,是从那年五月十七日开始的。那天是周五,天气预报说多云转晴,实际一早就闷得厉害,上午十点半之后甚至起了短暂的热风。羊壹照例在六点四十起床,七点整开工,八点二十替孟嫂修好了她儿子留下的一只闹钟,九点零五替街口裁缝铺换了门锁,九点五十收到了苏绫送来的半包白芍药——不是整束,只有五支,其中一支花瓣边缘被压皱了一点。苏绫说,今天店里进错了货,本来该来的是香槟色玫瑰,送来的却是两箱白芍药和一箱金雀花。羊壹把白芍药插在蓝边白瓷笔洗里,没说好不好看,只说“花开太快的东西,最好不要放西窗”。

事情出在中午十二点零七。那时羊壹刚吃完一小碗清汤挂面,正打算把门口那只歪了的风铃扶正,忽然来了一个陌生女人。女人穿一件黑色短外套,里面是酒红色真丝衬衫,手里提着一个米白色硬皮箱,箱角包着黄铜护角,右手戴一枚不算显眼的珍珠戒指。她头发挽得很整齐,眉尾微微上挑,年纪看着大约三十五到三十八之间,口音不太像本地人。她站在门口没立刻进来,先抬头看了看那块“修表、换锁、代写信”的牌子,又看了看台灯边上的白芍药,问的第一句话却是:“这里有人寄存过一只银色怀表吗?”

羊壹没有马上回答。他把风铃扶正,才转过身说:“要修什么?”

女人把箱子放到地上,发出轻轻一声闷响,说:“我不修东西。我来取东西。”

“名字。”

“段迟。”

羊壹听见这个名字,手指很轻地动了一下,像被细刺扎过,却仍然面无表情。他说:“我这里不替人保管来历不明的东西。”

段迟笑了一下,那笑意却没有进眼睛里。她从衬衫口袋里摸出一张对折得很整齐的卡纸,纸边微泛黄,上面只有一行字:“长林市,旧钟街二十三号,找羊壹取回玖表。”落款没有姓名,只盖了一枚暗蓝色圆章。圆章上的字已经模糊,只能勉强辨出一个“河”字和半个“文”字。羊壹看了一眼,说:“这不是我写的。”

段迟说:“但这张纸是从一个铁盒里找到的,和另一张照片放在一起。照片上有你。”

羊壹没接话。他向来不怕别人知道他的住址,也不怕别人来找,可那天周雁回如果在场,大概会看出他的呼吸比平时浅了半寸。

段迟没有逼问,只把米白色硬皮箱打开了半边。箱子里最上面是一条深蓝色围巾,下面压着一本黑皮笔记本、一只牛皮信封和一串铜钥匙。钥匙一共三把,大小不一,拴在红绳上,其中最小的一把尾部刻着数字“17”。段迟把那串钥匙拿起来晃了晃,说:“我不是来抢,也不是来闹。我只想确认两件事。第一,那只怀表还在不在你这里。第二,程九言是不是死在长林市。”

这句话一出口,屋里一下静得连风铃都像停了。羊壹终于抬眼认真看她:“你跟程九言什么关系?”

段迟合上箱子,声音比刚才低:“表面上算同事,实际上算朋友。更早一点,也许算半个家人。”

她这话说得很怪,但羊壹没有追问。他只说:“表在。我不知道你该不该拿。”

段迟问:“那你知道我该不该知道真相吗?”

羊壹说:“知道真相不等于拿得动真相。”

十二点二十,苏绫正好来送忘在羊壹这里的一本花账。她进门时看见段迟,明显怔了一下,因为那女人的穿着和旧钟街太不搭——旧钟街的人大多穿耐磨耐脏的衣服,很少有人会在大白天穿酒红色真丝衬衫配珍珠戒指。苏绫把账本放下,顺手看了一眼那只米白色硬皮箱,又看了看羊壹。羊壹只说:“今天先不修了。”苏绫明白这是让她别问。她点点头,走之前却把门口风铃的绳结重新理了一遍,仿佛在不动声色地提醒羊壹:你屋里来了外人,我知道。

那天晚上,羊壹没有像平时那样七点半准时关门,而是直到八点二十才拉下半扇木门。段迟没在旧钟街住,她去了鹤渡桥北头的安禾旅社,要了一间二楼朝东的单人房,房号二零七码。旅社老板娘姓梁,五十二岁,嘴快心细,看见她的米白色箱子和那身衣服,就猜她不是来旅游的。段迟登记时写的是“段迟,岚河人,停留三日”,身份证尾号是三一七四。她只要了一壶热水,没要饭,晚上九点十五又单独下楼问老板娘:“临水档案馆几点开门?”梁老板娘说:“九点。”段迟点头,回房前又加了一句:“如果有人来找我,先别告诉他我的房号。”

第二天是五月十八日,周六。清晨六点五十八,沉水河上起了一层薄雾,鹤渡桥像被削去了上半截。羊壹一夜几乎没睡踏实,凌晨两点四十起来烧过一次水,四点半又起身去看那只怀表。怀表被他放在右一抽屉下方那个暗格里,和一张旧照片、一枚车站寄存牌、一张发黄车票夹在一起。照片上是三个年轻人,最左边是羊壹,十九或二十岁的样子,瘦得比现在还厉害,穿浅灰衬衫;中间的男人更高,眉眼冷淡,手里捏着一顶旧礼帽;右边只拍到半个身子,是个短发女人,笑得很亮,看不清全脸。照片背后写着一行字:“岚河,七月初四,午后三时,桥东照相馆。”字迹不是羊壹的,更像程九言的。至于那张车站寄存牌,上面只有两个数字:17和48。

上午九点零二,段迟去了临水档案馆。周雁回正好当值。档案馆一楼大厅有四扇高窗,地砖是灰白相间的老式六角砖,最里面那台座钟总比标准时间快三分钟。段迟站在登记台前,说要查十二年前有关岚河县人员流入长林市的临时登记册,以及同年二月到四月的火灾案卷。周雁回抬头看了她一眼,先看见她指上的珍珠戒指,又看见她提着那只米白色硬皮箱,慢慢说:“查案卷要手续。”段迟从箱里取出一封盖章介绍信,章是苍青色的,内容写得很含糊,只说“因旧案清理需要,协助查阅相关档册”。周雁回接过信,却没有立刻让她进阅览室,而是问:“你查岚河人流入册,是找人还是找事?”段迟答:“先找人,再找事。”周雁回又问:“找谁?”段迟说:“程九言。”

周雁回的眼镜镜片在那一瞬反了一下光,像是恰好把眼睛藏住。他说:“这个名字不在馆里的公开索引里。”段迟回答:“不在索引,不等于不在档里。”周雁回安静了两秒,把她带到二楼东侧的第三阅览桌。那张桌子边角磨得很圆,桌面左下角刻着两个很浅的字母“LY”。段迟查了整整三个小时,只在十一点四十分抄下三条东西:其一,十二年前三月十一日,确有一名岚河县男子登记入住北港临时安置处,姓名栏写的是“杨一”,不是“羊壹”;其二,同年三月十九日,长林西仓火灾案卷里附了一份未公开证物交接单,交接人签名看不清,只剩一个“九”字右半边;其三,四月初五那天,城北车站寄存处登记簿缺了两页,页码从四十七直接跳到五十。

中午十二点零五,段迟离开档案馆时,周雁回问了她一句:“你相信一个人会把自己的名字改成更像自己的那个字吗?”段迟回头看他:“比如?”周雁回说:“比如‘杨一’改成‘羊壹’。前者像临时写上去的,后者像真心想留下来的。”段迟没回答,只记下了这句话。

与此同时,旧钟街上,苏绫在羊壹那里吃了一顿难得的午饭。平时羊壹吃得简单,这天却煎了两只荷包蛋,还把藏了很久的一小罐酱黄瓜也拿了出来。苏绫知道他心里有事,没问段迟,只问:“你打算让她拿走那只表吗?”羊壹沉默了一阵,说:“她不一定是来拿表的。她更像是来确认,还有没有人记得程九言。”苏绫说:“那你记得吗?”羊壹手里的筷子停了一下:“太记得了,反而有些地方分不清是记得,还是不愿意忘。”苏绫把酱黄瓜往他那边推了推,说:“你每次说这种话,都像把门只开半扇。”

羊壹轻轻笑了一下,那笑意比段迟真实得多,却也淡得多。他忽然问苏绫:“如果一个人明明已经走了很多年,可有人一来,所有东西都像没走过,你觉得这叫回来,还是叫追债?”苏绫想了想,说:“看来的那个人是想带你走,还是想把你按回去。”羊壹没再说话。

下午两点二十七,许澄来抄字帖。她今天穿的是校服外套,里面一件浅蓝T恤,鞋带左边白右边灰,是她前一天慌乱中系错了没发现。她一进门就看见桌上的白芍药只剩四支,便问少了一支去哪了。羊壹说扔了,花瓣烂得快。许澄问:“你心情不好?”羊壹说:“你今天不是来观察我的。”许澄吐了吐舌头,低头抄“静水流深”四个字,抄到第三遍时忽然说:“昨天有个穿很贵衣服的阿姨在桥头问路,问旧钟街二十三号怎么走,是不是找你的?”羊壹“嗯”了一声。许澄又问:“她像坏人吗?”羊壹反问:“坏人会写在脸上吗?”许澄认真想了两秒,说:“不会,但会让人想多看一眼。”羊壹难得地说:“你这句比上次作文那句好。”

当天傍晚六点零八,段迟再次来到旧钟街。她没有再提箱子,换了一件烟灰色外套,手里只拿着那本黑皮笔记本。她进门后,先把笔记本放到桌上,说:“这本不是程九言的,是陆白萤的。”羊壹这回终于有了明显反应:“她还活着?”段迟说:“至少三年前还活着。之后失联。”羊壹盯着那本笔记本,像盯着一块已经裂开却迟迟没掉下来的冰。陆白萤这个名字,苏绫没听过,周雁回听过一点,许槐则只在别人酒后乱谈时模糊听过,说那是个会开锁、会伪造章、笑起来像没把危险当回事的女人。

段迟翻开黑皮本的第七页,上面用蓝黑墨水写着一段话:“若表在羊壹处,则他仍在长林;若他仍在长林,则九言未彻底白死;若有人以‘岚河旧事’逼他交表,先查来者左手无名指与旧桥编号。”这段字下面还有两个圈,一个圈着“17”,一个圈着“左手无名指”。段迟把左手伸出来,她左手无名指上没有戒指痕,珍珠戒指始终戴在右手中指。羊壹看完只说:“陆白萤从前就爱故弄玄虚。”段迟说:“她不是故弄玄虚,她是在给后来的人留暗号。”羊壹抬头:“你凭什么确定后来的人是你?”段迟说:“因为笔记本最后一页夹着一张半烧过的车票,票根上写的是‘长林—岚河’,日期是三年前六月二十九,座位号十车十二A。那趟车只有我和她一起坐过。”

这一次,羊壹终于把那只银色怀表拿了出来。表壳很冷,边缘磨损得厉害,弹开时发出一声很轻但非常脆的响。表盘是乳白色的,数字不是常见的阿拉伯数字,而是纤细的罗马数字,秒针是极深的蓝,接近夜色。最特别的是内盖,内盖刻了两行字:上面一行是“给不肯回头的人”,下面一行是“岚河七月”。段迟看见那两行字,眼眶一下红了,却没有伸手去碰。她问:“程九言最后一次拿着它,是不是在西仓出事前一晚?”羊壹说:“不是。是更早。是在桥东照相馆拍那张照片的时候。”段迟怔住:“你见过那张照片?”羊壹说:“我手里有一半。”

“另一半呢?”

“可能在陆白萤那里,也可能早没了。”

段迟靠着桌沿站了几秒,才轻声说:“程九言不是我哥,也不是我爱人。他是我母亲以前收养过一年的孩子。后来手续没办成,他就被带走了。可在我家那一年,他一直把我当亲妹妹。我十一岁时,他送过我一块糖,一把红漆木梳,还有一句话:‘有一天要是我失踪了,别相信别人说我是自己不见的。’我记了二十多年。”她说到这里,眼睛却没看羊壹,而是看着桌上的白芍药花瓶——那时只剩四支花,其中两支已经有些垂头。

羊壹没安慰她,只把怀表合上,说:“他确实不是自己不见的,但也不是别人一句话就能找回来的那种失踪。”段迟问:“那是什么?”羊壹说:“是有人故意让一个人既活过,又像没活过。”

这句话之后,旧钟街进入了一段看似平静实则每个人都各怀心思的日子。五月十九日周日,段迟没有再去找羊壹,而是去了城北车站旧寄存处。那里如今只剩一间半荒废的小值班室和一排锈掉的寄存柜。老管理员姓韩,六十四岁,耳朵有点背,记人却很准。段迟拿着三年前那张半烧车票问他,有没有见过陆白萤。韩管理员眯着眼看了半天,说:“短发,瘦,高个,左手戴黑绳?”段迟说是。韩管理员点点头:“见过,三年前六月底,她来取过一个寄存包,号码好像是17还是71,我一时分不清。包不大,灰色,布的。她走的时候问过我一句:‘十二年前这里是不是也丢过两页登记簿?’我说姑娘你问这个做什么,她就笑,说随便问问。”段迟追问那天她穿什么,韩管理员说:“白衬衫,外面一件墨绿色薄外套,鞋是棕色平底鞋,左鞋边有一道划痕。”

同一天傍晚,周雁回独自去了沉水河边。他站在鹤渡桥中央,看了很久桥栏外侧新旧颜色不同的一小块水泥。十五年前翻修时,有一处护栏因为材料不足临时补得不够匀,如今那块颜色比别处更浅,像皮肤上一道旧伤。周雁回把手搭在那块地方,低低说了句:“原来你们是从这里开始分开的。”没人听见这句话,除了河风。

五月二十日,周一,长林三中开晨会,许澄因为迟到两分钟被记了名字。她中午来旧钟街时情绪很差,把字帖抄成了“静谁流深”。羊壹看了一眼,说:“错了两个字。”许澄蔫蔫地说知道。苏绫那天送来一小束金雀花,黄得很明快,和前两天的白芍药形成鲜明对照。她把花插好后对羊壹说:“段迟今天去你以前登记过的临时安置处旧址了。”羊壹抬眼:“你怎么知道?”苏绫说:“她来我店里买过一枝白菊,顺口问了路。”羊壹皱眉:“她买白菊做什么?”苏绫说:“也许不是祭人,是试探卖花的人能不能认路。”羊壹听完,没说是也没说不是,只把桌上的怀表往抽屉深处推了一寸。

临时安置处旧址在北港更北边,靠近废弃木材站,如今只剩半堵墙和一块被藤蔓缠住的牌子,上面还能勉强看见“临时居住点”几个字。段迟在那里找到一位曾经做过看守的老人,姓栾,七十一岁,左腿有旧伤,下雨前总会疼。栾老人记得十二年前确实来过一个很瘦的青年,登记写“杨一”,但那人第二天自己把名字改成了“羊壹”,理由是“羊比杨好,至少看起来不像随手写错的”。栾老人还记得那青年来时身边不是一个人,有个更沉默的男人陪着,姓程还是什么,不确定,只记得那人有一块表,总在夜里拿出来看,却几乎不看时间,更像在确认表还在不在。段迟听到这里,问:“他们后来怎么分开的?”栾老人想了很久,说:“一个去了西仓那边,一个留在旧钟街附近找活。隔了大概五六天吧,西仓那边就出了事。”

西仓那场火,官方记录一直很含糊,只写“夜间起火,仓内旧木材和布匹迅速燃烧,损失不小,伤亡不详”。长林市很多人都知道那不是普通意外,但具体烧掉了什么、死了谁、谁又从火里跑出来了,没人能说全。周雁回后来私下告诉段迟,馆里关于那场火最奇怪的地方不在起火原因,而在“伤亡不详”四个字。正常案卷即便不公开姓名,也会写人数,可西仓案卷里偏偏只写“有人失踪”。这个“有人”究竟是一人还是两人,从来没人能核准。

五月二十一日晚上,事情第一次真正失控。那天晚上八点四十,旧钟街突然停电,范围不大,只有从孟嫂烧饼铺到苏绫花店这一段全黑,隔一条巷子的裁缝铺还亮着灯。停电后不到五分钟,羊壹屋后那道平时很少走人的窄巷里传来一声金属撞击声,像有人不熟练地撬门。羊壹拿手电出去时,只看见墙角一截灰色衣摆闪过去。他追到巷口,地上留下了一枚很小的铜扣和一张撕了一半的纸,纸上残存三个字:“……右二抽……”。苏绫听见动静也提灯出来,她今天穿深青色外套,脚上是黑布鞋,左手还拿着剪花枝的短剪。她看见那半张纸,第一句不是问人跑了没有,而是问:“右二抽屉里到底是什么?”羊壹沉默几秒,说:“是程九言留的一封信。”苏绫望着他:“你以前说没收到过他的信。”羊壹说:“因为这封不是写给我的。”

这话刚落,段迟从巷口另一边赶来,呼吸有点急。她今天本该住旅社收拾东西,明早离开长林,却偏偏又折了回来。她说:“有人跟了我一整天。从档案馆门口,到车站旧寄存处,再到旅社。”周雁回竟然也在不远处,他手里提着一盏档案馆淘汰下来的老式应急灯,光线昏黄。他看着那枚铜扣,说:“这不是本地常见的扣子。像旧式制服上拆下来的。”许槐也被惊动,提着擀面杖赶来,许澄躲在他身后,眼睛亮得像在看小说现场。孟嫂站得最远,却最先说出一句关键的话:“我刚才看见一个戴鸭舌帽的人从桥那头跑过去,左脚有点跛,手里拎着黑包。”

羊壹把众人请进屋,把右二抽屉的锁打开。里面东西并不多,只有一只牛皮信封、一把没有齿纹的钥匙、一张折成四折的地图和一个小布袋。小布袋里是三枚车站寄存牌:17、48、71。牛皮信封外面写着“若白萤未至,此信勿启”。字迹沉稳锋利,确实像程九言。苏绫站得最近,看见那行字时,忽然明白为什么羊壹这么多年宁愿让人误会,也从不拿出来澄清。因为这封信的收件人不是他,他一直只是替别人守着。

段迟问:“现在能开了吗?”

羊壹没有立刻动手,反而看向周雁回:“你怎么看?”周雁回说:“从规矩上讲,不该开。从现在的局势看,不开也未必守得住。”苏绫则很直接:“既然已经有人来偷了,再装作没事只会更被动。”许槐没听太明白,却也点头:“要开就趁大家都在。”最终,羊壹用裁纸刀划开信封。里面只有两张纸。

第一张纸很短:“若你看到这里,说明我没能亲自把表带回岚河。羊壹知道的只是一半,另一半在桥、表、寄存牌三者之间。不要找官方案卷,它会把活人写薄,把死人写轻。白萤,若你还活着,去找二月十九那天在桥上没回头的人。他欠我一句实话,也欠自己一条命。”

第二张纸是一张手绘小图。图上画的不是整个长林,而只是沉水河、鹤渡桥、旧钟街、车站和西仓之间的关系线。四个地点分别用不同颜色标记:桥是蓝,旧钟街是黑,车站是红,西仓是灰。图右下角还写着一串数字:3-19,17,48,71,午后3:00,东栏第三节。

周雁回盯着“东栏第三节”六个字,脸色第一次明显变了。段迟立刻问:“你知道这是什么?”周雁回扶了扶眼镜,低声说:“十五年前鹤渡桥翻修时,东栏第三节内侧空过一段。因为当时模板尺寸不合,有一截后来是补浇的。”羊壹抬眼:“你以前为什么没说?”周雁回看着他,声音仍旧很慢,却比平常更沉:“因为我以为里面早就什么都没有了。”

第二天,五月二十二日,清晨五点五十,天还没全亮,羊壹、段迟、周雁回和苏绫四个人就去了鹤渡桥。许槐本来也想跟,被羊壹劝住,只让他照看许澄。桥面潮湿,东边天空泛出一种很浅的鱼肚白。周雁回按照信上的指示,找到东栏第三节靠里那块颜色略浅的地方,用随身带的小铁锤轻敲了几下,果然有一处回音发空。羊壹用起子沿着细缝慢慢撬开,里面藏着一个用油布包好的细长铁盒。铁盒长约二十五厘米,宽不过五厘米,表面锈得厉害,但锁扣还在。没有钥匙口,只能从侧边掰开。

盒子里放着四样东西:一张名单、一枚半融的徽章、一小卷未冲洗的胶卷和一片烧黑的衬衫布料。名单上有七个人名,最上面是程九言,第二个是陆白萤,第三个名字被墨水糊掉了,只剩“……一”,第四个是一个不认识的外地名,第五个是仓库保管员赵河生,第六第七则是两个像假名的代号。徽章上勉强能看见“仓运”两个字。至于那片烧黑布料,布边却残留一点非常显眼的酒红色丝线。

段迟看到那点酒红,手一下攥紧:“这颜色……”苏绫也想起来了——段迟第一次来时,里面穿的就是酒红色真丝衬衫。可这块布不可能是她的,它来自十二年前。周雁回则盯着名单第三个被糊掉的名字,喃喃说:“不是杨,不是羊,是原本有人故意不想让人看清。”羊壹没有说话,只把那卷胶卷握在手里,像握着一条还没醒来的蛇。

他们把东西带回旧钟街后,周雁回借档案馆的暗室设备帮忙冲洗胶卷。胶卷一共洗出六张照片,前三张是仓库内景,第四张拍到了一辆车牌尾号为47的黑色货车,第五张模糊拍到两个人在桥边交接什么,第六张最清楚:画面左边站着程九言,右边站着一个穿酒红衬衫、戴浅色外套的女人,只拍到侧脸,但轮廓和段迟有七分像。段迟看见那张照片后愣了很久,才哑声说:“这是我母亲。”她母亲二十多年前就死了,死因写的是急病。可从没人告诉过她,母亲和程九言竟在长林一起出现过。

故事至此,终于从一场寻找失踪者,拐向了另一个更深的方向:程九言为什么会和段迟母亲出现在同一张照片里?陆白萤又为什么知道那么多,却偏偏失联三年?而周雁回口中那个“在桥上没回头的人”,究竟是谁?

答案在五月二十三日午后慢慢浮上来。那天下午三点整,和图上写的时间一模一样,周雁回主动对羊壹说:“十二年前三月十九日下午,我在鹤渡桥上见过程九言。”这是他第一次正面承认。那天他二十五岁,刚进档案馆不到一年,替馆里送一份借阅记录去北港。走到桥中央时,他看见一个男人靠在东栏边,手里拿着一只银表,像在等人。几分钟后,另一个更瘦的青年从旧钟街方向跑来——那就是羊壹。两人低声说了几句,周雁回只听见一句:“你去西仓,我去车站;如果今天不成,就按17、48、71。”随后,桥北头又来了一个女人,穿浅色外套,里面是酒红衬衫。程九言看见她,脸色立刻变了。羊壹转身要走,却被程九言叫住。周雁回那时犹豫了两秒,最终没有上前,而是装作没看见从旁边过去。过桥时,他回过一次头,看见程九言把那只银表塞给羊壹,自己朝西仓方向走了。那个“没回头的人”,说的其实就是周雁回自己。

“后来呢?”段迟问。

周雁回闭了闭眼:“后来当天晚上西仓失火。我知道自己看到的东西可能有用,却没有立刻上报。因为我认出了那个酒红衬衫女人身上别着的一枚徽章,那徽章和馆里某位常来查旧商运资料的人有关。我怕卷进去,也怕说了没用。第二天再去问,很多记录已经被人先一步收走了。”

“所以你欠程九言一句实话。”羊壹说。

周雁回点头:“也欠你。”

段迟没有立刻责怪他,反而问:“我母亲为什么会在那里?”周雁回摇头:“这我不知道。我只知道她不是偶然路过。她像是去确认一件必须确认的事。”羊壹这时接过话:“她大概是去确认名单有没有被交出去。程九言当年查到一批借火灾销账、借车站寄存转运的东西。那批东西不是普通货,涉及一些本该在案却被抹掉的人和物。程九言想把证据带出长林,陆白萤负责开锁和转移,我负责跑旧钟街和车站两头。后来不知道哪一步被盯上了。”

“那我母亲呢?”

“她起初像是帮忙的。后来我不确定她到底站哪边。”

段迟沉默了很久,才说:“她临死前留下过一把红漆木梳和一句话,让我别轻易相信‘被整理过的真相’。我一直以为她只是说人生,现在看来不是。”说完,她从米白色箱子的夹层里拿出那把木梳。梳背里竟然藏着一张卷得极细的纸。纸上只写了一句:“九言若死,错不在桥,错在人。”下面画了一个很小的圈,圈里是个“馆”字。

所有人的目光都转向周雁回。周雁回却没有急着辩解,只说:“如果真是馆里的人,那也不止我一个看见过。”他摘下眼镜,第一次显出明显疲惫:“当年常来馆里借商运资料的,不止一个女人。还有一个男人,左脚微跛,爱戴鸭舌帽,姓赵还是姓邵,我始终没核准。昨晚你们追的人,可能就是他,或者跟他同一路的人。”孟嫂说的戴鸭舌帽、左脚微跛的人,顿时有了影子。

五月二十四日,长林下雨。从早上八点半到下午三点,雨一直没停,屋檐滴水像连成了线。这样的天气很适合把所有话都说完,也很适合有人在雨里消失。段迟原本计划那天离开,却留了下来。她和苏绫在花店里坐了一个上午,一个包白菊,一个擦花瓶。苏绫忽然问她:“你来之前,想过找到的会是这种真相吗?”段迟说:“我来之前以为真相只有一层,要么程九言死了,要么没死。现在才知道,原来真相像你店里的花泥,外面看是块黑的,里面却扎满看不见的茎。”

苏绫听了,笑了一下:“这个比喻有点怪,但挺准。”

而在羊壹那边,他终于把多年来没说出口的话说给了周雁回听。十二年前桥上分别后,羊壹按约定去了车站,把寄存牌17、48、71分别存放到三个不同的人手里,本想等程九言脱身再统一取回。结果当天夜里火起,第二天能找回来的只有17和48,71被人先一步取走。那枚71后来辗转落到谁手里,羊壹不知道,直到这次从右二抽屉的小布袋里拿出第三枚,才意识到陆白萤不知何时已经悄悄补齐了这条线。也就是说,陆白萤不是失联那么简单,她这些年很可能一直在暗中把散掉的证据重新串起来。

故事最后的转折,出现在五月二十五日的清晨。许澄抱着一本借来的《长林旧街图录》冲进旧钟街,大喊她发现了东西。书里夹着一张几十年前的桥梁施工老照片,照片背面印着一句说明:“鹤渡桥旧称归渡桥,东栏第三节为后补浇筑,因原节内藏旧木函。”原来东栏第三节那处空心,并不是十二年前才被人利用,而是更早以前施工时就遗留下来的结构。程九言只是知道这个秘密,借它藏证。而真正让周雁回、羊壹和段迟都一下怔住的,是照片角落里一个模糊的人影——那人戴圆框眼镜,穿深棕色中山装,站姿和如今的周雁回极像,但照片拍摄时间却是十七年前。

周雁回看了很久,才苦笑说:“那不是我,是我父亲。”

于是,一切又往前推了一代。馆里的人、桥上的人、车站寄存簿消失的两页、酒红衬衫女人、岚河来的三个年轻人、被抹掉的名字、没送到收件人手里的信,都不再只是某一年某几个人的事,而像是一条从十几年前就开始缠绕、到今天才被一点点拆开的绳。

可即便如此,程九言到底死在西仓,还是借火离开了长林,故事仍没有一个能让所有人满意的答案。羊壹后来对段迟说:“有些人死没死,和他还能不能回来,其实是两件事。”段迟问:“那你希望他回来吗?”羊壹看着窗台上长得过盛的薄荷,过了很久才说:“我更希望他当年没有把表给我。”

段迟最终没有带走那只银色怀表。她只带走了黑皮笔记本、冲洗出的照片复印件和那把红漆木梳。临走前,她把珍珠戒指摘下来,放在羊壹桌上,说这是她母亲留下的最后一件首饰,自己留着也许只会越看越偏,不如先放在这里。羊壹没收,说:“我这里不是寄存处。”段迟就笑了,第一次笑得像个真正轻一点的人:“可你明明就是。”这次羊壹没有反驳。

周雁回回档案馆后,花了三天时间重新整理西仓旧案所有边角材料。他把那些被折进附页、被藏在借阅单背面、被写得轻飘飘的“失踪”“疑似”“不详”一条条抄出来,另外誊成一份,题头只写了四个字:未竟之录。苏绫则把那四支最后枯败的白芍药晒成了花片,夹进一本旧账簿里。许澄后来在作文里写:“我以前以为记住就是把事情背下来,后来才发现,真正难的是在很多人都想把它改轻的时候,还敢承认自己见过它原本的重量。”老师给她这篇作文打了五十八分,评语是“语言有锋利感,但不像中学生写的”。许澄很不服,却悄悄把那张作文纸折好放进了文具盒最里层。

至于羊壹,他还是住在北港区旧钟街二十三号,还是每天六点四十起床,七点整开工,还是替人修表、换锁、代写信。只是从那之后,他把右二抽屉换了新的锁,钥匙不再随身带,而是压在胡桃木长桌最下面那本《长林街巷志》的第三十七页里。那页右上角被他折了一个极小的角,只有特别留意的人才看得见。窗台上的虎尾兰还是长得慢,薄荷还是疯长,收音机偶尔会在晚上九点零三分自己发出一阵短促杂音,像从很远的地方传来一句还没彻底断开的回声。

后来有一天傍晚,雨后初晴,旧钟街天边挂了一截很淡的橙色。苏绫在门口整理新到的香槟玫瑰,忽然听见羊壹屋里那只银色怀表自己响了一声。不是整点,也不是半点,只是很轻的一下。她抬头看过去,羊壹正站在窗边,手里什么也没拿,却像听见了谁隔着很多年重新敲了一下门。

他没有回头,只低低说了一句:“这次,我听见了。”

而门外风铃轻轻一晃,发出的却是两种不同的声响:一只清脆,一只发闷,像是有人终于走近,也像是还有人仍旧停在桥上,没有真正离开。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐