Agent / Local Harness

一套自己拥有的本地 Agent 运行时:可替换执行器、文件优先记忆、可加载的无人值守调度,以及带类型化决策、可被对抗性测量的验收层。

本地 Agent Harness

把记忆、权限、触发和验收握在自己手里,让 Agent 真的把事做完。

为什么做

Why this exists

我原本已经有一个每天 23:00 自动发布 AI 资讯的流程:抓取、撰写、渲染、推送,步骤都是我自己定的。但它跑在哪、什么时候醒、记住了什么,全都挂在别人的运行时里。行为归我,运行时归厂商——这是"看起来很像自己的 Agent"和"真的是自己的 Agent"之间最常见的一条界线。

I already had a workflow that publishes the AI News Digest at 23:00: fetch, write, render, push, all steps defined by me. But where it ran, when it woke up and what it remembered all belonged to someone else's runtime. My behaviour, their runtime - that is the most common boundary between something that looks like my own agent and something that actually is.

RAG 不是"自己的"的来源。检索只是上下文装填手段:通用 Agent 读本地文件本身就是按需检索,在个人规模下往往比向量召回更准。真正属于自己的是四件事——记忆归你、权限归你、触发归你、验收标准归你。

RAG is not what makes an agent yours. Retrieval is just context filling: a general coding agent already reads local files on demand, which at personal scale is usually more accurate than vector recall. What is genuinely yours are four things - memory, permissions, triggers and acceptance criteria.

解决什么问题

What it solves

  1. 01

    能力在,但所有权不在

    Capability without ownership

    会话、记忆、调度和凭据都在别人的产品里,换一个工具就从零开始,历史也拿不回来。

    Sessions, memory, scheduling and credentials live inside someone else's product; switching tools means starting over.

  2. 02

    通用 Agent 不会自己开始

    A general agent never starts itself

    它只在你打开窗口、开口提问时干活。夜里该跑的东西,需要有人先想起它。

    It only works when you open a window and type. Anything that should run overnight needs a human to remember it.

  3. 03

    权限是"大概能用",不是"明确允许"

    Permissions are vague, not explicit

    哪个任务能写哪些目录、能跑哪些命令、能不能联网,通常没有一处可审计的声明。

    Which task may write where, run what, or reach the network is rarely declared in one auditable place.

  4. 04

    验收靠人眼,失败靠运气

    Acceptance by eyeball, failure by luck

    输出合不合格由人翻一遍才知道;失败后是重跑、降级还是放弃,没有统一策略。

    Whether an output is acceptable is decided by scrolling through it; whether a failure retries, degrades or aborts is ad hoc.

能做什么

What it can do

无人值守跑完整流程

Run a full pipeline unattended

定时或事件唤醒,从抓取一路做到发布与通知,中途不需要人盯着。

Woken by a schedule or an event, it goes from data collection all the way to publishing and notification without supervision.

把重复流程变成一条声明

Turn a repeated process into a declaration

步骤、可用工具、可写路径、预算和降级策略写进一个任务文件,而不是散落在聊天记录里。

Steps, allowed tools, writable paths, budget and degradation policy live in one task file instead of scattered chat history.

会记住事的助手

An assistant that remembers

每次运行的事实自动落盘并建索引,长期结论由人确认后才沉淀,可检索、可审计、可删除。

Facts from every run are stored and indexed automatically; long-term conclusions are human-confirmed, searchable, auditable and deletable.

用实验台比较策略

Compare strategies with an eval lab

同一份输入跑多条路线,比较成功率、可核验率、token 与耗时,而不是凭感觉选方案。

Run several routes over the same input and compare success rate, traceability, tokens and wall-clock instead of guessing.

把重活外包给成熟执行器

Delegate heavy lifting to mature executors

Codex CLI、Claude Code、云端模型都只是可替换的执行器;调度、记忆和验收留在自己手里。

Codex CLI, Claude Code and cloud models are swappable executors; scheduling, memory and acceptance stay on your side.

失败看得见、说得清

Failures that are visible and explainable

每次运行都有台账:步骤、工具调用、token、耗时、失败分类与降级原因,回看就能定位。

Every run leaves a ledger: steps, tool calls, tokens, duration, failure class and degradation reasons.

系统结构

Architecture

内核是自己写的确定性循环,模型只是其中一层。任务声明式描述,工具按权限调用,产物经过代码化的验收标准才会落地。

The kernel is a deterministic loop I wrote; the model is only one layer inside it. Tasks are declared, tools are called under explicit permissions, and artifacts must pass acceptance criteria written in code.

