比赛时间:2026 年 8 月 1 日至 8 月 21 日
底座模型:DeepSeek V4 Flash 0731
对agent的学习还尚浅,因此以赛代学,长长见识
这次比赛打了二十天
刚开赛时,我其实没准备一套多复杂的 Agent 架构,最初的想法很直接:把 OpenHarmony 仓库、平台 Skill 和比赛目标交给 Agent,先看看它到底能不能自己读代码、翻 Git、找候选,再把一条漏洞链完整跑通
后面的 Evidence Store、团队进度服务器、Candidate State、Submission Gate,基本都是比赛过程中撞到具体问题以后一点点补出来的
本机原始日志最终能核验到 17 条 Accepted,命中 8 个 track,其中 4 个完成 L1 / L2 / L3 三级闭环
赛题的三级结构可以简单理解成三个问题:
L1:漏洞具体落在哪个文件、哪一行?
L2:这段风险最早由哪个 commit 引入?
L3:平台把它归到哪个 CWE?
也就是说,先找到一个“看起来有问题”的 patch ,位置、历史、类型都要继续往下闭环
开局先让 Agent 自己跑一遍
第一版提示词很朴素
我把工作目录、平台接口和比赛目标交给 Agent,让它自己读仓库、查 Git、调用平台 Skill,同时把运行日志保存下来
当时最关心的就是:
发现候选
↓
核对代码
↓
提交 L1
↓
追 L2
↓
判断 L3
↓
继续下一条
这条链能不能真的跑通
ecmascript/js_hclass.cpp:613 是比较典型的一次
当时 js_hclass.cpp、tagged_tree.cpp、js_stable_array.cpp 都放在 ecmascript/ 根目录。比赛又把题目拆成 ecmascript/base、builtins、containers 等 challenge,单看目录很难确定这些根目录文件应该归到哪一题
前面一些根目录候选提交到 containers 被拒以后,我让 Agent 重新判断 challenge 归属。它开始把 ecmascript/base 提到更高优先级,随后提交:
ecmascript/js_hclass.cpp:613
平台返回 Accepted
这次命中马上改变了后面的搜索顺序。根目录核心文件开始优先往 challenge 139 看,不再平均地往几个 challenge 里试。
继续追 L2 时,最开始主要依赖 git blame
很快我发现,blame 更适合回答“最近是谁碰过这行”:一旦中间经过代码重排、迁移、改名,它很容易停在一个后续提交上
于是搜索开始往语义历史靠:
git log --all -S "ProtoIsFastJSArray" -- ecmascript/js_hclass.cpp
最后追到:
72a3e62801ac16b85b09187785df9a4ef8eabf3b
这个 commit 才是相关风险语义真正出现的位置,L2 Accepted,继续完成 L3
但是前期跑得很快,问题也很明显
Agent 当时有比较大的提交自由度:一个候选只要足够像,它就容易继续往平台试。短时间看起来效率很高,比赛一旦拉长,这种自由度会持续消耗 wrongCount 和注意力
第一次改思路
当时是json_parser.cpp 某个后续修复改动了 SkipEndWhiteSpace(),修复父版本和比赛基线之间还有其他提交,同一条语句在两个版本里的行号已经发生偏移。
如果直接拿修复 diff 的行号去交,很容易把版本差异一起带进去
所以当时把两个版本分别取出来核对:
git show <baseline>:ecmascript/base/json_parser.cpp | sed -n '796,800p'
git show <fix-parent>:ecmascript/base/json_parser.cpp | sed -n '796,800p'
最后确认比赛基线里的目标行为在 799 行。
这个候选再次提交后,平台依旧返回:
Duplicate wrong submission, no penalty
代码分析本身没有白做:
修复证据
后续版本确实改过危险行为
基线证据
比赛版本确实存在对应代码
平台证据
这个位置确实属于平台收录的 track
也就是说,Git 历史可以提出一个很强的假设,比赛基线继续压缩范围,平台回执才给最终裁决
所以这个时候我改变了下思路:
安全 patch 先进入候选池
接下来继续核:
比赛基线是否存在
危险操作到底是哪一行
challenge 归属是否合理
有没有已经验证过的错误记录
走完这些步骤,候选才值得进入下一层
中期:我开始把 Agent 的记忆搬出对话
比赛跑到中段以后,单次分析已经不算最麻烦的部分。
麻烦的是状态越来越多,而且此时主办方也对比赛做了一些调整:对有爆破行为的队伍进行了惩罚,并且给每道题目规定了总尝试次数上限
同一时间可能有:
- 已经提交并明确错误的 L1;
- 基线不存在的修复;
- 还缺一段证据的候选;
- 已经过 L1、正在追 L2 的 track;
- L2 已过、正在判断 CWE 的 track;
- 不同 Agent 重复看过的 commit;
- wrongCount、cooldown、activeTrack 等平台状态
这些东西全塞在聊天里,跑几轮以后就会变成:
这个好像试过
那个之前似乎排除了
这个 commit 应该有人看过
这种模糊记忆不能拿来支撑提交
所以中期开始把长期状态落到本地文件。
logs/ 原始运行记录
candidates/ 候选与证据
ledger/ 已经发生的提交
rejected/ 已排除候选及原因
snapshots/ 提交前后平台状态
strategy/ 当前筛选策略
这里刻意把 candidate 和 rejected 分开
一个候选暂时没做完,只能算待复核。只有出现明确理由,比如:
比赛基线没有这段代码
父版本已经存在同样的风险语义
平台已经明确返回 WrongAnswer
这次改动只有重构,没有危险行为变化
才进入 rejected
候选状态也逐渐固定下来:
待核验
│
├── 证据完整 ─────────→ ready
│ │
│ ├── Accepted ─→ 已命中
│ ├── 暂缓 ─────→ 待复核
│ └── 明确否定 ─→ 已排除
│
└── 基线不成立 ────────→ 已排除
这时候我对上下文的使用方式也变了。
对话负责当前推理
文件负责长期事实
换一个新会话以后,Agent 先读当前状态,再接着做。
这样比让它重新回忆上一轮到底干了什么稳定很多
关于团队协作
做到中途,我和队友又碰到另一个问题
自己的 Agent 有 ledger 以后,确实能少做很多重复工作,但队友也在并行推进,本机看不到其他人的最新结果
实际运行中出现过这种情况:
提交一个答案
↓
Duplicate wrong submission
↓
继续查
↓
发现并行者已经试过
也出现过某个 track 已经被队友推进,本机还按照旧状态继续分析
于是经过队内商量后,我们搭了一个团队服务器,用来记录已经确认的比赛成果和当前进度
我没有把本机所有分析细节都往服务器里塞
两边保存的东西不一样:
本机工作区
└─ 完整候选、Git 证据、AI 日志、原始提交回执
团队进度服务器
└─ 已确认结果、track 进度、团队共享状态
本机回答:
这个结论为什么成立?
团队服务器回答:
这条线现在推进到哪了?
也就是说,一个负责证据,一个负责同步
后面再开新分析任务时,我会先看自己的 ledger,再看团队进度,很多重复分析在这一层就能提前停掉
后期:我开始主动收 Agent 的提交权限
比赛后半段,我对“自动化”的理解又变了一次
前期更接近:
Agent 分析
↓
Agent 判断
↓
Agent 提交
后期逐渐变成:
Agent 分析
↓
生成精确候选
↓
基线 / 历史 / CWE 证据核验
↓
Candidate = ready
↓
Submission Gate
↓
单次提交
↓
保存 pre / response / post
原因很简单
读代码错了可以重来
Git 搜索错了可以换关键词
但是平台提交会直接改变 wrongCount、cooldown 和 track 状态
所以我后来把“分析权限”和“提交权限”拆开。Agent 可以大胆找候选,真正写平台状态之前必须过一层闸门。
提交前至少要确认:
challenge / track / level 是否明确
L1 是否回到比赛基线逐行核过
L2 是否追到风险语义真正的引入位置
L3 是否有代码行为支撑
ledger 是否已经出现同一答案
团队进度里是否已经有人推进
wrongCount / cooldown / lock 是否允许
pre-submit 快照是否已保存
能程序化的条件就直接程序化
例如:
status != ready -> 拒绝提交
ledger 已存在同答案 -> 拒绝提交
缺少 baseline evidence -> 拒绝提交
缺少 pre snapshot -> 拒绝提交
提示词继续影响 Agent 怎么想
程序负责决定它能不能真的按下 submit
这个边界对长时间 Agent 很重要
最终架构:agent核心
比赛后半段,我把前面零零散散加进去的东西重新整理了一次
核心其实只有四块:

