Randy Xu
軟體工程師,探索可驗證的 AI agent 應用
我是 Randy,軟體工程師。在 Openfind 做了兩年半企業郵件系統 Mail2000 的核心開發;現在專注在 AI agent,讓 AI 的輸出可以驗證、可以測試,而不只是看起來對。
工作之外,我讀世界經典文學,偶爾也寫短篇。這個網站收錄我的 projects、值得一讀的技術文章,以及讀過的書。
Projects



01 / 06
- Python
- FastAPI
- React
- TypeScript
觀察與判斷分開
一篇故事進來,由兩個各自獨立 context 的 agent 分工。analyst 只觀察,拆出主題、意象、技法、效果、角色與節拍;criticizer 只拿到分析結果與原文,以出版編輯的角度下判斷。兩者都只有讀寫檔案的權限,不能執行指令。觀察和判斷放在同一個 context,判斷會回頭影響觀察。
source.md ─ analyst ───▶ analysis.json observe
criticizer ─▶ feedback.json judge
用 schema 固定形狀,內容自由
兩份輸出都有 JSON Schema。節點只有六種型別,不允許多出來的欄位;邊固定成「技法產生效果、效果服務主題」,整篇就是畫面上這條意圖鏈。回饋的每一點都指向分析裡的節點 id,才能把回饋釘在對應的節點旁邊。
node.type ∈ theme · motif · technique
effect · character · beat
additionalProperties: false
technique ─produces→ effect ─serves→ theme
找不到出處就擋下
每條主張都要附逐字原文,像畫面上回饋底下那句引文。純 Python 的確定性層先驗 schema,再到原文裡找每一句引文:先逐字比對,再容許全形半形標點的差異,最後忽略換行與空白。找不到就不放行,把錯誤明細交回同一個 agent 重寫,讓它記得自己剛才做了什麼。
把分析畫出來
通過閘門的引文會換算成在全文中的位置,於是能畫出兩張圖:意圖鏈把技法、效果、主題連成線;文本軸沿著全文攤開張力曲線、意象的復現與技法的落點。
83.6s → 20.3s討論一輪的回應時間
能來回討論,但只能讀
分析完可以點任一個節點,就這篇來回討論。討論用的 agent 全程唯讀:SDK 只給它讀檔,再加一層 hook 擋下任何寫入;討論得出的結論由它輸出 JSON,經確定性層驗過才寫入。一輪回應原本要 83.6 秒,查出是討論沒關掉 extended thinking,關掉後降到 20.3 秒。




