rad 编码 agent · rust · 一个二进制

快而高效,放得进任何流水线的编码 agent。

radian 是一个 Rust 二进制。启动不用先拉起运行时,上下文只花在任务需要的那几行上;无头命令把每一步按 JSON 逐行输出,退出码说明它是怎么结束的。

$ brew install hisea/tap/radian

开源,MIT 或 Apache-2.0 协议:github.com/hisea/radian。

◜ radian · ~/code/radian120 × 74
❯ finish ADR 0037 slice 3 and show me the diff                                                                          
                                                                                                                        
▾ thinking · 3.2s                                                                                                       
│ Checking the inbox lanes first.                                                                                       
│                                                                                                                       
│ The job notice must not lower the turn's source: observe_input_source only ever lowers the source, so an inject       
│ from a System job would turn a human turn into a non-human one.                                                       
│                                                                                                                       
│ - followup wakes a new turn, so the source is set fresh                                                               
│ - inject lands mid-turn and lowers it                                                                                 
│                                                                                                                       
│ So the completion should arrive as a notice with a System source, never as a user turn.                               
                                                                                                                        
• shell $ cargo test -p radian-host --locked · exit 0 · 12.4s ✓                                                         
│ running 41 tests                                                                                                      
│ test job_notify::settled_job_wakes_the_idle_model ... ok                                                              
│ test job_notify::a_waited_job_sends_no_notice ... ok                                                                  
│ test result: ok. 41 passed; 0 failed; 0 ignored                                                                       
                                                                                                                        
• read crates/radian-host/src/job_notify.rs 100-160 · 0.01s ✓                                                           
                                                                                                                        