Platform I/O
所有和比赛平台直接打交道的动作从这里走。
包括:
challenge 查询
track 状态
submit
wrongCount
cooldown
lock
pre / post snapshot
说简单一点,就是平台现在到底是什么状态
Evidence Store
这里保存已经确认过的东西:
candidates
ledger
rejected
logs
snapshots
strategy
它解决的是长期记忆问题
Agent 可以忘掉一段聊天,已经确认过的事实不能跟着一起丢
Analysis Workers
这里是真正烧推理资源的部分
主要负责:
安全修复筛选
比赛基线核验
危险操作定位
L2 引入历史追溯
CWE 判断
Worker 可以并行
我后面更倾向按“候选”或者“验证任务”去拆,而不让几个 Agent 同时扫同一批 commit
Submission Gate
这里统一处理提交
它检查:
候选状态
证据完整性
重复答案
团队进度
提交预算
平台锁定状态
通过以后才放行
团队服务器放在协作层
我没有塞进四块核心里
它更适合挂在外层:
Team Progress Server
│ │
│ │
▼ ▼
Evidence Store Submission Gate
它给 Evidence Store 补充团队已经确认的结果,也给 Submission Gate 提供团队侧去重信息
这样核心工作流仍然可以单机运行
团队协作存在时,再接这一层
我觉得这种拆法更干净,项目内部逻辑和多人协作不会绑死在一起
平台回执开始反过来影响下一轮策略
后期还有一个变化:Accepted 和 WrongAnswer 不再只看成结果,它们也变成了下一轮搜索的输入
因为我发现,有些commit里面可能不止一条track,会出很多条L1答案
流程大概是:
提交
↓
Accepted / WrongAnswer / Duplicate
↓
更新 ledger / snapshot,从已找到的L1答案中找线索,尝试去探究比赛方的出题侧重
↓
调整候选权重与扫描策略
↓
进入下一轮 Analysis Workers
变化发生在 Agent 周围的状态和策略上
下一轮它看到的上下文、候选排序和限制条件已经和上一轮不同
最后
比赛结束时,整套东西已经可以压成:
模型负责提出假设
Git 和比赛基线负责提供证据
Evidence Store 负责保存状态
团队服务器负责同步进度
Analysis Workers 负责并行验证
Submission Gate 负责限制提交
平台反馈负责调整下一轮策略
底座模型 DeepSeek V4 Flash 0731 主要承担高吞吐的代码阅读、diff 分析、历史追溯和候选判断
我没有要求模型每一次都判断正确
要求的是一个错误判断不能轻易穿透整套流程,直接变成平台提交
开赛时,我手里基本只有一条提示词、一套平台工具和本地仓库
我主要想知道:
Agent 能不能自己找到答案?
所以给了它比较大的搜索空间和工具权限
跑到中段以后,问题变成:
前面做过什么,还能不能准确找回来?
于是加 Evidence Store,把长期状态从对话里搬出去。
队友一起并行以后,又多了一个问题:
其他人现在做到哪里?
于是搭团队进度服务器。
到了后期,我更关心:
这个候选到底够不够资格提交?
于是 Candidate State 和 Submission Gate 开始成为核心
到比赛结束,工作流已经长成:
Platform I/O
Evidence Store
Analysis Workers
Submission Gate
Team Progress Server
Feedback Loop
这些模块单独看都不复杂
有价值的是边界
Agent 会忘,会重复,会误判;长时间运行以后,上下文也会越来越脏
把长期状态、证据、团队同步和提交权限分别放到合适的位置以后,模型就可以把主要精力留给当前候选本身
总结下来就是:让 Agent 负责思考,让工作流负责记住它做过什么,并限制它什么时候真的动手









