esaldgut/lessongate
GitHub: esaldgut/lessongate
一个本地 Go agent,将私有项目 PR 中可推广的工程经验经过确定性脱敏后自动提炼为公开知识库的草稿 PR。
Stars: 0 | Forks: 0
# lessongate
一个本地的 Go runtime agent,用于维护一个公开的工程知识库
**动态化**:它会监听私有项目已合并的 pull requests,询问 Claude
从其中提取的哪些经验教训是*可推广的*(可复用的模式,而非特定业务相关的),通过 NDA 闸门对它们进行脱敏处理,并向公开的 skills 仓库提交**草稿** pull requests 以供人工审查。
它将原本需要手动完成的上游流程自动化,并且是按照生产环境标准构建的:每个阶段都由 interface 驱动,安全关键闸门是确定性的,并针对 golden corpus 进行了测试,整个 pipeline 在 CI 中可以确定性地离线运行。
```
private repo (merged PRs + a curated lesson registry)
│
┌────────────────────────────────┼────────────────────────────────────────┐
│ lessongate (single-process Go binary, single-run flock) │
│ │
│ WATCH ─▶ PREFILTER ─▶ REDACT ─▶ EXTRACT ─▶ GATE ─▶ RECONCILE ─▶ EMIT │
│ go-github (no API) (det., (Claude: (det. (novel/ (draft │
│ mergedAt before is it regex + overlaps/ PR, │
│ cursor any API) general- corpus duplicate) idempo- │
│ izable?) + canary tent) │
│ + Claude │
│ verify │
│ ledger (JSONL, atomic, fail-closed) · slog (redacted) · core dumps off │
└──────────────────────────────────────────────────────────────────────┬──┘
│
DRAFT PR (fingerprinted) ─────┘
│
public repo (branch-protected) ─▶ human review
```
## 为什么它很有趣
- **信任边界位于网络传输的正确一侧。** 早期设计会在脱敏处理*之前*将原始 PR diff 发送给 Claude API。这会在每次 PR 时将所有私有标识符发送给第三方。修复方案是:消费*人工策划的经验文本*(已经提炼为模式,NDA 密度低)而不是原始 diff,并在任何数据离开进程**之前**运行确定性的脱敏操作。diff 永远不会离开本机。
- **保密性控制是确定性闸门,而不是 LLM。** 字面量的黑名单注定是不完整的。主要的控制手段是结构化正则表达式(12 位账号 ID、ARN、反向 DNS bundle ID、密钥形状的 token)加上一个模板允许列表,并针对包含泄漏/干净内容的 golden corpus 进行了测试,该测试带有 seed 的 canary,如果发生泄漏则会导致构建失败。Claude 的验证过程是*额外的召回*——它只能将判定结果降级为不安全,绝不能反过来。
- **在关键环节保证确定性。** 非确定性的 Claude 阶段在 CI 中针对录制的 cassette 运行,因此安全关键路径可进行离线回归测试。确定性闸门、merge-cursor 逻辑、ledger 的崩溃恢复以及幂等性检查均由快速单元测试覆盖。
- **重用而非重构。** Skill 验证通过 shell 调用官方 Claude Code `skill-creator` 插件的 `quick_validate.py`,该插件通过 glob 发现(没有硬编码的版本哈希),并带有 fail-closed 的启动断言。
## 设计决策,经过验证而非假设
依赖栈已与最新资料进行过核对,这推翻了之前的几个假设:
| 决策 | 原因 |
|---|---|
| `anthropic-sdk-go` v1.46.0 | 当前版本。Opus 4.8 **仅支持 adaptive-thinking** —— `temperature`/`top_p`/`budget_tokens` 全部返回 400 错误,因此确定性来自强制结构化输出(tool-choice),而不是 temperature 旋钮。 |
| `go-github` v88 | 当前版本。`NewClient` 返回 `(*Client, error)` 并且认证是一个 `ClientOptionsFunc` (`WithAuthToken`) —— 相比旧的主版本这是一个破坏性变更。通过 `NewPullRequest{Draft: true}` 创建草稿 PR。 |
| **无** MCP go-sdk | 虽然真实存在,但在本项目是推测性的 —— agent 直接调用 API 和 GitHub;数据流中没有 MCP server。 |
| **无** OpenTelemetry | 对于单进程的本地二进制文件来说属于过度设计。改为使用 `slog` + counters。 |
| Go 1.26 惯用法 | `errors.AsType[T]`, `wg.Go`, `strings.SplitSeq`, `omitzero` —— 在合适的地方使用。 |
该设计在实现之前经过了对抗性审计。被标记为 CRITICAL 的两个发现 —— *原始 diffs 在闸门处理前跨网络传输* 和 *将黑名单作为主要的保密性控制手段* —— 改变了上述架构,而不仅仅是停留在脚注里。
## 威胁模型(摘要)
| 边界 | 传输内容 | 控制手段 |
|---|---|---|
| 进程 → Claude API | 脱敏后的 lesson 文本(非原始 diff) | 发送前进行确定性脱敏;需要 ZDR 密钥 |
| 进程 → 公开仓库 | SKILL.md + PR 正文 | 前置完整闸门(确定性 + 结构化 + 允许列表 + Claude 验证 + canary);仅限草稿;人工审查;服务端密钥扫描 |
| 进程 → 磁盘 | 隔离区(如有) | 位于同步路径之外,`chmod 700`,TTL-shred,无 core dumps |
| 进程 → 日志 | 遥测数据 | `slog` 脱敏器 —— 仅限 ID/哈希/计数,绝不包含 payload |
| GitHub token | 凭证 | 细粒度 PAT,2 个仓库,Keychain,不被子进程继承 |
请参阅 [`docs/RUNBOOK.md`](docs/RUNBOOK.md) 了解飞行前检查清单和泄漏响应流程。
## 布局
```
cmd/lessongate/ CLI: run / backfill / dry-run / status
internal/
config/ defaults, DisableCoreDumps, state/quarantine dirs, model
watch/ go-github merged-PR cursor (mergedAt, not PR number)
prefilter/ drop docs-only / dep-bump / own capture commits, no API
lessons/ parse the curated lesson registry (1:N, fenced-code safe)
redact/ deterministic pre-flight: deny-list + structural + allow-list
extract/ Claude: is this lesson generalizable?
gate/ deterministic gate + golden corpus + canary + Claude verify
reconcile/ novel / overlaps-existing / duplicate
skillcreator/ shell-out to the skill-creator plugin (glob discovery, fail-closed)
emit/ idempotent draft PR via go-github (fingerprint in body)
claude/ anthropic-sdk-go wrapper + cassette client for offline CI
ledger/ JSONL state, atomic writes, flock, fail-closed
obs/ redacting slog handler + counters
pipeline/ the orchestrator wiring the six stages
```
## 运行说明
```
go build ./... && go test ./... # 73 tests pass offline (+2 opt-in, see below)
export ANTHROPIC_API_KEY=... # must be a ZDR-enabled key
export LESSONGATE_GITHUB_TOKEN=... # fine-grained PAT, 2 repos
./bin/lessongate dry-run --repo owner/name # list candidates, open NO PRs
./bin/lessongate run --repo owner/name --max 1 # open one draft PR
./bin/lessongate run --repo owner/name --max 1 # re-run: idempotent, no duplicate
```
如果没有凭证,agent 会**以失败安全**的方式退出并给出清晰的提示信息——它绝不会退化为静默的 no-op 从而跳过闸门。
## 状态
v0.1 —— 整个 pipeline 已连通并经过离线测试(13 个测试包中共有 75 个测试,确定性 CI:73 个离线运行,2 个可通过 env 标志选择针对真实的 skill-creator 插件 / lesson registry 运行)。Agent 已经完成;阻碍首次生产环境运行的是一个**深思熟虑的前置条件,而不是缺失的工作**。
处理来自私有项目的真实 lesson 意味着要将脱敏后的 lesson 文本发送给第三方 API。威胁模型要求在发生这种情况之前,Anthropic 组织必须启用 **Zero-Data-Retention**。ZDR 并不是一个免费层级的开关——它是在商业协议下授予的(参见 [`docs/RUNBOOK.md`](docs/RUNBOOK.md))。
因此 lessongate **等待 ZDR** 就绪,而不是在没有 ZDR 的情况下处理私有数据。
Agent 强制执行其自身的威胁模型;它不会仅仅因为作者拥有数据就被绕过。
在此之前,**synthetic smoke** 路径会在不携带任何私有项目数据的 lesson 文本上,测试整个 pipeline —— 包含真实的 Claude API 调用和真实的草稿 PR:
```
./bin/lessongate dry-run --once --lessons-file testdata/smoke/synthetic_lessons.md
```
v0.1 的非目标:MCP、OpenTelemetry、自动合并,以及监听除了单个私有垂直切片之外的任何内容。
标签:DLL 劫持, EVTX分析, Go, Ruby工具, 代码审查, 信息过滤, 大语言模型, 日志审计, 时序数据库, 自动化Agent