本地 Agent Harness 的五层结构:触发与验收、任务与编排、工具与权限、记忆、模型适配 Five layers of the local agent harness: triggers and acceptance, tasks and orchestration, tools and permissions, memory, model adapters
图 1 · 五层结构:上面两层是你的规矩,中间两层是你的资产与边界,最下面才是可替换的模型。
Figure 1 - Five layers: the top two are your rules, the middle two are your assets and boundaries, and the bottom layer is the swappable model.

一次无人值守运行

One unattended run

以"AI资讯速递"为例,这是它每晚实际走过的路径。任何一步降级都会被记录,而不是悄悄糊过去。

Using the daily tech digest as the example, this is the path it actually walks every night. Every degradation is recorded rather than quietly smoothed over.

一次无人值守运行的七个步骤,以及降级路径 Seven steps of one unattended run, plus the degradation path
图 2 · 定时唤醒到发布:任务锁保证同一个任务只跑一份,四项校验通过之后才允许推送。
Figure 2 - From scheduled wake-up to release: a task lock keeps a single run in flight, and publishing only happens after all four gates pass.
  • 重复触发:同一天同一任务只成功一次(数据库唯一索引 + 文件锁),被中断的运行下次自动标记为 crashed 并重跑。
  • Idempotency: one successful run per task and day (unique index plus a file lock); an interrupted run is marked crashed and safely retried.
  • 降级:代理不可用或数据源额度耗尽时,沿用已有数据继续,并如实标记 degraded。
  • Degradation: when a proxy or a data source quota fails, it continues on existing data and marks the run degraded.
  • 中止:必需校验不通过或推送失败就判失败并发通知,绝不用半成品凑数。
  • Abort: a failed required gate or a failed push stops the release and raises a notification - no half-finished output is shipped.

实测数据

Measured evidence

下面这组数字来自真实运行,不是设计文档里的估计。它同时解释了一个工程结论:一篇 20 条的双语稿件塞不进一次模型响应。

These numbers come from real runs, not estimates in a design document. They also explain one engineering conclusion: a 20-item bilingual article does not fit in a single model response.

三条撰写路线的输入输出 token 与结果对比 Input and output tokens for three composition routes, with outcomes
图 3 · 同一份 2026-09-22 抓取数据下的三条路线:一次性撰写两次都被输出上限截断,分片撰写在更省 token 的前提下通过全部校验。
Figure 3 - Three routes over the same 2026-09-22 capture: single-shot composition was truncated twice by the output ceiling, while chunking passed every gate on fewer tokens.
  • 发布闸门:结构、引用完整性、来源可核验性、生成物漂移四项校验,全部代码化。
  • Release gates: structure, reference integrity, source traceability and generated-file drift - all enforced in code.
  • 真实的失败案例:模型多写了 4 条热点,被上限校验挡下,运行判失败并通知,而不是把超限内容发出去。
  • A real failure: the model wrote four items over the limit; the gate failed the run and notified instead of publishing over-limit content.
  • 崩溃恢复:在步骤执行中途强制 kill 进程,下一次运行自动识别并重跑,没有产生重复发布。
  • Crash recovery: killing the process mid-step led the next run to detect and retry it, with no duplicate release.

闸门自己也要被测量

The gate gets measured too

「每条引用都能回溯」这句话曾经是假的。旧闸门只回答是/否,于是"引用了一条真实抓取过的 URL 的前缀延伸"也被算成通过。把它换成类型化决策(通过 / 不通过 / 无法核验)之后,用 1085 条自动生成的标注语料把旧闸门和新闸门放在同一份样本上对比:

The claim "every citation is traceable" was once false. The old gate only answered yes or no, so a citation that merely extended a genuine fetched URL counted as passing. After replacing it with typed decisions (pass / fail / cannot verify), 1085 auto-generated labelled samples put the old gate and the new gate on the same set:

判定方式Matcher 漏检率(放行编造来源)False pass (fabricated source released) 误杀率(拦下真实引用)False reject (real citation blocked) 判「无法核验」Cannot verify
旧布尔闸门Old boolean gate 62.1%62.1%0.6%0.6%0.0%0.0%
类型化决策(当前)Typed decisions (current) 0.0%0.0% 0.0%0.0% 32.3%32.3%
LLM 裁判LLM judge 0.0%0.0% 5.3%5.3% 0.0%0.0%

LLM 裁判只在 175 条子样本上运行过,不能与上面的 930 条主表直接比较;它是历史对照,不是当前默认基线。

The LLM judge was only run on a 175-sample subset; it is not directly comparable to the 930-sample rows above and is not the current default baseline.

三行不是"谁赢"。LLM 裁判一次都没漏,却会把合法的等价引用判错,而且每次判决要烧掉约 9.2k input token;类型化决策免费、可复现,代价是把两类前缀型攻击显式判成"无法核验"——而这些不确定性全部落在攻击样本上,在 6 天真实内容里一次都没触发。真正错的是旧闸门:它把"不知道"算成了"通过"。

