【jcode】DAG-first 编码 Agent Harness:Deep/Light 双模式深度解析
拆了 14 个 Harness 项目之后,我以为”成熟编码 Agent Harness”的形状基本定型了——Claude Code / Codex CLI / pi 是 IDE 化、aden-hive / oh-my-openagent 是多 Agent 化、archon / orca 是 YAML 化。直到我看到
1jehuang/jcode的 v0.47.0 release:它同时把 Harness 6 件套中”Sub-Agent(Deep/Light 双模式 DAG)+ Workflow(DAG-first 重构)+ Script(Pre-Tool Gate Hook)+ Memory(本地嵌入 + 安全 Compact)”四件套做成了一个工程化、内存占用仅 Claude Code 43% 的统一 Harness——并且只用 Rust + 一份 25KB 的 DAG engine 就拿到了 8,334⭐。
一、为什么挑 jcode?——填补 Harness 横评的”性能空白”
过去 14 天的 Harness 文章有一个共同盲点:我们很少正面回答”为什么 Claude Code 占 387MB RAM,而 jcode 只占 167MB?” 也很少拆一个真正工程化、面向生产 Harness 的项目——大多数是教学演示(OpenHarness)、个人作品(oh-my-openagent、orca)、或者创业项目(aden-hive)。
1jehuang/jcode 不一样。它的 README 第一段就摆出硬数据:
The next generation coding agent harness to raise the skill ceiling. Built for multi-session workflows, infinite customizability, and performance.
| 工具 | 1 个活跃会话 PSS | 10 个活跃会话 PSS | 对比 jcode baseline |
|---|---|---|---|
| jcode (local embedding off) | 27.8 MB | 117.0 MB | baseline |
| jcode | 167.1 MB | 260.8 MB | baseline |
| pi | 144.4 MB | — | 0.86× |
| Codex CLI | 140.0 MB | — | 0.84× |
| Claude Code | 386.6 MB | — | 2.31× |
| GitHub Copilot CLI | 333.3 MB | — | 2.00× |
| Cursor Agent | 214.9 MB | — | 1.29× |
关键洞察:jcode 在 1 个 session 时仅比 Claude Code 少 1.43× RAM,但当 10 个 session 并行时(multi-session workflow 是它的核心卖点),差距会进一步拉大。
更重要的是 jcode 解决了一个我们之前文章都没正面拆解的工程难题:如何让一个 1000-智能体的 DAG 引擎和 30+ LLM Provider 在同一个 Rust 进程里同时不打架。
二、项目定位与 Harness 6 件套覆盖矩阵
jcode 在 Harness 6 件套中的覆盖非常完整:
| 6 件套组件 | jcode 对应实现 | 备注 |
|---|---|---|
| Rule | SAFETY_SYSTEM.md 的 Action Classifier + .jcode/config.toml | 二元 Tier(auto-allowed / requires-permission) |
| Skill | PLAN_MCP_SKILLS.md 中的 SkillRegistry + reload() 方法 | 支持热重载,agent 可调用 reload_skills |
| Sub-Agent | jcode-swarm-core + jcode-plan/dag 引擎 | Deep/Light 双模式,最多 1000 智能体 |
| Workflow | DAG-first TaskGraph + NodeKind/NodeOrigin/NodeStatus | 状态机 + DAG 双层抽象 |
| Script | pre_tool / post_tool Gate Hook | Exit 2 = 拦截,Fail-open 默认 |
| MCP | PLAN_MCP_SKILLS.md Phase 3 MCP Client | JSON-RPC 2.0 over stdio |
接下来我从 4 个最有 Harness 价值的原语切入:DAG-first 重构、Epoch 信号量、Safe Compaction、Local Embedding。
二点五、整体架构全景
graph TB
subgraph USER["👤 用户层"]
U["CLI / TUI / Desktop"]
end
subgraph HOOK["🪝 Script 层"]
H1["pre_tool Hook<br/>同步 Gate"]
H2["post_tool Hook<br/>Observer"]
H3["turn_end Hook"]
end
subgraph CORE["⚙️ Agent Runtime"]
IS["InterruptSignal<br/>Epoch 信号量"]
TC["ToolContext<br/>+ for_subcall"]
end
subgraph PLAN["🧠 Plan + Swarm"]
DG["TaskGraph DAG<br/>Deep/Light 双模式"]
SW["Swarm Engine<br/>最多 1000 智能体"]
GT["Adversarial Gate<br/>audited_ids 穷举"]
end
subgraph MEM["📚 Memory 层"]
EM["Local Embedder<br/>MiniLM-L6-v2"]
CP["Safe Compaction<br/>+ IMAGE_TOKEN_COST"]
MG["Memory Graph<br/>(设计阶段)"]
end
subgraph PROV["🔌 Provider 层"]
P1["Anthropic / OpenAI"]
P2["Bedrock / Gemini"]
P3["Cursor / Copilot"]
P4["30+ Providers"]
end
U --> HOOK
HOOK --> CORE
CORE --> PLAN
PLAN --> MEM
PLAN --> PROV
PROV -.stream.-> CORE
style U fill:#C7CEEA,stroke:#9FA8DA,color:#333
style H1 fill:#FFB3C6,stroke:#F48FB1,color:#333
style H2 fill:#FFB3C6,stroke:#F48FB1,color:#333
style H3 fill:#FFB3C6,stroke:#F48FB1,color:#333
style IS fill:#E8D5F5,stroke:#CE93D8,color:#333
style TC fill:#E8D5F5,stroke:#CE93D8,color:#333
style DG fill:#FFDAB9,stroke:#FFAB76,color:#333
style SW fill:#FFDAB9,stroke:#FFAB76,color:#333
style GT fill:#FFDAB9,stroke:#FFAB76,color:#333
style EM fill:#B5EAD7,stroke:#80CBC4,color:#333
style CP fill:#B5EAD7,stroke:#80CBC4,color:#333
style MG fill:#FFF9C4,stroke:#F9A825,color:#333
style P1 fill:#F5F5F5,stroke:#BDBDBD,color:#333
style P2 fill:#F5F5F5,stroke:#BDBDBD,color:#333
style P3 fill:#F5F5F5,stroke:#BDBDBD,color:#333
style P4 fill:#F5F5F5,stroke:#BDBDBD,color:#333三、核心架构:DAG-First Swarm 重构(Deep/Light 双模式)
3.1 为什么需要 DAG-First?
jcode 之前的 Swarm 架构是**”agent-first”:用户启动 coordinator agent,由它 spawn 出子 agent,通过 DM/channel/role 沟通,VersionedPlan 只是附属**的 PlanItem 列表。
这种设计有 3 个痛点:
- 没有结构化的”完成保证”:coordinator 想停就停,没有”必须验证才能 close”的硬关卡
- coverage 不可观测:节点完成时没有”我没检查什么”的字段,下游 verifier 不知道哪里该追问
- 递归扩展是隐式的:coordinator 决定是否再 spawn,没有模式化的”composite node → expand → gate → synthesize”
jcode 在 docs/SWARM_TASK_GRAPH.md 显式给出重构对比:
flowchart LR
subgraph NOW["❌ 旧版 Agent-First"]
C1["Coordinator"] --> A1["Agent"]
A1 --> P1["PlanItem"]
end
subgraph NEXT["✅ 新版 DAG-First"]
T1["Task A"] --> T2["Task B"]
T1 --> T3["Task C"]
T2 --> T4["Task D"]
T3 --> T4
W["Worker Pool<br/>最多 1000 个"] -.executes.-> T1
end
style C1 fill:#FFB3C6,stroke:#F48FB1,color:#333
style A1 fill:#FFB3C6,stroke:#F48FB1,color:#333
style P1 fill:#FFB3C6,stroke:#F48FB1,color:#333
style T1 fill:#B5EAD7,stroke:#80CBC4,color:#333
style T2 fill:#B5EAD7,stroke:#80CBC4,color:#333
style T3 fill:#B5EAD7,stroke:#80CBC4,color:#333
style T4 fill:#B5EAD7,stroke:#80CBC4,color:#333
style W fill:#E8D5F5,stroke:#CE93D8,color:#3333.2 Deep vs Light 双模式
关键设计哲学:jcode 把 Deep/Light 做成同一个引擎的两个 preset(而不是两套系统)。两者共享 DAG 数据模型、调度器、edge dataflow、member cap 机制——唯一的差别是”结构化压力”是否开启:
| 维度 | Deep(综合) | Light(扇出) |
|---|---|---|
| 目标 | 横扫每一个死角 | 并行提速 / 质量小幅提升 |
| 形状 | 递归、自深化树 | 主要扁平、单层扇出 |
| 分解 | 强制(composite by default) | 可选 |
| Critique/Verify Gate | 节点关闭前必须 | 关闭(可选一次最终检查) |
| 递归 | 鼓励、无深度上限 | 抑制 / 禁用 |
| Handoff artifact | 完整类型化 + what_i_did_not_check | 轻量、自由格式 |
| Member cap | 最多 1000 | 小(4-16) |
| 用例 | 大型重构、风险调研 | 5 个独立 edit 并行 |
3.3 数据模型:5 个核心枚举
jcode-plan 的 dag/mod.rs 用 5 个枚举 + 1 个结构体定义了整个执行语义:
1 |
|
关键设计洞察:Blocked 故意不存。它由 scheduler 在调度时从 blocked_by 字段算出来——保证整个系统只有一个”是否阻塞”的真相源,避免 cache invalidation 问题。
3.4 DAG 引擎工作流时序
sequenceDiagram
participant U as 用户
participant S as Scheduler
participant W as Worker Pool
participant G as Gate (auto-inserted)
participant A as Artifact Store
U->>S: seed(plan_items)
S->>S: classify Origin=Seed
S->>W: dispatch Explore Task
W-->>A: write findings + what_i_did_not_check
W->>S: complete_node(artifact)
S->>S: insert Critique Gate (since Explore)
S->>G: dispatch Critique gate
G->>G: read parent's what_i_did_not_check
alt 发现 gap
G->>S: inject_gap(new_nodes)
S->>W: fan-out new nodes
else 通过
G->>S: complete_node(audited_ids=parent)
end
S->>U: plan complete
style S fill:#E8D5F5,stroke:#CE93D8,color:#333
style W fill:#B5EAD7,stroke:#80CBC4,color:#333
style G fill:#FFDAB9,stroke:#FFAB76,color:#333
style A fill:#FFF9C4,stroke:#F9A825,color:#3333.5 Adversarial Gate:唯一能”挖出”未检查项的设计
jcode 的 gate 设计是整套设计中最反直觉的部分——它不是”reviewer”(复审),它是 adversary(对抗式 gap 查找):
1 | pub fn append_deep_gate_instructions( |
audited_ids 机制:gate 的 artifact 必须逐个点名所有被审计的子节点——这强制 gate 做”穷举式问责”,而不是写一句”全部通过”就完事。任何 gate 的 artifact 漏掉一个 audited_ids,都会被 server 拒收。
这种设计哲学可以总结为:gate 不是 verifier,是 auditor。verifier 看 artifact 是否合规;auditor 看 artifact 是否完整。
四、Epoch 信号量:解决 Cancel 唤醒丢失(issue #428)
jcode 的 InterruptSignal 是我看过的最严谨的并发 cancel 实现。它的核心洞察是:单纯用 AtomicBool + tokio::Notify 的 cancel 信号在 race 条件下会丢唤醒——而 jcode 通过引入 monotonic epoch counter 让”cancel 意图”和”cancel 是否被处理”在代码层完全分离。
4.1 问题(issue #428)
代码注释直接点出问题:
Probabilistic race hammer for issue #428:
fire()must never be lost regardless of where the waiter is between creating thenotified()future and its first poll.
具体场景:agent 的 stream loop 每个 stream event 都会重建一个 notified() future。如果在重建 future 之前 fire() 触发了 notify_waiters(),新 future 还没注册到 Notify——唤醒会丢失。结果就是用户按 Esc / Ctrl+C 后,agent “看起来没反应”。
4.2 解法:epoch + double-check reset
1 | pub struct InterruptSignal { |
核心设计原则(来自代码注释):
Registration invariant: the agent is unregistered from the process registry unconditionally at the end of this method, regardless of whether the termination callback succeeded.
翻译:fire 意图 ≠ fire 成功。”我要取消你”这件事必须持久化,即便 callback 超时——否则会留下”agent 看起来活着但实际已死”的孤儿状态。
4.3 测试用例:2000 次并发验证
1 |
|
这是一个生产级工程基线:用 2000 次随机 race 来证明”cancel 永不丢失”。任何严肃的 Agent Harness 都应该有这个测试。
五、Safe Compaction:防止 Compact 后出现 Orphan Tool Result
5.1 问题
Compaction(压缩历史 context)是长 session 必备功能。但朴素实现会把”中间某些 tool_use + 后续 tool_result”切到保留侧之外——这会让模型看到没有 tool_use 的孤立 tool_result,Anthropic API 会返回 400 错误。
5.2 解法:safe_compaction_cutoff
1 | pub fn safe_compaction_cutoff(messages: &[Message], initial_cutoff: usize) -> usize { |
算法本质:从候选 cutoff 出发向后扫描收集 tool_use id,再反向扩展保留区直到所有 missing 都被填上——本质是二分查找的”满足约束的最小保留窗口”。
5.3 IMAGE_TOKEN_COST:避免”三重 Compact”
另一个反直觉的设计是图像 token 计费:
1 | /// Approximate token cost charged for a single inline image. |
代码注释解释为什么不用 base64 长度:
Counting that raw base64 length as message text (len / 4) massively overestimates the real context cost: providers tokenize images by resolution, not by transport-encoded byte length, and a typical screenshot costs on the order of ~1-2k tokens regardless of base64 size. Using the raw length caused the token estimate to balloon far above the real provider-observed input, spuriously tripping the compaction threshold and driving repeated back-to-back (“triple”) compactions that could not bring the estimate down because the images stayed in the recent kept turns.
翻译:用 base64 长度除以 4 会虚高 token 计数,让 token estimate 远超 provider 实际看到的输入,触发 compaction 阈值;但因为图像还在 RECENT_TURNS_TO_KEEP 里,compact 后 estimate 下不来——陷入”compact → 还是超 → 再 compact”的死循环。
这是一个典型的”实测出真知”——不是理论推出来的,是跑生产负载发现的 bug。
5.4 PAYLOAD_IMAGE_CHAR_BUDGET:另一种失败模式
1 | /// We target a conservative budget well under the hard provider cap |
Anthropic API 拒绝 >32MB 的 request body——这是和 token budget 不同的失败模式。jcode 用一个独立的 base64 char budget 来单独控制 request body 大小,而不是和 token budget 混在一起。
六、本地 MiniLM 嵌入:Rust + tract-onnx 离线嵌入
jcode 内置了一个完全离线的 sentence embedding 模型——all-MiniLM-L6-v2(384 维),通过 tract(Rust ONNX runtime)本地推理:
1 | pub const MODEL_NAME: &str = "all-MiniLM-L6-v2"; |
关键工程亮点:处理不同 ONNX 导出器的输入顺序差异:
1 | /// Exporters differ in both input ORDER (MiniLM puts input_ids first; e5/bge |
为什么这个细节重要:all-MiniLM-L6-v2 的 input 顺序是 [input_ids, attention_mask],但 e5 / bge 是 [attention_mask, input_ids]。如果硬编码位置,加载任何非 MiniLM 的模型就会 panic。jcode 用 name-based binding + dtype declaration——模型无关。
6.1 top_k_scored:Heap-Based Top-K
1 | fn top_k_scored<T, I>(items: I, limit: usize) -> Vec<(T, f32)> |
关键设计:TopKItem<T> 同时携带 score 和 ordinal,比较时 score 相等时用 ordinal 打破——保证 Top-K 提取是确定的(deterministic)。BinaryHeap<Reverse<TopKItem>> 是 min-heap 模式,O(n log k) 复杂度。
6.2 离线嵌入的实战价值
为什么要在 Coding Agent Harness 里嵌入本地 embedding 模型?
- Memory recall:
MEMORY_ARCHITECTURE.md描述了用 embedding 做 session 之间的”经验回溯” - Topic shift detection:
EMBEDDING_HISTORY_WINDOW = 10维护最近 10 轮的 embedding,检测话题切换 - Compaction summary:对 keep 区域外的消息做语义 embedding,cluster 后取中心点作为压缩后的”代表”
不需要调外部 embedding API——这对长时间 autonomous agent 至关重要:env 离线的环境(CI runner、air-gapped build)依然可以工作。
graph LR
A["📥 输入文本<br/>User prompt / Tool result"]
T["🔤 Tokenizer<br/>HuggingFace tokenizer.json"]
B["📊 input_ids<br/>attention_mask<br/>token_type_ids"]
M["🤖 ONNX Model<br/>all-MiniLM-L6-v2<br/>(tract-onnx 推理)"]
V["📐 384 维向量<br/>L2 normalized"]
USES["📚 用途<br/>Memory recall<br/>Topic shift<br/>Compaction cluster"]
A --> T --> B --> M --> V --> USES
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style T fill:#E8D5F5,stroke:#CE93D8,color:#333
style B fill:#E8D5F5,stroke:#CE93D8,color:#333
style M fill:#FFDAB9,stroke:#FFAB76,color:#333
style V fill:#B5EAD7,stroke:#80CBC4,color:#333
style USES fill:#FFF9C4,stroke:#F9A825,color:#333七、Tool Name Aliases:Provider 兼容性层
jcode 用一个纯函数解决了 30+ Provider 的 tool 命名不一致问题:
1 | /// This lives in `jcode-tool-types` (rather than the tool `Registry`) so that |
3 个值得借鉴的设计哲学:
- 放在 types crate 而非 registry:低层 crate(config、parse)也能做 normalize,不依赖完整 tool 子系统
- 保留旧名 → 新名映射:内部重命名
bash时,老的shell_exec/functions.shell_exec仍然能工作——零迁移成本 - 承认 model 行为:注释明说 “models still frequently call
grep“ ——不是”模型错了”,而是”我们必须兼容模型的记忆”。
八、Pre-Tool Gate Hook:Fail-Open 默认
8.1 配置
1 | # ~/.jcode/config.toml |
8.2 Hook 契约
| 退出码 | 含义 |
|---|---|
| 0 | 允许工具调用 |
| 2 | 拦截。Hook 的 stderr(trim 到 2000 字符)作为 tool error 返回给 model |
| 其他 | Fail-open:视为放行 + 日志警告 |
fail-open 是 jcode 显式的设计哲学:
Fail-open is deliberate: a broken policy script should degrade to “no policy” rather than brick every session. If you need fail-closed semantics, make the hook itself robust (it is your trust boundary, not jcode).
翻译:policy 脚本坏了应该降级成”无 policy”,不应该让所有 session 崩溃。要 fail-closed?自己把 hook 写稳——hook 才是 trust boundary,不是 jcode。
8.3 Recursion Guard
1 | // Every hook receives: |
所有 hook 都收到 JCODE_HOOKS_DISABLED=1 环境变量。如果 policy 脚本里调用了 jcode,嵌套调用会自动跳过 hooks——避免递归爆炸。
8.4 Hook 决策流程
flowchart TD
A["🚀 Agent 发起 tool_call"] --> B{"ToolContext 准备<br/>+ for_subcall"}
B --> C{"pre_tool Hook<br/>已配置?"}
C -->|"否"| ALLOW["✅ 直接放行"]
C -->|"是"| D["Spawn policy 脚本<br/>(带 stdin JSON)"]
D --> E{"5s 超时?"}
E -->|"是"| WARN1["⚠️ Fail-open<br/>日志警告 + 放行"]
D --> F{"Exit code?"}
F -->|"0"| ALLOW
F -->|"2"| BLOCK["❌ 拦截<br/>stderr → tool error"]
F -->|"其他"| WARN2["⚠️ Fail-open<br/>日志警告 + 放行"]
BLOCK --> R["📝 Tool error 返回给 model<br/>(trim 到 2000 字符)"]
ALLOW --> G["🔧 执行 tool"]
G --> H["post_tool Hook<br/>(fire-and-forget)"]
H --> DONE["🏁 完成"]
R --> DONE
style A fill:#C7CEEA,stroke:#9FA8DA,color:#333
style B fill:#FFF9C4,stroke:#F9A825,color:#333
style C fill:#FFF9C4,stroke:#F9A825,color:#333
style D fill:#FFDAB9,stroke:#FFAB76,color:#333
style E fill:#FFF9C4,stroke:#F9A825,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
style ALLOW fill:#B5EAD7,stroke:#80CBC4,color:#333
style BLOCK fill:#FFB3C6,stroke:#F48FB1,color:#333
style WARN1 fill:#FFF9C4,stroke:#F9A825,color:#333
style WARN2 fill:#FFF9C4,stroke:#F9A825,color:#333
style G fill:#E8D5F5,stroke:#CE93D8,color:#333
style H fill:#E8D5F5,stroke:#CE93D8,color:#333
style R fill:#FFB3C6,stroke:#F48FB1,color:#333
style DONE fill:#B5EAD7,stroke:#80CBC4,color:#3338.5 可运行示例:本地 Rust 复刻 Pre-Tool Hook
1 | // hooks/pre_tool_policy.rs |
编译并配置:
1 | rustc hooks/pre_tool_policy.rs -o ~/bin/jcode-tool-policy |
关键点:jcode 的 policy hook 用退出码而非 JSON 响应表达决策——这让任何 shell 脚本(Python、Node、Rust 都可以)都能成为 policy 实现,没有 vendor lock-in。
九、可运行示例:从零复刻 jcode 的 Epoch InterruptSignal
1 | # epoch_signal.py — Python 复刻 jcode InterruptSignal 的核心思想 |
运行结果:
1 | Test 1: cancel during tool execution |
这个设计在 Sub-Agent 场景特别重要:Sub-Agent 经常有”agent 在跑工具时被外层 cancel”的情况,如果 cancel 信号丢失或被错误 reset,外层会以为”agent 还活着”但实际上 agent 已经停在某个 tool 里死锁。
十、横向对比:jcode vs Claude Code vs aden-hive vs oh-my-openagent
| 维度 | jcode (v0.47.0) | Claude Code | aden-hive | oh-my-openagent |
|---|---|---|---|---|
| Sub-Agent 模式 | DAG-first,Deep/Light 双模式 | Task tool 单层 spawn | Pipeline Stage 装饰器 | Team Mode 22 packages |
| Sub-Agent 上限 | 1000 (Deep) / 16 (Light) | 无显式上限 | 配置项 | 配置项 |
| 完成保证 | Adversarial gate + audited_ids | 无 | EventBus Hook | Hashline 锚点 |
| Cancel 机制 | Epoch 信号量 + 2000× race test | 未公开 | 未公开 | 未公开 |
| Compaction | Safe cutoff + IMAGE_TOKEN_COST | 未公开 | 未公开 | 自动 |
| 嵌入模型 | 本地 MiniLM-L6-v2 (offline) | 远程 API | 远程 API | 远程 API |
| Provider 数 | 30+ (Anthropic/OpenAI/Bedrock/Gemini/Copilot/Cursor/Antigravity 等) | 2 (Anthropic + Bedrock) | 1 (自研) | 3 (OpenCode/Codex/Pi) |
| RAM(1 session) | 167 MB | 387 MB | 未公开 | 未公开 |
| DAG 模拟器 | ✅ dag/sim.rs 离线模拟 | ❌ | ❌ | ❌ |
| Gate Hook | pre_tool / post_tool / turn_end | PreToolUse hook | EventBus | Session 拦截 |
| MCP 状态 | 设计阶段(PLAN_MCP_SKILLS.md) | 原生支持 | 部分支持 | 通过 Pi 继承 |
关键差异解读
为什么 jcode 的”DAG-first”比 aden-hive 的”Pipeline Stage”更工程化?
aden-hive 的 Pipeline 是线性的 stage 装饰器——A 完成后跑 B,B 完成后跑 C,没有”composite node → 自动 insert gate → 强制 verify”的递归结构。jcode 的 DAG 模型支持递归 expand:一个 Implement 节点在 Deep 模式下自动插入 Verify gate;Verify gate 发现失败可以注入 Fix 节点;Fix 节点又触发新的 Verify gate——整个 DAG 可以自深化。
为什么 jcode 的 Epoch 信号量比 Claude Code 的 cancel 更严谨?
jcode 给出了 2000 次并发 race 测试的硬证据。Claude Code 的 cancel 机制源码未公开,但实际使用中偶发”按 Esc 没反应”的现象(社区报告)——很可能也是 issue #428 类似的问题。
为什么 jcode 坚持本地 MiniLM 嵌入?
对长时间 autonomous agent(比如 ambient mode 跑一整夜)至关重要。远程 embedding API 有 rate limit 和成本,本地模型零成本、零延迟、零 rate limit——是 CI runner 和 air-gapped 环境唯一可行方案。
十一、优缺点分析
优点(架构 / 扩展性 / 易用性)
| 维度 | 评价 | 证据 |
|---|---|---|
| 架构简洁性 | ⭐⭐⭐⭐⭐ | 单一 DAG 引擎 + 双模式 preset,避免了”为不同场景设计不同 runtime” |
| 扩展性 | ⭐⭐⭐⭐⭐ | 30+ Provider 通过统一 Provider trait + resolve_tool_name aliases 接入 |
| 易用性 | ⭐⭐⭐⭐ | ~/.jcode/config.toml 单文件配置 + shell completion 自动生成 |
| 可测试性 | ⭐⭐⭐⭐⭐ | DAG sim.rs 离线模拟器 + 2000× race test + compaction-core 单测 |
| 模型无关性 | ⭐⭐⭐⭐⭐ | Name-based input binding,支持任意 ONNX 导出器 |
缺点(性能 / 复杂度 / 维护性)
| 维度 | 评价 | 风险 |
|---|---|---|
| 学习曲线 | ⚠️ 陡峭 | DAG + Gate + Adversarial 三层抽象对新手不友好 |
| 文档完整度 | ⚠️ 偏弱 | 60+ 个 .md 设计文档,但用户文档少;上手要读源码 |
| 生态成熟度 | ⚠️ 早期 | MCP 还在 Phase 3 设计阶段;Skill hot-reload 才完成 |
| 依赖体积 | ⚠️ 中 | tract-onnx 拉了部分 ONNX runtime,纯 Rust 但编译时间长 |
| 维护负担 | ⚠️ 高 | 70+ crates workspace,每次升级都得管 dependency graph |
| 生产可观测性 | ⚠️ 中 | Telemetry 已设计但未默认开启;需要手动配置 worker |
适用 vs 不适用场景
| 场景 | 适用? | 原因 |
|---|---|---|
| 大型 monorepo 自动化重构 | ✅ 强 | Deep 模式 + DAG 模拟器可以预先 plan 风险 |
| 多 session 并行(CI/CD pipeline) | ✅ 强 | 内存占用低,10 session 仅 260 MB |
| 教学 / 入门 Agent 开发 | ❌ 不适用 | 抽象层级太高,建议先用 Pi/Claude Code |
| MCP 生态深度集成 | ⚠️ 等 | MCP 还在 Phase 3 设计中 |
| Air-gapped / 离线环境 | ✅ 强 | 本地 embedding + 不依赖远程 API |
| 单 session 个人 coding | ⚠️ 中 | 1 session 时 RAM 优势不明显 |
十二、从零搭建启示
如果你想自己复刻 jcode 的 Harness 6 件套,**最小可行实现(MVP)**需要哪些组件?
12.1 必须有的(最小集)
1 | # mvp_harness.py — jcode 6 件套的最小复刻 |
运行:
1 | Initial ready: ['design-schema'] |
150 行 Python 复刻了 jcode 6 件套中 4 件的核心抽象。
12.2 可以暂时省略的
| 组件 | 是否 MVP 必须? | 何时需要 |
|---|---|---|
| 本地 embedding | ❌ MVP 跳过 | 长 session + 跨 session 回溯时 |
| DAG 离线模拟器 | ❌ MVP 跳过 | 需要预先 plan 风险时 |
| Multi-Provider 抽象 | ⚠️ 选 1-2 个 | 需要 A/B 测试模型时 |
| Telemetry | ❌ MVP 跳过 | 团队规模 > 5 人 |
| DAG Visualizer (Mermaid) | ⚠️ 后期加 | 调试 DAG 行为时 |
| MCP Client | ❌ MVP 跳过 | 接外部工具时 |
| Pre-Tool Gate Hook | ✅ MVP 必备 | 任何”对模型有副作用”的工具 |
12.3 踩坑预警
AtomicBool+Notify在 race 下会丢唤醒:必须用 epoch counter 或 AtomicU64 + 双检查 reset。jcode 的 issue #428 是必经之路- Compact 后保留区要有 tool_use 配对:直接切
[..cutoff]会让 model 看到孤儿 tool_result,Anthropic API 直接 400 - 不要用 base64 长度除以 4 估算 image tokens:会触发”compact → 还超 → 再 compact”的死循环
- ONNX 模型 input 顺序因导出器而异:必须按 name binding,不能硬编码位置
- Hook 默认 fail-open:policy 脚本写错了不能让所有 session 崩溃
Blocked状态不要存在 node 上:它是 scheduler 从 dependency 算出来的,单一真相源
十三、总结与行动建议
jcode 给我最大的启发是:“机制 vs 策略分离”在 Harness 工程中不是口号,而是可量化的。它通过 Epoch 信号量、Safe Compaction Cutoff、IMAGE_TOKEN_COST 平摊、Adversarial Gate、Name-based ONNX binding 这一系列具体设计,把”让模型跑得更稳”这件事落到了代码层面的最小不变量。
给不同读者的建议
如果你在做 Harness 项目:
- 抄走
safe_compaction_cutoff算法——这是 Anthropic API 400 错误的唯一根治方案 - 抄走
IMAGE_TOKEN_COST常量——别让 base64 长度除以 4 忽悠你 - 抄走
InterruptSignal的 epoch 设计——你早晚会遇到 issue #428
如果你在选型 Coding Agent:
- 想要最低 RAM(10 session 并行)→ 选 jcode
- 想要生态最全(MCP、Hooks)→ 选 Claude Code
- 想要学术研究 / 学习 → 读 jcode 源码(60+ 设计文档 + 单 crate 职责清晰)
如果你想自己写 Sub-Agent:
- 先用 150 行 Python MVP(见 12.1 节)跑通”implement → auto-insert gate”的核心循环
- 然后把 DAG 状态持久化到 SQLite(jcode 用的 VersionedPlan)
- 最后接上 Provider 抽象层(30+ Provider 的
Providertrait 看 jcode-provider-*)
2026-07-15 截止覆盖进度
| Harness 6 件套组件 | 已覆盖项目 | 状态 |
|---|---|---|
| Rule | agents-md, SkillOpt, spec-kit | ✅ |
| Skill | SkillOpt, jcode (reload_skills) | ✅ |
| Sub-Agent | OpenHands, GoClaw, AGT, aden-hive, jcode (DAG-first) | ✅ |
| Workflow | archon, Restate, spec-kit, jcode (TaskGraph) | ✅ |
| Script | AGT (Policy Engine), jcode (Pre-Tool Gate) | ✅ |
| MCP | microsoft/mcp-gateway, InsForge | ✅ |
| 横评类 | harness-coding-agent-comparison, LiteLLM hooks | ✅ |
下一阶段进入单组件深度对比专题——Sub-Agent 失败恢复横评预计本周内推出,敬请期待。
本文核心数据来源:1jehuang/jcode(v0.47.0,2026-07-14 推送,8,334⭐)+ 仓库内 60+ 设计文档(特别是
docs/SWARM_TASK_GRAPH.md、docs/SAFETY_SYSTEM.md、docs/HOOKS.md、docs/MEMORY_ARCHITECTURE.md)+ 5 个核心 crate 源码(jcode-agent-runtime、jcode-plan/dag、jcode-tool-core、jcode-compaction-core、jcode-embedding)。所有引用代码均能在这些路径下找到原文。