OpenSRE 开发参考:从 CI 守门到代码规范的 Agent 协作工程指南
OpenSRE(Build your own AI SRE agents)是一个面向 AI 时代的开源 SRE 工具包,其根目录的 AGENTS.md 是仓库的开发总纲:它面向所有自动化编码 Agent 与人类贡献者,定义了"必须闭环的 CI/测试纪律"、构建运行方式、代码风格、测试哲学、性能约束、文件放置策略、仓库地图与高频踩坑清单。本文以该文档为骨架,结合 Makefile、CI.md、docs/ARCHITECTURE.md 与相关源码,完整展开这套工程规范,让读者既能照章提交合格 PR,也能理解每条规则背后的实现依据。
一、铁律:Agent 必须闭环 CI 与测试(每个 PR / 每次 Push)
AGENTS.md 开篇即立下基调:"推送了修复"或"打开了 PR"不等于完成。对自动化 Agent 而言,CI 是交付的一部分,必须盯到绿为止。
1.1 每次推送后的标准动作
只要分支上有打开的 PR,每次 push 后必须执行:
gh pr checks --watch
# 或:gh pr view --json statusCheckRollup,url
1.2 失败时的处理流程
任何检查失败(CI Gate、quality、test (*)、导入图等),按以下顺序闭环:
- 拉取失败任务日志:
gh run view <id> --log-failed; - 修复产品代码或测试代码的根因——禁止通过跳过测试或"常量条件开关"(constant-condition toggles)来掩盖;
- 对涉及的模块重跑 CI.md 中规定的聚焦本地命令,再 push;
- 反复
gh pr checks,直到必需任务全绿(纯文档改动可跳过)。
1.3 三类必须当"真 bug"处理的情况
- CI 负载下的测试失败(xdist、barrier、fan-out 并发):这是测试基础设施自身的缺陷,应加固同步逻辑,不能当作 flake 忽略;
- 导入 / API 边界失败(
check_imports.py、test_integrations_api_border):说明用了错误的模块边缘,应改从.importlinter.strict或边界允许列表(border allowlist)中列出的包级公开 API 导入,而不是内部叶子模块——除非 ignore 列表明确点名了该边; - 合并后的失败:merge 之后仍要监控
main分支 CI、完整 CodeQL 与 release 工作流。合并后失败等于交付未完成:必须先修复或回滚,再报告完成。
1.4 钩子自动化:把守则注入 Agent 的 stop 时刻
规范还落地为 Cursor 项目钩子 .cursor/hooks/check-ci-failures.sh,由 .cursor/hooks.json 以 stop 事件注册(timeout 90 秒,loop_limit 3)。钩子在 Agent 停止时读取当前分支的 gh pr view --json statusCheckRollup,若存在 FAILURE / TIMED_OUT / ACTION_REQUIRED / STARTUP_FAILURE 结论的检查,就自动注入一条阻塞性 follow-up 消息,要求 Agent 继续修复而不是收工。脚本内部对状态为 aborted / error 或缺少 gh CLI 的情况直接放行,并用 loop_count 上限防止无限自修复循环。换句话说:这条守则不是建议,而是会重新打断 Agent 的阻塞工作项。
二、构建与运行:三条基本命令
| 命令 | 用途 |
|---|---|
make install | 通过 uv sync 建立项目环境并以 editable 模式安装本仓库(同时安装阻塞式 pre-push 钩子,见下文) |
uv run opensre … | 开发期推荐入口,从仓库根目录运行,确保使用本 checkout 而非 PATH 上的其他 opensre |
uv run python … | 执行任意 Python 命令的标准方式 |
从 Makefile 的源码看,install 目标实际执行 uv sync --frozen --extra dev、$(MAKE) install-hooks,并运行 uv run python -m infrastructure.analytics.install 完成安装期埋点;pre-push 目标则调用 .github/ci/run_checks.py --scope $(ARGS),即一套由仓库脚本驱动的统一质量闸门,与 GitHub Actions 使用同一套检查定义(详见 CI.md)。开发中所有 Python 命令都建议通过 uv run 前缀执行,避免污染全局环境。
三、代码风格:让模块边界清晰可审
3.1 总原则
- 严格类型标注(strict typing),遵循 DRY;
- 每个文件只有一个清晰职责(separation of concerns)。
3.2 __init__.py 必须是轻量门面
每个 Python 包的 __init__.py 只做两件事:声明公开接口(imports + __all__)。实现、编排、副作用全部放在聚焦模块中,再只 re-export 预期公开 API,绝不允许 __init__.py 变成 god file。
仓库中最典型的范例是 config/constants/init.py:它通过 from config.constants.exports import __getattr__ 实现运行时惰性属性解析,只在 TYPE_CHECKING 分支下做静态 re-export 以便 mypy 看到真实类型。这样调用方 from config.constants import NAME 时不会因为想取一个常量而把全部 vendor 模块拉进内存——这正是"轻量门面"的工程化落地。
3.3 docstring 精简且有契约感
- 简单 API 用一句话说明;
- 仅在必要时补充非显而易见的不变量、失败行为、分层约束;
- bug 历史与实现叙事放进测试、commit 或 PR 描述,而不是 docstring——但也不要为了缩短而删掉有意义的设计理由。
3.4 HTTP 状态码必须用命名常量
源码与测试中一律使用 http.HTTPStatus 命名常量(如 HTTPStatus.PAYMENT_REQUIRED),禁止硬编码 402 这类数字字面量。
3.5 环境变量与共享常量只放 config/constants/
- 环境变量名与共享静态常量一律收进
config/下的领域模块(如config/constants/billing.py、config/constants/llm.py),通过 config/constants/init.py re-export;不得内联在功能模块里或跨文件复制; - 特别地,不要在
config/llm_settings.py里定义共享环境变量名:因为它会导入config.llm_auth.*,若某个名字同时被它和这些模块需要,就会形成循环导入。只有当某个名字只被config/llm_settings.py自己使用(它导入的任何模块都不需要)时,才可以放在那里。
这一约束在 config/constants/llm.py 的模块 docstring 中有直接印证:LLM_PROVIDER_ENV、AZURE_OPENAI_BASE_URL_ENV 等连接类变量放在这个"只依赖标准库的叶子模块"中,正是因为 config.llm_settings 导入的 config.llm_auth.provider_catalog 也需要它们——放在 config.llm_settings 里会循环。同理可参考 config/constants/billing.py 中 ORGANIZATION_ID_ENV、WEBAPP_URL_ENV、CREDITS_IDEMPOTENCY_HEADER 等常量的组织方式。
3.6 重构后不留兼容转发模块
重构迁移完导入与测试后,要在同一次变更中删除旧模块路径,只保留一个 canonical 导入路径,避免代码库出现"兼容性残留"。
3.7 测试替身的写法
禁止在 monkeypatch.setattr / patch 里内联一个构建临时 type(...) 对象的 lambda(或嵌套 lambda)。应抽出具名 def 再传入——函数体内使用 type(...) 是允许的:
def _build_harness() -> Any:
return type("H", (), {"resolve_env_variables": lambda _self: None})()
monkeypatch.setattr(startup, "_build_harness", _build_harness)
平凡 lambda(如 lambda **_kw: None、lambda: sentinel)可以保持内联。仓库既有范例见 tests/infrastructure/safety/guardrails/test_llm_integration.py(_anthropic_fake_response)与 tests/cli/test_integrations_setup_github.py(_prompt_answering)。
3.8 Protocol 方法体:只用 docstring,不用 ...
你新增或修改的 Protocol 方法,方法体必须只有 docstring——不允许 ...、pass、raise NotImplementedError,也不允许 docstring 后再跟 .../pass:
class ObjectStore(Protocol):
def put_object(self, key: str, data: bytes) -> None:
"""Store ``data`` under ``key``."""
合规范例见 infrastructure/filestorage/contracts.py、core/agent/loop_host.py、infrastructure/turn_host/turn_output.py、core/llm/types.py。
重要现实:整个代码库尚未完全合规,而且没有任何工具会主动告诉你。对产品代码做 AST 扫描发现:89 个 docstring-only Protocol 方法 vs 23 个文件中的 108 个 raise NotImplementedError stub(集中在 core/agent_harness/ports.py、infrastructure/harness_providers/、gateway/core/storage/session/binding_store.py 等)。这些是既有存量、不属于顺手改造的范围——不要批量转换,也不要未经检查就把某个文件当作先例引用。
合规只靠 review 执行:
- CodeQL
py/ineffectual-statement只抓裸...形式; pass和raise NotImplementedError不会触发任何告警;- mypy 豁免 Protocol 方法体的"缺 return",所以
-> list[str]签名下写pass也能通过make typecheck。
四、测试哲学:高信号优先,拒绝穷举式覆盖
"小而精、钉住真实失败模式"优先于"宽泛的行覆盖率":每个不同的 bug 类别写一个测试,不要用不同字面量重复覆盖同一分支的用例。
应当编写/保留的测试:
- 安全与授权:allowlist、request-scoped authority、跨 actor / 跨 channel 隔离;
- 并发、崩溃、关闭场景下的正确性:cursor、in-flight 工作、ack vs replay、drain vs approval wait;
- 防止静默耦合的包 / 传输边界(无 peer import、会话键形状对 surface 唯一);
- 已经咬过 review 或生产的回归(曾经迫使修复的 P1)。
应当跳过或精简的测试(除非它是某个契约的唯一覆盖):
- 纯 happy-path 的"mock 被调用一次"包装(客户端发送、初始化时发状态);
- 纯字符串 / 词汇表,且 approve/deny/leave-open 路径已覆盖该辅助函数;
- 已被 fail/cancel/on_handled 覆盖的 ack/dispatch 路径上的冗余成功变体;
- 不移动持久状态的防御性解析 /"抓取失败返回空"边界;
- 只复述控制流意图、不真正走循环或接线的"占位测试"(例如"
create_task不会阻塞创建者")。
五、面向用户的文档纪律(docs/ 下)
docs/ 是用户可见的,因此要求:逐句检验——这句话是否改变读者的行为? 不改变就删掉。
- 砍掉:vendor API 端点名(如
getMe)、内部函数名、值落在哪个凭据层级、某次变更修了什么 bug——这些属于 PR 描述或模块 docstring; - 保留:必选 vs 可选、真正省力的捷径、看不见直到踩中的坑、读者要原样输入的精确命令与环境变量;
- 用行为语言而非内部 API 语言表述,例如说"机器人从未被添加到的会话在 setup 阶段会失败",而不是"
getChat返回ok: false"。
六、性能规范:热路径上的算法与数据结构
性能约束只强加在热路径(每个请求 / 每次迭代 / 每次工具调用);冷代码保持简单。复杂度必须是有意为之——选择满足渐进需求的最简结构。
| 场景 | 正确做法 | 理由 |
|---|---|---|
| 循环内做成员判断 / 去重 | set / dict,绝不 x in list | list 的 in 是 O(n),set 是 O(1);frozen dataclass 可哈希,可直接入 set |
| 已经查过的对象 | 直接读其字段,不再二次线性扫描;构建 {name: obj} 的 O(1) 映射 | 消除重复查找 |
| 构造后不变的结果 | 计算一次(functools.cached_property 或存字段),只读使用 | 热路径上禁止 deepcopy / json.dumps;先确认没有调用方会改共享缓存对象 |
| 排序 | 只排轻量物(key/name 字符串)而非重对象,同一集合不要为两个输出排两次 | 最小化开销 |
| 队列 / top-k / 有序查找 / 有界缓存 | collections.deque / heapq / bisect / OrderedDict.move_to_end 或 functools.lru_cache | 选对结构 |
| 字符串拼接 | "".join(parts),绝不在循环里 += | 避免二次复杂度 |
行为保持型重构必须用 TDD 守护:先加一个钉住可观察行为的表征测试(characterization test),确认它在重构前的代码上通过,再让它在整个变更中保持绿色;优化之后才做,benchmark 留在 PR 里。
七、文件放置策略:归属模块优先
新增或修改行为时,代码先放进归属模块(owning module),而不是放进"恰好已经导入相似东西"的共享文件。
| 文件类型 | 应包含 | 不应包含 |
|---|---|---|
编排(flow.py、controller.py、lifecycle.py、factory.py) | 阶段排序、接线、分发给专家模块 | vendor/provider/domain 逻辑、API 客户端、重型 UI |
共享 UI / 提示词(_ui.py、prompts.py、通用 validation.py) | 可复用提示词、表格、渲染、薄分发 | 某个 provider / integration / vendor 的逻辑 |
Domain / provider / vendor 模块(providers/<name>.py、surfaces/cli/wizard/<name>.py、integrations/<vendor>/) | 该 provider / vendor / 功能区的全部行为 | 无关 provider 或横切编排 |
Registry / catalog(config.py、*_catalog.py、provider_registry.py) | 元数据、默认值、发现表 | 实时 API 调用、onboarding 提示词、重试循环 |
具体规则:
- 同一 provider / vendor / 功能区出现两个及以上函数 → 新增或扩展该区的专用模块(或子包),不要用 provider 分支去撑大共享编排 / UI 文件;
- 编辑共享文件前,先看同包内是否已有同类模式(
local_llm/、providers/azure_openai.py、integrations/<vendor>/tools/等),先匹配既有布局再谈创新; - 分发要薄:共享入口(
validate_provider_credentials、get_llm、slash-command 处理器)应在几行内委派,实现留在下游; - 尊重包边界(见 docs/ARCHITECTURE.md):surfaces 组合下层;
core/与integrations/不得从surfaces/导入; - 包内细节遵循该包自带的
AGENTS.md(如 surfaces/interactive_shell/AGENTS.md、core/llm/AGENTS.md),在该树做结构变更前先读它; - 工具位置遵循 docs/tool-placement-policy.md(vendor 专属 vs
tools/system/vs cross-vendor)。
如果某次改动要在"已服务多个 provider 的文件"里新增一个 provider 专属的 if provider.value == ... 分支,立即停下,抽独立模块。
这套"五层依赖只许向下"的架构在 docs/ARCHITECTURE.md 中有完整定义:Tier 1(surfaces、gateway)→ Tier 2(bootstrap,唯一的组合根,可同时导入 tools 与 integrations)→ Tier 3(tools、integrations 互为 peer 不得互导)→ Tier 4(core ⟷ infrastructure,唯一允许的双向对)→ Tier 5(config,叶子)。这些依赖规则由 make check-imports 在 CI 强制执行,是真实不变量而非愿景。
八、持久化与 PR 流程
- 持久化归属、存储包布局、迁移边界、并发要求遵循 PERSISTENCE.md;
- 任何 push / 开 PR 前必须遵循 CI.md——lint、format、typecheck、test 命令都在其中;
- 开 PR 必须填写 PR 模板:它不是可选样板,其中包含必填的 AI 使用披露(AI-usage disclosure)部分。
8.1 CI.md 的关键机制补充
作为配套,CI.md 定义了本地推送闸门:克隆后运行 make install 会安装锁定依赖与阻塞式 pre-push 钩子(.github/ci/install_hooks.py);钩子会在临时 Git worktree 里用锁定依赖验证"被推送的已提交修订"——未提交的修复救不了已损坏的 commit。日常提交前运行:
make pre-push # 60 秒目标,覆盖工作树含未跟踪文件
make pre-push ARGS=--dry-run # 只预览选中范围,不执行
make test-scope # 只跑受影响测试(与推送闸门同一映射)
文档-only 的 diff 可跳过代码检查;运行时提示词、脚本、工作流、依赖变更不享受该捷径。紧急情况下可用 git -c opensre.prePushOverride='理由' push 单次绕过(会在 pre-push-overrides.jsonl 留痕,且不豁免远端 CI 与合并要求)。
九、仓库地图(Repo Map)
AGENTS.md 用一张表给出仓库全局视图,下表按原意浓缩为目录与职责(更细的五层架构见 docs/ARCHITECTURE.md):
| 路径 | 职责 |
|---|---|
bootstrap/ | 组合根:共享进程启动(process.py 的 BootStep 表:env、Sentry、适配器、能力警告、LLM 预加载)与注册步骤(adapters.py)。每个 host 选择 ProcessProfile 而非自造启动顺序。唯一被允许同时导入 tools 与 integrations 的包 |
core/ | Agent 编排、上下文组装、共享运行时工具调用循环、领域逻辑。core/tool/ 拥有工具契约 / schema / 注册表端口 / 执行与错误上报;core/tool_framework/ 提供 @tool、skill guidance 等作者辅助 |
surfaces/cli/ | CLI、onboarding 向导、本地 LLM 辅助。Provider onboarding → wizard/<provider>.py;新子命令 → commands/<name>.py |
surfaces/interactive_shell/ | 交互式终端(REPL)循环、slash 命令、chat/help 界面、行动规划 harness、终端 UI |
integrations/ | 每个集成的配置归一化、验证、客户端、辅助、store/catalog 逻辑,及 integrations/<vendor>/tools/ 下的 vendor 工具包 |
tools/ | 工具注册表、非 vendor 专属的横切工具包(tools/system/fleet_monitoring/、tools/system/sre_guidance_tool/ 等)、交互式 shell 行动工具 |
config/ | 共享常量、提示词、UI 主题 |
tests/ | 单元、集成、部署、e2e 与支撑测试 |
docs/ | 用户文档、集成指南、文档站点资源 |
Dockerfile | 可选生产容器镜像(FastAPI health app,uvicorn 驱动) |
pyproject.toml / Makefile | 项目元数据与依赖;本地安装、测试、验证、部署、清理的规范自动化 |
更深一层的关键子包:infrastructure/analytics/(分析事件管道与 onboarding 安装辅助)、infrastructure/safety/auth/(本地/托管运行时访问的 JWT 与认证辅助)、infrastructure/safety/guardrails/(护栏规则与评估引擎)、infrastructure/harness_providers/(harness provider 层,启动时经 integrations/harness_adapters.py 与 tools/harness_adapters.py 接线)、integrations/llm_cli/(子进程 LLM CLI,如 Codex)、core/llm/(托管 LLM provider 客户端与工具调用适配器)、core/state/(共享 Agent 状态与会话存储)、gateway/web/webapp.py(网关守护进程托管的 Web 健康应用,而 opensre CLI 是 surfaces/cli/app.py)。
十、入口点:如何新增一个 Tool 或 Integration
10.1 新增 Tool
工具注册表会自动发现 tools/ 下的模块,所以常规路径是:在那里新增一个模块或包,让发现机制接管。完整文件清单与"完成的定义"见 docs/adding-tools-and-integrations.md。步骤:
- 选最贴合工具形态的方式:复杂行为用
BaseTool子类(来自core.tool),轻量函数用@tool(...)(来自core.tool_framework)。必须通过这些层级公开 API 导入,而非内部子模块——边界测试tests/shared/test_tool_api_border.py强制这一约束; - 声明清晰元数据:
name、description、source、input_schema,以及按需的use_cases、requires、outputs、retrieval_controls; - 开 / 审 PR 前,先过 docs/adding-tools-and-integrations.md。
从源码看,core/tool_framework/tool_decorator.py 的 @tool 重载签名完整支持上述元数据:input_schema / input_model(pydantic BaseModel)、evidence_type、side_effect_level、surfaces(ToolSurface 元组)、use_cases / examples / anti_examples、requires、outputs / output_schema、requires_approval、approval_reason、approval_expiry_seconds、tags 等。这些字段既是 LLM 选工具的依据,也是护栏与审计的数据来源。
10.2 新增 Integration
集成工作通常横跨:配置归一化、验证、集成专属客户端 / 辅助、工具、文档、测试(Datadog、Grafana 是仓库内的现成范例)。步骤:
- 先加配置与归一化逻辑,让上层栈消费一致的数据形状;
- 配置路径稳定后再接工具层;
- 开 / 审 PR 前遵循 docs/adding-tools-and-integrations.md(含
make verify-integrations与最终 demo 门禁)。
十一、Footguns:必须避开的常见坑(全清单)
AGENTS.md 用最大篇幅列出的实践教训,每一条背后都有静态检查或真实事故:
- 常量条件开关(constant-condition toggles):绝不写
if False and …、if True or …、if False:来让失败测试"闭嘴"。这会隐藏真实行为(如 cancel 短路)并以死代码形式上线。正确做法是保留真实条件并修测试,或删除分支。由 tests/quality/test_no_constant_condition_toggles.py 用 AST 遍历产品包(bootstrap/config/core/gateway/integrations/infrastructure/surfaces/tools)强制执行。 - 不要重新引入 fail-closed 计划护栏(v0.1 教训):交互式 shell 的行动规划器从不拒绝 turn——不要重新引入 planner 拒绝、
mark_unhandled或UNHANDLED:约定。完整理由见 docs/interactive-shell-action-policy.md,包级规则见surfaces/interactive_shell/AGENTS.md。 - docs 导航:在
docs/下新增.mdx不够——Mintlify 只显示 docs/docs.json 中登记过的页面;漏了pages条目,文档在站点侧边栏就不可达。 - 工具 schema:draft-07 的宽松写法(如
"type": ["object", "null"])能过宽松检查,却会在首次调用时因全部工具被一起发送而触发 LLM API 失败。应在 provider adapter 中归一化,并扩展注册表契约测试。 - action-agent 路径:不要在 harness 编排器 /
SessionGoal循环 / evidence 层级策略中实现 regex / 关键词 / 模糊意图路由或确定性绕过。意图应留在 action turn 的结构化交接标签中(evidence_kind:…、session_goal:…、database_query:…),host 只对这些标签或显式 API 做反应(字面/slash是唯一批准的例外)。 - 异常信息外泄(CWE-209 / CodeQL
py/stack-trace-exposure):绝不要把str(exc)、repr(exc)、traceback.format_exc()、exc.args、provider/model/field 内部细节发给外部 surface——外部 surface 指 HTTP 响应(gateway/web/中的JSONResponse/HTTPException.detail)与发送给 Slack/Telegram 用户的聊天网关消息(gateway sink 上的OutputSink.render_error)。完整细节只在服务端记录(logger+capture_exception),对外只返回通用消息或type(exc).__name__。本地 CLI/终端 sink 不算外部,可展示细节。脱敏在 sink / 响应边界做,而不是每个调用点,这样共享 turn 引擎可为本地开发保留细节。 - 循环导入(CodeQL
py/cyclic-import):CodeQL 把函数内局部导入和TYPE_CHECKING导入也算进环,所以"惰性导入"并不能消除告警。必须结构性地断环——把共享符号(类型、异常、辅助)移进双方共同导入的叶子模块,且永远不要从低层模块向上层模块加反向边。先例:surfaces/shared/llm_setup/validation_result.py与surfaces/shared/llm_setup/persist.py仅用于承载共享符号,使validation↔azure_openai和_ui→service保持无环。 - CodeQL 不建模
NoReturn:它把pytest.skip、pytest.fail、sys.exit、typer.Exit及自定义 raise 辅助当作"会返回",导致其后的代码看起来可达,产生两类告警:py/uninitialized-local-variable(名字在try中绑定、except只调这类函数)与 unreachable-code(with体以裸raise结尾)。不要用注释压制:让名字在 CodeQL 能看到的每条路径上都绑定。普通"未找到"场景优先用哨兵而非异常控制流——next(iterable, None)加显式if x is None:守卫,而不是try: next(...) except StopIteration:(mypy 在守卫后能正确收窄,因为它确实尊重NoReturn)。裸raise场景抽一个_raise()辅助。 - Protocol stub 方法体(CodeQL
py/ineffectual-statement):裸...是合法 PEP-544 惯用法但会被 CodeQL 当成无效语句。不要写def foo(self) -> T: ...;用一行 docstring 作为唯一方法体,也不要同时保留 docstring 和尾部.../pass。注意范围:CodeQL 只抓...,所以扫描干净不代表代码库合规——pass与raise NotImplementedError对它不可见(存量 108 个)。 py/ineffectual-statement不理解await:裸await some_task被 CodeQL 当成丢弃表达式——但它不是,await 本身就是副作用(如收割被取消的任务以保证client.close()执行)。不要删 await。优选绑定结果的小辅助(gateway/transports/discord/worker.py的_reap_cancelled_task中_finished = await task),而不是跳过 await 来"修复"。- 列表内隐式字符串拼接(
py/implicit-string-concatenation-in-list):list/tuple 显示里两个相邻字符串字面量与漏了逗号无法区分,跨行长消息会触发。不要用显式+压制——把文本提为模块常量再引用。在括号赋值里同款拼接没问题(那里本就不可能想写逗号)。先例:tools/system/python_execution_tool/__init__.py的_RUNTIME_FACTS_ANTI_EXAMPLE。最容易咬到的是use_cases/anti_examples/examples工具元数据——散文式条目常常超 100 字符行宽。 - 混合导入风格(
py/import-and-import-from):同一模块既import X as alias又from X import name——即使from导入是函数内局部的——也会告警。通常发生在往已有文件追加代码时:沿用该文件既有的导入风格。 - except 捕获
BaseException:应捕获Exception。requests.exceptions.ConnectTimeout是Exception子类,测试线程里收集请求/传输失败仍然有效;捕获BaseException还会吞掉KeyboardInterrupt/SystemExit。不要用noqa: BLE001压制。 - 未使用的全局变量(CodeQL code-quality):CodeQL 经常不把跨模块导入算作模块级常量的使用。
text.py里只在别的文件通过from …text import FOO读取的FOO = "..."仍可能告警。优先把相关文案组织在定义模块中确实被使用的结构里(如HANDOFF_GUIDANCE["database_query:"]的 dict 项、前缀匹配由消费方完成),或让常量与其唯一读者同居一模块。不要加空自引用或# noqa压制。 - 并发 turn 下的共享客户端状态:LLM 客户端按 role 缓存(
get_llm),网关并行跑 turn,所以一个客户端实例服务多个在途请求。在错误处理器里被改写的实例标志,会被"早已在旧值下构建"的请求读到。应基于当前请求携带的事实分支,而不是标志的现值——在请求构建处本地捕获事实(marked = strip_cache_markers(kwargs) != kwargs),在except里查它。先例:prompt-cache fallback 事故——首个 400 清掉了_cache_markers_enabled,第二个已标记请求跳过了未缓存重试而失败。测试方法:让 fake 依赖在抛错前先改共享状态,即可无线程地确定性复现竞态。 - CI 的 typecheck 不覆盖
tests/:make typecheck只对PYTHON_SOURCE_PATHS(config core gateway integrations infrastructure surfaces tools)跑 mypy。测试文件里的类型错误永远不会让 CI 失败——别以为make typecheck干净就代表新写测试类型干净,必要时直接对测试路径跑 mypy。
结语:规范即产品的一部分
对 OpenSRE 这种"Agent 即用户"的开源项目,AGENTS.md 本质上是一份可执行的协作契约:CI 闭环由 .cursor 钩子机械化,代码风格由 config/constants 的叶子组织与 __init__.py 门面模式约束,包边界由 make check-imports 在 CI 强制,工具与集成入口由注册表自动发现与 docs/adding-tools-and-integrations.md 把关,而 footguns 清单则是把 CodeQL / mypy / 生产事故沉淀成可检索的工程经验。理解并遵守这份参考,是向 OpenSRE 提交高质量、可持续维护代码的前提,也是自动化 Agent 正确协作的底线。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