01 / 05
- Python
- FastAPI
- PostgreSQL
- Docker
四種比對工具,接成一份 Super-MSA
上傳序列後,MAFFT、MUSCLE、ClustalW、T-Coffee 各自做一次多序列比對,再用實驗室的 concatenate.pl 依隨機順序左右接起來,輸出 PHYLIP 格式的 Super-MSA。結果頁即時顯示每個工具的進度與耗時。
用 PostgreSQL 當工作佇列
不另外架訊息佇列。worker 用一條 SQL 認領最早排隊的工作,FOR UPDATE SKIP LOCKED 讓多個 worker 同時搶也不會拿到同一份。執行中定期更新心跳,順便檢查使用者是否按了取消。
UPDATE jobs SET status = 'running', locked_by = :worker,
heartbeat_at = now()
WHERE id = (SELECT id FROM jobs WHERE status = 'queued'
ORDER BY created_at
FOR UPDATE SKIP LOCKED LIMIT 1)
120 s沒有心跳就重新排隊
worker 當掉,工作不會卡住
維護程序定期找出超過 120 秒沒有心跳的工作,放回佇列給其他 worker 接手;同一份工作被中斷兩次就標成失敗並告知原因,不會無限重試。正常關機的 worker 會主動把工作交回,不算一次失敗。
結果可以重現
每份結果都記下當次使用的工具版本,論文引用或日後重跑時對得上。整套服務用 Docker Compose 部署,測試會在容器裡實際跑過四種比對工具。
經歷
企業郵件系統 Mail2000 核心開發。
負責新功能開發與既有功能的架構轉換。新版介面上線時不必重寫功能,團隊只需維護一份邏輯;客戶的開發團隊也能直接串接,把郵件功能整合進自家系統。
伺服器出問題時由備援機自動接手。在維運人員接手排查之前,服務不中斷、有保障。
讓使用者透過手機 App 完成驗證,安全、方便一把抓。
開發 Outlook 同步工具。企業轉換到 Mail2000 之後,員工照樣用熟悉的 Outlook 收信、排行程、找聯絡人。
技能
- LLM / Agents
- Claude Agent SDKTool use & hooksStructured outputJSON SchemaEvals & validation gatesMCPRAGSQLite FTS, MemPalaceToken / cost tracking
- 後端
- PythonFastAPIPostgreSQLCC#Perl
- 前端
- ReactTypeScript
- 維運
- LinuxGitDockerShellGitHub Actions
學歷
書房近況
正在研究
- 讓 AI agent 可控、可驗證
LLM 只負責判斷;格式、引用與權限交給程式驗證,沒過就帶著錯誤明細重試。
- 用 Claude Agent SDK 打造多 agent 執行框架
每個 agent 各有工具白名單、可寫路徑與輪數上限,流程交給確定性的程式,每一輪的用量都記進帳本。
- 資訊不完全遊戲的 AI 教練
以日本麻將為題,解說的 agent 只看得到當下可見的資訊,不能拿結果倒推道理。
- Hermes Agent
讀過它的原始碼,替它寫文學分析技能、接 MemPalace 語意記憶與 Spotify 的 MCP server;這是 hyenovel 的前身。
最近轉發
- Orca — The agent development environmentonorca.dev
- EP229: 8 Chunking strategies for RAG-based agentsblog.bytebytego.com
- blobatarblobatar.dev
Randy Xu
Software engineer exploring verifiable AI agent applications
I'm Randy, a software engineer. I spent two and a half years on the core of Mail2000, Openfind's enterprise email system; these days I focus on AI agents, and on making their output verifiable and testable rather than merely plausible.
Outside of work I read world classic literature and occasionally write short stories. This site collects my projects, technical articles worth reading, and the books I've read.
Projects



01 / 06
- Python
- FastAPI
- React
- TypeScript
hyenovel
Literary fiction and creative writing aren't easy to share or talk through with others. hyenovel lets an AI respond to your text, giving writers another angle and some room to reconsider other possibilities.
GitHub ↗Observation and judgment
A story goes to two agents with separate contexts. The analyst only observes, breaking out themes, motifs, techniques, effects, characters and beats; the criticizer sees only that analysis and the text, and judges like a publishing editor. Both can only read and write files, never run commands. Put observation and judgment in one context and the judgment leaks back into what gets observed.
source.md ─ analyst ───▶ analysis.json observe
criticizer ─▶ feedback.json judge
Schema: fixed shape, free content
Both outputs have a JSON Schema. Nodes come in six types with no extra fields allowed; edges are fixed as "technique produces effect, effect serves theme", which is exactly the chain of intent on screen. Every feedback point references analysis node ids, which is how feedback gets pinned next to the right node.
node.type ∈ theme · motif · technique
effect · character · beat
additionalProperties: false
technique ─produces→ effect ─serves→ theme
No source
Every claim must quote the text verbatim, like the quote under the feedback on screen. A deterministic Python layer checks the schema, then looks for each quote in the source — exact match first, then tolerating full-width vs half-width punctuation, then ignoring line breaks and spaces. Anything it can't find is rejected and sent back, with the errors, to the same agent so it keeps the context of what it just did.
Drawing the analysis
Quotes that pass the gate are mapped to positions in the text, which makes two views possible. The chain of intent links techniques to effects to themes; the text axis lays out the tension curve, recurring motifs and where each technique lands.
83.6s → 20.3sResponse time per discussion turn
Discussion
After the analysis you can pick any node and talk the story through. The discussion agent is read-only throughout — the SDK grants only file reads, and a hook blocks any write on top of that; conclusions come back as JSON and are saved only after the deterministic layer validates them. A turn used to take 83.6s; the cause was extended thinking left on for discussion, and turning it off brought it to 20.3s.