Three rows, not a leaderboard. The LLM judge never leaked but wrongly rejected legitimate equivalent citations, at roughly 9.2k input tokens per verdict; typed decisions are free and reproducible, paying instead with an explicit "cannot verify" on two attack shapes - and that uncertainty landed only on attacks, never on six days of real content. The old gate was the one that was simply wrong: it counted "I do not know" as "it passed".

语料还包含一类"真实但从未被引用"的样本:URL 确实在当天抓取里、文章却没引用它。三种判定全判通过,而且这是对的——闸门证明的是来源确实被抓到过,不是来源支撑了那句话。这条边界被写进报告,而不是藏起来。

The corpus also includes "real but never cited" samples: the URL is genuinely in that day's capture yet the article never used it. All three matchers pass them, correctly - the gate proves provenance, not support. That ceiling is stated in the report rather than hidden.

怎么用

How to use it

标准库优先,不需要 Docker,也不需要额外服务。仓库里带一条真实任务(AI资讯速递)作为参考实现。

Standard library first: no Docker and no extra services. The repository ships one real task (the daily digest) as a reference implementation.

./agent doctor ./agent doctor ./agent run daily-trends --compose replay --dry-run ./agent run daily-trends --compose replay --dry-run ./agent experiment run daily-trends-compare --date 2026-09-22 --allow-llm ./agent experiment run daily-trends-compare --date 2026-09-22 --allow-llm ./agent verify eval   # 用标注语料测量闸门自身的漏检率与误杀率 ./agent verify eval   # measure the gate itself against a labelled corpus ./agent verify probes   # 用对抗载荷撞校验器的不变式 ./agent verify probes   # hit validators with payloads that break their invariants ./agent status / runs / report <run_id> ./agent status / runs / report <run_id>

质量闸门

Quality gates

  • 同一天同一任务只允许一次成功产物,重跑必须显式声明。
  • One successful artifact per task and day; a forced re-run must be explicit.
  • 工具调用受可写路径与命令白名单约束,越权直接拒绝并记入台账。
  • Tool calls are bounded by writable paths and a command allow-list; violations are refused and recorded.
  • 内容必须通过结构、引用与可核验性校验,缺一不发。
  • Content must pass structure, reference and traceability checks; missing one blocks release.
  • 校验器返回的不是布尔值,而是带类型的判决:通过 / 不通过 / 无法核验 / 不适用,外加失败分类与逐条证据。"无法核验"默认按失败处理(fail-closed),只有任务声明里显式放宽才放行。
  • Validators return a typed decision - pass, fail, cannot verify, not applicable - with a failure class and itemised evidence. "Cannot verify" blocks by default (fail-closed); only an explicit task-level policy can loosen it.
  • 被拦下的运行会写出机器可读的修复简报:哪个校验器、哪一条引用、哪个 URL、为什么,以及该怎么修。失败是可交接的,不是一句 exit 1。
  • A blocked run writes a machine-readable repair brief: which validator, which citation, which URL, why, and what to do. A failure is handover-ready, not an exit code.
  • 指标有基线:冻结一次结果,之后每次测量自动比对,漏检率或误杀率任一回升就报错。闸门变差不会没人发现。
  • Metrics have a baseline: freeze a run, and every later measurement compares against it. A rising leak or false-reject rate fails loudly.
  • 生成物漂移同样会被拦下(例如站点 sitemap 反复累积空行这类问题)。
  • Generated-file drift is blocked too - for example a sitemap that accumulates blank lines on every run.

边界与说明

Limits and notes

  • 面向个人规模:一台笔记本、少量任务,不追求多 Agent 花哨编排或通用聊天界面。
  • Designed for personal scale: one laptop, a handful of tasks. No elaborate multi-agent choreography and no general chat UI.
  • 语义检索只保留可选接口,当前版本以关键词索引与结构化检索为主。
  • Semantic retrieval is an optional interface only; the current version relies on keyword indexing and structured lookup.
  • 模型是可换的:云端模型为主,本地模型留同一接口,能力上限由所选模型决定。
  • Models are swappable: cloud by default with the same interface for a local endpoint, and the ceiling is set by whichever model you pick.
  • 闸门能证明来源确实被抓到过,不能证明来源支撑了那句话——后者需要另一层,当前不在范围内。这条边界在测量报告里被显式标出,也计入语料。
  • The gate proves that a source was genuinely fetched; it does not prove that the source supports the claim - that needs another layer and is out of scope today. The boundary is named in the measurement report and has its own corpus class.
  • 密钥不落仓库:所有凭据走环境变量或本地 .env,仓库里不含任何明文密钥。
  • No secrets in the repository: credentials come from environment variables or a local .env file.