• patch crates/radian-host/src/job_notify.rs · +5 −2 · 0.03s ✓                                                          
│ @@ -120,9 +120,12 @@ impl JobNotify {                                                                                 
│  120       fn notice(&self, job: &Settled) -> Message {                                                               
│  121 -         let source = InputSource::User;                                                                        
│  121 +         // ADR 0037: a notice is low-trust data, never a user turn.                                            
│  122 +         let source = InputSource::System;                                                                      
│  123           let mut meta = MessageMeta::default();                                                                 
│  124           meta.source = Some(source);                                                                            
│  124 -         Message::user(format!("job {} done", job.id)).with_meta(meta)                                          
│  125 +         let body = format!("<job id={} exit={} />", job.id, job.exit);                                         
│  126 +         Message::user(body).with_meta(meta)                                                                    
│  127       }                                                                                                          
                                                                                                                        
╭─ ask ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ How should the UI extension be loaded?                                                                                
│ ◉ External process plugins                                                                                            
│   ↳ a separate program, JSON-RPC over stdio                                                                           
│ ○ Built-in Rust components                                                                                            
╰──                                                                                                                     
                                                                                                                        
▸ thinking · 3.2s · 8 lines                                                                                             
                                                                                                                        
● What I did                                                                                                            
                                                                                                                        
  1. Added background: true to the shell tool and a job registry in radian-host.                                        
  2. Ran the Verify commands; cargo test --workspace --locked passed with 0 warnings.                                   
                                                                                                                        
╭─ rust ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ fn notice(&self, job: &Settled) -> Message {                                                                          
│     Message::user(body).with_meta(meta) // never a user turn                                                          
│ }                                                                                                                     
╰──                                                                                                                     
                                                                                                                        
  ┌─────────────────────────────────┬────────────────┐                                                                  
  │ Command                         │ Result         │                                                                  
  ├─────────────────────────────────┼────────────────┤                                                                  
  │ cargo clippy --workspace        │ ok, 0 warnings │                                                                  
  ├─────────────────────────────────┼────────────────┤                                                                  
  │ cargo test --workspace --locked │ 412 passed     │                                                                  
  └─────────────────────────────────┴────────────────┘                                                                  
                                                                                                                        
── context compacted (rev 2, 148k → 61k) ──                                                                             
                                                                                                                        
* background job bg-2 finished · cargo test -p radian-host · exit 0 · 38s                                               
! background job bg-3 has printed nothing for 6m                                                                        
                                                                                                                        
── ◜  Working · 12s · esc to interrupt ─────────────────────────────────────────────────────────────────────────────────
❯ explain the inbox lanes▏                                                                                              
                                                                                                                        
Usage: 5h: 80% · 7d: 19% · 30d: 40% · 5h ↺1h35m                                                                         
 glm-5.3-flash ·  radian ·  notice-source +2 ·  render-study                              ▣ ━━━━━━━━──────── 41%  ◐ high
                                                                                    ↑15M ↓2.6M R1962M $14.510 41.3%/1.0M
                                                                                                                        
theme: built-in light
❯ finish ADR 0037 slice 3 and show me the diff                                                                          
                                                                                                                        
▾ thinking · 3.2s                                                                                                       
│ Checking the inbox lanes first.                                                                                       
│                                                                                                                       
│ The job notice must not lower the turn's source: observe_input_source only ever lowers the source, so an inject       
│ from a System job would turn a human turn into a non-human one.                                                       
│                                                                                                                       
│ - followup wakes a new turn, so the source is set fresh                                                               
│ - inject lands mid-turn and lowers it                                                                                 
│                                                                                                                       
│ So the completion should arrive as a notice with a System source, never as a user turn.                               
                                                                                                                        
• shell $ cargo test -p radian-host --locked · exit 0 · 12.4s ✓                                                         
│ running 41 tests                                                                                                      
│ test job_notify::settled_job_wakes_the_idle_model ... ok                                                              
│ test job_notify::a_waited_job_sends_no_notice ... ok                                                                  
│ test result: ok. 41 passed; 0 failed; 0 ignored                                                                       
                                                                                                                        
• read crates/radian-host/src/job_notify.rs 100-160 · 0.01s ✓                                                           
                                                                                                                        
• patch crates/radian-host/src/job_notify.rs · +5 −2 · 0.03s ✓                                                          
│ @@ -120,9 +120,12 @@ impl JobNotify {                                                                                 
│  120       fn notice(&self, job: &Settled) -> Message {                                                               
│  121 -         let source = InputSource::User;                                                                        
│  121 +         // ADR 0037: a notice is low-trust data, never a user turn.                                            
│  122 +         let source = InputSource::System;                                                                      
│  123           let mut meta = MessageMeta::default();                                                                 
│  124           meta.source = Some(source);                                                                            
│  124 -         Message::user(format!("job {} done", job.id)).with_meta(meta)                                          
│  125 +         let body = format!("<job id={} exit={} />", job.id, job.exit);                                         
│  126 +         Message::user(body).with_meta(meta)                                                                    
│  127       }                                                                                                          
                                                                                                                        
╭─ ask ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ How should the UI extension be loaded?                                                                                
│ ◉ External process plugins                                                                                            
│   ↳ a separate program, JSON-RPC over stdio                                                                           
│ ○ Built-in Rust components                                                                                            
╰──                                                                                                                     
                                                                                                                        
▸ thinking · 3.2s · 8 lines                                                                                             
                                                                                                                        
● What I did                                                                                                            
                                                                                                                        
  1. Added background: true to the shell tool and a job registry in radian-host.                                        
  2. Ran the Verify commands; cargo test --workspace --locked passed with 0 warnings.                                   
                                                                                                                        
╭─ rust ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ fn notice(&self, job: &Settled) -> Message {                                                                          
│     Message::user(body).with_meta(meta) // never a user turn                                                          
│ }                                                                                                                     
╰──                                                                                                                     
                                                                                                                        
  ┌─────────────────────────────────┬────────────────┐                                                                  
  │ Command                         │ Result         │                                                                  
  ├─────────────────────────────────┼────────────────┤                                                                  
  │ cargo clippy --workspace        │ ok, 0 warnings │                                                                  
  ├─────────────────────────────────┼────────────────┤                                                                  
  │ cargo test --workspace --locked │ 412 passed     │                                                                  
  └─────────────────────────────────┴────────────────┘                                                                  
                                                                                                                        
── context compacted (rev 2, 148k → 61k) ──                                                                             
                                                                                                                        
* background job bg-2 finished · cargo test -p radian-host · exit 0 · 38s                                               
! background job bg-3 has printed nothing for 6m                                                                        
                                                                                                                        
── ◜  Working · 12s · esc to interrupt ─────────────────────────────────────────────────────────────────────────────────
❯ explain the inbox lanes▏                                                                                              
                                                                                                                        
Usage: 5h: 80% · 7d: 19% · 30d: 40% · 5h ↺1h35m                                                                         
 glm-5.3-flash ·  radian ·  notice-source +2 ·  render-study                              ▣ ━━━━━━━━──────── 41%  ◐ high
                                                                                    ↑15M ↓2.6M R1962M $14.510 41.3%/1.0M
                                                                                                                        
theme: built-in dark
图 1 · 120 列的 radian,由原型脚本渲染
/01 为什么是 radian
◜ 快

启动快,回来也快。

原生二进制,不用先拉起一个运行时。前台命令不会自己转到后台:只有 background: true 才会起一个 job,跑完的通知会送到模型那里,模型不用干等。

无运行时启动 · 每个会话一个文件
◝ 高效

上下文关注任务。

读文件按行号范围读。预算守卫盯着上下文窗口,快满之前先压缩。footer 里随时看得到 token 和费用。

按范围读 · 自动压缩
footer 显示用量和费用
◞ 编排

为被编排而生。

radian 不调度外部 agent,它是为了被调度而做的。一句话从 radian exec 进去,每一步按 JSON 逐行出来,退出码分得清完成、失败和超出预算。在 herdr 里它自己报告 working、idle、blocked;radian attach 可以只读地看任何一个会话。并行只发生在一条回复之内:几个 subagent 调用同时跑。

exec --output jsonl · exit codes by outcome
herdr-ready · attach · control socket
◟

三者底下:每一步都写进可恢复、可 fork 的会话日志,沙箱是一个开关(一个参数,或按项目保存,只能收窄),审批只能由你来给。

/02 无头

无头,可观测。

一句话进,journal 出:每一步一行 JSON,最后一行是结局。同一个会话文件,脚本跑到一半,可以在全屏界面里接着跑。

在 herdr 里,radian 自己报告 working、idle、blocked,不报命令、路径和内容。

/03 兼容

用你已有的。

指令文件、skills、MCP server 和主题可以直接带过来。

  • AGENTS.md · CLAUDE.md从工作区一路读到仓库根,支持 @path 导入。
  • skills~/.agents/skills/<name>/SKILL.md,和 pi、Codex、opencode 共用的目录。
  • MCPmcpServers 格式的配置,支持 ${VAR} 展开,stdio server。
  • hooksClaude / Codex 的 JSON 子集:PreToolUse、PostToolUse、Stop。
  • themespi 的主题文件,用 radian theme import 一次性导入。
  • memory全局和项目两层。agent 改不了你自己写的记录。
/04 arc

计划进去,合并出来。

arc 内置在同一个二进制里。它把需求拆成任务,每个任务交给一个独立的无头 radian worker,只合并通过固定门禁的结果。

了解 arc →
arc TUI:计划页,显示每个计划的优先级、自动级别、已合入任务数和花费
/05 安装

安装。

macOS 和 Linux 都能用 Homebrew,也可以下载 release 安装包,或者用 Cargo 从源码装。

$ brew install hisea/tap/radian
$ cargo install --git https://github.com/hisea/radian radian-tui
github.com/hisea/radian/releases 签名的 macOS 包和静态 Linux 包
  • Homebrew 只链接 radian,因为 homebrew-core 里已经有一个叫 rad 的包;安装包和 Cargo 还会带上 rad 这个别名。
  • Linux 上 workspace 等级需要先装 bubblewrap。
  • 装好后运行 radian login,再运行 radian。完整步骤见快速开始。
/06 运行

一个二进制,两种用法。

坐在键盘前用全屏界面;脚本、CI 和别的编排器用无头命令。

radian

全屏终端界面。

radian
# 沙箱
radian --profile workspace

radian exec

一句话进,每一步按 JSON 逐行出。

radian exec --resume demo \
  --output jsonl \
  -p "now read docs/"
模型来源:Anthropic、OpenAI、ChatGPT(浏览器 OAuth 登录)、opencode-go,以及任何兼容 OpenAI 的端点。/login 把凭据存进 ~/.radian/credentials/auth.json。
同一个会话文件~/.radian/sessions/demo.jsonl
θ = 1 r s = r
/∠ 名字

一弧度,就是弧长正好等于半径时的那个角。

1 rad = 180°/π ≈ 57.2958°