01 / 05
- Python
- FastAPI
- PostgreSQL
- Docker
wpSBOOT
A phylogenetic method from Prof. Jia-Ming Chang's lab at NCCU, published in Bioinformatics. I build its web server: upload sequences, get a Super-MSA built from four aligners.
GitHub ↗Four aligners
After upload, MAFFT, MUSCLE, ClustalW and T-Coffee each align the sequences, then the lab's concatenate.pl joins the alignments side by side in a random order into a PHYLIP Super-MSA. The result page shows each tool's progress and timing live.
PostgreSQL as the job queue
No separate message broker. A worker claims the oldest queued job with one SQL statement; FOR UPDATE SKIP LOCKED means several workers can claim at once without taking the same job. While running it updates a heartbeat, which is also where it notices a cancel request.
UPDATE jobs SET status = 'running', locked_by = :worker,
heartbeat_at = now()
WHERE id = (SELECT id FROM jobs WHERE status = 'queued'
ORDER BY created_at
FOR UPDATE SKIP LOCKED LIMIT 1)
120 sNo heartbeat
A crashed worker doesn't strand the job
A maintenance pass finds running jobs with no heartbeat for 120 seconds and puts them back in the queue for another worker. A job interrupted twice is marked failed with a reason instead of retrying forever, and a worker shutting down cleanly hands its job back without counting it as an attempt.
Reproducible results
Every result records the exact tool versions used, so a citation or a rerun later lines up. The service deploys with Docker Compose, and the tests actually run all four aligners inside the container.
Experience
Core developer on Mail2000, an enterprise email system.
Built new features and migrated existing ones to a shared backend. The new interface launched without rewriting features, the team maintains one codebase, and customers' developers can integrate mail into their own systems.
A standby takes over automatically when a server fails, so service keeps running until operators step in to investigate.
Users verify through a mobile app, getting security and convenience in one step.
Built the Outlook sync tool. After a company moves to Mail2000, employees keep using Outlook for mail, calendars and contacts.
Skills
- LLM / Agents
- Claude Agent SDKTool use & hooksStructured outputJSON SchemaEvals & validation gatesMCPRAGSQLite FTS, MemPalaceToken / cost tracking
- Backend
- PythonFastAPIPostgreSQLCC#Perl
- Frontend
- ReactTypeScript
- Ops
- LinuxGitDockerShellGitHub Actions
Education
From the study
Currently exploring
- Making AI agents controllable and verifiable
The LLM only makes judgment calls; code checks the format, the citations and the permissions, and failures go back with the errors for a retry.
- A multi-agent harness on the Claude Agent SDK
Each agent has its own tool allowlist, writable paths and turn limit; deterministic code runs the flow, and every turn's usage goes into a ledger.
- An AI coach for imperfect-information games
Japanese mahjong as the subject: the explaining agent only sees what was visible at the time, so it can't reason backwards from the outcome.
- Hermes Agent
Read its source, wrote literary-analysis skills for it on MemPalace's semantic memory, and hooked it up to Spotify's MCP server; it was hyenovel's predecessor.
Recently shared
- Orca — The agent development environmentonorca.dev
- EP229: 8 Chunking strategies for RAG-based agentsblog.bytebytego.com
- blobatarblobatar.dev
Recently read
- Coin Locker BabiesRyū Murakami
- The English PatientMichael Ondaatje
- Allah is Not ObligedAhmadou Kourouma